@omercnet/paseo-shared-browser 1.0.1 → 1.0.2-next.179.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.
package/README.md CHANGED
@@ -77,9 +77,9 @@ paseo plugin ls
77
77
  ```
78
78
 
79
79
  When Paseo acquires the npm package, it runs the `npm run prepare:runtime` build hook automatically. The
80
- hook installs the pinned browser runtime and builds both `supervisor.cjs` and
81
- `shared-browser-mcp.cjs` under
82
- `$PASEO_HOME/plugin-data/shared-browser/runtime`.
80
+ hook installs the pinned browser runtime, builds both `supervisor.cjs` and `shared-browser-mcp.cjs`
81
+ under a versioned directory in `$PASEO_HOME/plugin-data/shared-browser`, then atomically updates the
82
+ `runtime-current` pointer. Existing processes keep using their immutable runtime during an update.
83
83
 
84
84
  The build requires Node.js 24 or newer and npm on the daemon host. On Linux x64, macOS, and
85
85
  Windows, runtime preparation installs and stages the platform's Chrome for Testing distribution. On
@@ -125,8 +125,8 @@ The plugin recognizes only these deployment overrides:
125
125
  | Variable | Meaning |
126
126
  | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127
127
  | `PASEO_HOME` | Paseo data root. Defaults to `~/.paseo`; browser runtime, supervisor IPC, and profiles live below `plugin-data/shared-browser`. |
128
- | `PASEO_SHARED_BROWSER_AGENT_BROWSER_BINARY` | Absolute path to the pinned `agent-browser` executable. Defaults to `$PASEO_HOME/plugin-data/shared-browser/runtime/node_modules/.bin/agent-browser` (`agent-browser.exe` on Windows). |
129
- | `PASEO_SHARED_BROWSER_CHROMIUM_EXECUTABLE` | Absolute path to Chromium. Defaults to `$PASEO_HOME/plugin-data/shared-browser/runtime/chromium/chrome` (`chrome.exe` on Windows). On Linux ARM64, installation automatically links `/usr/bin/chromium`; use this override for another compatible, non-Snap location. |
128
+ | `PASEO_SHARED_BROWSER_AGENT_BROWSER_BINARY` | Absolute path to the pinned `agent-browser` executable. Defaults to the active runtime's `node_modules/.bin/agent-browser` (`agent-browser.exe` on Windows). |
129
+ | `PASEO_SHARED_BROWSER_CHROMIUM_EXECUTABLE` | Absolute path to Chromium. Defaults to the active runtime's `chromium/chrome` (`chrome.exe` on Windows). On Linux ARM64, installation automatically links `/usr/bin/chromium`; use this override for another compatible, non-Snap location. |
130
130
  | `PASEO_SHARED_BROWSER_CHROMIUM_ARGS` | Optional Chromium arguments passed through the managed runtime. Intended for host requirements such as `--no-sandbox` in an already-isolated CI runner; do not disable the browser sandbox on a general-purpose host. |
131
131
 
132
132
  User-supplied `AGENT_BROWSER_*` variables are deliberately ignored.
package/index.server.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  issueAgentTicket,
20
20
  revokeAgentBrowserAccess,
21
21
  } from "./server/browser";
22
+ import { resolveBrowserRuntimeRoot } from "./server/runtime-path";
22
23
  import {
23
24
  acquireControlRpc,
24
25
  applyDevicePresetRpc,
@@ -62,7 +63,7 @@ function paseoHome(): string {
62
63
  }
63
64
 
64
65
  function mcpBundlePath(): string {
65
- return join(paseoHome(), "plugin-data", "shared-browser", "runtime", "shared-browser-mcp.cjs");
66
+ return join(resolveBrowserRuntimeRoot(paseoHome()), "shared-browser-mcp.cjs");
66
67
  }
67
68
 
68
69
  export default function contribute(server: PluginServerContext) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-shared-browser",
3
- "version": "1.0.1",
3
+ "version": "1.0.2-next.179.1",
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
  "client",
30
30
  "scripts/prepare-dependencies.mjs",
31
31
  "scripts/prepare-runtime.mjs",
32
+ "scripts/replace-runtime.mjs",
32
33
  "server",
33
34
  "shared",
34
35
  "paseo-plugin.json"
@@ -1,23 +1,14 @@
1
1
  import { execFile } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { constants } from "node:fs";
3
- import {
4
- access,
5
- chmod,
6
- cp,
7
- lstat,
8
- mkdir,
9
- readdir,
10
- rename,
11
- rm,
12
- symlink,
13
- writeFile,
14
- } from "node:fs/promises";
4
+ import { access, chmod, cp, lstat, mkdir, readdir, rm, symlink, writeFile } from "node:fs/promises";
15
5
  import { createRequire } from "node:module";
16
6
  import { arch, homedir, platform, userInfo } from "node:os";
17
7
  import { dirname, join, resolve } from "node:path";
18
8
  import { fileURLToPath } from "node:url";
19
9
  import { promisify } from "node:util";
20
10
  import { build } from "esbuild";
11
+ import { replaceRuntime } from "./replace-runtime.mjs";
21
12
 
22
13
  const require = createRequire(import.meta.url);
23
14
 
@@ -34,7 +25,7 @@ const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
34
25
  const paseoHome = process.env.PASEO_HOME || join(homedir(), ".paseo");
35
26
  const pluginDataRoot = join(paseoHome, "plugin-data", "shared-browser");
36
27
  const runtimeRoot = join(pluginDataRoot, "runtime");
37
- const stagingRoot = join(pluginDataRoot, `.runtime-${process.pid}`);
28
+ const stagingRoot = join(pluginDataRoot, `.runtime-${randomUUID()}`);
38
29
  const runtimeModules = join(stagingRoot, "node_modules");
39
30
  const packagedAgentBrowser = resolveDependencyRoot("agent-browser");
40
31
  const runtimeEntry = join(
@@ -257,6 +248,6 @@ await applyPrivatePermissions(stagingRoot);
257
248
  if (hostPlatform !== "win32") {
258
249
  await validateChromium(await requireExecutable(chromiumEntry, "Staged Chromium executable"));
259
250
  }
260
- await rename(stagingRoot, runtimeRoot);
251
+ await replaceRuntime(stagingRoot, runtimeRoot);
261
252
 
262
- console.log(`Prepared immutable Shared Browser runtime assets in ${runtimeRoot}`);
253
+ console.log(`Prepared immutable Shared Browser runtime assets in ${stagingRoot}`);
@@ -0,0 +1,15 @@
1
+ import { access, rename, rm, writeFile } from "node:fs/promises";
2
+ import { basename, dirname, join } from "node:path";
3
+
4
+ export async function replaceRuntime(stagingRoot, runtimeRoot) {
5
+ await access(stagingRoot);
6
+ const pluginDataRoot = dirname(runtimeRoot);
7
+ const pointer = join(pluginDataRoot, "runtime-current");
8
+ const pendingPointer = join(pluginDataRoot, `.runtime-current-${process.pid}`);
9
+ await writeFile(pendingPointer, `${basename(stagingRoot)}\n`, { mode: 0o600 });
10
+ try {
11
+ await rename(pendingPointer, pointer);
12
+ } finally {
13
+ await rm(pendingPointer, { force: true });
14
+ }
15
+ }
package/server/browser.ts CHANGED
@@ -17,6 +17,7 @@ import type {
17
17
  resizeBrowserRpc,
18
18
  sendBrowserInputRpc,
19
19
  } from "../shared/browser";
20
+ import { resolveBrowserRuntimeRoot } from "./runtime-path";
20
21
  import type { JsonValue } from "./runtime-protocol";
21
22
  import { resolveSupervisorPaths } from "./supervisor";
22
23
  import { SupervisorClient } from "./supervisor-client";
@@ -156,13 +157,7 @@ let productionStart: Promise<RemoteBrowserManager> | null = null;
156
157
  let productionStopped = false;
157
158
 
158
159
  async function launchSupervisor(): Promise<void> {
159
- const supervisorPath = join(
160
- paseoHome(),
161
- "plugin-data",
162
- "shared-browser",
163
- "runtime",
164
- "supervisor.cjs",
165
- );
160
+ const supervisorPath = join(resolveBrowserRuntimeRoot(paseoHome()), "supervisor.cjs");
166
161
  try {
167
162
  await access(supervisorPath);
168
163
  } catch {
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { AgentBrowserRuntime, type BrowserViewport } from "./agent-browser-runtime";
5
+ import { resolveBrowserRuntimeRoot } from "./runtime-path";
5
6
  import type { JsonValue } from "./runtime-protocol";
6
7
  import type { RuntimeOwner } from "./supervisor";
7
8
 
@@ -23,7 +24,9 @@ function paseoHome(): string {
23
24
  export async function createRuntimeOwner(
24
25
  options: RuntimeOwnerOptions = {},
25
26
  ): Promise<RuntimeOwner<OwnedRuntime>> {
26
- const root = join(paseoHome(), "plugin-data", "shared-browser");
27
+ const home = paseoHome();
28
+ const root = join(home, "plugin-data", "shared-browser");
29
+ const runtimeRoot = resolveBrowserRuntimeRoot(home);
27
30
  const ipcDirectory =
28
31
  process.platform === "win32"
29
32
  ? join(root, "ipc")
@@ -34,15 +37,14 @@ export async function createRuntimeOwner(
34
37
  const binaryPath =
35
38
  process.env.PASEO_SHARED_BROWSER_AGENT_BROWSER_BINARY ??
36
39
  join(
37
- root,
38
- "runtime",
40
+ runtimeRoot,
39
41
  "node_modules",
40
42
  ".bin",
41
43
  process.platform === "win32" ? "agent-browser.exe" : "agent-browser",
42
44
  );
43
45
  const executablePath =
44
46
  process.env.PASEO_SHARED_BROWSER_CHROMIUM_EXECUTABLE ??
45
- join(root, "runtime", "chromium", process.platform === "win32" ? "chrome.exe" : "chrome");
47
+ join(runtimeRoot, "chromium", process.platform === "win32" ? "chrome.exe" : "chrome");
46
48
  return {
47
49
  async create(workspaceId) {
48
50
  const hash = createHash("sha256").update(workspaceId).digest("hex");
@@ -0,0 +1,21 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ export function resolveBrowserRuntimeRoot(paseoHome: string): string {
5
+ const pluginDataRoot = join(paseoHome, "plugin-data", "shared-browser");
6
+ let directory: string;
7
+ try {
8
+ directory = readFileSync(join(pluginDataRoot, "runtime-current"), "utf8").trim();
9
+ } catch (error) {
10
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return join(pluginDataRoot, "runtime");
11
+ throw error;
12
+ }
13
+ if (
14
+ !/^\.runtime-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(
15
+ directory,
16
+ )
17
+ ) {
18
+ throw new Error("Shared Browser runtime pointer is invalid");
19
+ }
20
+ return join(pluginDataRoot, directory);
21
+ }