negotium 0.1.47 → 0.1.49
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/dist/agent-helpers.js +4897 -4411
- package/dist/agent-helpers.js.map +30 -27
- package/dist/background-bash.js +12 -15
- package/dist/background-bash.js.map +3 -3
- package/dist/browser-runtime.js +465 -63
- package/dist/browser-runtime.js.map +9 -6
- package/dist/{chunk-x8dze1pj.js → chunk-5rpw9d1f.js} +147 -16
- package/dist/{chunk-x8dze1pj.js.map → chunk-5rpw9d1f.js.map} +3 -3
- package/dist/hosted-agent.js +156 -429
- package/dist/hosted-agent.js.map +8 -10
- package/dist/main.js +6199 -1476
- package/dist/main.js.map +61 -33
- package/dist/mcp-factories.js +4600 -3708
- package/dist/mcp-factories.js.map +39 -35
- package/dist/prompts.js +12 -15
- package/dist/prompts.js.map +3 -3
- package/dist/query-runtime.js +12 -15
- package/dist/query-runtime.js.map +3 -3
- package/dist/registry.js +116 -24
- package/dist/registry.js.map +8 -9
- package/dist/rollout.js +14 -2
- package/dist/rollout.js.map +3 -3
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +3 -2
- package/dist/runtime/src/agents/claude-registry.ts +3 -1
- package/dist/runtime/src/agents/codex-registry.ts +3 -1
- package/dist/runtime/src/agents/compaction-support.ts +75 -0
- package/dist/runtime/src/agents/contracts.ts +4 -0
- package/dist/runtime/src/agents/fork.ts +2 -2
- package/dist/runtime/src/agents/index.ts +13 -10
- package/dist/runtime/src/agents/maestro-registry.ts +108 -36
- package/dist/runtime/src/agents/registry.ts +15 -4
- package/dist/runtime/src/agents/topic-agent-switch.ts +3 -3
- package/dist/runtime/src/agents/topic-cleanup.ts +2 -2
- package/dist/runtime/src/config-public.ts +2 -0
- package/dist/runtime/src/index.ts +11 -1
- package/dist/runtime/src/mcp/agent-health-server.ts +4 -305
- package/dist/runtime/src/mcp/background-bash-server.ts +2 -0
- package/dist/runtime/src/mcp/factories/agent-health.ts +391 -0
- package/dist/runtime/src/mcp/factories/index.ts +4 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +114 -0
- package/dist/runtime/src/mcp/session-comm/default-host.ts +368 -0
- package/dist/runtime/src/mcp-runtime-host.ts +58 -0
- package/dist/runtime/src/node-host.ts +79 -0
- package/dist/runtime/src/platform/config.ts +16 -20
- package/dist/runtime/src/platform/mcp-config.ts +87 -32
- package/dist/runtime/src/platform/playwright/browser-processes.ts +4 -8
- package/dist/runtime/src/platform/playwright/manager.ts +61 -58
- package/dist/runtime/src/platform/playwright/vault-broker.ts +251 -0
- package/dist/runtime/src/prompts/sessions/_shared-tools.md +2 -2
- package/dist/runtime/src/runtime/inbox.ts +4 -4
- package/dist/runtime/src/runtime/turn-runner.ts +2 -2
- package/dist/runtime/src/topics/derive.ts +4 -4
- package/dist/runtime/src/topics/session.ts +11 -9
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +12 -15
- package/dist/runtime-helpers.js.map +3 -3
- package/dist/types/packages/core/src/agents/claude-registry.d.ts +2 -1
- package/dist/types/packages/core/src/agents/codex-registry.d.ts +2 -1
- package/dist/types/packages/core/src/agents/compaction-support.d.ts +4 -0
- package/dist/types/packages/core/src/agents/contracts.d.ts +3 -0
- package/dist/types/packages/core/src/agents/maestro-registry.d.ts +2 -14
- package/dist/types/packages/core/src/agents/registry.d.ts +3 -2
- package/dist/types/packages/core/src/mcp/factories/agent-health.d.ts +5 -0
- package/dist/types/packages/core/src/mcp/factories/index.d.ts +1 -0
- package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +22 -0
- package/dist/types/packages/core/src/platform/config.d.ts +6 -5
- package/dist/types/packages/core/src/platform/playwright/browser-processes.d.ts +1 -1
- package/dist/types/packages/core/src/platform/playwright/manager.d.ts +0 -1
- package/dist/types/packages/core/src/platform/playwright/vault-broker.d.ts +6 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +12 -15
- package/dist/vault.js.map +3 -3
- package/install-browser-rs.mjs +5 -5
- package/package.json +1 -2
- package/dist/runtime/scripts/browser-passkey-policy.mjs +0 -47
- package/dist/runtime/scripts/browser-vault-transform.mjs +0 -173
- package/dist/runtime/scripts/browser-webauthn-policy.mjs +0 -37
- package/dist/runtime/scripts/mcp-patchright-http.mjs +0 -600
package/dist/browser-runtime.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
readFileSync,
|
|
20
20
|
writeFileSync
|
|
21
21
|
} from "fs";
|
|
22
|
+
import { createRequire } from "module";
|
|
22
23
|
import { homedir } from "os";
|
|
23
24
|
import { dirname, join, resolve } from "path";
|
|
24
25
|
import { fileURLToPath } from "url";
|
|
@@ -108,20 +109,8 @@ var DM_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "dm");
|
|
|
108
109
|
var SESSION_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "sessions");
|
|
109
110
|
var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
110
111
|
var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (override) {
|
|
114
|
-
if (!/(^|\/)(mcp-patchright|mcp-patchright-http\.mjs)$/.test(override)) {
|
|
115
|
-
throw new Error("NEGOTIUM_BROWSER_MCP_BIN must point to the authenticated mcp-patchright wrapper.");
|
|
116
|
-
}
|
|
117
|
-
return override;
|
|
118
|
-
}
|
|
119
|
-
return PATCHRIGHT_MCP_BIN;
|
|
120
|
-
}
|
|
121
|
-
var PATCHRIGHT_MCP_BIN = resolve(PROJECT_ROOT, "scripts/mcp-patchright-http.mjs");
|
|
122
|
-
var PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
|
|
123
|
-
var BROWSER_RS_VERSION = "v0.1.13";
|
|
124
|
-
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.13";
|
|
112
|
+
var BROWSER_RS_VERSION = "v0.1.16";
|
|
113
|
+
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
125
114
|
function versionAtLeast(actualVersion, minimumVersion) {
|
|
126
115
|
const actual = actualVersion.split(".").map(Number);
|
|
127
116
|
const minimum = minimumVersion.split(".").map(Number);
|
|
@@ -164,6 +153,13 @@ function resolveBrowserRsBin(envValue) {
|
|
|
164
153
|
}
|
|
165
154
|
}
|
|
166
155
|
var BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
|
|
156
|
+
function resolveBrowserMcpBin(envValue) {
|
|
157
|
+
const override = envValue?.trim();
|
|
158
|
+
if (override)
|
|
159
|
+
return resolve(override);
|
|
160
|
+
return BROWSER_RS_BIN ?? resolve(STATE_DIR, "bin", "browser-rs", BROWSER_RS_VERSION, "browser-rs");
|
|
161
|
+
}
|
|
162
|
+
var PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
|
|
167
163
|
function resolveBrowserProxy() {
|
|
168
164
|
const raw = envText("BROWSER_PROXY_URL");
|
|
169
165
|
if (!raw)
|
|
@@ -187,6 +183,7 @@ function resolveBrowserProxy() {
|
|
|
187
183
|
return proxy;
|
|
188
184
|
}
|
|
189
185
|
var TSX_BIN = resolveDependencyBin("tsx");
|
|
186
|
+
var TSX_LOADER = createRequire(import.meta.url).resolve("tsx");
|
|
190
187
|
var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
191
188
|
var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
192
189
|
var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
@@ -738,18 +735,18 @@ function killProcessTreeChildren(pid) {
|
|
|
738
735
|
}
|
|
739
736
|
// ../../packages/core/src/platform/playwright/manager.ts
|
|
740
737
|
import { execFileSync as execFileSync3, spawn } from "child_process";
|
|
741
|
-
import { createHash, randomBytes as
|
|
738
|
+
import { createHash as createHash2, randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
742
739
|
import {
|
|
743
740
|
cpSync,
|
|
744
741
|
existsSync as existsSync2,
|
|
745
|
-
mkdirSync as
|
|
742
|
+
mkdirSync as mkdirSync4,
|
|
746
743
|
readFileSync as readFileSync2,
|
|
747
744
|
renameSync,
|
|
748
745
|
rmSync,
|
|
749
746
|
unlinkSync as unlinkSync2,
|
|
750
747
|
writeFileSync as writeFileSync2
|
|
751
748
|
} from "fs";
|
|
752
|
-
import { dirname as dirname3, join as
|
|
749
|
+
import { dirname as dirname3, join as join5, resolve as resolve6 } from "path";
|
|
753
750
|
|
|
754
751
|
// ../../packages/core/src/platform/delay.ts
|
|
755
752
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -854,6 +851,388 @@ async function probePlaywrightMcpTransports(port, capability, fetchImpl = fetch)
|
|
|
854
851
|
});
|
|
855
852
|
}
|
|
856
853
|
|
|
854
|
+
// ../../packages/core/src/platform/playwright/vault-broker.ts
|
|
855
|
+
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
856
|
+
import { chmodSync as chmodSync3 } from "fs";
|
|
857
|
+
import { createServer as createServer2 } from "net";
|
|
858
|
+
import { tmpdir } from "os";
|
|
859
|
+
import { join as join4 } from "path";
|
|
860
|
+
|
|
861
|
+
// ../../packages/core/src/storage/vault.ts
|
|
862
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
863
|
+
import { join as join3 } from "path";
|
|
864
|
+
|
|
865
|
+
// ../../packages/core/src/storage/vault-crypto.ts
|
|
866
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes2 } from "crypto";
|
|
867
|
+
var ENVELOPE_PREFIX = "otium-vault:v1:";
|
|
868
|
+
var IV_BYTES = 12;
|
|
869
|
+
var KEY_BYTES = 32;
|
|
870
|
+
function encryptionKey(masterKey = VAULT_MASTER_KEY) {
|
|
871
|
+
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
872
|
+
}
|
|
873
|
+
function aad(userId, key) {
|
|
874
|
+
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
875
|
+
}
|
|
876
|
+
function isEncryptedVaultValue(value) {
|
|
877
|
+
return value.startsWith(ENVELOPE_PREFIX);
|
|
878
|
+
}
|
|
879
|
+
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
880
|
+
const iv = randomBytes2(IV_BYTES);
|
|
881
|
+
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
882
|
+
cipher.setAAD(aad(userId, key));
|
|
883
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
884
|
+
const tag = cipher.getAuthTag();
|
|
885
|
+
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
886
|
+
}
|
|
887
|
+
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
888
|
+
if (!isEncryptedVaultValue(storedValue)) {
|
|
889
|
+
return { value: storedValue, legacyPlaintext: true };
|
|
890
|
+
}
|
|
891
|
+
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
892
|
+
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
893
|
+
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
894
|
+
throw new Error("Invalid encrypted vault value");
|
|
895
|
+
}
|
|
896
|
+
const iv = Buffer.from(ivPart, "base64url");
|
|
897
|
+
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
898
|
+
const tag = Buffer.from(tagPart, "base64url");
|
|
899
|
+
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
900
|
+
throw new Error("Invalid encrypted vault value");
|
|
901
|
+
}
|
|
902
|
+
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
903
|
+
decipher.setAAD(aad(userId, key));
|
|
904
|
+
decipher.setAuthTag(tag);
|
|
905
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
906
|
+
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// ../../packages/core/src/storage/vault.ts
|
|
910
|
+
var vaultDb;
|
|
911
|
+
var vaultMasterKey = VAULT_MASTER_KEY;
|
|
912
|
+
function initializeVaultDatabase(database) {
|
|
913
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
914
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
915
|
+
database.exec(`
|
|
916
|
+
CREATE TABLE IF NOT EXISTS vault (
|
|
917
|
+
user_id TEXT NOT NULL,
|
|
918
|
+
key TEXT NOT NULL,
|
|
919
|
+
value TEXT NOT NULL,
|
|
920
|
+
description TEXT NOT NULL DEFAULT '',
|
|
921
|
+
PRIMARY KEY (user_id, key)
|
|
922
|
+
)
|
|
923
|
+
`);
|
|
924
|
+
{
|
|
925
|
+
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
926
|
+
const uid = cols.find((c) => c.name === "user_id");
|
|
927
|
+
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
928
|
+
database.exec("BEGIN");
|
|
929
|
+
database.exec(`
|
|
930
|
+
CREATE TABLE vault_migrated (
|
|
931
|
+
user_id TEXT NOT NULL,
|
|
932
|
+
key TEXT NOT NULL,
|
|
933
|
+
value TEXT NOT NULL,
|
|
934
|
+
description TEXT NOT NULL DEFAULT '',
|
|
935
|
+
PRIMARY KEY (user_id, key)
|
|
936
|
+
)
|
|
937
|
+
`);
|
|
938
|
+
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
939
|
+
database.exec("DROP TABLE vault");
|
|
940
|
+
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
941
|
+
database.exec("COMMIT");
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
function openVaultDatabase(dataDir) {
|
|
946
|
+
const path = join3(dataDir, "vault.db");
|
|
947
|
+
mkdirSync3(dataDir, { recursive: true });
|
|
948
|
+
const database = new Database(path, { create: true });
|
|
949
|
+
chmodSync2(path, 384);
|
|
950
|
+
initializeVaultDatabase(database);
|
|
951
|
+
return database;
|
|
952
|
+
}
|
|
953
|
+
function activeVaultDatabase() {
|
|
954
|
+
if (!vaultDb)
|
|
955
|
+
vaultDb = openVaultDatabase(DATA_DIR);
|
|
956
|
+
return vaultDb;
|
|
957
|
+
}
|
|
958
|
+
var VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
959
|
+
function normalizeVaultKey(key) {
|
|
960
|
+
return key.trim().toUpperCase();
|
|
961
|
+
}
|
|
962
|
+
function decryptRow(userId, key, storedValue) {
|
|
963
|
+
const database = activeVaultDatabase();
|
|
964
|
+
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
965
|
+
if (decoded.legacyPlaintext) {
|
|
966
|
+
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
967
|
+
}
|
|
968
|
+
return decoded.value;
|
|
969
|
+
}
|
|
970
|
+
function vaultListWithValues(userId) {
|
|
971
|
+
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
972
|
+
return rows.map((row) => ({
|
|
973
|
+
key: row.key,
|
|
974
|
+
description: row.description,
|
|
975
|
+
value: decryptRow(userId, row.key, row.value)
|
|
976
|
+
}));
|
|
977
|
+
}
|
|
978
|
+
function encodedSecretForms(value) {
|
|
979
|
+
const forms = new Set([
|
|
980
|
+
value,
|
|
981
|
+
encodeURIComponent(value),
|
|
982
|
+
Buffer.from(value, "utf8").toString("base64"),
|
|
983
|
+
Buffer.from(value, "utf8").toString("base64url"),
|
|
984
|
+
Buffer.from(value, "utf8").toString("hex")
|
|
985
|
+
]);
|
|
986
|
+
forms.delete("");
|
|
987
|
+
return [...forms].sort((a, b) => b.length - a.length);
|
|
988
|
+
}
|
|
989
|
+
function redactVaultSecrets(userId, text) {
|
|
990
|
+
const candidates = vaultListWithValues(userId).flatMap((entry) => encodedSecretForms(entry.value).map((form) => ({ form, key: entry.key }))).sort((a, b) => b.form.length - a.form.length || a.key.localeCompare(b.key));
|
|
991
|
+
if (candidates.length === 0)
|
|
992
|
+
return text;
|
|
993
|
+
const candidatesByFirstCharacter = new Map;
|
|
994
|
+
for (const candidate of candidates) {
|
|
995
|
+
const first = candidate.form[0];
|
|
996
|
+
if (!first)
|
|
997
|
+
continue;
|
|
998
|
+
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
999
|
+
bucket.push(candidate);
|
|
1000
|
+
candidatesByFirstCharacter.set(first, bucket);
|
|
1001
|
+
}
|
|
1002
|
+
let redacted = "";
|
|
1003
|
+
let offset = 0;
|
|
1004
|
+
while (offset < text.length) {
|
|
1005
|
+
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
1006
|
+
if (!match) {
|
|
1007
|
+
redacted += text[offset];
|
|
1008
|
+
offset += 1;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
redacted += `[REDACTED:${match.key}]`;
|
|
1012
|
+
offset += match.form.length;
|
|
1013
|
+
}
|
|
1014
|
+
return redacted;
|
|
1015
|
+
}
|
|
1016
|
+
// ../../packages/core/src/platform/playwright/vault-broker.ts
|
|
1017
|
+
var MAX_REQUEST_BYTES = 16 * 1024 * 1024;
|
|
1018
|
+
var DEFAULT_OUTPUT_LIMIT = 1e5;
|
|
1019
|
+
var LEASE_TTL_MS = 5 * 60000;
|
|
1020
|
+
var MAX_ACTIVE_LEASES = 1024;
|
|
1021
|
+
var BOUNDED_OUTPUTS = {
|
|
1022
|
+
browser_snapshot: { argument: "maxLength", field: "snapshot" },
|
|
1023
|
+
browser_api_request: { argument: "maxBytes", field: "body" },
|
|
1024
|
+
browser_get_visible_text: { argument: "maxLength", field: "text" },
|
|
1025
|
+
browser_get_visible_html: { argument: "maxLength", field: "html" }
|
|
1026
|
+
};
|
|
1027
|
+
function deepMapStrings(value, transform) {
|
|
1028
|
+
if (typeof value === "string")
|
|
1029
|
+
return transform(value);
|
|
1030
|
+
if (Array.isArray(value))
|
|
1031
|
+
return value.map((entry) => deepMapStrings(entry, transform));
|
|
1032
|
+
if (!value || typeof value !== "object")
|
|
1033
|
+
return value;
|
|
1034
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1035
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
1036
|
+
return value;
|
|
1037
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, deepMapStrings(entry, transform)]));
|
|
1038
|
+
}
|
|
1039
|
+
function encodedSecretForms2(value) {
|
|
1040
|
+
return [
|
|
1041
|
+
...new Set([
|
|
1042
|
+
value,
|
|
1043
|
+
encodeURIComponent(value),
|
|
1044
|
+
Buffer.from(value, "utf8").toString("base64"),
|
|
1045
|
+
Buffer.from(value, "utf8").toString("base64url"),
|
|
1046
|
+
Buffer.from(value, "utf8").toString("hex")
|
|
1047
|
+
])
|
|
1048
|
+
].filter(Boolean).sort((left, right) => right.length - left.length);
|
|
1049
|
+
}
|
|
1050
|
+
function prepareInput(tool, value) {
|
|
1051
|
+
const config = BOUNDED_OUTPUTS[tool];
|
|
1052
|
+
if (!config || !value || typeof value !== "object" || Array.isArray(value))
|
|
1053
|
+
return { value };
|
|
1054
|
+
const input = value;
|
|
1055
|
+
const requested = input[config.argument];
|
|
1056
|
+
if (requested !== undefined && (!Number.isSafeInteger(requested) || Number(requested) <= 0)) {
|
|
1057
|
+
return { value };
|
|
1058
|
+
}
|
|
1059
|
+
const limit = requested === undefined ? DEFAULT_OUTPUT_LIMIT : Number(requested);
|
|
1060
|
+
return {
|
|
1061
|
+
value: { ...input, [config.argument]: Number.MAX_SAFE_INTEGER },
|
|
1062
|
+
boundary: { field: config.field, limit }
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
function redactTextEntry(entry, redact, boundary) {
|
|
1066
|
+
if (!entry || typeof entry !== "object")
|
|
1067
|
+
return redact(entry);
|
|
1068
|
+
const content = entry;
|
|
1069
|
+
if (content.type !== "text" || typeof content.text !== "string")
|
|
1070
|
+
return redact(entry);
|
|
1071
|
+
try {
|
|
1072
|
+
const parsed = redact(JSON.parse(content.text));
|
|
1073
|
+
if (boundary && parsed && typeof parsed === "object") {
|
|
1074
|
+
const object = parsed;
|
|
1075
|
+
const fieldValue = object[boundary.field];
|
|
1076
|
+
if (typeof fieldValue === "string") {
|
|
1077
|
+
const originalExceeded = typeof object.length === "number" && object.length > boundary.limit;
|
|
1078
|
+
if (fieldValue.length > boundary.limit)
|
|
1079
|
+
object[boundary.field] = fieldValue.slice(0, boundary.limit);
|
|
1080
|
+
if (originalExceeded || fieldValue.length > boundary.limit)
|
|
1081
|
+
object.truncated = true;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return { ...content, text: JSON.stringify(parsed, null, 2) };
|
|
1085
|
+
} catch {
|
|
1086
|
+
const redacted = redact(content.text);
|
|
1087
|
+
if (boundary && typeof redacted === "string" && redacted.length > boundary.limit) {
|
|
1088
|
+
return {
|
|
1089
|
+
...content,
|
|
1090
|
+
text: `${redacted.slice(0, boundary.limit)}
|
|
1091
|
+
[truncated]`
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
return { ...content, text: redacted };
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
function secureOutput(result, redact, boundary) {
|
|
1098
|
+
if (!result || typeof result !== "object")
|
|
1099
|
+
return redact(result);
|
|
1100
|
+
const object = result;
|
|
1101
|
+
const content = Array.isArray(object.content) ? object.content.map((entry) => redactTextEntry(entry, redact, boundary)) : object.content;
|
|
1102
|
+
return redact({ ...object, content });
|
|
1103
|
+
}
|
|
1104
|
+
function authorized(actual, expected) {
|
|
1105
|
+
if (typeof actual !== "string")
|
|
1106
|
+
return false;
|
|
1107
|
+
const left = Buffer.from(actual);
|
|
1108
|
+
const right = Buffer.from(expected);
|
|
1109
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
1110
|
+
}
|
|
1111
|
+
async function createBrowserVaultBroker(userId) {
|
|
1112
|
+
const token = randomBytes3(32).toString("hex");
|
|
1113
|
+
const socketPath = join4(process.platform === "win32" ? tmpdir() : "/tmp", `negotium-browser-vault-${process.pid}-${randomBytes3(8).toString("hex")}.sock`);
|
|
1114
|
+
const retainedForms = new Map;
|
|
1115
|
+
const leases = new Map;
|
|
1116
|
+
const sockets = new Set;
|
|
1117
|
+
const redactRetained = (text) => {
|
|
1118
|
+
let output = text;
|
|
1119
|
+
for (const [form, key] of [...retainedForms].sort(([left], [right]) => right.length - left.length)) {
|
|
1120
|
+
output = output.replaceAll(form, `[REDACTED:${key}]`);
|
|
1121
|
+
}
|
|
1122
|
+
return output;
|
|
1123
|
+
};
|
|
1124
|
+
const redact = (value) => deepMapStrings(value, (text) => redactRetained(redactVaultSecrets(userId, text)));
|
|
1125
|
+
const handleRequest = (request) => {
|
|
1126
|
+
const id = request.id;
|
|
1127
|
+
if (!authorized(request.token, token))
|
|
1128
|
+
return { id, ok: false, error: "unauthorized" };
|
|
1129
|
+
if (request.op === "transform_input") {
|
|
1130
|
+
if (typeof request.tool !== "string")
|
|
1131
|
+
return { id, ok: false, error: "invalid tool" };
|
|
1132
|
+
const leaseCutoff = Date.now() - LEASE_TTL_MS;
|
|
1133
|
+
for (const [lease2, binding] of leases) {
|
|
1134
|
+
if (binding.createdAt < leaseCutoff)
|
|
1135
|
+
leases.delete(lease2);
|
|
1136
|
+
}
|
|
1137
|
+
if (leases.size >= MAX_ACTIVE_LEASES) {
|
|
1138
|
+
return { id, ok: false, error: "too many active leases" };
|
|
1139
|
+
}
|
|
1140
|
+
const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
|
|
1141
|
+
const substituted = deepMapStrings(request.value, (text) => text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
1142
|
+
const key = normalizeVaultKey(rawKey);
|
|
1143
|
+
const secret = entries.get(key);
|
|
1144
|
+
if (secret === undefined)
|
|
1145
|
+
return match;
|
|
1146
|
+
for (const form of encodedSecretForms2(secret))
|
|
1147
|
+
retainedForms.set(form, key);
|
|
1148
|
+
return secret;
|
|
1149
|
+
}));
|
|
1150
|
+
const prepared = prepareInput(request.tool, substituted);
|
|
1151
|
+
const lease = randomBytes3(24).toString("hex");
|
|
1152
|
+
leases.set(lease, { boundary: prepared.boundary, createdAt: Date.now() });
|
|
1153
|
+
return {
|
|
1154
|
+
id,
|
|
1155
|
+
ok: true,
|
|
1156
|
+
value: prepared.value,
|
|
1157
|
+
lease,
|
|
1158
|
+
boundary: prepared.boundary
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
if (request.op === "redact_output") {
|
|
1162
|
+
if (typeof request.lease !== "string") {
|
|
1163
|
+
return { id, ok: false, error: "invalid lease" };
|
|
1164
|
+
}
|
|
1165
|
+
const binding = leases.get(request.lease);
|
|
1166
|
+
leases.delete(request.lease);
|
|
1167
|
+
if (!binding || binding.createdAt < Date.now() - LEASE_TTL_MS) {
|
|
1168
|
+
return { id, ok: false, error: "invalid lease" };
|
|
1169
|
+
}
|
|
1170
|
+
try {
|
|
1171
|
+
const value = secureOutput(request.value, redact, binding.boundary);
|
|
1172
|
+
const serialized = JSON.stringify(value);
|
|
1173
|
+
if (redactRetained(serialized) !== serialized) {
|
|
1174
|
+
return { id, ok: false, error: "redaction verification failed" };
|
|
1175
|
+
}
|
|
1176
|
+
return { id, ok: true, value };
|
|
1177
|
+
} catch {
|
|
1178
|
+
return { id, ok: false, error: "redaction failed" };
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return { id, ok: false, error: "unknown operation" };
|
|
1182
|
+
};
|
|
1183
|
+
const server = createServer2((socket) => {
|
|
1184
|
+
sockets.add(socket);
|
|
1185
|
+
let pending = "";
|
|
1186
|
+
socket.setEncoding("utf8");
|
|
1187
|
+
socket.on("data", (chunk) => {
|
|
1188
|
+
pending += chunk;
|
|
1189
|
+
if (Buffer.byteLength(pending) > MAX_REQUEST_BYTES) {
|
|
1190
|
+
socket.destroy();
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
while (true) {
|
|
1194
|
+
const newline = pending.indexOf(`
|
|
1195
|
+
`);
|
|
1196
|
+
if (newline < 0)
|
|
1197
|
+
break;
|
|
1198
|
+
const line = pending.slice(0, newline);
|
|
1199
|
+
pending = pending.slice(newline + 1);
|
|
1200
|
+
if (!line)
|
|
1201
|
+
continue;
|
|
1202
|
+
let response;
|
|
1203
|
+
try {
|
|
1204
|
+
response = handleRequest(JSON.parse(line));
|
|
1205
|
+
} catch {
|
|
1206
|
+
response = { ok: false, error: "invalid request" };
|
|
1207
|
+
}
|
|
1208
|
+
socket.write(`${JSON.stringify(response)}
|
|
1209
|
+
`);
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
socket.once("close", () => sockets.delete(socket));
|
|
1213
|
+
});
|
|
1214
|
+
await new Promise((resolveListen, reject) => {
|
|
1215
|
+
server.once("error", reject);
|
|
1216
|
+
server.listen(socketPath, resolveListen);
|
|
1217
|
+
});
|
|
1218
|
+
chmodSync3(socketPath, 384);
|
|
1219
|
+
let closed = false;
|
|
1220
|
+
return {
|
|
1221
|
+
socketPath,
|
|
1222
|
+
token,
|
|
1223
|
+
async close() {
|
|
1224
|
+
if (closed)
|
|
1225
|
+
return;
|
|
1226
|
+
closed = true;
|
|
1227
|
+
leases.clear();
|
|
1228
|
+
retainedForms.clear();
|
|
1229
|
+
for (const socket of sockets)
|
|
1230
|
+
socket.destroy();
|
|
1231
|
+
await new Promise((resolveClose) => server.close(() => resolveClose()));
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
|
|
857
1236
|
// ../../packages/core/src/security/sanitize.ts
|
|
858
1237
|
function sanitizeTopicName(name, lowercase = false) {
|
|
859
1238
|
const safe = name.replace(/[^a-zA-Z0-9\uAC00-\uD7A3_-]/g, "_") || "_";
|
|
@@ -946,7 +1325,7 @@ function defaultNamedBinding(ownerId, rawProfile) {
|
|
|
946
1325
|
};
|
|
947
1326
|
}
|
|
948
1327
|
function legacyBrowserProfileName(topic) {
|
|
949
|
-
return `legacy_${
|
|
1328
|
+
return `legacy_${createHash2("sha256").update(topic).digest("hex").slice(0, 12)}`;
|
|
950
1329
|
}
|
|
951
1330
|
function migrateLegacyTopicProfile(ownerId, topic) {
|
|
952
1331
|
const current = getTopicBrowserProfile(topic);
|
|
@@ -957,7 +1336,7 @@ function migrateLegacyTopicProfile(ownerId, topic) {
|
|
|
957
1336
|
return current;
|
|
958
1337
|
const profile = legacyBrowserProfileName(topic);
|
|
959
1338
|
const profileDir = defaultProfileDir(ownerId, profile);
|
|
960
|
-
|
|
1339
|
+
mkdirSync4(dirname3(profileDir), { recursive: true });
|
|
961
1340
|
if (!existsSync2(profileDir))
|
|
962
1341
|
renameSync(legacyDir, profileDir);
|
|
963
1342
|
assignTopicBrowserProfile({ topicId: topic, actorUserId: ownerId, profile });
|
|
@@ -965,26 +1344,39 @@ function migrateLegacyTopicProfile(ownerId, topic) {
|
|
|
965
1344
|
return profile;
|
|
966
1345
|
}
|
|
967
1346
|
function defaultChildEnvironment(context) {
|
|
968
|
-
const
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1347
|
+
const childEnvironment = {};
|
|
1348
|
+
for (const key of [
|
|
1349
|
+
"HOME",
|
|
1350
|
+
"PATH",
|
|
1351
|
+
"TMPDIR",
|
|
1352
|
+
"TMP",
|
|
1353
|
+
"TEMP",
|
|
1354
|
+
"LANG",
|
|
1355
|
+
"LC_ALL",
|
|
1356
|
+
"LC_CTYPE",
|
|
1357
|
+
"DISPLAY",
|
|
1358
|
+
"WAYLAND_DISPLAY",
|
|
1359
|
+
"XAUTHORITY",
|
|
1360
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
1361
|
+
"XDG_RUNTIME_DIR",
|
|
1362
|
+
"XDG_CONFIG_HOME",
|
|
1363
|
+
"XDG_CACHE_HOME",
|
|
1364
|
+
"XDG_DATA_HOME",
|
|
1365
|
+
"AB_CHROME",
|
|
1366
|
+
"AB_CONNECT",
|
|
1367
|
+
"RUST_LOG",
|
|
1368
|
+
"RUST_BACKTRACE"
|
|
1369
|
+
]) {
|
|
1370
|
+
if (context.environment[key] !== undefined)
|
|
1371
|
+
childEnvironment[key] = context.environment[key];
|
|
1372
|
+
}
|
|
1373
|
+
return childEnvironment;
|
|
981
1374
|
}
|
|
982
1375
|
var defaultManagerHost = Object.freeze({
|
|
983
1376
|
portsDir: PLAYWRIGHT_PORTS_DIR,
|
|
984
1377
|
basePort: PLAYWRIGHT_BASE_PORT,
|
|
985
1378
|
maxPort: PLAYWRIGHT_MAX_PORT,
|
|
986
1379
|
browserBin: PLAYWRIGHT_MCP_BIN,
|
|
987
|
-
fallbackBrowserBin: PATCHRIGHT_MCP_BIN,
|
|
988
1380
|
browserRsBin: BROWSER_RS_BIN,
|
|
989
1381
|
resolveProxy: resolveBrowserProxy,
|
|
990
1382
|
resolveTopicBinding: defaultTopicBinding,
|
|
@@ -1009,8 +1401,8 @@ function configurePlaywrightManagerHost(overrides) {
|
|
|
1009
1401
|
if (next.basePort < 1 || next.maxPort > 65535 || next.basePort > next.maxPort) {
|
|
1010
1402
|
throw new Error("invalid Playwright manager port range");
|
|
1011
1403
|
}
|
|
1012
|
-
if (!next.browserBin
|
|
1013
|
-
throw new Error("
|
|
1404
|
+
if (!next.browserBin) {
|
|
1405
|
+
throw new Error("Browser manager gateway is required");
|
|
1014
1406
|
}
|
|
1015
1407
|
if (next.resolveInstanceDataDir !== defaultManagerHost.resolveInstanceDataDir && (next.cleanupBrowserProcessesForDataDir === defaultManagerHost.cleanupBrowserProcessesForDataDir || next.reapOrphanBrowsers === defaultManagerHost.reapOrphanBrowsers)) {
|
|
1016
1408
|
throw new Error("custom Playwright profile paths require host crash cleanup and orphan sweep hooks");
|
|
@@ -1034,26 +1426,26 @@ function parseInstanceKey(instanceKey) {
|
|
|
1034
1426
|
};
|
|
1035
1427
|
}
|
|
1036
1428
|
function portFileName(instanceKey) {
|
|
1037
|
-
return
|
|
1429
|
+
return createHash2("sha256").update(instanceKey).digest("hex").slice(0, 24);
|
|
1038
1430
|
}
|
|
1039
1431
|
function writePortFile(instanceKey, port) {
|
|
1040
1432
|
try {
|
|
1041
|
-
|
|
1042
|
-
writeFileSync2(
|
|
1433
|
+
mkdirSync4(managerHost.portsDir, { recursive: true });
|
|
1434
|
+
writeFileSync2(join5(managerHost.portsDir, portFileName(instanceKey)), String(port));
|
|
1043
1435
|
} catch (e) {
|
|
1044
1436
|
logger.warn({ err: e, instanceKey, port }, "Failed to save playwright port file");
|
|
1045
1437
|
}
|
|
1046
1438
|
}
|
|
1047
1439
|
function deletePortFile(instanceKey) {
|
|
1048
1440
|
try {
|
|
1049
|
-
unlinkSync2(
|
|
1441
|
+
unlinkSync2(join5(managerHost.portsDir, portFileName(instanceKey)));
|
|
1050
1442
|
} catch (e) {
|
|
1051
1443
|
logger.warn({ err: e, instanceKey }, "Failed to delete playwright port file");
|
|
1052
1444
|
}
|
|
1053
1445
|
}
|
|
1054
1446
|
function readPortFile(instanceKey) {
|
|
1055
1447
|
try {
|
|
1056
|
-
const port = Number.parseInt(readFileSync2(
|
|
1448
|
+
const port = Number.parseInt(readFileSync2(join5(managerHost.portsDir, portFileName(instanceKey)), "utf8").trim(), 10);
|
|
1057
1449
|
return Number.isInteger(port) ? port : null;
|
|
1058
1450
|
} catch {
|
|
1059
1451
|
return null;
|
|
@@ -1150,7 +1542,7 @@ function resolvePlaywrightCapabilityOwner(capability) {
|
|
|
1150
1542
|
const provided = Buffer.from(capability);
|
|
1151
1543
|
for (const instance of instances.values()) {
|
|
1152
1544
|
const expected = Buffer.from(instance.capability);
|
|
1153
|
-
if (provided.length === expected.length &&
|
|
1545
|
+
if (provided.length === expected.length && timingSafeEqual2(provided, expected)) {
|
|
1154
1546
|
return instance.ownerId;
|
|
1155
1547
|
}
|
|
1156
1548
|
}
|
|
@@ -1207,7 +1599,7 @@ function releasePort(port) {
|
|
|
1207
1599
|
usedPorts.delete(port);
|
|
1208
1600
|
}
|
|
1209
1601
|
function ownerDirectory(ownerId) {
|
|
1210
|
-
const digest =
|
|
1602
|
+
const digest = createHash2("sha256").update(ownerId).digest("hex").slice(0, 16);
|
|
1211
1603
|
return `${sanitizeTopicName(ownerId).slice(0, 24)}_${digest}`;
|
|
1212
1604
|
}
|
|
1213
1605
|
function defaultProfileDir(ownerId, profile) {
|
|
@@ -1228,6 +1620,7 @@ function killInstance(instanceKey, opts) {
|
|
|
1228
1620
|
} catch (e) {
|
|
1229
1621
|
logger.warn({ err: e, instanceKey }, "Failed to kill playwright instance");
|
|
1230
1622
|
}
|
|
1623
|
+
inst.vaultBroker.close();
|
|
1231
1624
|
inst.process.removeAllListeners("error");
|
|
1232
1625
|
inst.process.removeAllListeners("exit");
|
|
1233
1626
|
cleanSingletonFiles(resolveUserDataDir(instanceKey));
|
|
@@ -1282,10 +1675,13 @@ ${stderr}` : "";
|
|
|
1282
1675
|
};
|
|
1283
1676
|
return { failure, stop };
|
|
1284
1677
|
}
|
|
1285
|
-
async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin = managerHost.browserBin
|
|
1678
|
+
async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin = managerHost.browserBin) {
|
|
1679
|
+
if (!managerHost.browserRsBin) {
|
|
1680
|
+
throw new Error("Browser.rs is unavailable or below the minimum secure version");
|
|
1681
|
+
}
|
|
1286
1682
|
const userDataDir = resolveUserDataDir(instanceKey);
|
|
1287
1683
|
const port = reservedPort ?? await allocatePort();
|
|
1288
|
-
|
|
1684
|
+
mkdirSync4(userDataDir, { recursive: true });
|
|
1289
1685
|
const mcpArgs = [
|
|
1290
1686
|
"--port",
|
|
1291
1687
|
String(port),
|
|
@@ -1296,8 +1692,19 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
|
|
|
1296
1692
|
userDataDir
|
|
1297
1693
|
];
|
|
1298
1694
|
const proxy = managerHost.resolveProxy();
|
|
1299
|
-
|
|
1300
|
-
|
|
1695
|
+
if (proxy) {
|
|
1696
|
+
releasePort(port);
|
|
1697
|
+
throw new Error("Browser.rs managed mode does not support authenticated egress proxies");
|
|
1698
|
+
}
|
|
1699
|
+
const capability = randomBytes4(32).toString("hex");
|
|
1700
|
+
const spawnNonce = randomBytes4(32).toString("hex");
|
|
1701
|
+
let vaultBroker;
|
|
1702
|
+
try {
|
|
1703
|
+
vaultBroker = await createBrowserVaultBroker(ownerId);
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
releasePort(port);
|
|
1706
|
+
throw error;
|
|
1707
|
+
}
|
|
1301
1708
|
const childEnv = {
|
|
1302
1709
|
...managerHost.createChildEnvironment({
|
|
1303
1710
|
instanceKey,
|
|
@@ -1307,18 +1714,19 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
|
|
|
1307
1714
|
browserRsBin: managerHost.browserRsBin,
|
|
1308
1715
|
environment: process.env
|
|
1309
1716
|
}),
|
|
1310
|
-
|
|
1311
|
-
|
|
1717
|
+
AB_MANAGED: "1",
|
|
1718
|
+
AB_HTTP_CAPABILITY: capability,
|
|
1719
|
+
AB_SPAWN_NONCE: spawnNonce,
|
|
1720
|
+
AB_SECRET_BROKER_SOCKET: vaultBroker.socketPath,
|
|
1721
|
+
AB_SECRET_BROKER_TOKEN: vaultBroker.token
|
|
1312
1722
|
};
|
|
1313
|
-
if (proxy) {
|
|
1314
|
-
logger.info({ instanceKey, proxyServer: proxy.server }, "Browser egress proxy enabled");
|
|
1315
|
-
}
|
|
1316
1723
|
const command = browserBin.endsWith(".mjs") ? process.execPath : browserBin;
|
|
1317
1724
|
const args = browserBin.endsWith(".mjs") ? [browserBin, ...mcpArgs] : mcpArgs;
|
|
1318
1725
|
let spawnSpec;
|
|
1319
1726
|
try {
|
|
1320
1727
|
spawnSpec = resolveHeadedPlaywrightSpawn(command, args, { environment: childEnv });
|
|
1321
1728
|
} catch (err) {
|
|
1729
|
+
await vaultBroker.close();
|
|
1322
1730
|
releasePort(port);
|
|
1323
1731
|
throw err;
|
|
1324
1732
|
}
|
|
@@ -1330,6 +1738,7 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
|
|
|
1330
1738
|
env: childEnv
|
|
1331
1739
|
});
|
|
1332
1740
|
} catch (err) {
|
|
1741
|
+
await vaultBroker.close();
|
|
1333
1742
|
releasePort(port);
|
|
1334
1743
|
throw err;
|
|
1335
1744
|
}
|
|
@@ -1345,6 +1754,7 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
|
|
|
1345
1754
|
if (instances.get(instanceKey)?.process === proc) {
|
|
1346
1755
|
releasePort(port);
|
|
1347
1756
|
instances.delete(instanceKey);
|
|
1757
|
+
vaultBroker.close();
|
|
1348
1758
|
reapCrashedBrowser();
|
|
1349
1759
|
notifyPlaywrightFailure(instanceKey, {
|
|
1350
1760
|
reason: "process-error",
|
|
@@ -1358,6 +1768,7 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
|
|
|
1358
1768
|
if (wasOurs) {
|
|
1359
1769
|
releasePort(port);
|
|
1360
1770
|
instances.delete(instanceKey);
|
|
1771
|
+
vaultBroker.close();
|
|
1361
1772
|
reapCrashedBrowser();
|
|
1362
1773
|
notifyPlaywrightFailure(instanceKey, { reason: "exit", code });
|
|
1363
1774
|
}
|
|
@@ -1369,7 +1780,8 @@ async function spawnPlaywright(instanceKey, ownerId, reservedPort, browserBin =
|
|
|
1369
1780
|
ownerId,
|
|
1370
1781
|
startedAt: now,
|
|
1371
1782
|
lastUsedAt: now,
|
|
1372
|
-
capability
|
|
1783
|
+
capability,
|
|
1784
|
+
vaultBroker
|
|
1373
1785
|
});
|
|
1374
1786
|
let startupError;
|
|
1375
1787
|
let ready = false;
|
|
@@ -1392,16 +1804,6 @@ ${stderrTail()}` : ""));
|
|
|
1392
1804
|
if (!ready) {
|
|
1393
1805
|
const exitCode = proc.exitCode;
|
|
1394
1806
|
killInstance(instanceKey);
|
|
1395
|
-
if (allowFallback && browserBin !== managerHost.fallbackBrowserBin) {
|
|
1396
|
-
logger.warn({
|
|
1397
|
-
err: startupError,
|
|
1398
|
-
instanceKey,
|
|
1399
|
-
browserBin,
|
|
1400
|
-
fallback: managerHost.fallbackBrowserBin,
|
|
1401
|
-
stderr: stderrTail() || undefined
|
|
1402
|
-
}, "Preferred browser MCP unavailable or lacks owner isolation; using Patchright fallback");
|
|
1403
|
-
return spawnPlaywright(instanceKey, ownerId, undefined, managerHost.fallbackBrowserBin, false);
|
|
1404
|
-
}
|
|
1405
1807
|
if (startupError)
|
|
1406
1808
|
throw startupError;
|
|
1407
1809
|
throw new Error(`Playwright MCP failed health check after spawn on port ${port}` + (exitCode === null ? "" : ` (exitCode=${exitCode})`) + (stderrTail() ? `
|
|
@@ -1639,4 +2041,4 @@ export {
|
|
|
1639
2041
|
browserProcessMatchesExpectedProfile
|
|
1640
2042
|
};
|
|
1641
2043
|
|
|
1642
|
-
//# debugId=
|
|
2044
|
+
//# debugId=2CF8E30F8AE6C02A64756E2164756E21
|