@omercnet/paseo-shared-browser 4.1.1-next.356.1 → 4.1.1-next.358.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 +1 -1
- package/npm-shrinkwrap.json +4 -4
- package/package.json +2 -2
- package/scripts/prepare-runtime.mjs +1 -1
- package/server/agent-browser-runtime.ts +25 -17
- package/server/daemon-shutdown.test.ts +39 -4
- package/server/runtime-launch-environment.test.ts +1 -1
- package/server/runtime-video-opt-in.test.ts +1 -1
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.
|
|
43
|
+
- The plugin owns `agent-browser` version `0.40.0`, 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.
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@getpaseo/plugin": "0.14.0",
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
13
|
-
"agent-browser": "0.
|
|
13
|
+
"agent-browser": "0.40.0",
|
|
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.
|
|
610
|
-
"resolved": "https://registry.npmjs.org/agent-browser/-/agent-browser-0.
|
|
611
|
-
"integrity": "sha512-
|
|
609
|
+
"version": "0.40.0",
|
|
610
|
+
"resolved": "https://registry.npmjs.org/agent-browser/-/agent-browser-0.40.0.tgz",
|
|
611
|
+
"integrity": "sha512-uqDAR3ozwJKYzVENMr/ZKYTZT1LUZauI49rTiK/KsFBxQDVTdtmrWGWTMWZlaIIQWToEtZUfWVXBSSTY6aHzrA==",
|
|
612
612
|
"hasInstallScript": true,
|
|
613
613
|
"license": "Apache-2.0",
|
|
614
614
|
"bin": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-shared-browser",
|
|
3
|
-
"version": "4.1.1-next.
|
|
3
|
+
"version": "4.1.1-next.358.1",
|
|
4
4
|
"description": "One real Chromium session per Paseo workspace, shared live across every connected client.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"browser",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@getpaseo/plugin": "0.14.0",
|
|
59
59
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
60
|
-
"agent-browser": "0.
|
|
60
|
+
"agent-browser": "0.40.0",
|
|
61
61
|
"esbuild": "0.28.2",
|
|
62
62
|
"zod": "4.6.7"
|
|
63
63
|
},
|
|
@@ -18,7 +18,7 @@ function resolveDependencyRoot(packageName) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
-
const expectedVersion = "0.
|
|
21
|
+
const expectedVersion = "0.40.0";
|
|
22
22
|
const hostPlatform = platform();
|
|
23
23
|
const hostArch = arch();
|
|
24
24
|
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -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.
|
|
60
|
+
export const AGENT_BROWSER_VERSION = "0.40.0";
|
|
61
61
|
const PRIVATE_DIRECTORY_MODE = 0o700;
|
|
62
62
|
const PRIVATE_FILE_MODE = 0o600;
|
|
63
63
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
@@ -379,22 +379,30 @@ export class AgentBrowserRuntime {
|
|
|
379
379
|
// From here a nonce-owned daemon may exist even if open rejects (e.g. navigation failure).
|
|
380
380
|
this.daemonLaunched = true;
|
|
381
381
|
this.daemon = null;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
this.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
382
|
+
let opened: unknown;
|
|
383
|
+
try {
|
|
384
|
+
opened = await this.invoke([
|
|
385
|
+
"--session",
|
|
386
|
+
this.session,
|
|
387
|
+
"--profile",
|
|
388
|
+
this.profilePath,
|
|
389
|
+
"--executable-path",
|
|
390
|
+
this.executablePath,
|
|
391
|
+
...(videoExtension ? ["--extension", videoExtension] : []),
|
|
392
|
+
"--hide-scrollbars",
|
|
393
|
+
"false",
|
|
394
|
+
...(this.headed ? ["--headed"] : []),
|
|
395
|
+
...(chromiumArguments ? ["--args", chromiumArguments] : []),
|
|
396
|
+
"--json",
|
|
397
|
+
"open",
|
|
398
|
+
this.initialUrl,
|
|
399
|
+
]);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
// Record the daemon a failed open left behind: since agent-browser 0.40.0 `close`
|
|
402
|
+
// removes the PID file before returning, after which its exit is unobservable.
|
|
403
|
+
await this.captureDaemon().catch(() => undefined);
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
398
406
|
this.assertRuntimeOpen();
|
|
399
407
|
const targetId = findString(opened, ["targetId", "target_id"]);
|
|
400
408
|
await this.protectIpcMetadata();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** The session daemon is signalled only while it is still the exact process this runtime launched. */
|
|
2
2
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
3
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
-
import { AgentBrowserRuntime } from "./agent-browser-runtime";
|
|
8
|
+
import { AGENT_BROWSER_VERSION, AgentBrowserRuntime } from "./agent-browser-runtime";
|
|
8
9
|
import { RUNTIME_OWNER_VARIABLE } from "./process-identity";
|
|
9
10
|
|
|
10
11
|
const probe = vi.hoisted(() => ({
|
|
@@ -32,20 +33,31 @@ vi.mock("./process-identity", async (importOriginal) => {
|
|
|
32
33
|
};
|
|
33
34
|
});
|
|
34
35
|
|
|
35
|
-
const cli = vi.hoisted(() => ({
|
|
36
|
+
const cli = vi.hoisted(() => ({
|
|
37
|
+
onClose: null as null | (() => void),
|
|
38
|
+
closes: 0,
|
|
39
|
+
/** Set to make `open` fail after its side effect, like a navigation failure. */
|
|
40
|
+
onOpen: null as null | (() => void),
|
|
41
|
+
version: "0.37.1",
|
|
42
|
+
}));
|
|
36
43
|
vi.mock("node:child_process", async (importOriginal) => ({
|
|
37
44
|
...(await importOriginal<typeof import("node:child_process")>()),
|
|
38
45
|
execFile: (
|
|
39
46
|
_file: string,
|
|
40
47
|
args: string[],
|
|
41
48
|
_options: unknown,
|
|
42
|
-
callback: (error: null, result
|
|
49
|
+
callback: (error: Error | null, result?: { stdout: string }) => void,
|
|
43
50
|
) => {
|
|
44
51
|
if (args.includes("close")) {
|
|
45
52
|
cli.closes += 1;
|
|
46
53
|
cli.onClose?.();
|
|
47
54
|
}
|
|
48
|
-
|
|
55
|
+
if (args.includes("open") && cli.onOpen) {
|
|
56
|
+
cli.onOpen();
|
|
57
|
+
callback(new Error("Navigation failed: net::ERR_CONNECTION_REFUSED"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
callback(null, { stdout: args.includes("--version") ? `${cli.version}\n` : "{}\n" });
|
|
49
61
|
},
|
|
50
62
|
}));
|
|
51
63
|
|
|
@@ -58,6 +70,8 @@ afterEach(async () => {
|
|
|
58
70
|
);
|
|
59
71
|
cli.onClose = null;
|
|
60
72
|
cli.closes = 0;
|
|
73
|
+
cli.onOpen = null;
|
|
74
|
+
cli.version = "0.37.1";
|
|
61
75
|
probe.unreadable = false;
|
|
62
76
|
probe.simulated = null;
|
|
63
77
|
vi.restoreAllMocks();
|
|
@@ -273,6 +287,27 @@ describe.each(ownershipModes)(
|
|
|
273
287
|
expect(daemon.pid && alive(daemon.pid)).toBe(false);
|
|
274
288
|
});
|
|
275
289
|
|
|
290
|
+
it("confirms the exit of a daemon left by a failed open even when close removes its PID file", async () => {
|
|
291
|
+
const f = await fixture();
|
|
292
|
+
const daemon = await ownChild(f.native.ownerNonce);
|
|
293
|
+
own(daemon);
|
|
294
|
+
const pidFile = join(f.ipc, "owned.pid");
|
|
295
|
+
cli.version = AGENT_BROWSER_VERSION;
|
|
296
|
+
// The daemon starts and records its PID, then the initial navigation fails.
|
|
297
|
+
cli.onOpen = () => writeFileSync(pidFile, String(daemon.pid));
|
|
298
|
+
// agent-browser >= 0.40.0: close returns only after the daemon removed its PID file.
|
|
299
|
+
cli.onClose = () => {
|
|
300
|
+
daemon.kill("SIGTERM");
|
|
301
|
+
rmSync(pidFile, { force: true });
|
|
302
|
+
};
|
|
303
|
+
await expect(f.runtime.launch()).rejects.toThrow("agent-browser command failed");
|
|
304
|
+
const kill = vi.spyOn(process, "kill");
|
|
305
|
+
await f.runtime.shutdown();
|
|
306
|
+
await exited(daemon);
|
|
307
|
+
expect(kill).not.toHaveBeenCalledWith(daemon.pid, "SIGKILL");
|
|
308
|
+
expect(daemon.pid && alive(daemon.pid)).toBe(false);
|
|
309
|
+
});
|
|
310
|
+
|
|
276
311
|
it("shares one in-flight shutdown and never reports success after a failed one", async () => {
|
|
277
312
|
const f = await fixture(150);
|
|
278
313
|
const daemon = await ownChild();
|
|
@@ -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.
|
|
26
|
+
callback(null, { stdout: args.includes("--version") ? "0.40.0\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.
|
|
18
|
+
) => callback(null, { stdout: "0.40.0\n" }),
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
21
|
const temporary: string[] = [];
|