negotium 0.1.25 → 0.1.26
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 +2 -0
- package/dist/agent-helpers.js +2 -2
- package/dist/agent-helpers.js.map +2 -2
- package/dist/cron.js +93 -38
- package/dist/cron.js.map +6 -5
- package/dist/hosted-agent.js +2 -2
- package/dist/hosted-agent.js.map +2 -2
- package/dist/main.js +149 -94
- package/dist/main.js.map +6 -5
- package/dist/runtime/src/platform/playwright/headed-launch.ts +61 -0
- package/dist/runtime/src/platform/playwright/manager.ts +39 -9
- package/dist/runtime/src/version.ts +1 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { accessSync, constants } from "node:fs";
|
|
2
|
+
import { delimiter, isAbsolute, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface HeadedPlaywrightSpawnSpec {
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
virtualDisplay: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface HeadedPlaywrightSpawnOptions {
|
|
11
|
+
platform?: NodeJS.Platform;
|
|
12
|
+
environment?: NodeJS.ProcessEnv;
|
|
13
|
+
findExecutable?: (command: string, environment: NodeJS.ProcessEnv) => string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findExecutableOnPath(
|
|
17
|
+
command: string,
|
|
18
|
+
environment: NodeJS.ProcessEnv = process.env,
|
|
19
|
+
): string | null {
|
|
20
|
+
const candidates = isAbsolute(command)
|
|
21
|
+
? [command]
|
|
22
|
+
: (environment.PATH ?? "")
|
|
23
|
+
.split(delimiter)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((directory) => resolve(directory, command));
|
|
26
|
+
for (const candidate of candidates) {
|
|
27
|
+
try {
|
|
28
|
+
accessSync(candidate, constants.X_OK);
|
|
29
|
+
return candidate;
|
|
30
|
+
} catch {
|
|
31
|
+
// Keep searching PATH.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Select the safe argv-only launcher for a visible browser MCP. */
|
|
38
|
+
export function resolveHeadedPlaywrightSpawn(
|
|
39
|
+
command: string,
|
|
40
|
+
args: readonly string[],
|
|
41
|
+
options: HeadedPlaywrightSpawnOptions = {},
|
|
42
|
+
): HeadedPlaywrightSpawnSpec {
|
|
43
|
+
const platform = options.platform ?? process.platform;
|
|
44
|
+
const environment = options.environment ?? process.env;
|
|
45
|
+
const hasDisplay = Boolean(environment.DISPLAY?.trim() || environment.WAYLAND_DISPLAY?.trim());
|
|
46
|
+
if (platform !== "linux" || hasDisplay) {
|
|
47
|
+
return { command, args: [...args], virtualDisplay: false };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const xvfbRun = (options.findExecutable ?? findExecutableOnPath)("xvfb-run", environment);
|
|
51
|
+
if (!xvfbRun) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"Headed Playwright on Linux requires DISPLAY/WAYLAND_DISPLAY or xvfb-run; install Xvfb and ensure xvfb-run is on PATH",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
command: xvfbRun,
|
|
58
|
+
args: ["-a", "-s", "-screen 0 1440x1000x24", command, ...args],
|
|
59
|
+
virtualDisplay: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -22,8 +22,11 @@ import {
|
|
|
22
22
|
resolveBrowserProxy,
|
|
23
23
|
} from "#platform/config";
|
|
24
24
|
import { delay } from "#platform/delay";
|
|
25
|
-
|
|
26
25
|
import { logger } from "#platform/logger";
|
|
26
|
+
import {
|
|
27
|
+
type HeadedPlaywrightSpawnSpec,
|
|
28
|
+
resolveHeadedPlaywrightSpawn,
|
|
29
|
+
} from "#platform/playwright/headed-launch";
|
|
27
30
|
import { sanitizeTopicName } from "#security/sanitize";
|
|
28
31
|
import {
|
|
29
32
|
assignTopicBrowserProfile,
|
|
@@ -783,11 +786,25 @@ async function spawnPlaywright(
|
|
|
783
786
|
|
|
784
787
|
const command = browserBin.endsWith(".mjs") ? process.execPath : browserBin;
|
|
785
788
|
const args = browserBin.endsWith(".mjs") ? [browserBin, ...mcpArgs] : mcpArgs;
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
789
|
+
let spawnSpec: HeadedPlaywrightSpawnSpec;
|
|
790
|
+
try {
|
|
791
|
+
spawnSpec = resolveHeadedPlaywrightSpawn(command, args, { environment: childEnv });
|
|
792
|
+
} catch (err) {
|
|
793
|
+
releasePort(port);
|
|
794
|
+
throw err;
|
|
795
|
+
}
|
|
796
|
+
let proc: ChildProcess;
|
|
797
|
+
try {
|
|
798
|
+
proc = spawn(spawnSpec.command, spawnSpec.args, {
|
|
799
|
+
stdio: "ignore",
|
|
800
|
+
detached: false,
|
|
801
|
+
env: childEnv,
|
|
802
|
+
});
|
|
803
|
+
} catch (err) {
|
|
804
|
+
releasePort(port);
|
|
805
|
+
throw err;
|
|
806
|
+
}
|
|
807
|
+
const spawnError = waitForChildProcessSpawnError(proc);
|
|
791
808
|
|
|
792
809
|
// When the MCP dies on its own (crash / OOM), its Chrome subtree is reparented
|
|
793
810
|
// to init and escapes the tracked-instance map. Reap it by user-data-dir here
|
|
@@ -835,8 +852,11 @@ async function spawnPlaywright(
|
|
|
835
852
|
capability,
|
|
836
853
|
});
|
|
837
854
|
|
|
838
|
-
const ready =
|
|
839
|
-
(
|
|
855
|
+
const ready = await Promise.race([
|
|
856
|
+
(async () =>
|
|
857
|
+
(await waitForServer(port, 10_000)) && (await supportsOwnerCleanup(port, capability)))(),
|
|
858
|
+
spawnError,
|
|
859
|
+
]);
|
|
840
860
|
if (!ready) {
|
|
841
861
|
const exitCode = proc.exitCode;
|
|
842
862
|
killInstance(instanceKey);
|
|
@@ -853,7 +873,10 @@ async function spawnPlaywright(
|
|
|
853
873
|
);
|
|
854
874
|
}
|
|
855
875
|
writePortFile(instanceKey, port);
|
|
856
|
-
logger.info(
|
|
876
|
+
logger.info(
|
|
877
|
+
{ instanceKey, port, pid: proc.pid, ready, virtualDisplay: spawnSpec.virtualDisplay },
|
|
878
|
+
"Playwright MCP started",
|
|
879
|
+
);
|
|
857
880
|
return port;
|
|
858
881
|
}
|
|
859
882
|
|
|
@@ -1234,6 +1257,13 @@ export function waitForChildProcessExit(proc: ChildProcess, timeoutMs: number):
|
|
|
1234
1257
|
});
|
|
1235
1258
|
}
|
|
1236
1259
|
|
|
1260
|
+
/** Reject immediately when the OS cannot launch a child process. */
|
|
1261
|
+
export function waitForChildProcessSpawnError(proc: Pick<ChildProcess, "once">): Promise<never> {
|
|
1262
|
+
return new Promise<never>((_resolve, reject) => {
|
|
1263
|
+
proc.once("error", reject);
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1237
1267
|
// Proactively evict all idle instances every 30 minutes
|
|
1238
1268
|
// Every 30 minutes: evict idle instances, then reap any orphaned browser the
|
|
1239
1269
|
// tracked-instance map has lost sight of. The orphan reap is only a backstop —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.1.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.1.26";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const NEGOTIUM_VERSION = "0.1.
|
|
1
|
+
export declare const NEGOTIUM_VERSION = "0.1.26";
|