@introspection-ai/recipes 0.19.2 → 0.20.0
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/agents.d.ts +17 -1
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +15 -1
- package/dist/agents.js.map +1 -1
- package/dist/child-agent.d.ts +3 -1
- package/dist/child-agent.d.ts.map +1 -1
- package/dist/child-agent.js +1 -0
- package/dist/child-agent.js.map +1 -1
- package/dist/mcp-chunks/{auth-command-3K7V3VCW.js → auth-command-IQ4J332A.js} +26 -38
- package/dist/mcp-chunks/call-arguments-MCCBPJMY.js +15 -0
- package/dist/mcp-chunks/{call-command-OFPHPFM5.js → call-command-WPQ3MDOE.js} +74 -40
- package/dist/mcp-chunks/{chunk-LNSUME65.js → chunk-24FKKIXI.js} +29 -13
- package/dist/mcp-chunks/{chunk-HTZ2URXW.js → chunk-2CTVPSHF.js} +3 -3
- package/dist/mcp-chunks/{chunk-NCXEJ35U.js → chunk-3FB4ZJ4Z.js} +4 -4
- package/dist/mcp-chunks/{chunk-P3RN22HQ.js → chunk-3MIFROM7.js} +3 -2
- package/dist/mcp-chunks/{chunk-GFYTST6N.js → chunk-54UGGTOX.js} +1 -1
- package/dist/mcp-chunks/{chunk-L4VKBFYI.js → chunk-5HQYKAE7.js} +101 -16
- package/dist/mcp-chunks/{chunk-E6RVTWJY.js → chunk-5YBCGQA7.js} +1 -1
- package/dist/mcp-chunks/{chunk-6D6KPSXN.js → chunk-AJPFOQLF.js} +1 -1
- package/dist/mcp-chunks/{chunk-C2PQFLWS.js → chunk-B4XFULJ6.js} +15 -6
- package/dist/mcp-chunks/{chunk-HAO6PL7Z.js → chunk-DIIFWDZ7.js} +7 -7
- package/dist/mcp-chunks/{chunk-55JIHDU3.js → chunk-EEYUPDRX.js} +1 -1
- package/dist/mcp-chunks/{chunk-7A63PAGP.js → chunk-ETYYCEQF.js} +21 -5
- package/dist/mcp-chunks/{chunk-E6T37ED6.js → chunk-FNQV2ONG.js} +2 -2
- package/dist/mcp-chunks/{chunk-2JHHONW4.js → chunk-FW5QVG6V.js} +1 -1
- package/dist/mcp-chunks/{chunk-YCYSQA5V.js → chunk-FZ7K5EGL.js} +5 -5
- package/dist/mcp-chunks/{chunk-KSMY7PXI.js → chunk-G5C4WVDQ.js} +1 -1
- package/dist/mcp-chunks/{chunk-XRGLP3Z2.js → chunk-GPCLDF6U.js} +1 -1
- package/dist/mcp-chunks/{chunk-KSGYBW3F.js → chunk-GQHTGV5E.js} +72 -22
- package/dist/mcp-chunks/{chunk-64EP2BVO.js → chunk-HULRE4D2.js} +1 -1
- package/dist/mcp-chunks/{chunk-UENXT3NE.js → chunk-ILSK6AWI.js} +1 -1
- package/dist/mcp-chunks/{chunk-RBVYCKCD.js → chunk-ILULMM2E.js} +2 -2
- package/dist/mcp-chunks/{chunk-NZOIX6XW.js → chunk-ITJWLDF5.js} +2 -2
- package/dist/mcp-chunks/{chunk-XUF6KARU.js → chunk-IUFDFNNP.js} +1 -1
- package/dist/mcp-chunks/chunk-JJG2OEHI.js +12 -0
- package/dist/mcp-chunks/{chunk-YPJ5EHXR.js → chunk-K56RW4HB.js} +4 -4
- package/dist/mcp-chunks/{chunk-H7F5RZXS.js → chunk-KJ3CMHWS.js} +2 -1
- package/dist/mcp-chunks/{chunk-WXBPMFSN.js → chunk-LI4L4FOV.js} +91 -34
- package/dist/mcp-chunks/{chunk-ETWN4IIA.js → chunk-LYZRCACV.js} +6 -6
- package/dist/mcp-chunks/{chunk-ZBFZ5UFE.js → chunk-MDVNQ4HM.js} +3 -1
- package/dist/mcp-chunks/{chunk-3IZQW4H4.js → chunk-MNAVFYYV.js} +212 -18
- package/dist/mcp-chunks/{chunk-7DH57OKF.js → chunk-MWD5PZ4G.js} +2 -2
- package/dist/mcp-chunks/{chunk-YSPT6F7K.js → chunk-N2HVJU7X.js} +1 -1
- package/dist/mcp-chunks/{chunk-WWO5WGH5.js → chunk-NACLB5YH.js} +5 -5
- package/dist/mcp-chunks/{chunk-QJACOI2W.js → chunk-NMF5S5DC.js} +4 -2
- package/dist/mcp-chunks/{chunk-54EWGONA.js → chunk-NPU7EMGE.js} +10 -6
- package/dist/mcp-chunks/{chunk-SFREPU3D.js → chunk-NSZY5WLR.js} +1 -1
- package/dist/mcp-chunks/{chunk-7RDLPFIO.js → chunk-QK6LUI5Q.js} +2 -2
- package/dist/mcp-chunks/{chunk-XTL4MHFH.js → chunk-QONTHFPO.js} +11 -7
- package/dist/mcp-chunks/{chunk-YTKRAHO6.js → chunk-RDOD354S.js} +4 -4
- package/dist/mcp-chunks/{chunk-2MEHGB7D.js → chunk-RXSZ2W6Y.js} +1 -1
- package/dist/mcp-chunks/{chunk-UHOKCTZT.js → chunk-RYAUNXFA.js} +1 -1
- package/dist/mcp-chunks/{chunk-GIF7LOYU.js → chunk-SCD2NAX5.js} +2 -2
- package/dist/mcp-chunks/{chunk-F3LL4EBL.js → chunk-SHZVDY6Z.js} +1 -1
- package/dist/mcp-chunks/{chunk-CNDY7WB4.js → chunk-TAVMVGD5.js} +2 -2
- package/dist/mcp-chunks/{chunk-JAKI24KR.js → chunk-U23EY5DN.js} +1 -1
- package/dist/mcp-chunks/{chunk-SB2UUURE.js → chunk-UAIJZBGL.js} +10 -10
- package/dist/mcp-chunks/{chunk-PKOJCYNA.js → chunk-UDG3RHKX.js} +2 -2
- package/dist/mcp-chunks/{chunk-CA5WFJZA.js → chunk-VC6SKXTI.js} +1 -1
- package/dist/mcp-chunks/chunk-VJ2TSA5R.js +818 -0
- package/dist/mcp-chunks/{chunk-FWP4RACK.js → chunk-VREKED5H.js} +2 -2
- package/dist/mcp-chunks/{chunk-2FSDGCHW.js → chunk-WKUCIR2F.js} +374 -527
- package/dist/mcp-chunks/chunk-XRRT3SZM.js +1931 -0
- package/dist/mcp-chunks/{chunk-GSZOK5TS.js → chunk-YDJ7E2PH.js} +3 -3
- package/dist/mcp-chunks/{chunk-LICH4Q7A.js → chunk-ZI44YPFF.js} +4 -4
- package/dist/mcp-chunks/{chunk-DEUU7WSN.js → chunk-ZNKNF2BV.js} +2 -2
- package/dist/mcp-chunks/{chunk-DXPHJFSA.js → chunk-ZSKQI4TS.js} +4 -4
- package/dist/mcp-chunks/{cli-E4P6SZLQ.js → cli-Z7KV4PCE.js} +70 -69
- package/dist/mcp-chunks/client-B3FMQNGF.js +21 -0
- package/dist/mcp-chunks/{config-command-MWF3RHPQ.js → config-command-XXCDOB3M.js} +27 -27
- package/dist/mcp-chunks/daemon-command-BAREANTC.js +35 -0
- package/dist/mcp-chunks/{dist-ZIPUENTM.js → dist-V4UB7RTR.js} +547 -402
- package/dist/mcp-chunks/{emit-ts-command-GOVJJ64A.js → emit-ts-command-FEHTMXXB.js} +16 -16
- package/dist/mcp-chunks/{generate-cli-runner-L5OQIMEY.js → generate-cli-runner-OA6XL77R.js} +49 -42
- package/dist/mcp-chunks/{inspect-cli-command-7OMMREPE.js → inspect-cli-command-UQKKE5VO.js} +9 -9
- package/dist/mcp-chunks/{launch-BWXPLPB7.js → launch-KMPDJOUH.js} +1 -1
- package/dist/mcp-chunks/{lifecycle-B5UC65PT.js → lifecycle-R3IP22MJ.js} +1 -1
- package/dist/mcp-chunks/{list-command-ZO4HVFQK.js → list-command-7ENYKBF6.js} +31 -31
- package/dist/mcp-chunks/{output-utils-M3TJ2N4M.js → output-utils-N75TOWBX.js} +3 -3
- package/dist/mcp-chunks/{prompt-CH6TK0bC-C7OTKGRD.js → prompt-CH6TK0bC-NSBYYJZH.js} +1 -1
- package/dist/mcp-chunks/{record-command-VD3HUQIC.js → record-command-XHPCL7IG.js} +5 -5
- package/dist/mcp-chunks/{replay-command-AX6IN7YD.js → replay-command-CB56L3ZA.js} +6 -6
- package/dist/mcp-chunks/{resource-command-JEZ2DW2G.js → resource-command-STEBHVT7.js} +9 -9
- package/dist/mcp-chunks/{result-utils-3YHYGS45.js → result-utils-UONY2KNY.js} +3 -3
- package/dist/mcp-chunks/runtime-Z45IVD24.js +39 -0
- package/dist/mcp-chunks/{runtime-wrapper-UREU7GC3.js → runtime-wrapper-SYYAQVO6.js} +6 -6
- package/dist/mcp-chunks/{serve-command-M47YZ6JN.js → serve-command-VYSFRZFE.js} +35 -27
- package/dist/mcp-chunks/{timeouts-THZRYP4B.js → timeouts-T3JP3MUF.js} +5 -3
- package/dist/mcp-chunks/{vault-command-53P7MLCB.js → vault-command-AJ3X7GDI.js} +58 -12
- package/dist/mcp-chunks/{webapi-PHEQZQKH.js → webapi-BLTNWSB2.js} +68 -60
- package/dist/mcp-daemon.js +30 -28
- package/dist/mcp-run-worker.js +30 -28
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +2 -1
- package/dist/mcp.js.map +1 -1
- package/dist/pi-extension.d.ts +3 -0
- package/dist/pi-extension.d.ts.map +1 -1
- package/dist/pi-extension.js +27 -0
- package/dist/pi-extension.js.map +1 -1
- package/dist/run-controller.d.ts +3 -1
- package/dist/run-controller.d.ts.map +1 -1
- package/dist/run-controller.js +10 -0
- package/dist/run-controller.js.map +1 -1
- package/dist/session.d.ts +3 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +4 -1
- package/dist/session.js.map +1 -1
- package/docs/host-api.md +12 -0
- package/docs/pi-extension.md +10 -0
- package/package.json +11 -11
- package/vendor/introspection-recipe-check/win32-x64/introspection-recipe-check.exe +0 -0
- package/dist/mcp-chunks/call-arguments-TX3F2TEE.js +0 -15
- package/dist/mcp-chunks/chunk-SRZZQ6TL.js +0 -266
- package/dist/mcp-chunks/client-2EJC4PLL.js +0 -18
- package/dist/mcp-chunks/daemon-command-6CJ26MKI.js +0 -33
- package/dist/mcp-chunks/runtime-HSYVOME7.js +0 -37
|
@@ -0,0 +1,1931 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
resolveCommandArgument,
|
|
4
|
+
resolveCommandArguments
|
|
5
|
+
} from "./chunk-LYZRCACV.js";
|
|
6
|
+
import {
|
|
7
|
+
parseTree
|
|
8
|
+
} from "./chunk-5HQYKAE7.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveEnvValue
|
|
11
|
+
} from "./chunk-DIIFWDZ7.js";
|
|
12
|
+
|
|
13
|
+
// node_modules/.pnpm/mcporter@0.13.6/node_modules/mcporter/dist/chrome-devtools-relay.js
|
|
14
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import path2 from "node:path";
|
|
18
|
+
|
|
19
|
+
// node_modules/.pnpm/mcporter@0.13.6/node_modules/mcporter/dist/chrome-devtools-command.js
|
|
20
|
+
var AUTO_CONNECT_FLAGS = /* @__PURE__ */ new Set(["--autoConnect", "--auto-connect"]);
|
|
21
|
+
var NEGATED_AUTO_CONNECT_FLAGS = /* @__PURE__ */ new Set(["--no-autoConnect", "--no-auto-connect"]);
|
|
22
|
+
var CONNECTION_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
23
|
+
"--browserUrl",
|
|
24
|
+
"--browser-url",
|
|
25
|
+
"--channel",
|
|
26
|
+
"--executablePath",
|
|
27
|
+
"--executable-path",
|
|
28
|
+
"--userDataDir",
|
|
29
|
+
"--user-data-dir",
|
|
30
|
+
"--wsEndpoint",
|
|
31
|
+
"--ws-endpoint",
|
|
32
|
+
"--wsHeaders",
|
|
33
|
+
"--ws-headers",
|
|
34
|
+
"-e",
|
|
35
|
+
"-u",
|
|
36
|
+
"-w"
|
|
37
|
+
]);
|
|
38
|
+
var NPX_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
39
|
+
"--cache",
|
|
40
|
+
"--package",
|
|
41
|
+
"--prefix",
|
|
42
|
+
"--registry",
|
|
43
|
+
"--userconfig",
|
|
44
|
+
"--workspace",
|
|
45
|
+
"-p",
|
|
46
|
+
"-w"
|
|
47
|
+
]);
|
|
48
|
+
var NPX_COMMAND_STRING_OPTIONS = /* @__PURE__ */ new Set(["--call", "-c"]);
|
|
49
|
+
var NPX_NO_VALUE_OPTIONS = /* @__PURE__ */ new Set(["--ignore-existing", "--no-install", "--quiet", "--silent", "--yes", "-y"]);
|
|
50
|
+
var COMMON_WRAPPERS = /* @__PURE__ */ new Set([
|
|
51
|
+
"ash",
|
|
52
|
+
"bash",
|
|
53
|
+
"csh",
|
|
54
|
+
"dash",
|
|
55
|
+
"cmd",
|
|
56
|
+
"doas",
|
|
57
|
+
"env",
|
|
58
|
+
"fish",
|
|
59
|
+
"ksh",
|
|
60
|
+
"mksh",
|
|
61
|
+
"node",
|
|
62
|
+
"npm",
|
|
63
|
+
"npx",
|
|
64
|
+
"bunx",
|
|
65
|
+
"pnpm",
|
|
66
|
+
"powershell",
|
|
67
|
+
"pwsh",
|
|
68
|
+
"sh",
|
|
69
|
+
"sudo",
|
|
70
|
+
"tcsh",
|
|
71
|
+
"yarn",
|
|
72
|
+
"zsh"
|
|
73
|
+
]);
|
|
74
|
+
function isChromeDevtoolsToken(token) {
|
|
75
|
+
const normalized = token.replaceAll("\\", "/").toLowerCase();
|
|
76
|
+
const basename = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
77
|
+
return /^chrome-devtools-mcp(?:@[^/]+)?(?:\.(?:bat|cjs|cmd|exe|js|mjs))?$/u.test(basename) || normalized.includes("/chrome-devtools-mcp/") && /\.(?:bat|cjs|cmd|exe|js|mjs)$/u.test(basename);
|
|
78
|
+
}
|
|
79
|
+
function isAmbiguousChromeDevtoolsAutoConnectCommand(command, args) {
|
|
80
|
+
if (isChromeDevtoolsToken(command))
|
|
81
|
+
return false;
|
|
82
|
+
if (isSupportedPackageLauncher(command)) {
|
|
83
|
+
const target = findPackageLauncherTarget(args);
|
|
84
|
+
if (!target)
|
|
85
|
+
return false;
|
|
86
|
+
if (target.ambiguous)
|
|
87
|
+
return true;
|
|
88
|
+
const nestedTarget = commandBasename(args[target.index] ?? "");
|
|
89
|
+
return COMMON_WRAPPERS.has(nestedTarget) && containsChromeAutoConnectInvocation(args.slice(target.index + 1));
|
|
90
|
+
}
|
|
91
|
+
const launcher = commandBasename(command);
|
|
92
|
+
if (COMMON_WRAPPERS.has(launcher))
|
|
93
|
+
return containsChromeAutoConnectInvocation(args);
|
|
94
|
+
const scopedArgs = args.slice(0, optionTerminator(args));
|
|
95
|
+
const tokenIndex = scopedArgs.findIndex(isChromeDevtoolsToken);
|
|
96
|
+
return tokenIndex >= 0 && parseChromeDevtoolsAutoConnectArgs(scopedArgs.slice(tokenIndex + 1)).enabled;
|
|
97
|
+
}
|
|
98
|
+
function resolveChromeDevtoolsAutoConnectCommand(command, args) {
|
|
99
|
+
const target = resolveChromeDevtoolsTarget(command, args);
|
|
100
|
+
if (!target)
|
|
101
|
+
return { enabled: false, withoutAutoConnect: args };
|
|
102
|
+
const parsed = parseChromeDevtoolsAutoConnectArgs(target.args);
|
|
103
|
+
return {
|
|
104
|
+
enabled: parsed.enabled,
|
|
105
|
+
withoutAutoConnect: [...target.prefix, ...parsed.withoutAutoConnect]
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function replaceChromeDevtoolsAutoConnectArgs(command, args, replacement) {
|
|
109
|
+
const target = resolveChromeDevtoolsTarget(command, args);
|
|
110
|
+
if (!target)
|
|
111
|
+
return args;
|
|
112
|
+
const parsed = parseChromeDevtoolsAutoConnectArgs(target.args);
|
|
113
|
+
const stripped = stripConnectionSelectionArgs(parsed.withoutAutoConnect);
|
|
114
|
+
const targetArgs = [...stripped.args];
|
|
115
|
+
targetArgs.splice(stripped.insertionIndex, 0, ...replacement);
|
|
116
|
+
return [...target.prefix, ...targetArgs];
|
|
117
|
+
}
|
|
118
|
+
function resolveChromeDevtoolsTarget(command, args) {
|
|
119
|
+
if (isChromeDevtoolsToken(command))
|
|
120
|
+
return { prefix: [], args };
|
|
121
|
+
if (!isSupportedPackageLauncher(command))
|
|
122
|
+
return void 0;
|
|
123
|
+
const target = findPackageLauncherTarget(args);
|
|
124
|
+
if (!target || target.ambiguous || !isChromeDevtoolsToken(args[target.index] ?? ""))
|
|
125
|
+
return void 0;
|
|
126
|
+
const separatorIndex = target.index + 1;
|
|
127
|
+
const consumesSeparator = target.consumePostTargetSeparator && args[separatorIndex] === "--";
|
|
128
|
+
const targetStart = consumesSeparator ? separatorIndex + 1 : separatorIndex;
|
|
129
|
+
return { prefix: args.slice(0, targetStart), args: args.slice(targetStart) };
|
|
130
|
+
}
|
|
131
|
+
function findPackageLauncherTarget(args) {
|
|
132
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
133
|
+
const arg = args[index];
|
|
134
|
+
if (arg === void 0)
|
|
135
|
+
continue;
|
|
136
|
+
if (arg === "--") {
|
|
137
|
+
return index + 1 < args.length ? { index: index + 1, ambiguous: false, consumePostTargetSeparator: false } : void 0;
|
|
138
|
+
}
|
|
139
|
+
if (arg.startsWith("--call=")) {
|
|
140
|
+
const value = arg.slice("--call=".length);
|
|
141
|
+
return containsInlineChromeAutoConnect([value]) ? { index, ambiguous: true, consumePostTargetSeparator: false } : void 0;
|
|
142
|
+
}
|
|
143
|
+
if (NPX_COMMAND_STRING_OPTIONS.has(arg)) {
|
|
144
|
+
const value = args[index + 1] ?? "";
|
|
145
|
+
return containsInlineChromeAutoConnect([value]) ? { index: index + 1, ambiguous: true, consumePostTargetSeparator: false } : void 0;
|
|
146
|
+
}
|
|
147
|
+
if (NPX_VALUE_OPTIONS.has(arg)) {
|
|
148
|
+
index += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (NPX_NO_VALUE_OPTIONS.has(arg) || /^--[^=]+=.+/u.test(arg))
|
|
152
|
+
continue;
|
|
153
|
+
if (arg.startsWith("-")) {
|
|
154
|
+
return {
|
|
155
|
+
index,
|
|
156
|
+
ambiguous: containsChromeAutoConnectInvocation(args.slice(index)),
|
|
157
|
+
consumePostTargetSeparator: false
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return { index, ambiguous: false, consumePostTargetSeparator: true };
|
|
161
|
+
}
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
function isSupportedPackageLauncher(command) {
|
|
165
|
+
const launcher = commandBasename(command);
|
|
166
|
+
return launcher === "npx" || launcher === "bunx";
|
|
167
|
+
}
|
|
168
|
+
function containsChromeAutoConnectInvocation(args) {
|
|
169
|
+
const tokenIndex = args.findIndex(isChromeDevtoolsToken);
|
|
170
|
+
if (tokenIndex >= 0)
|
|
171
|
+
return parseChromeDevtoolsAutoConnectArgs(args.slice(tokenIndex + 1)).enabled;
|
|
172
|
+
return containsInlineChromeAutoConnect(args);
|
|
173
|
+
}
|
|
174
|
+
function containsInlineChromeAutoConnect(args) {
|
|
175
|
+
const rendered = args.join(" ").toLowerCase();
|
|
176
|
+
return rendered.includes("chrome-devtools-mcp") && /--(?:auto-connect|autoconnect)(?:=true)?(?:[;'"&|)\]}]|\s|$)/u.test(rendered);
|
|
177
|
+
}
|
|
178
|
+
function commandBasename(command) {
|
|
179
|
+
const normalized = command.replaceAll("\\", "/").toLowerCase();
|
|
180
|
+
return normalized.slice(normalized.lastIndexOf("/") + 1).replace(/\.(?:cmd|exe)$/u, "");
|
|
181
|
+
}
|
|
182
|
+
function optionTerminator(args) {
|
|
183
|
+
const index = args.indexOf("--");
|
|
184
|
+
return index >= 0 ? index : args.length;
|
|
185
|
+
}
|
|
186
|
+
function stripConnectionSelectionArgs(args) {
|
|
187
|
+
const result = [];
|
|
188
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
189
|
+
const arg = args[index];
|
|
190
|
+
if (arg === void 0)
|
|
191
|
+
continue;
|
|
192
|
+
if (arg === "--") {
|
|
193
|
+
result.push(...args.slice(index));
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
if (CONNECTION_VALUE_FLAGS.has(arg)) {
|
|
197
|
+
index += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if ([...CONNECTION_VALUE_FLAGS].some((flag) => arg.startsWith(`${flag}=`)))
|
|
201
|
+
continue;
|
|
202
|
+
result.push(arg);
|
|
203
|
+
}
|
|
204
|
+
const terminatorIndex = result.indexOf("--");
|
|
205
|
+
return { args: result, insertionIndex: terminatorIndex >= 0 ? terminatorIndex : result.length };
|
|
206
|
+
}
|
|
207
|
+
function parseChromeDevtoolsAutoConnectArgs(args) {
|
|
208
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
209
|
+
let enabled = false;
|
|
210
|
+
let sawOption = false;
|
|
211
|
+
let terminatorIndex = args.length;
|
|
212
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
213
|
+
const arg = args[index];
|
|
214
|
+
if (arg === void 0)
|
|
215
|
+
continue;
|
|
216
|
+
if (arg === "--") {
|
|
217
|
+
terminatorIndex = index;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
if (NEGATED_AUTO_CONNECT_FLAGS.has(arg)) {
|
|
221
|
+
sawOption = true;
|
|
222
|
+
enabled = false;
|
|
223
|
+
consumed.add(index);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
let matchedNegatedInline = false;
|
|
227
|
+
for (const flag of NEGATED_AUTO_CONNECT_FLAGS) {
|
|
228
|
+
if (!arg.startsWith(`${flag}=`))
|
|
229
|
+
continue;
|
|
230
|
+
sawOption = true;
|
|
231
|
+
enabled = false;
|
|
232
|
+
consumed.add(index);
|
|
233
|
+
matchedNegatedInline = true;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
if (matchedNegatedInline)
|
|
237
|
+
continue;
|
|
238
|
+
if (AUTO_CONNECT_FLAGS.has(arg)) {
|
|
239
|
+
sawOption = true;
|
|
240
|
+
consumed.add(index);
|
|
241
|
+
const pairedValue = booleanLiteral(args[index + 1]);
|
|
242
|
+
if (pairedValue !== void 0) {
|
|
243
|
+
enabled = pairedValue;
|
|
244
|
+
consumed.add(index + 1);
|
|
245
|
+
index += 1;
|
|
246
|
+
} else {
|
|
247
|
+
enabled = true;
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
for (const flag of AUTO_CONNECT_FLAGS) {
|
|
252
|
+
if (!arg.startsWith(`${flag}=`))
|
|
253
|
+
continue;
|
|
254
|
+
sawOption = true;
|
|
255
|
+
enabled = arg.slice(flag.length + 1) === "true";
|
|
256
|
+
consumed.add(index);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const withoutAutoConnect = args.filter((_arg, index) => !consumed.has(index));
|
|
261
|
+
let insertionIndex = 0;
|
|
262
|
+
for (let index = 0; index < terminatorIndex; index += 1) {
|
|
263
|
+
if (!consumed.has(index))
|
|
264
|
+
insertionIndex += 1;
|
|
265
|
+
}
|
|
266
|
+
return { enabled: sawOption && enabled, withoutAutoConnect, insertionIndex };
|
|
267
|
+
}
|
|
268
|
+
function booleanLiteral(value) {
|
|
269
|
+
if (value === "true")
|
|
270
|
+
return true;
|
|
271
|
+
if (value === "false")
|
|
272
|
+
return false;
|
|
273
|
+
return void 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// node_modules/.pnpm/mcporter@0.13.6/node_modules/mcporter/dist/chrome-devtools-relay-proxy.js
|
|
277
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
278
|
+
import http, {} from "node:http";
|
|
279
|
+
var WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
280
|
+
async function startChromeDevtoolsRelayProxy(options) {
|
|
281
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
282
|
+
const clientAuthorization = `Bearer ${randomBytes(32).toString("base64url")}`;
|
|
283
|
+
let downstreamAccepted = false;
|
|
284
|
+
let closed = false;
|
|
285
|
+
const server = http.createServer((_request, response) => {
|
|
286
|
+
response.writeHead(404, { Connection: "close", "Content-Length": "0" }).end();
|
|
287
|
+
});
|
|
288
|
+
server.on("connection", (socket) => trackSocket(sockets, socket));
|
|
289
|
+
server.on("upgrade", (request, downstream, head) => {
|
|
290
|
+
if (closed || downstreamAccepted || !isValidDownstreamUpgrade(request, clientAuthorization) || options.upstream.socket.destroyed) {
|
|
291
|
+
downstream.end("HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
downstreamAccepted = true;
|
|
295
|
+
const key = request.headers["sec-websocket-key"];
|
|
296
|
+
if (typeof key !== "string") {
|
|
297
|
+
downstream.destroy();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const accept = createHash("sha1").update(`${key}${WEBSOCKET_GUID}`).digest("base64");
|
|
301
|
+
downstream.write([
|
|
302
|
+
"HTTP/1.1 101 Switching Protocols",
|
|
303
|
+
"Connection: Upgrade",
|
|
304
|
+
"Upgrade: websocket",
|
|
305
|
+
`Sec-WebSocket-Accept: ${accept}`,
|
|
306
|
+
"",
|
|
307
|
+
""
|
|
308
|
+
].join("\r\n"));
|
|
309
|
+
if (options.upstream.head.length > 0)
|
|
310
|
+
downstream.write(options.upstream.head);
|
|
311
|
+
if (head.length > 0)
|
|
312
|
+
options.upstream.socket.write(head);
|
|
313
|
+
options.upstream.socket.once("error", () => downstream.destroy());
|
|
314
|
+
options.upstream.socket.once("close", () => downstream.destroy());
|
|
315
|
+
downstream.once("error", () => options.upstream.socket.destroy());
|
|
316
|
+
downstream.once("close", () => options.upstream.socket.destroy());
|
|
317
|
+
downstream.pipe(options.upstream.socket).pipe(downstream);
|
|
318
|
+
server.close();
|
|
319
|
+
});
|
|
320
|
+
const onUpstreamClose = () => {
|
|
321
|
+
void closeServer(server, sockets);
|
|
322
|
+
};
|
|
323
|
+
options.upstream.socket.once("close", onUpstreamClose);
|
|
324
|
+
try {
|
|
325
|
+
await new Promise((resolve, reject) => {
|
|
326
|
+
const onError = (error) => reject(error);
|
|
327
|
+
server.once("error", onError);
|
|
328
|
+
server.listen(0, "127.0.0.1", () => {
|
|
329
|
+
server.off("error", onError);
|
|
330
|
+
resolve();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
} catch (error) {
|
|
334
|
+
closeUpstream(options.upstream.socket);
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
const address = server.address();
|
|
338
|
+
if (!address || address.address !== "127.0.0.1") {
|
|
339
|
+
closeUpstream(options.upstream.socket);
|
|
340
|
+
server.close();
|
|
341
|
+
throw new Error("Chrome relay proxy failed to bind to IPv4 loopback.");
|
|
342
|
+
}
|
|
343
|
+
let authorizationAvailable = true;
|
|
344
|
+
return {
|
|
345
|
+
endpoint: `ws://127.0.0.1:${address.port}/cdp`,
|
|
346
|
+
consumeClientAuthorization() {
|
|
347
|
+
if (!authorizationAvailable)
|
|
348
|
+
throw new Error("Chrome relay authorization handoff already consumed.");
|
|
349
|
+
authorizationAvailable = false;
|
|
350
|
+
return clientAuthorization;
|
|
351
|
+
},
|
|
352
|
+
async close() {
|
|
353
|
+
if (closed)
|
|
354
|
+
return;
|
|
355
|
+
closed = true;
|
|
356
|
+
options.upstream.socket.off("close", onUpstreamClose);
|
|
357
|
+
closeUpstream(options.upstream.socket);
|
|
358
|
+
await closeServer(server, sockets);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function isValidDownstreamUpgrade(request, expectedAuthorization) {
|
|
363
|
+
if (request.url !== "/cdp" || request.method !== "GET" || request.headers.upgrade?.toLowerCase() !== "websocket" || !hasToken(request.headers.connection, "upgrade") || request.headers["sec-websocket-version"] !== "13" || countRawHeader(request, "authorization") !== 1 || countRawHeader(request, "sec-websocket-key") !== 1 || !safeHeaderEqual(request.headers.authorization, expectedAuthorization)) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
const key = request.headers["sec-websocket-key"];
|
|
367
|
+
if (typeof key !== "string" || !/^[A-Za-z0-9+/]{22}==$/u.test(key))
|
|
368
|
+
return false;
|
|
369
|
+
const decoded = Buffer.from(key, "base64");
|
|
370
|
+
return decoded.byteLength === 16 && decoded.toString("base64") === key;
|
|
371
|
+
}
|
|
372
|
+
function countRawHeader(request, expectedName) {
|
|
373
|
+
let count = 0;
|
|
374
|
+
for (let index = 0; index < request.rawHeaders.length; index += 2) {
|
|
375
|
+
if (request.rawHeaders[index]?.toLowerCase() === expectedName)
|
|
376
|
+
count += 1;
|
|
377
|
+
}
|
|
378
|
+
return count;
|
|
379
|
+
}
|
|
380
|
+
function safeHeaderEqual(actual, expected) {
|
|
381
|
+
if (!actual)
|
|
382
|
+
return false;
|
|
383
|
+
const actualBytes = Buffer.from(actual);
|
|
384
|
+
const expectedBytes = Buffer.from(expected);
|
|
385
|
+
return actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes);
|
|
386
|
+
}
|
|
387
|
+
function hasToken(value, expected) {
|
|
388
|
+
return Boolean(value?.split(",").some((token) => token.trim().toLowerCase() === expected));
|
|
389
|
+
}
|
|
390
|
+
function trackSocket(sockets, socket) {
|
|
391
|
+
if (sockets.has(socket))
|
|
392
|
+
return;
|
|
393
|
+
sockets.add(socket);
|
|
394
|
+
socket.once("close", () => sockets.delete(socket));
|
|
395
|
+
}
|
|
396
|
+
function closeUpstream(socket) {
|
|
397
|
+
if (socket.destroyed)
|
|
398
|
+
return;
|
|
399
|
+
if (!socket.remoteAddress) {
|
|
400
|
+
socket.destroy();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
socket.resetAndDestroy();
|
|
405
|
+
} catch {
|
|
406
|
+
socket.destroy();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function closeServer(server, sockets) {
|
|
410
|
+
for (const socket of sockets)
|
|
411
|
+
socket.destroy();
|
|
412
|
+
if (!server.listening)
|
|
413
|
+
return;
|
|
414
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// node_modules/.pnpm/mcporter@0.13.6/node_modules/mcporter/dist/browser-relay-auth-v2.js
|
|
418
|
+
import { createHash as createHash2, createHmac, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
419
|
+
var BROWSER_RELAY_AUTH_LABEL = "openclaw.browser-relay.auth";
|
|
420
|
+
var BROWSER_RELAY_AUTH_VERSION = 2;
|
|
421
|
+
var BROWSER_RELAY_CHALLENGE_MAX_LIFETIME_MS = 1e4;
|
|
422
|
+
var BROWSER_RELAY_CLOCK_SKEW_MS = 3e4;
|
|
423
|
+
var BROWSER_RELAY_AUTH_CHALLENGE_PATH = "/_openclaw/relay/auth/v2/challenge";
|
|
424
|
+
var BROWSER_RELAY_AUTH_COMPLETE_PATH = "/_openclaw/relay/auth/v2/complete";
|
|
425
|
+
var BROWSER_RELAY_CDP_PATH = "/cdp";
|
|
426
|
+
var BROWSER_RELAY_CDP_ROLE = "cdp";
|
|
427
|
+
var BROWSER_RELAY_CDP_TRANSPORT = "connection";
|
|
428
|
+
var BROWSER_RELAY_CDP_METHOD = "SEQUENCE";
|
|
429
|
+
var BROWSER_RELAY_CDP_RESOURCE = "/json/version -> /cdp";
|
|
430
|
+
var BROWSER_RELAY_CDP_FLOW = "cdp";
|
|
431
|
+
var BROWSER_RELAY_VERSION_PATH = "/json/version";
|
|
432
|
+
function deriveBrowserRelayKeyId(key) {
|
|
433
|
+
assertRelayKey(key);
|
|
434
|
+
return createHash2("sha256").update(key).digest("base64url").slice(0, 22);
|
|
435
|
+
}
|
|
436
|
+
function canonicalizeBrowserRelayProof(proofKind, fields, clientProof) {
|
|
437
|
+
const canonical = [
|
|
438
|
+
BROWSER_RELAY_AUTH_LABEL,
|
|
439
|
+
BROWSER_RELAY_AUTH_VERSION,
|
|
440
|
+
proofKind,
|
|
441
|
+
fields.keyId,
|
|
442
|
+
fields.instanceId,
|
|
443
|
+
fields.sessionId,
|
|
444
|
+
fields.clientNonce,
|
|
445
|
+
fields.serverNonce,
|
|
446
|
+
fields.issuedAtMs,
|
|
447
|
+
fields.expiresAtMs,
|
|
448
|
+
fields.role,
|
|
449
|
+
fields.transport,
|
|
450
|
+
fields.method,
|
|
451
|
+
fields.resource,
|
|
452
|
+
fields.flow
|
|
453
|
+
];
|
|
454
|
+
if (proofKind === "accept") {
|
|
455
|
+
if (!clientProof || !isCanonicalBase64Url(clientProof, 32)) {
|
|
456
|
+
throw new Error("Browser relay accept proof requires a 32-byte client proof.");
|
|
457
|
+
}
|
|
458
|
+
canonical.push(clientProof);
|
|
459
|
+
} else if (clientProof !== void 0) {
|
|
460
|
+
throw new Error("Browser relay client proof is only valid for an accept proof.");
|
|
461
|
+
}
|
|
462
|
+
return Buffer.from(JSON.stringify(canonical), "utf8");
|
|
463
|
+
}
|
|
464
|
+
function createBrowserRelayProof(key, proofKind, fields, clientProof) {
|
|
465
|
+
assertRelayKey(key);
|
|
466
|
+
return createHmac("sha256", key).update(canonicalizeBrowserRelayProof(proofKind, fields, clientProof)).digest("base64url");
|
|
467
|
+
}
|
|
468
|
+
function verifyBrowserRelayProof(key, proofKind, fields, proof, clientProof) {
|
|
469
|
+
if (!isCanonicalBase64Url(proof, 32))
|
|
470
|
+
return false;
|
|
471
|
+
const actual = Buffer.from(proof, "base64url");
|
|
472
|
+
const expected = Buffer.from(createBrowserRelayProof(key, proofKind, fields, clientProof), "base64url");
|
|
473
|
+
return actual.byteLength === expected.byteLength && timingSafeEqual2(actual, expected);
|
|
474
|
+
}
|
|
475
|
+
function assertRelayKey(key) {
|
|
476
|
+
if (key.byteLength !== 32)
|
|
477
|
+
throw new Error("Browser relay key must contain 32 bytes.");
|
|
478
|
+
}
|
|
479
|
+
function isCanonicalBase64Url(value, bytes) {
|
|
480
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(value))
|
|
481
|
+
return false;
|
|
482
|
+
const decoded = Buffer.from(value, "base64url");
|
|
483
|
+
return decoded.byteLength === bytes && decoded.toString("base64url") === value;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// node_modules/.pnpm/mcporter@0.13.6/node_modules/mcporter/dist/chrome-devtools-relay-client.js
|
|
487
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
|
|
488
|
+
import { lookup } from "node:dns/promises";
|
|
489
|
+
import net from "node:net";
|
|
490
|
+
import { TextDecoder } from "node:util";
|
|
491
|
+
var MAX_HEADER_BYTES = 16 * 1024;
|
|
492
|
+
var MAX_JSON_BYTES = 64 * 1024;
|
|
493
|
+
var WEBSOCKET_GUID2 = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
494
|
+
var RelayV2Error = class extends Error {
|
|
495
|
+
reason;
|
|
496
|
+
status;
|
|
497
|
+
constructor(reason, status) {
|
|
498
|
+
super(`Chrome relay authentication failed (${reason}).`);
|
|
499
|
+
this.reason = reason;
|
|
500
|
+
this.status = status;
|
|
501
|
+
this.name = "RelayV2Error";
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
async function connectChromeDevtoolsRelayV2(options) {
|
|
505
|
+
const startedAt = performance.now();
|
|
506
|
+
const durationMs = () => Math.max(0, Math.round(performance.now() - startedAt));
|
|
507
|
+
let socket;
|
|
508
|
+
let timer;
|
|
509
|
+
try {
|
|
510
|
+
let timedOut = false;
|
|
511
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
512
|
+
timer = setTimeout(() => {
|
|
513
|
+
timedOut = true;
|
|
514
|
+
const error = new RelayV2Error("timeout");
|
|
515
|
+
socket?.destroy(error);
|
|
516
|
+
reject(error);
|
|
517
|
+
}, options.timeoutMs);
|
|
518
|
+
});
|
|
519
|
+
timer?.unref();
|
|
520
|
+
const addresses = await Promise.race([
|
|
521
|
+
resolveLoopbackAddresses(options.baseUrl.hostname, options.resolve ?? lookup),
|
|
522
|
+
timeout
|
|
523
|
+
]);
|
|
524
|
+
const port = resolvePort(options.baseUrl);
|
|
525
|
+
let lastConnectionError;
|
|
526
|
+
for (const address of addresses) {
|
|
527
|
+
socket = net.createConnection({ host: address.address, family: address.family, port });
|
|
528
|
+
try {
|
|
529
|
+
await waitForConnect(socket);
|
|
530
|
+
lastConnectionError = void 0;
|
|
531
|
+
break;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
socket.destroy();
|
|
534
|
+
if (timedOut || isTimeoutError(error))
|
|
535
|
+
throw new RelayV2Error("timeout");
|
|
536
|
+
lastConnectionError = error;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (lastConnectionError)
|
|
540
|
+
throw lastConnectionError;
|
|
541
|
+
if (!socket || socket.destroyed)
|
|
542
|
+
throw new RelayV2Error(timedOut ? "timeout" : "network-error");
|
|
543
|
+
if (!socket.remoteAddress || !isNumericLoopback(socket.remoteAddress))
|
|
544
|
+
throw new RelayV2Error("protocol");
|
|
545
|
+
const reader = new SocketReader(socket);
|
|
546
|
+
const flow = options.flow ?? "cdp";
|
|
547
|
+
const clientNonce = randomBytes2(32).toString("base64url");
|
|
548
|
+
const hello = {
|
|
549
|
+
v: BROWSER_RELAY_AUTH_VERSION,
|
|
550
|
+
keyId: options.credential.keyId,
|
|
551
|
+
clientNonce,
|
|
552
|
+
role: BROWSER_RELAY_CDP_ROLE,
|
|
553
|
+
transport: BROWSER_RELAY_CDP_TRANSPORT,
|
|
554
|
+
method: flow === BROWSER_RELAY_CDP_FLOW ? BROWSER_RELAY_CDP_METHOD : "GET",
|
|
555
|
+
resource: flow === BROWSER_RELAY_CDP_FLOW ? BROWSER_RELAY_CDP_RESOURCE : "/json/list",
|
|
556
|
+
flow
|
|
557
|
+
};
|
|
558
|
+
await writeJsonRequest(socket, options.baseUrl.host, BROWSER_RELAY_AUTH_CHALLENGE_PATH, hello);
|
|
559
|
+
const challengeResponse = await reader.readHttpResponse(MAX_JSON_BYTES);
|
|
560
|
+
ensureAuthStatus(challengeResponse, "challenge");
|
|
561
|
+
const challenge = parseStrictJsonObject(challengeResponse.body);
|
|
562
|
+
const fields = validateChallenge(challenge, hello, options.now?.() ?? Date.now());
|
|
563
|
+
const serverProof = requireString(challenge, "serverProof");
|
|
564
|
+
if (!verifyBrowserRelayProof(options.credential.key, "server", fields, serverProof)) {
|
|
565
|
+
throw new RelayV2Error("bad-server-proof");
|
|
566
|
+
}
|
|
567
|
+
const clientProof = createBrowserRelayProof(options.credential.key, "client", fields);
|
|
568
|
+
await writeJsonRequest(socket, options.baseUrl.host, BROWSER_RELAY_AUTH_COMPLETE_PATH, {
|
|
569
|
+
v: BROWSER_RELAY_AUTH_VERSION,
|
|
570
|
+
sessionId: fields.sessionId,
|
|
571
|
+
clientProof
|
|
572
|
+
});
|
|
573
|
+
const completeResponse = await reader.readHttpResponse(MAX_JSON_BYTES);
|
|
574
|
+
ensureAuthStatus(completeResponse, "complete");
|
|
575
|
+
const accepted = parseStrictJsonObject(completeResponse.body);
|
|
576
|
+
validateAccept(accepted, fields.sessionId);
|
|
577
|
+
if (!verifyBrowserRelayProof(options.credential.key, "accept", fields, requireString(accepted, "acceptProof"), clientProof)) {
|
|
578
|
+
throw new RelayV2Error("bad-server-proof");
|
|
579
|
+
}
|
|
580
|
+
if (flow === "json-list") {
|
|
581
|
+
await writeRequest(socket, options.baseUrl.host, "GET", "/json/list", []);
|
|
582
|
+
const listResponse = await reader.readHttpResponse(MAX_JSON_BYTES, true);
|
|
583
|
+
if (listResponse.status !== 200)
|
|
584
|
+
throw classifyStatus(listResponse.status, true);
|
|
585
|
+
const json = parseStrictJson(listResponse.body);
|
|
586
|
+
if (!Array.isArray(json))
|
|
587
|
+
throw new RelayV2Error("protocol", 200);
|
|
588
|
+
socket.destroy();
|
|
589
|
+
return { reason: "success", durationMs: durationMs(), status: 200, json };
|
|
590
|
+
}
|
|
591
|
+
await writeRequest(socket, options.baseUrl.host, "GET", BROWSER_RELAY_VERSION_PATH, []);
|
|
592
|
+
const versionResponse = await reader.readHttpResponse(MAX_JSON_BYTES);
|
|
593
|
+
if (versionResponse.status === 503) {
|
|
594
|
+
socket.destroy();
|
|
595
|
+
return { reason: "extension-disconnected", durationMs: durationMs(), status: 503 };
|
|
596
|
+
}
|
|
597
|
+
if (versionResponse.status !== 200)
|
|
598
|
+
throw classifyStatus(versionResponse.status, true);
|
|
599
|
+
parseStrictJsonObject(versionResponse.body);
|
|
600
|
+
const websocketKey = randomBytes2(16).toString("base64");
|
|
601
|
+
await writeRequest(socket, options.baseUrl.host, "GET", BROWSER_RELAY_CDP_PATH, [
|
|
602
|
+
["Connection", "Upgrade"],
|
|
603
|
+
["Upgrade", "websocket"],
|
|
604
|
+
["Sec-WebSocket-Key", websocketKey],
|
|
605
|
+
["Sec-WebSocket-Version", "13"]
|
|
606
|
+
]);
|
|
607
|
+
const upgrade = await reader.readHttpUpgrade();
|
|
608
|
+
if (upgrade.status !== 101)
|
|
609
|
+
throw classifyStatus(upgrade.status, true);
|
|
610
|
+
const expectedAccept = createHash3("sha1").update(`${websocketKey}${WEBSOCKET_GUID2}`).digest("base64");
|
|
611
|
+
if (upgrade.headers.get("upgrade")?.toLowerCase() !== "websocket" || !hasToken2(upgrade.headers.get("connection"), "upgrade") || upgrade.headers.get("sec-websocket-accept") !== expectedAccept) {
|
|
612
|
+
throw new RelayV2Error("sequence", 101);
|
|
613
|
+
}
|
|
614
|
+
const head = reader.release();
|
|
615
|
+
if (timer)
|
|
616
|
+
clearTimeout(timer);
|
|
617
|
+
return { reason: "success", durationMs: durationMs(), status: 200, upstream: { socket, head } };
|
|
618
|
+
} catch (error) {
|
|
619
|
+
socket?.destroy();
|
|
620
|
+
if (error instanceof RelayV2Error) {
|
|
621
|
+
return { reason: error.reason, durationMs: durationMs(), status: error.status };
|
|
622
|
+
}
|
|
623
|
+
return { reason: isTimeoutError(error) ? "timeout" : "network-error", durationMs: durationMs() };
|
|
624
|
+
} finally {
|
|
625
|
+
if (timer)
|
|
626
|
+
clearTimeout(timer);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async function resolveLoopbackAddresses(hostname, resolver) {
|
|
630
|
+
const normalized = hostname.replace(/^\[(.*)\]$/u, "$1");
|
|
631
|
+
const addresses = await resolver(normalized, { all: true, verbatim: true });
|
|
632
|
+
if (addresses.length === 0 || addresses.some((address) => !isNumericLoopback(address.address))) {
|
|
633
|
+
throw new RelayV2Error("protocol");
|
|
634
|
+
}
|
|
635
|
+
return addresses;
|
|
636
|
+
}
|
|
637
|
+
function resolvePort(url) {
|
|
638
|
+
const port = url.port === "" ? 80 : Number(url.port);
|
|
639
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65535)
|
|
640
|
+
throw new RelayV2Error("protocol");
|
|
641
|
+
return port;
|
|
642
|
+
}
|
|
643
|
+
function isNumericLoopback(address) {
|
|
644
|
+
if (net.isIPv4(address))
|
|
645
|
+
return address.startsWith("127.");
|
|
646
|
+
if (!net.isIPv6(address))
|
|
647
|
+
return false;
|
|
648
|
+
const normalized = address.toLowerCase();
|
|
649
|
+
return normalized === "::1" || normalized.startsWith("::ffff:127.");
|
|
650
|
+
}
|
|
651
|
+
async function waitForConnect(socket) {
|
|
652
|
+
await new Promise((resolve, reject) => {
|
|
653
|
+
const onConnect = () => {
|
|
654
|
+
cleanup();
|
|
655
|
+
resolve();
|
|
656
|
+
};
|
|
657
|
+
const onError = (error) => {
|
|
658
|
+
cleanup();
|
|
659
|
+
reject(error);
|
|
660
|
+
};
|
|
661
|
+
const cleanup = () => {
|
|
662
|
+
socket.off("connect", onConnect);
|
|
663
|
+
socket.off("error", onError);
|
|
664
|
+
};
|
|
665
|
+
socket.once("connect", onConnect);
|
|
666
|
+
socket.once("error", onError);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
var SocketReader = class {
|
|
670
|
+
socket;
|
|
671
|
+
buffered = Buffer.alloc(0);
|
|
672
|
+
failure;
|
|
673
|
+
ended = false;
|
|
674
|
+
onData = (chunk) => {
|
|
675
|
+
this.buffered = Buffer.concat([this.buffered, chunk]);
|
|
676
|
+
};
|
|
677
|
+
onError = (error) => {
|
|
678
|
+
this.failure = error;
|
|
679
|
+
};
|
|
680
|
+
onClose = () => {
|
|
681
|
+
this.ended = true;
|
|
682
|
+
};
|
|
683
|
+
constructor(socket) {
|
|
684
|
+
this.socket = socket;
|
|
685
|
+
socket.on("data", this.onData);
|
|
686
|
+
socket.on("error", this.onError);
|
|
687
|
+
socket.on("close", this.onClose);
|
|
688
|
+
}
|
|
689
|
+
async readHttpResponse(maxBodyBytes, allowConnectionClose = false) {
|
|
690
|
+
const parsed = await this.readHeaders();
|
|
691
|
+
if (parsed.status === 101)
|
|
692
|
+
throw new RelayV2Error("protocol", 101);
|
|
693
|
+
if (parsed.headers.has("transfer-encoding"))
|
|
694
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
695
|
+
if (!allowConnectionClose && parsed.status === 200 && hasToken2(parsed.headers.get("connection"), "close")) {
|
|
696
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
697
|
+
}
|
|
698
|
+
const rawLength = parsed.headers.get("content-length");
|
|
699
|
+
if (!rawLength || !/^\d+$/u.test(rawLength))
|
|
700
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
701
|
+
const length = Number(rawLength);
|
|
702
|
+
if (!Number.isSafeInteger(length) || length > maxBodyBytes)
|
|
703
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
704
|
+
await this.waitForBytes(length);
|
|
705
|
+
const body = this.buffered.subarray(0, length);
|
|
706
|
+
this.buffered = this.buffered.subarray(length);
|
|
707
|
+
if (this.buffered.length > 0)
|
|
708
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
709
|
+
return { ...parsed, body };
|
|
710
|
+
}
|
|
711
|
+
async readHttpUpgrade() {
|
|
712
|
+
const parsed = await this.readHeaders();
|
|
713
|
+
if (parsed.headers.has("transfer-encoding"))
|
|
714
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
715
|
+
if (parsed.status === 101) {
|
|
716
|
+
if (parsed.headers.has("content-length"))
|
|
717
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
718
|
+
return { ...parsed, body: Buffer.alloc(0) };
|
|
719
|
+
}
|
|
720
|
+
const rawLength = parsed.headers.get("content-length");
|
|
721
|
+
if (!rawLength || !/^\d+$/u.test(rawLength))
|
|
722
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
723
|
+
const length = Number(rawLength);
|
|
724
|
+
if (!Number.isSafeInteger(length) || length > MAX_JSON_BYTES)
|
|
725
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
726
|
+
await this.waitForBytes(length);
|
|
727
|
+
const body = this.buffered.subarray(0, length);
|
|
728
|
+
this.buffered = this.buffered.subarray(length);
|
|
729
|
+
if (this.buffered.length > 0)
|
|
730
|
+
throw new RelayV2Error("protocol", parsed.status);
|
|
731
|
+
return { ...parsed, body };
|
|
732
|
+
}
|
|
733
|
+
release() {
|
|
734
|
+
this.socket.off("data", this.onData);
|
|
735
|
+
this.socket.off("error", this.onError);
|
|
736
|
+
this.socket.off("close", this.onClose);
|
|
737
|
+
const head = this.buffered;
|
|
738
|
+
this.buffered = Buffer.alloc(0);
|
|
739
|
+
return head;
|
|
740
|
+
}
|
|
741
|
+
async readHeaders() {
|
|
742
|
+
let boundary = this.buffered.indexOf("\r\n\r\n");
|
|
743
|
+
while (boundary < 0) {
|
|
744
|
+
if (this.buffered.length > MAX_HEADER_BYTES)
|
|
745
|
+
throw new RelayV2Error("protocol");
|
|
746
|
+
await this.waitForData();
|
|
747
|
+
boundary = this.buffered.indexOf("\r\n\r\n");
|
|
748
|
+
}
|
|
749
|
+
if (boundary > MAX_HEADER_BYTES)
|
|
750
|
+
throw new RelayV2Error("protocol");
|
|
751
|
+
const raw = this.buffered.subarray(0, boundary).toString("latin1");
|
|
752
|
+
this.buffered = this.buffered.subarray(boundary + 4);
|
|
753
|
+
const lines = raw.split("\r\n");
|
|
754
|
+
const statusLine = lines.shift();
|
|
755
|
+
const match = /^HTTP\/1\.1 ([1-5]\d\d)(?: [\x20-\x7e]*)?$/u.exec(statusLine ?? "");
|
|
756
|
+
if (!match)
|
|
757
|
+
throw new RelayV2Error("protocol");
|
|
758
|
+
const status = Number(match[1]);
|
|
759
|
+
const headers = /* @__PURE__ */ new Map();
|
|
760
|
+
for (const line of lines) {
|
|
761
|
+
if (/^[ \t]/u.test(line))
|
|
762
|
+
throw new RelayV2Error("protocol", status);
|
|
763
|
+
const separator = line.indexOf(":");
|
|
764
|
+
if (separator <= 0)
|
|
765
|
+
throw new RelayV2Error("protocol", status);
|
|
766
|
+
const name = line.slice(0, separator).toLowerCase();
|
|
767
|
+
const value = line.slice(separator + 1).trim();
|
|
768
|
+
if (!/^[!#$%&'*+.^_`|~0-9a-z-]+$/u.test(name) || value.includes("\0") || value.includes("\r") || value.includes("\n") || value.includes("\x7F")) {
|
|
769
|
+
throw new RelayV2Error("protocol", status);
|
|
770
|
+
}
|
|
771
|
+
if (headers.has(name))
|
|
772
|
+
throw new RelayV2Error("protocol", status);
|
|
773
|
+
headers.set(name, value);
|
|
774
|
+
}
|
|
775
|
+
return { status, headers };
|
|
776
|
+
}
|
|
777
|
+
async waitForBytes(length) {
|
|
778
|
+
while (this.buffered.length < length)
|
|
779
|
+
await this.waitForData();
|
|
780
|
+
}
|
|
781
|
+
async waitForData() {
|
|
782
|
+
if (this.failure)
|
|
783
|
+
throw this.failure;
|
|
784
|
+
if (this.ended)
|
|
785
|
+
throw new RelayV2Error("network-error");
|
|
786
|
+
await new Promise((resolve, reject) => {
|
|
787
|
+
const onData = () => {
|
|
788
|
+
cleanup();
|
|
789
|
+
resolve();
|
|
790
|
+
};
|
|
791
|
+
const onError = (error) => {
|
|
792
|
+
cleanup();
|
|
793
|
+
reject(error);
|
|
794
|
+
};
|
|
795
|
+
const onClose = () => {
|
|
796
|
+
cleanup();
|
|
797
|
+
reject(new RelayV2Error("network-error"));
|
|
798
|
+
};
|
|
799
|
+
const cleanup = () => {
|
|
800
|
+
this.socket.off("data", onData);
|
|
801
|
+
this.socket.off("error", onError);
|
|
802
|
+
this.socket.off("close", onClose);
|
|
803
|
+
};
|
|
804
|
+
this.socket.once("data", onData);
|
|
805
|
+
this.socket.once("error", onError);
|
|
806
|
+
this.socket.once("close", onClose);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
async function writeJsonRequest(socket, host, pathname, body) {
|
|
811
|
+
const encoded = Buffer.from(JSON.stringify(body), "utf8");
|
|
812
|
+
await writeRequest(socket, host, "POST", pathname, [
|
|
813
|
+
["Content-Type", "application/json"],
|
|
814
|
+
["Content-Length", String(encoded.byteLength)]
|
|
815
|
+
], encoded);
|
|
816
|
+
}
|
|
817
|
+
async function writeRequest(socket, host, method, pathname, headers, body) {
|
|
818
|
+
const lines = [`${method} ${pathname} HTTP/1.1`, `Host: ${host}`, "Connection: keep-alive"];
|
|
819
|
+
for (const [name, value] of headers) {
|
|
820
|
+
if (name.toLowerCase() === "connection")
|
|
821
|
+
lines[2] = `${name}: ${value}`;
|
|
822
|
+
else
|
|
823
|
+
lines.push(`${name}: ${value}`);
|
|
824
|
+
}
|
|
825
|
+
const head = Buffer.from(`${lines.join("\r\n")}\r
|
|
826
|
+
\r
|
|
827
|
+
`, "ascii");
|
|
828
|
+
const output = body ? Buffer.concat([head, body]) : head;
|
|
829
|
+
await new Promise((resolve, reject) => {
|
|
830
|
+
socket.write(output, (error) => error ? reject(error) : resolve());
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
function ensureAuthStatus(response, phase) {
|
|
834
|
+
if (response.status === 200)
|
|
835
|
+
return;
|
|
836
|
+
if (phase === "challenge" && response.status === 401 && responseError(response) === "Relay auth challenge rejected") {
|
|
837
|
+
throw new RelayV2Error("replay", response.status);
|
|
838
|
+
}
|
|
839
|
+
if (response.status === 409)
|
|
840
|
+
throw new RelayV2Error("sequence", response.status);
|
|
841
|
+
throw classifyStatus(response.status, false);
|
|
842
|
+
}
|
|
843
|
+
function classifyStatus(status, authenticated) {
|
|
844
|
+
if (!authenticated && (status === 401 || status === 404 || status === 426)) {
|
|
845
|
+
return new RelayV2Error("unsupported-auth", status);
|
|
846
|
+
}
|
|
847
|
+
if (!authenticated && status === 503)
|
|
848
|
+
return new RelayV2Error("server-auth-failed", status);
|
|
849
|
+
if (status === 408 || status === 410)
|
|
850
|
+
return new RelayV2Error("freshness", status);
|
|
851
|
+
if (status === 409)
|
|
852
|
+
return new RelayV2Error("sequence", status);
|
|
853
|
+
if (status === 412)
|
|
854
|
+
return new RelayV2Error("sequence", status);
|
|
855
|
+
return new RelayV2Error("protocol", status);
|
|
856
|
+
}
|
|
857
|
+
function responseError(response) {
|
|
858
|
+
try {
|
|
859
|
+
const value = parseStrictJsonObject(response.body);
|
|
860
|
+
return typeof value.error === "string" ? value.error : void 0;
|
|
861
|
+
} catch {
|
|
862
|
+
return void 0;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function validateChallenge(value, hello, nowMs) {
|
|
866
|
+
assertExactKeys(value, [
|
|
867
|
+
"type",
|
|
868
|
+
"v",
|
|
869
|
+
"keyId",
|
|
870
|
+
"instanceId",
|
|
871
|
+
"sessionId",
|
|
872
|
+
"clientNonce",
|
|
873
|
+
"serverNonce",
|
|
874
|
+
"issuedAtMs",
|
|
875
|
+
"expiresAtMs",
|
|
876
|
+
"role",
|
|
877
|
+
"transport",
|
|
878
|
+
"method",
|
|
879
|
+
"resource",
|
|
880
|
+
"flow",
|
|
881
|
+
"serverProof"
|
|
882
|
+
]);
|
|
883
|
+
if (value.type !== "auth.challenge")
|
|
884
|
+
throw new RelayV2Error("protocol");
|
|
885
|
+
if (value.v !== BROWSER_RELAY_AUTH_VERSION)
|
|
886
|
+
throw new RelayV2Error("protocol");
|
|
887
|
+
for (const key of ["keyId", "clientNonce", "role", "transport", "method", "resource", "flow"]) {
|
|
888
|
+
if (value[key] !== hello[key])
|
|
889
|
+
throw new RelayV2Error("protocol");
|
|
890
|
+
}
|
|
891
|
+
const fields = {
|
|
892
|
+
keyId: requireToken(value, "keyId", 22),
|
|
893
|
+
instanceId: requireEncoded(value, "instanceId", 22),
|
|
894
|
+
sessionId: requireEncoded(value, "sessionId", 22),
|
|
895
|
+
clientNonce: requireEncoded(value, "clientNonce", 43),
|
|
896
|
+
serverNonce: requireEncoded(value, "serverNonce", 43),
|
|
897
|
+
issuedAtMs: requireInteger(value, "issuedAtMs"),
|
|
898
|
+
expiresAtMs: requireInteger(value, "expiresAtMs"),
|
|
899
|
+
role: requireString(value, "role"),
|
|
900
|
+
transport: requireString(value, "transport"),
|
|
901
|
+
method: requireString(value, "method"),
|
|
902
|
+
resource: requireString(value, "resource"),
|
|
903
|
+
flow: requireString(value, "flow")
|
|
904
|
+
};
|
|
905
|
+
requireEncoded(value, "serverProof", 43);
|
|
906
|
+
const lifetime = fields.expiresAtMs - fields.issuedAtMs;
|
|
907
|
+
if (lifetime <= 0 || lifetime > BROWSER_RELAY_CHALLENGE_MAX_LIFETIME_MS || fields.issuedAtMs - nowMs > BROWSER_RELAY_CLOCK_SKEW_MS || nowMs > fields.expiresAtMs) {
|
|
908
|
+
throw new RelayV2Error("freshness");
|
|
909
|
+
}
|
|
910
|
+
return fields;
|
|
911
|
+
}
|
|
912
|
+
function validateAccept(value, sessionId) {
|
|
913
|
+
assertExactKeys(value, ["type", "v", "sessionId", "acceptProof"]);
|
|
914
|
+
if (value.type !== "auth.ok")
|
|
915
|
+
throw new RelayV2Error("protocol");
|
|
916
|
+
if (value.v !== BROWSER_RELAY_AUTH_VERSION || value.sessionId !== sessionId)
|
|
917
|
+
throw new RelayV2Error("protocol");
|
|
918
|
+
requireEncoded(value, "acceptProof", 43);
|
|
919
|
+
}
|
|
920
|
+
function parseStrictJsonObject(body) {
|
|
921
|
+
const value = parseStrictJson(body);
|
|
922
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
923
|
+
throw new RelayV2Error("protocol");
|
|
924
|
+
return value;
|
|
925
|
+
}
|
|
926
|
+
function parseStrictJson(body) {
|
|
927
|
+
let source;
|
|
928
|
+
try {
|
|
929
|
+
source = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
930
|
+
} catch {
|
|
931
|
+
throw new RelayV2Error("protocol");
|
|
932
|
+
}
|
|
933
|
+
const errors = [];
|
|
934
|
+
const tree = parseTree(source, errors, { allowTrailingComma: false, disallowComments: true });
|
|
935
|
+
if (!tree || errors.length > 0)
|
|
936
|
+
throw new RelayV2Error("protocol");
|
|
937
|
+
rejectDuplicateJsonKeys(tree);
|
|
938
|
+
try {
|
|
939
|
+
return JSON.parse(source);
|
|
940
|
+
} catch {
|
|
941
|
+
throw new RelayV2Error("protocol");
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function rejectDuplicateJsonKeys(node) {
|
|
945
|
+
if (node.type === "object") {
|
|
946
|
+
const keys = /* @__PURE__ */ new Set();
|
|
947
|
+
for (const property of node.children ?? []) {
|
|
948
|
+
const key = property.children?.[0]?.value;
|
|
949
|
+
if (typeof key !== "string" || keys.has(key))
|
|
950
|
+
throw new RelayV2Error("protocol");
|
|
951
|
+
keys.add(key);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
for (const child of node.children ?? [])
|
|
955
|
+
rejectDuplicateJsonKeys(child);
|
|
956
|
+
}
|
|
957
|
+
function assertExactKeys(value, required) {
|
|
958
|
+
const actual = Object.keys(value);
|
|
959
|
+
if (actual.length !== required.length || required.some((key) => !Object.hasOwn(value, key))) {
|
|
960
|
+
throw new RelayV2Error("protocol");
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function requireString(value, key) {
|
|
964
|
+
const field = value[key];
|
|
965
|
+
if (typeof field !== "string")
|
|
966
|
+
throw new RelayV2Error("protocol");
|
|
967
|
+
return field;
|
|
968
|
+
}
|
|
969
|
+
function requireEncoded(value, key, length) {
|
|
970
|
+
const field = requireString(value, key);
|
|
971
|
+
const bytes = length === 22 ? 16 : length === 43 ? 32 : 0;
|
|
972
|
+
const decoded = Buffer.from(field, "base64url");
|
|
973
|
+
if (bytes === 0 || field.length !== length || !/^[A-Za-z0-9_-]+$/u.test(field) || decoded.byteLength !== bytes || decoded.toString("base64url") !== field) {
|
|
974
|
+
throw new RelayV2Error("protocol");
|
|
975
|
+
}
|
|
976
|
+
return field;
|
|
977
|
+
}
|
|
978
|
+
function requireToken(value, key, length) {
|
|
979
|
+
const field = requireString(value, key);
|
|
980
|
+
if (field.length !== length || !/^[A-Za-z0-9_-]+$/u.test(field))
|
|
981
|
+
throw new RelayV2Error("protocol");
|
|
982
|
+
return field;
|
|
983
|
+
}
|
|
984
|
+
function requireInteger(value, key) {
|
|
985
|
+
const field = value[key];
|
|
986
|
+
if (typeof field !== "number" || !Number.isSafeInteger(field))
|
|
987
|
+
throw new RelayV2Error("protocol");
|
|
988
|
+
return field;
|
|
989
|
+
}
|
|
990
|
+
function hasToken2(value, expected) {
|
|
991
|
+
return Boolean(value?.split(",").some((token) => token.trim().toLowerCase() === expected));
|
|
992
|
+
}
|
|
993
|
+
function isTimeoutError(error) {
|
|
994
|
+
return Boolean(error && typeof error === "object" && error.reason === "timeout");
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// node_modules/.pnpm/mcporter@0.13.6/node_modules/mcporter/dist/chrome-devtools-relay-discovery.js
|
|
998
|
+
import { spawn } from "node:child_process";
|
|
999
|
+
import { statSync } from "node:fs";
|
|
1000
|
+
import net2 from "node:net";
|
|
1001
|
+
import path from "node:path";
|
|
1002
|
+
var OPENCLAW_CDP_ARGS = ["browser", "extension", "cdp", "--json"];
|
|
1003
|
+
var OPENCLAW_WINDOWS_COMMAND_SUFFIX = "browser extension cdp --json";
|
|
1004
|
+
var OPENCLAW_WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
1005
|
+
var OPENCLAW_WINDOWS_TREE_KILL_TIMEOUT_MS = 1e3;
|
|
1006
|
+
var OPENCLAW_PROFILE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
|
1007
|
+
var OPENCLAW_RELAY_DISCOVERY_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
1008
|
+
var OPENCLAW_RELAY_DISCOVERY_ENV_KEYS = [
|
|
1009
|
+
"APPDATA",
|
|
1010
|
+
"COMSPEC",
|
|
1011
|
+
"ComSpec",
|
|
1012
|
+
"HOME",
|
|
1013
|
+
"HOMEDRIVE",
|
|
1014
|
+
"HOMEPATH",
|
|
1015
|
+
"LANG",
|
|
1016
|
+
"LC_ALL",
|
|
1017
|
+
"LOCALAPPDATA",
|
|
1018
|
+
"OPENCLAW_CONFIG_PATH",
|
|
1019
|
+
"OPENCLAW_GATEWAY_PORT",
|
|
1020
|
+
"OPENCLAW_HOME",
|
|
1021
|
+
"OPENCLAW_OAUTH_DIR",
|
|
1022
|
+
"OPENCLAW_PROFILE",
|
|
1023
|
+
"OPENCLAW_STATE_DIR",
|
|
1024
|
+
"PATH",
|
|
1025
|
+
"Path",
|
|
1026
|
+
"PATHEXT",
|
|
1027
|
+
"SYSTEMROOT",
|
|
1028
|
+
"SystemRoot",
|
|
1029
|
+
"TEMP",
|
|
1030
|
+
"TMP",
|
|
1031
|
+
"TMPDIR",
|
|
1032
|
+
"USERPROFILE",
|
|
1033
|
+
"WINDIR",
|
|
1034
|
+
"XDG_CONFIG_HOME",
|
|
1035
|
+
"XDG_STATE_HOME"
|
|
1036
|
+
];
|
|
1037
|
+
function normalizeOpenClawProfile(raw) {
|
|
1038
|
+
const profile = raw?.trim();
|
|
1039
|
+
if (!profile || profile.toLowerCase() === "default" || !OPENCLAW_PROFILE_PATTERN.test(profile)) {
|
|
1040
|
+
return void 0;
|
|
1041
|
+
}
|
|
1042
|
+
return profile;
|
|
1043
|
+
}
|
|
1044
|
+
async function discoverOpenClawRelayUrl(options) {
|
|
1045
|
+
const platform = options.platform ?? process.platform;
|
|
1046
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1047
|
+
const filteredEnv = openClawDiscoveryEnvironment(options.env);
|
|
1048
|
+
const command = resolveOpenClawRelayDiscoveryCommandPlan(filteredEnv, platform, options.isFile, cwd);
|
|
1049
|
+
if (!command)
|
|
1050
|
+
return { reason: "unavailable" };
|
|
1051
|
+
const env = sanitizeOpenClawDiscoveryCommandEnvironment(filteredEnv, command, platform, cwd);
|
|
1052
|
+
const result = await (options.run ?? runOpenClawRelayDiscovery)({
|
|
1053
|
+
...command,
|
|
1054
|
+
env,
|
|
1055
|
+
timeoutMs: options.timeoutMs,
|
|
1056
|
+
maxOutputBytes: OPENCLAW_RELAY_DISCOVERY_MAX_OUTPUT_BYTES,
|
|
1057
|
+
shell: false
|
|
1058
|
+
});
|
|
1059
|
+
return validateDiscoveryCommandResult(result, options.keyId);
|
|
1060
|
+
}
|
|
1061
|
+
function runOpenClawRelayDiscovery(options, runtime = {}) {
|
|
1062
|
+
return new Promise((resolve) => {
|
|
1063
|
+
const child = (runtime.spawn ?? spawn)(options.executable, [...options.args], {
|
|
1064
|
+
cwd: options.cwd,
|
|
1065
|
+
env: options.env,
|
|
1066
|
+
shell: options.shell,
|
|
1067
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1068
|
+
windowsVerbatimArguments: options.platform === "win32",
|
|
1069
|
+
windowsHide: true
|
|
1070
|
+
});
|
|
1071
|
+
const stdout = [];
|
|
1072
|
+
let outputBytes = 0;
|
|
1073
|
+
let settled = false;
|
|
1074
|
+
let terminating = false;
|
|
1075
|
+
let timer;
|
|
1076
|
+
const finish = (result) => {
|
|
1077
|
+
if (settled)
|
|
1078
|
+
return;
|
|
1079
|
+
settled = true;
|
|
1080
|
+
if (timer)
|
|
1081
|
+
clearTimeout(timer);
|
|
1082
|
+
resolve(result);
|
|
1083
|
+
};
|
|
1084
|
+
const terminate = (kind) => {
|
|
1085
|
+
if (settled || terminating)
|
|
1086
|
+
return;
|
|
1087
|
+
terminating = true;
|
|
1088
|
+
child.stdout.destroy();
|
|
1089
|
+
child.stderr.destroy();
|
|
1090
|
+
const plan = createOpenClawRelayDiscoveryTerminationPlan(options, child.pid);
|
|
1091
|
+
const terminateProcessTree = runtime.terminateProcessTree ?? terminateOpenClawRelayDiscoveryProcessTree;
|
|
1092
|
+
let termination = Promise.resolve();
|
|
1093
|
+
if (plan) {
|
|
1094
|
+
try {
|
|
1095
|
+
termination = terminateProcessTree(plan, child);
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
void boundedTreeTermination(termination).finally(() => {
|
|
1100
|
+
child.unref();
|
|
1101
|
+
finish({ kind });
|
|
1102
|
+
});
|
|
1103
|
+
};
|
|
1104
|
+
const collect = (chunk, keep) => {
|
|
1105
|
+
if (settled || terminating)
|
|
1106
|
+
return;
|
|
1107
|
+
outputBytes += chunk.byteLength;
|
|
1108
|
+
if (outputBytes > options.maxOutputBytes) {
|
|
1109
|
+
terminate("overflow");
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (keep)
|
|
1113
|
+
stdout.push(chunk);
|
|
1114
|
+
};
|
|
1115
|
+
child.stdout.on("data", (chunk) => collect(chunk, true));
|
|
1116
|
+
child.stderr.on("data", (chunk) => collect(chunk, false));
|
|
1117
|
+
child.once("error", (error) => {
|
|
1118
|
+
if (!terminating)
|
|
1119
|
+
finish({ kind: classifyCommandError(error) });
|
|
1120
|
+
});
|
|
1121
|
+
child.once("close", (code) => {
|
|
1122
|
+
if (!terminating) {
|
|
1123
|
+
finish(code === 0 ? { kind: "success", stdout: Buffer.concat(stdout) } : { kind: "nonzero" });
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
timer = setTimeout(() => terminate("timeout"), options.timeoutMs);
|
|
1127
|
+
timer.unref();
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
function resolveOpenClawRelayDiscoveryCommandPlan(env, platform = process.platform, isFile = isRegularFile, cwd = process.cwd()) {
|
|
1131
|
+
if (platform !== "win32")
|
|
1132
|
+
return { executable: "openclaw", args: OPENCLAW_CDP_ARGS, platform };
|
|
1133
|
+
const executable = resolveWindowsCommandInterpreter(env);
|
|
1134
|
+
const openClawShim = resolveWindowsOpenClawShim(env, isFile, cwd);
|
|
1135
|
+
if (!executable || !openClawShim)
|
|
1136
|
+
return void 0;
|
|
1137
|
+
const taskkillExecutable = path.win32.join(path.win32.dirname(executable), "taskkill.exe");
|
|
1138
|
+
if (!isSafeWindowsSystemToolPath(taskkillExecutable, "taskkill.exe"))
|
|
1139
|
+
return void 0;
|
|
1140
|
+
return {
|
|
1141
|
+
executable,
|
|
1142
|
+
// The only variable bytes are the validated absolute shim path. No config,
|
|
1143
|
+
// profile, URL, metadata, or unresolved command name reaches cmd.exe.
|
|
1144
|
+
args: ["/d", "/s", "/c", `""${openClawShim}" ${OPENCLAW_WINDOWS_COMMAND_SUFFIX}"`],
|
|
1145
|
+
cwd: path.win32.dirname(openClawShim),
|
|
1146
|
+
platform,
|
|
1147
|
+
taskkillExecutable
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function resolveWindowsCommandInterpreter(env) {
|
|
1151
|
+
const systemRoot = env.SYSTEMROOT?.trim() || env.SystemRoot?.trim();
|
|
1152
|
+
const systemCmd = systemRoot && isSafeWindowsSystemRoot(systemRoot) ? path.win32.join(systemRoot, "System32", "cmd.exe") : void 0;
|
|
1153
|
+
const comSpec = env.COMSPEC?.trim() || env.ComSpec?.trim();
|
|
1154
|
+
if (comSpec && isSafeWindowsCmdPath(comSpec)) {
|
|
1155
|
+
const normalizedComSpec = path.win32.normalize(comSpec);
|
|
1156
|
+
if (systemCmd && normalizedComSpec.toLowerCase() === systemCmd.toLowerCase() || !systemCmd && isConventionalWindowsSystemCmd(normalizedComSpec)) {
|
|
1157
|
+
return normalizedComSpec;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
return systemCmd;
|
|
1161
|
+
}
|
|
1162
|
+
function resolveWindowsOpenClawShim(env, isFile, cwd) {
|
|
1163
|
+
const pathEntries = safeWindowsPathEntries(env.PATH ?? env.Path, cwd);
|
|
1164
|
+
const extensions = windowsPathExtensions(env.PATHEXT);
|
|
1165
|
+
for (const directory of pathEntries) {
|
|
1166
|
+
for (const extension of extensions) {
|
|
1167
|
+
if (extension.toLowerCase() !== ".cmd")
|
|
1168
|
+
continue;
|
|
1169
|
+
const candidate = path.win32.join(directory, `openclaw${extension}`);
|
|
1170
|
+
if (isSafeWindowsCommandPath(candidate) && isFile(candidate))
|
|
1171
|
+
return path.win32.normalize(candidate);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return void 0;
|
|
1175
|
+
}
|
|
1176
|
+
function isSafeWindowsCmdPath(value) {
|
|
1177
|
+
return isSafeWindowsSystemToolPath(value, "cmd.exe");
|
|
1178
|
+
}
|
|
1179
|
+
function isSafeWindowsSystemToolPath(value, basename) {
|
|
1180
|
+
const parent = path.win32.dirname(value);
|
|
1181
|
+
const root = path.win32.dirname(parent);
|
|
1182
|
+
return isSafeWindowsCommandPath(value) && path.win32.basename(value).toLowerCase() === basename && path.win32.basename(parent).toLowerCase() === "system32" && ["windows", "winnt"].includes(path.win32.basename(root).toLowerCase());
|
|
1183
|
+
}
|
|
1184
|
+
function isSafeWindowsSystemRoot(value) {
|
|
1185
|
+
const basename = path.win32.basename(value).toLowerCase();
|
|
1186
|
+
return isSafeWindowsAbsolutePath(value) && basename !== "cmd.exe" && basename !== "system32";
|
|
1187
|
+
}
|
|
1188
|
+
function isConventionalWindowsSystemCmd(value) {
|
|
1189
|
+
const parent = path.win32.dirname(value);
|
|
1190
|
+
const root = path.win32.dirname(parent);
|
|
1191
|
+
return path.win32.basename(parent).toLowerCase() === "system32" && ["windows", "winnt"].includes(path.win32.basename(root).toLowerCase());
|
|
1192
|
+
}
|
|
1193
|
+
function isSafeWindowsAbsolutePath(value) {
|
|
1194
|
+
if (!/^[a-z]:[\\/]/i.test(value) || /["<>|?*]/u.test(value) || hasControlCharacter(value) || value.slice(2).includes(":")) {
|
|
1195
|
+
return false;
|
|
1196
|
+
}
|
|
1197
|
+
const segments = value.replaceAll("/", "\\").split("\\");
|
|
1198
|
+
return !segments.some((segment) => segment === "." || segment === "..");
|
|
1199
|
+
}
|
|
1200
|
+
function isSafeWindowsCommandPath(value) {
|
|
1201
|
+
return isSafeWindowsAbsolutePath(value) && !/["%&|<>^!]/u.test(value) && !hasControlCharacter(value);
|
|
1202
|
+
}
|
|
1203
|
+
function hasControlCharacter(value) {
|
|
1204
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1205
|
+
if (value.charCodeAt(index) < 32)
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
return false;
|
|
1209
|
+
}
|
|
1210
|
+
function safeWindowsPathEntries(value, excludedDirectory) {
|
|
1211
|
+
if (!value)
|
|
1212
|
+
return [];
|
|
1213
|
+
const excluded = excludedDirectory && isSafeWindowsCommandPath(excludedDirectory) ? path.win32.resolve(excludedDirectory).toLowerCase() : void 0;
|
|
1214
|
+
return value.split(";").map((entry) => entry.trim()).filter((entry) => entry !== "" && path.win32.isAbsolute(entry) && isSafeWindowsCommandPath(entry)).map((entry) => path.win32.resolve(entry)).filter((entry) => entry.toLowerCase() !== excluded);
|
|
1215
|
+
}
|
|
1216
|
+
function windowsPathExtensions(value) {
|
|
1217
|
+
const raw = value?.trim() || OPENCLAW_WINDOWS_DEFAULT_PATHEXT;
|
|
1218
|
+
return raw.split(";").map((extension) => extension.trim()).filter((extension) => /^\.[a-z0-9]+$/iu.test(extension));
|
|
1219
|
+
}
|
|
1220
|
+
function isRegularFile(candidate) {
|
|
1221
|
+
try {
|
|
1222
|
+
return statSync(candidate).isFile();
|
|
1223
|
+
} catch {
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function sanitizeOpenClawDiscoveryCommandEnvironment(env, command, platform, sourceCwd) {
|
|
1228
|
+
if (platform !== "win32")
|
|
1229
|
+
return env;
|
|
1230
|
+
const sanitized = { ...env };
|
|
1231
|
+
delete sanitized.ComSpec;
|
|
1232
|
+
delete sanitized.COMSPEC;
|
|
1233
|
+
sanitized.ComSpec = command.executable;
|
|
1234
|
+
const safePath = safeWindowsPathEntries(sanitized.PATH ?? sanitized.Path, sourceCwd).join(";");
|
|
1235
|
+
delete sanitized.Path;
|
|
1236
|
+
delete sanitized.PATH;
|
|
1237
|
+
if (safePath)
|
|
1238
|
+
sanitized.Path = safePath;
|
|
1239
|
+
const systemRoot = sanitized.SYSTEMROOT?.trim() || sanitized.SystemRoot?.trim();
|
|
1240
|
+
if (systemRoot && !isSafeWindowsSystemRoot(systemRoot)) {
|
|
1241
|
+
delete sanitized.SystemRoot;
|
|
1242
|
+
delete sanitized.SYSTEMROOT;
|
|
1243
|
+
}
|
|
1244
|
+
return sanitized;
|
|
1245
|
+
}
|
|
1246
|
+
function createOpenClawRelayDiscoveryTerminationPlan(options, pid) {
|
|
1247
|
+
if (!Number.isSafeInteger(pid) || !pid || pid <= 0)
|
|
1248
|
+
return void 0;
|
|
1249
|
+
if (options.platform !== "win32")
|
|
1250
|
+
return { kind: "posix-sigkill", pid, signal: "SIGKILL" };
|
|
1251
|
+
if (!options.taskkillExecutable || !isSafeWindowsSystemToolPath(options.taskkillExecutable, "taskkill.exe")) {
|
|
1252
|
+
return void 0;
|
|
1253
|
+
}
|
|
1254
|
+
return {
|
|
1255
|
+
kind: "windows-taskkill",
|
|
1256
|
+
pid,
|
|
1257
|
+
executable: options.taskkillExecutable,
|
|
1258
|
+
args: ["/PID", String(pid), "/T", "/F"],
|
|
1259
|
+
shell: false,
|
|
1260
|
+
windowsHide: true
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
async function terminateOpenClawRelayDiscoveryProcessTree(plan, child) {
|
|
1264
|
+
if (plan.kind === "posix-sigkill") {
|
|
1265
|
+
try {
|
|
1266
|
+
child.kill(plan.signal);
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
await new Promise((resolve) => {
|
|
1272
|
+
let killer;
|
|
1273
|
+
try {
|
|
1274
|
+
killer = spawn(plan.executable, [...plan.args], {
|
|
1275
|
+
shell: plan.shell,
|
|
1276
|
+
stdio: "ignore",
|
|
1277
|
+
windowsHide: plan.windowsHide
|
|
1278
|
+
});
|
|
1279
|
+
} catch {
|
|
1280
|
+
resolve();
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
let done = false;
|
|
1284
|
+
const finish = () => {
|
|
1285
|
+
if (done)
|
|
1286
|
+
return;
|
|
1287
|
+
done = true;
|
|
1288
|
+
clearTimeout(timer);
|
|
1289
|
+
resolve();
|
|
1290
|
+
};
|
|
1291
|
+
killer.once("error", finish);
|
|
1292
|
+
killer.once("close", finish);
|
|
1293
|
+
const timer = setTimeout(() => {
|
|
1294
|
+
try {
|
|
1295
|
+
killer.kill("SIGKILL");
|
|
1296
|
+
} catch {
|
|
1297
|
+
}
|
|
1298
|
+
finish();
|
|
1299
|
+
}, OPENCLAW_WINDOWS_TREE_KILL_TIMEOUT_MS);
|
|
1300
|
+
timer.unref();
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
async function boundedTreeTermination(termination) {
|
|
1304
|
+
let timer;
|
|
1305
|
+
try {
|
|
1306
|
+
await Promise.race([
|
|
1307
|
+
termination.catch(() => void 0),
|
|
1308
|
+
new Promise((resolve) => {
|
|
1309
|
+
timer = setTimeout(resolve, OPENCLAW_WINDOWS_TREE_KILL_TIMEOUT_MS + 250);
|
|
1310
|
+
timer.unref();
|
|
1311
|
+
})
|
|
1312
|
+
]);
|
|
1313
|
+
} finally {
|
|
1314
|
+
if (timer)
|
|
1315
|
+
clearTimeout(timer);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
function classifyCommandError(error) {
|
|
1319
|
+
const details = error;
|
|
1320
|
+
if (details.code === "ENOENT")
|
|
1321
|
+
return "unavailable";
|
|
1322
|
+
if (details.code === "ETIMEDOUT" || details.killed === true)
|
|
1323
|
+
return "timeout";
|
|
1324
|
+
if (details.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || details.code === "ENOBUFS")
|
|
1325
|
+
return "overflow";
|
|
1326
|
+
return "nonzero";
|
|
1327
|
+
}
|
|
1328
|
+
function validateDiscoveryCommandResult(result, expectedKeyId) {
|
|
1329
|
+
if (result.kind !== "success")
|
|
1330
|
+
return { reason: result.kind };
|
|
1331
|
+
if (result.stdout.byteLength > OPENCLAW_RELAY_DISCOVERY_MAX_OUTPUT_BYTES)
|
|
1332
|
+
return { reason: "overflow" };
|
|
1333
|
+
const encoded = result.stdout.toString("utf8").trim();
|
|
1334
|
+
const parseErrors = [];
|
|
1335
|
+
const tree = parseTree(encoded, parseErrors, { allowTrailingComma: false, disallowComments: true });
|
|
1336
|
+
if (!tree || parseErrors.length > 0 || hasDuplicateObjectKeys(tree))
|
|
1337
|
+
return { reason: "malformed" };
|
|
1338
|
+
let metadata;
|
|
1339
|
+
try {
|
|
1340
|
+
metadata = JSON.parse(encoded);
|
|
1341
|
+
} catch {
|
|
1342
|
+
return { reason: "malformed" };
|
|
1343
|
+
}
|
|
1344
|
+
if (!isPlainObject(metadata) || !hasExactKeys(metadata, ["auth", "browserUrl", "wsEndpoint"])) {
|
|
1345
|
+
return { reason: "incompatible" };
|
|
1346
|
+
}
|
|
1347
|
+
if (!isPlainObject(metadata.auth) || !hasExactKeys(metadata.auth, [
|
|
1348
|
+
"challengeUrl",
|
|
1349
|
+
"completeUrl",
|
|
1350
|
+
"flow",
|
|
1351
|
+
"keyId",
|
|
1352
|
+
"label",
|
|
1353
|
+
"method",
|
|
1354
|
+
"resource",
|
|
1355
|
+
"role",
|
|
1356
|
+
"transport",
|
|
1357
|
+
"version"
|
|
1358
|
+
])) {
|
|
1359
|
+
return { reason: "incompatible" };
|
|
1360
|
+
}
|
|
1361
|
+
const browserUrl = parseBrowserUrl(metadata.browserUrl);
|
|
1362
|
+
if (!browserUrl)
|
|
1363
|
+
return { reason: "unsafe" };
|
|
1364
|
+
const wsEndpoint = parseWebSocketUrl(metadata.wsEndpoint);
|
|
1365
|
+
if (!wsEndpoint || wsEndpoint.host !== browserUrl.host)
|
|
1366
|
+
return { reason: "unsafe" };
|
|
1367
|
+
const auth = metadata.auth;
|
|
1368
|
+
if (auth.label !== BROWSER_RELAY_AUTH_LABEL || auth.version !== BROWSER_RELAY_AUTH_VERSION || auth.role !== BROWSER_RELAY_CDP_ROLE || auth.transport !== BROWSER_RELAY_CDP_TRANSPORT || auth.method !== BROWSER_RELAY_CDP_METHOD || auth.resource !== BROWSER_RELAY_CDP_RESOURCE || auth.flow !== BROWSER_RELAY_CDP_FLOW || auth.challengeUrl !== new URL(BROWSER_RELAY_AUTH_CHALLENGE_PATH, browserUrl).toString() || auth.completeUrl !== new URL(BROWSER_RELAY_AUTH_COMPLETE_PATH, browserUrl).toString()) {
|
|
1369
|
+
return { reason: "incompatible" };
|
|
1370
|
+
}
|
|
1371
|
+
if (auth.keyId !== expectedKeyId)
|
|
1372
|
+
return { reason: "key-id-mismatch" };
|
|
1373
|
+
return { reason: "success", url: browserUrl };
|
|
1374
|
+
}
|
|
1375
|
+
function parseBrowserUrl(value) {
|
|
1376
|
+
if (typeof value !== "string")
|
|
1377
|
+
return void 0;
|
|
1378
|
+
let url;
|
|
1379
|
+
try {
|
|
1380
|
+
url = new URL(value);
|
|
1381
|
+
} catch {
|
|
1382
|
+
return void 0;
|
|
1383
|
+
}
|
|
1384
|
+
if (url.protocol !== "http:" || !isLoopbackHostname(url.hostname) || url.username !== "" || url.password !== "" || !hasValidPort(url) || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
return new URL(url.origin);
|
|
1388
|
+
}
|
|
1389
|
+
function parseWebSocketUrl(value) {
|
|
1390
|
+
if (typeof value !== "string")
|
|
1391
|
+
return void 0;
|
|
1392
|
+
let url;
|
|
1393
|
+
try {
|
|
1394
|
+
url = new URL(value);
|
|
1395
|
+
} catch {
|
|
1396
|
+
return void 0;
|
|
1397
|
+
}
|
|
1398
|
+
if (url.protocol !== "ws:" || !isLoopbackHostname(url.hostname) || url.username !== "" || url.password !== "" || !hasValidPort(url) || url.pathname !== BROWSER_RELAY_CDP_PATH || url.search !== "" || url.hash !== "") {
|
|
1399
|
+
return void 0;
|
|
1400
|
+
}
|
|
1401
|
+
return url;
|
|
1402
|
+
}
|
|
1403
|
+
function isLoopbackHostname(hostname) {
|
|
1404
|
+
const normalized = hostname.replace(/^\[(.*)\]$/u, "$1").toLowerCase();
|
|
1405
|
+
if (normalized === "localhost" || normalized === "::1")
|
|
1406
|
+
return true;
|
|
1407
|
+
if (net2.isIPv4(normalized))
|
|
1408
|
+
return normalized.startsWith("127.");
|
|
1409
|
+
if (!net2.isIPv6(normalized))
|
|
1410
|
+
return false;
|
|
1411
|
+
return normalized.startsWith("::ffff:127.") || /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/u.test(normalized);
|
|
1412
|
+
}
|
|
1413
|
+
function hasValidPort(url) {
|
|
1414
|
+
if (url.port === "")
|
|
1415
|
+
return true;
|
|
1416
|
+
const port = Number(url.port);
|
|
1417
|
+
return Number.isSafeInteger(port) && port >= 1 && port <= 65535;
|
|
1418
|
+
}
|
|
1419
|
+
function isPlainObject(value) {
|
|
1420
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1421
|
+
}
|
|
1422
|
+
function hasExactKeys(value, expected) {
|
|
1423
|
+
const actual = Object.keys(value).toSorted();
|
|
1424
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
1425
|
+
}
|
|
1426
|
+
function hasDuplicateObjectKeys(node) {
|
|
1427
|
+
if (node.type === "object") {
|
|
1428
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1429
|
+
for (const property of node.children ?? []) {
|
|
1430
|
+
const key = property.children?.[0]?.value;
|
|
1431
|
+
if (typeof key !== "string" || seen.has(key))
|
|
1432
|
+
return true;
|
|
1433
|
+
seen.add(key);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return (node.children ?? []).some(hasDuplicateObjectKeys);
|
|
1437
|
+
}
|
|
1438
|
+
function openClawDiscoveryEnvironment(env) {
|
|
1439
|
+
const filtered = Object.fromEntries(OPENCLAW_RELAY_DISCOVERY_ENV_KEYS.flatMap((key) => {
|
|
1440
|
+
const value = env[key];
|
|
1441
|
+
return value === void 0 ? [] : [[key, value]];
|
|
1442
|
+
}));
|
|
1443
|
+
const profile = normalizeOpenClawProfile(filtered.OPENCLAW_PROFILE);
|
|
1444
|
+
if (profile)
|
|
1445
|
+
filtered.OPENCLAW_PROFILE = profile;
|
|
1446
|
+
else
|
|
1447
|
+
delete filtered.OPENCLAW_PROFILE;
|
|
1448
|
+
return filtered;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// node_modules/.pnpm/mcporter@0.13.6/node_modules/mcporter/dist/chrome-devtools-relay.js
|
|
1452
|
+
var DEFAULT_RELAY_PROBE_TIMEOUT_MS = 5e3;
|
|
1453
|
+
var MIN_RELAY_PROBE_TIMEOUT_MS = 100;
|
|
1454
|
+
var MAX_RELAY_PROBE_TIMEOUT_MS = 3e4;
|
|
1455
|
+
var DEFAULT_RELAY_URL = "http://127.0.0.1:18799";
|
|
1456
|
+
var DEFAULT_RELAY_IDENTITY_URL = new URL(DEFAULT_RELAY_URL).toString();
|
|
1457
|
+
var RELAY_CWD_SENTINEL = "$MCPORTER_CHROME_RELAY_CWD";
|
|
1458
|
+
var RELAY_CREDENTIAL_SENTINEL = "$MCPORTER_CHROME_RELAY_CREDENTIAL:";
|
|
1459
|
+
var RELAY_DISCOVERY_SENTINEL = "$MCPORTER_CHROME_RELAY_DISCOVERY:";
|
|
1460
|
+
var CHROME_DEVTOOLS_RELAY_RUNTIME_IDENTITY_VERSION = 1;
|
|
1461
|
+
var CHROME_DEVTOOLS_RELAY_RUNTIME_ENV_KEYS = [
|
|
1462
|
+
"MCPORTER_CHROME_DEVTOOLS_RELAY_POLICY",
|
|
1463
|
+
"MCPORTER_DISABLE_CHROME_DEVTOOLS_RELAY",
|
|
1464
|
+
"MCPORTER_CHROME_DEVTOOLS_RELAY_URL",
|
|
1465
|
+
"MCPORTER_CHROME_DEVTOOLS_RELAY_TIMEOUT_MS",
|
|
1466
|
+
...OPENCLAW_RELAY_DISCOVERY_ENV_KEYS
|
|
1467
|
+
];
|
|
1468
|
+
var ChromeDevtoolsRelayRequiredError = class extends Error {
|
|
1469
|
+
decision;
|
|
1470
|
+
constructor(decision) {
|
|
1471
|
+
super(`Chrome DevTools relay policy 'require' could not use the OpenClaw extension relay (${decision.reason}). No chrome-devtools-mcp process was launched.`);
|
|
1472
|
+
this.decision = decision;
|
|
1473
|
+
this.name = "ChromeDevtoolsRelayRequiredError";
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
var lastDecisions = /* @__PURE__ */ new Map();
|
|
1477
|
+
function recordChromeDevtoolsRelayDecision(server, decision) {
|
|
1478
|
+
lastDecisions.set(server, decision);
|
|
1479
|
+
}
|
|
1480
|
+
function getChromeDevtoolsRelayDecision(server) {
|
|
1481
|
+
return lastDecisions.get(server);
|
|
1482
|
+
}
|
|
1483
|
+
function formatChromeDevtoolsRelayDecision(decision) {
|
|
1484
|
+
return JSON.stringify(decision);
|
|
1485
|
+
}
|
|
1486
|
+
function resolveChromeDevtoolsRelayProbeTimeoutMs(env = process.env) {
|
|
1487
|
+
const raw = env.MCPORTER_CHROME_DEVTOOLS_RELAY_TIMEOUT_MS?.trim();
|
|
1488
|
+
if (!raw || !/^\d+$/.test(raw))
|
|
1489
|
+
return DEFAULT_RELAY_PROBE_TIMEOUT_MS;
|
|
1490
|
+
const parsed = Number(raw);
|
|
1491
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0)
|
|
1492
|
+
return DEFAULT_RELAY_PROBE_TIMEOUT_MS;
|
|
1493
|
+
return Math.min(MAX_RELAY_PROBE_TIMEOUT_MS, Math.max(MIN_RELAY_PROBE_TIMEOUT_MS, parsed));
|
|
1494
|
+
}
|
|
1495
|
+
function resolveChromeDevtoolsRelayPolicy(configured, env = process.env) {
|
|
1496
|
+
if (env.MCPORTER_DISABLE_CHROME_DEVTOOLS_RELAY === "1")
|
|
1497
|
+
return "off";
|
|
1498
|
+
const raw = env.MCPORTER_CHROME_DEVTOOLS_RELAY_POLICY?.trim().toLowerCase();
|
|
1499
|
+
if (!raw)
|
|
1500
|
+
return configured ?? "prefer";
|
|
1501
|
+
if (raw === "off" || raw === "prefer" || raw === "require")
|
|
1502
|
+
return raw;
|
|
1503
|
+
throw new Error(`Invalid MCPORTER_CHROME_DEVTOOLS_RELAY_POLICY '${raw}'. Expected off, prefer, or require.`);
|
|
1504
|
+
}
|
|
1505
|
+
function isChromeDevtoolsAutoConnectCommand(command, args) {
|
|
1506
|
+
return resolveChromeDevtoolsAutoConnectCommand(command, args).enabled;
|
|
1507
|
+
}
|
|
1508
|
+
function explicitRelayBaseUrl(env) {
|
|
1509
|
+
const raw = env.MCPORTER_CHROME_DEVTOOLS_RELAY_URL?.trim();
|
|
1510
|
+
if (!raw)
|
|
1511
|
+
return { explicit: false };
|
|
1512
|
+
let url;
|
|
1513
|
+
try {
|
|
1514
|
+
url = new URL(raw);
|
|
1515
|
+
} catch {
|
|
1516
|
+
return { explicit: true };
|
|
1517
|
+
}
|
|
1518
|
+
if (url.protocol !== "http:" || !["127.0.0.1", "localhost", "[::1]", "::1"].includes(url.hostname) || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") {
|
|
1519
|
+
return { explicit: true };
|
|
1520
|
+
}
|
|
1521
|
+
return { explicit: true, url };
|
|
1522
|
+
}
|
|
1523
|
+
function resolveOpenClawCredentialDir(env) {
|
|
1524
|
+
const oauthOverride = env.OPENCLAW_OAUTH_DIR?.trim();
|
|
1525
|
+
if (oauthOverride)
|
|
1526
|
+
return resolveOpenClawPath(oauthOverride, env);
|
|
1527
|
+
const stateOverride = env.OPENCLAW_STATE_DIR?.trim();
|
|
1528
|
+
const profile = normalizeOpenClawProfile(env.OPENCLAW_PROFILE);
|
|
1529
|
+
const stateDir = stateOverride ? resolveOpenClawPath(stateOverride, env) : path2.join(resolveOpenClawHome(env), profile ? `.openclaw-${profile}` : ".openclaw");
|
|
1530
|
+
return path2.join(stateDir, "credentials");
|
|
1531
|
+
}
|
|
1532
|
+
function resolveOpenClawHome(env) {
|
|
1533
|
+
const override = env.OPENCLAW_HOME?.trim();
|
|
1534
|
+
const osHome = resolveOpenClawOsHome(env);
|
|
1535
|
+
if (!override)
|
|
1536
|
+
return osHome;
|
|
1537
|
+
if (override === "~")
|
|
1538
|
+
return osHome;
|
|
1539
|
+
if (override.startsWith("~/") || override.startsWith("~\\")) {
|
|
1540
|
+
return path2.resolve(osHome, override.slice(2));
|
|
1541
|
+
}
|
|
1542
|
+
return path2.resolve(override);
|
|
1543
|
+
}
|
|
1544
|
+
function resolveOpenClawOsHome(env) {
|
|
1545
|
+
for (const value of [env.HOME, env.USERPROFILE]) {
|
|
1546
|
+
const normalized = value?.trim();
|
|
1547
|
+
if (normalized && normalized !== "undefined" && normalized !== "null")
|
|
1548
|
+
return path2.resolve(normalized);
|
|
1549
|
+
}
|
|
1550
|
+
return os.homedir();
|
|
1551
|
+
}
|
|
1552
|
+
function resolveOpenClawPath(value, env) {
|
|
1553
|
+
if (value === "~")
|
|
1554
|
+
return resolveOpenClawHome(env);
|
|
1555
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
1556
|
+
return path2.resolve(resolveOpenClawHome(env), value.slice(2));
|
|
1557
|
+
}
|
|
1558
|
+
return path2.resolve(value);
|
|
1559
|
+
}
|
|
1560
|
+
function defaultReadCredential(env) {
|
|
1561
|
+
const secretPath = path2.join(resolveOpenClawCredentialDir(env), "browser-extension-relay.secret");
|
|
1562
|
+
return readCredentialPath(secretPath);
|
|
1563
|
+
}
|
|
1564
|
+
function readCredentialPath(secretPath) {
|
|
1565
|
+
let descriptor;
|
|
1566
|
+
try {
|
|
1567
|
+
descriptor = fs.openSync(secretPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
return { reason: isErrno(error, "ENOENT") ? "missing-credential" : "invalid-credential" };
|
|
1570
|
+
}
|
|
1571
|
+
try {
|
|
1572
|
+
const stat = fs.fstatSync(descriptor);
|
|
1573
|
+
if (!stat.isFile())
|
|
1574
|
+
return { reason: "invalid-credential" };
|
|
1575
|
+
if (process.platform !== "win32") {
|
|
1576
|
+
if ((stat.mode & 63) !== 0)
|
|
1577
|
+
return { reason: "invalid-credential" };
|
|
1578
|
+
if (typeof process.getuid === "function" && stat.uid !== process.getuid())
|
|
1579
|
+
return { reason: "invalid-credential" };
|
|
1580
|
+
}
|
|
1581
|
+
const encoded = fs.readFileSync(descriptor, "utf8").trim();
|
|
1582
|
+
if (!/^[0-9a-f]{64}$/.test(encoded))
|
|
1583
|
+
return { reason: "invalid-credential" };
|
|
1584
|
+
const key = Buffer.from(encoded, "hex");
|
|
1585
|
+
return { credential: { key, keyId: deriveBrowserRelayKeyId(key) } };
|
|
1586
|
+
} catch {
|
|
1587
|
+
return { reason: "invalid-credential" };
|
|
1588
|
+
} finally {
|
|
1589
|
+
fs.closeSync(descriptor);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
async function defaultConnect(baseUrl, credential, timeoutMs) {
|
|
1593
|
+
return await connectChromeDevtoolsRelayV2({ baseUrl, credential, timeoutMs });
|
|
1594
|
+
}
|
|
1595
|
+
async function rewriteChromeDevtoolsArgsForRelay(command, args, env = process.env, options = {}, configuredPolicy) {
|
|
1596
|
+
const relayEnv = selectChromeDevtoolsRelayRuntimeEnvironment(env);
|
|
1597
|
+
const eligible = isChromeDevtoolsAutoConnectCommand(command, args);
|
|
1598
|
+
if (!eligible) {
|
|
1599
|
+
const ambiguous = isAmbiguousChromeDevtoolsAutoConnectCommand(command, args);
|
|
1600
|
+
if (!ambiguous) {
|
|
1601
|
+
return decide({
|
|
1602
|
+
args,
|
|
1603
|
+
applied: false,
|
|
1604
|
+
decision: { route: "legacy", reason: "not-eligible", policy: configuredPolicy ?? "prefer" }
|
|
1605
|
+
}, options);
|
|
1606
|
+
}
|
|
1607
|
+
const ambiguousPolicy = resolveChromeDevtoolsRelayPolicy(configuredPolicy, relayEnv);
|
|
1608
|
+
if (ambiguousPolicy === "require") {
|
|
1609
|
+
return unavailableOrLegacy(args, ambiguousPolicy, "unsupported-command", void 0, options);
|
|
1610
|
+
}
|
|
1611
|
+
return decide({
|
|
1612
|
+
args,
|
|
1613
|
+
applied: false,
|
|
1614
|
+
decision: { route: "legacy", reason: "not-eligible", policy: ambiguousPolicy }
|
|
1615
|
+
}, options);
|
|
1616
|
+
}
|
|
1617
|
+
const policy = resolveChromeDevtoolsRelayPolicy(configuredPolicy, relayEnv);
|
|
1618
|
+
if (policy === "off") {
|
|
1619
|
+
return decide({ args, applied: false, decision: { route: "legacy", reason: "disabled", policy } }, options);
|
|
1620
|
+
}
|
|
1621
|
+
const explicitBase = explicitRelayBaseUrl(relayEnv);
|
|
1622
|
+
if (explicitBase.explicit && !explicitBase.url) {
|
|
1623
|
+
return unavailableOrLegacy(args, policy, "invalid-endpoint", void 0, options);
|
|
1624
|
+
}
|
|
1625
|
+
const loaded = options.readToken ? (() => {
|
|
1626
|
+
const encoded = options.readToken?.();
|
|
1627
|
+
if (!encoded)
|
|
1628
|
+
return { reason: "missing-credential" };
|
|
1629
|
+
if (!/^[0-9a-f]{64}$/u.test(encoded))
|
|
1630
|
+
return { reason: "invalid-credential" };
|
|
1631
|
+
const key = Buffer.from(encoded, "hex");
|
|
1632
|
+
return { credential: { key, keyId: deriveBrowserRelayKeyId(key) } };
|
|
1633
|
+
})() : defaultReadCredential(relayEnv);
|
|
1634
|
+
if (!loaded.credential) {
|
|
1635
|
+
const base = explicitBase.url ?? new URL(DEFAULT_RELAY_URL);
|
|
1636
|
+
const upstreamEndpoint = new URL("/cdp", base);
|
|
1637
|
+
upstreamEndpoint.protocol = "ws:";
|
|
1638
|
+
return unavailableOrLegacy(args, policy, loaded.reason ?? "invalid-credential", upstreamEndpoint.toString(), options);
|
|
1639
|
+
}
|
|
1640
|
+
const timeoutMs = resolveChromeDevtoolsRelayProbeTimeoutMs(relayEnv);
|
|
1641
|
+
try {
|
|
1642
|
+
const base = explicitBase.url ?? (await discoverOpenClawRelayUrl({
|
|
1643
|
+
env: relayEnv,
|
|
1644
|
+
keyId: loaded.credential.keyId,
|
|
1645
|
+
timeoutMs,
|
|
1646
|
+
run: options.discover,
|
|
1647
|
+
platform: options.discovery?.platform,
|
|
1648
|
+
isFile: options.discovery?.isFile,
|
|
1649
|
+
cwd: options.discovery?.cwd
|
|
1650
|
+
})).url ?? new URL(DEFAULT_RELAY_URL);
|
|
1651
|
+
const upstreamEndpoint = new URL("/cdp", base);
|
|
1652
|
+
upstreamEndpoint.protocol = "ws:";
|
|
1653
|
+
const endpoint = upstreamEndpoint.toString();
|
|
1654
|
+
let probe;
|
|
1655
|
+
try {
|
|
1656
|
+
probe = await (options.connect ?? defaultConnect)(base, loaded.credential, timeoutMs);
|
|
1657
|
+
} catch {
|
|
1658
|
+
return unavailableOrLegacy(args, policy, "network-error", endpoint, options);
|
|
1659
|
+
}
|
|
1660
|
+
const probeDetails = { probeDurationMs: probe.durationMs, probeStatus: probe.status };
|
|
1661
|
+
if (probe.reason !== "success" || !probe.upstream) {
|
|
1662
|
+
probe.upstream?.socket.destroy();
|
|
1663
|
+
const reason = probe.reason === "success" ? "protocol" : probe.reason;
|
|
1664
|
+
return unavailableOrLegacy(args, policy, reason, endpoint, options, probeDetails);
|
|
1665
|
+
}
|
|
1666
|
+
let proxy;
|
|
1667
|
+
try {
|
|
1668
|
+
proxy = await (options.startProxy ?? startChromeDevtoolsRelayProxy)({ upstream: probe.upstream });
|
|
1669
|
+
} catch {
|
|
1670
|
+
probe.upstream.socket.destroy();
|
|
1671
|
+
return unavailableOrLegacy(args, policy, "network-error", endpoint, options, probeDetails);
|
|
1672
|
+
}
|
|
1673
|
+
const rewritten = replaceChromeDevtoolsAutoConnectArgs(command, args, ["--wsEndpoint", proxy.endpoint]);
|
|
1674
|
+
try {
|
|
1675
|
+
return decide({
|
|
1676
|
+
args: rewritten,
|
|
1677
|
+
applied: true,
|
|
1678
|
+
endpoint: proxy.endpoint,
|
|
1679
|
+
proxy,
|
|
1680
|
+
decision: { route: "relay", reason: "success", policy, endpoint, ...probeDetails }
|
|
1681
|
+
}, options);
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
await proxy.close().catch(() => {
|
|
1684
|
+
});
|
|
1685
|
+
throw error;
|
|
1686
|
+
}
|
|
1687
|
+
} finally {
|
|
1688
|
+
loaded.credential.key.fill(0);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
function unavailableOrLegacy(args, policy, reason, endpoint, options, probe = {}) {
|
|
1692
|
+
const decision = {
|
|
1693
|
+
route: policy === "require" ? "unavailable" : "legacy",
|
|
1694
|
+
reason,
|
|
1695
|
+
policy,
|
|
1696
|
+
endpoint,
|
|
1697
|
+
...probe
|
|
1698
|
+
};
|
|
1699
|
+
options.onDecision?.(decision);
|
|
1700
|
+
if (policy === "require")
|
|
1701
|
+
throw new ChromeDevtoolsRelayRequiredError(decision);
|
|
1702
|
+
return { args, applied: false, decision };
|
|
1703
|
+
}
|
|
1704
|
+
function decide(result, options) {
|
|
1705
|
+
options.onDecision?.(result.decision);
|
|
1706
|
+
return result;
|
|
1707
|
+
}
|
|
1708
|
+
function resolveChromeDevtoolsRelayEnvironment(overrides, env = process.env) {
|
|
1709
|
+
return { ...env, ...resolveRelayDefinitionEnvOverrides(overrides, env) };
|
|
1710
|
+
}
|
|
1711
|
+
function selectChromeDevtoolsRelayRuntimeEnvironment(env) {
|
|
1712
|
+
return Object.fromEntries(CHROME_DEVTOOLS_RELAY_RUNTIME_ENV_KEYS.flatMap((key) => {
|
|
1713
|
+
const value = env[key];
|
|
1714
|
+
return value === void 0 ? [] : [[key, value]];
|
|
1715
|
+
}));
|
|
1716
|
+
}
|
|
1717
|
+
function sanitizeRelayIdentityEnvironment(env) {
|
|
1718
|
+
const selected = selectChromeDevtoolsRelayRuntimeEnvironment(env);
|
|
1719
|
+
if (selected.MCPORTER_CHROME_DEVTOOLS_RELAY_URL !== void 0) {
|
|
1720
|
+
const explicit = explicitRelayBaseUrl(selected);
|
|
1721
|
+
selected.MCPORTER_CHROME_DEVTOOLS_RELAY_URL = explicit.url?.toString() ?? "invalid-endpoint";
|
|
1722
|
+
}
|
|
1723
|
+
return selected;
|
|
1724
|
+
}
|
|
1725
|
+
function chromeDevtoolsRelayEnvironmentKeys(definitions, env = process.env) {
|
|
1726
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1727
|
+
for (const definition of definitions) {
|
|
1728
|
+
if (definition.command.kind !== "stdio")
|
|
1729
|
+
continue;
|
|
1730
|
+
const commandValues = [definition.command.command, ...definition.command.args];
|
|
1731
|
+
const commandEnvironmentKeys = new Set(commandValues.flatMap(referencedEnvironmentVariables));
|
|
1732
|
+
const relevant = commandEnvironmentKeys.size > 0 || isChromeDevtoolsAutoConnectCommand(definition.command.command, definition.command.args) || isAmbiguousChromeDevtoolsAutoConnectCommand(definition.command.command, definition.command.args);
|
|
1733
|
+
if (!relevant) {
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
for (const key of commandEnvironmentKeys)
|
|
1737
|
+
keys.add(key);
|
|
1738
|
+
const resolvedDefinitionEnv = resolveRelayIdentityEnvOverrides(definition.env, env);
|
|
1739
|
+
for (const pathKey of [
|
|
1740
|
+
"HOME",
|
|
1741
|
+
"USERPROFILE",
|
|
1742
|
+
"OPENCLAW_HOME",
|
|
1743
|
+
"OPENCLAW_STATE_DIR",
|
|
1744
|
+
"OPENCLAW_OAUTH_DIR",
|
|
1745
|
+
"OPENCLAW_CONFIG_PATH"
|
|
1746
|
+
]) {
|
|
1747
|
+
const override = resolvedDefinitionEnv[pathKey]?.trim();
|
|
1748
|
+
if (override && override !== "~" && !override.startsWith("~/") && !override.startsWith("~\\") && !path2.isAbsolute(override)) {
|
|
1749
|
+
keys.add(RELAY_CWD_SENTINEL);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
for (const key of CHROME_DEVTOOLS_RELAY_RUNTIME_ENV_KEYS) {
|
|
1753
|
+
keys.add(key);
|
|
1754
|
+
const override = definition.env?.[key];
|
|
1755
|
+
if (!override)
|
|
1756
|
+
continue;
|
|
1757
|
+
for (const referenced of referencedEnvironmentVariables(override))
|
|
1758
|
+
keys.add(referenced);
|
|
1759
|
+
}
|
|
1760
|
+
const effectiveEnv = { ...env, ...resolvedDefinitionEnv };
|
|
1761
|
+
const resolvedCommand = resolveCommandArgument(definition.command.command, env);
|
|
1762
|
+
const resolvedArgs = resolveCommandArguments(definition.command.args, env);
|
|
1763
|
+
if (!isChromeDevtoolsAutoConnectCommand(resolvedCommand, resolvedArgs))
|
|
1764
|
+
continue;
|
|
1765
|
+
const credentialPath = path2.join(resolveOpenClawCredentialDir(effectiveEnv), "browser-extension-relay.secret");
|
|
1766
|
+
keys.add(`${RELAY_CREDENTIAL_SENTINEL}${credentialPath}`);
|
|
1767
|
+
const environment = sanitizeRelayIdentityEnvironment(Object.fromEntries(CHROME_DEVTOOLS_RELAY_RUNTIME_ENV_KEYS.flatMap((key) => {
|
|
1768
|
+
const value = resolvedDefinitionEnv[key];
|
|
1769
|
+
return value === void 0 ? [] : [[key, value]];
|
|
1770
|
+
})));
|
|
1771
|
+
keys.add(`${RELAY_DISCOVERY_SENTINEL}${Buffer.from(JSON.stringify({ credentialPath, environment, configuredPolicy: definition.chromeDevtoolsRelay }), "utf8").toString("base64url")}`);
|
|
1772
|
+
}
|
|
1773
|
+
return [...keys].toSorted();
|
|
1774
|
+
}
|
|
1775
|
+
async function resolveChromeDevtoolsRelayRuntimeIdentity(keys, env = process.env, options = {}) {
|
|
1776
|
+
const credentialId = createRelayCredentialIdResolver();
|
|
1777
|
+
const environmentValues = relayEnvironmentIdentityValues(keys, env, credentialId);
|
|
1778
|
+
const discoveryValues = [];
|
|
1779
|
+
for (const key of keys) {
|
|
1780
|
+
if (!key.startsWith(RELAY_DISCOVERY_SENTINEL))
|
|
1781
|
+
continue;
|
|
1782
|
+
const payload = parseRelayDiscoveryIdentity(key.slice(RELAY_DISCOVERY_SENTINEL.length));
|
|
1783
|
+
if (!payload) {
|
|
1784
|
+
discoveryValues.push([key, DEFAULT_RELAY_IDENTITY_URL]);
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
const effectiveEnv = {
|
|
1788
|
+
...selectChromeDevtoolsRelayRuntimeEnvironment(env),
|
|
1789
|
+
...payload.environment
|
|
1790
|
+
};
|
|
1791
|
+
const explicit = explicitRelayBaseUrl(effectiveEnv);
|
|
1792
|
+
if (explicit.explicit) {
|
|
1793
|
+
discoveryValues.push([key, explicit.url?.toString() ?? "invalid-endpoint"]);
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
if (resolveChromeDevtoolsRelayPolicy(payload.configuredPolicy, effectiveEnv) === "off") {
|
|
1797
|
+
discoveryValues.push([key, "disabled"]);
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
const keyId = credentialId(payload.credentialPath);
|
|
1801
|
+
if (keyId === "missing-credential" || keyId === "invalid-credential") {
|
|
1802
|
+
discoveryValues.push([key, DEFAULT_RELAY_IDENTITY_URL]);
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
try {
|
|
1806
|
+
const discovered = await discoverOpenClawRelayUrl({
|
|
1807
|
+
env: effectiveEnv,
|
|
1808
|
+
keyId,
|
|
1809
|
+
timeoutMs: resolveChromeDevtoolsRelayProbeTimeoutMs(effectiveEnv),
|
|
1810
|
+
run: options.discover,
|
|
1811
|
+
platform: options.discovery?.platform,
|
|
1812
|
+
isFile: options.discovery?.isFile,
|
|
1813
|
+
cwd: options.discovery?.cwd
|
|
1814
|
+
});
|
|
1815
|
+
discoveryValues.push([key, discovered.url?.toString() ?? DEFAULT_RELAY_IDENTITY_URL]);
|
|
1816
|
+
} catch {
|
|
1817
|
+
discoveryValues.push([key, DEFAULT_RELAY_IDENTITY_URL]);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (discoveryValues.length === 0)
|
|
1821
|
+
return hashRelayIdentityValues(environmentValues);
|
|
1822
|
+
return hashRelayIdentityValues([
|
|
1823
|
+
...environmentValues,
|
|
1824
|
+
["$relayRuntimeIdentity", CHROME_DEVTOOLS_RELAY_RUNTIME_IDENTITY_VERSION],
|
|
1825
|
+
...discoveryValues
|
|
1826
|
+
]);
|
|
1827
|
+
}
|
|
1828
|
+
function createRelayCredentialIdResolver() {
|
|
1829
|
+
const credentialIds = /* @__PURE__ */ new Map();
|
|
1830
|
+
return (credentialPath) => {
|
|
1831
|
+
const cached = credentialIds.get(credentialPath);
|
|
1832
|
+
if (cached)
|
|
1833
|
+
return cached;
|
|
1834
|
+
const loaded = readCredentialPath(credentialPath);
|
|
1835
|
+
const value = loaded.credential?.keyId ?? loaded.reason ?? "missing-credential";
|
|
1836
|
+
loaded.credential?.key.fill(0);
|
|
1837
|
+
credentialIds.set(credentialPath, value);
|
|
1838
|
+
return value;
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function relayEnvironmentIdentityValues(keys, env, credentialId) {
|
|
1842
|
+
const values = keys.map((key) => {
|
|
1843
|
+
if (key === RELAY_CWD_SENTINEL)
|
|
1844
|
+
return [key, process.cwd()];
|
|
1845
|
+
if (key.startsWith(RELAY_CREDENTIAL_SENTINEL)) {
|
|
1846
|
+
const credentialPath = key.slice(RELAY_CREDENTIAL_SENTINEL.length);
|
|
1847
|
+
return [key, credentialId(credentialPath)];
|
|
1848
|
+
}
|
|
1849
|
+
if (key.startsWith(RELAY_DISCOVERY_SENTINEL)) {
|
|
1850
|
+
return [key, "async-discovery"];
|
|
1851
|
+
}
|
|
1852
|
+
return [key, env[key] ?? null];
|
|
1853
|
+
});
|
|
1854
|
+
if (keys.length > 0) {
|
|
1855
|
+
values.push(["$relayAuth", "v2-async-metadata-discovery"]);
|
|
1856
|
+
}
|
|
1857
|
+
return values;
|
|
1858
|
+
}
|
|
1859
|
+
function hashRelayIdentityValues(values) {
|
|
1860
|
+
return createHash4("sha256").update(JSON.stringify(values)).digest("hex").slice(0, 16);
|
|
1861
|
+
}
|
|
1862
|
+
function parseRelayDiscoveryIdentity(encoded) {
|
|
1863
|
+
try {
|
|
1864
|
+
const value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
1865
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1866
|
+
return void 0;
|
|
1867
|
+
const candidate = value;
|
|
1868
|
+
if (typeof candidate.credentialPath !== "string" || !candidate.environment || typeof candidate.environment !== "object") {
|
|
1869
|
+
return void 0;
|
|
1870
|
+
}
|
|
1871
|
+
if (candidate.configuredPolicy !== void 0 && !["off", "prefer", "require"].includes(candidate.configuredPolicy)) {
|
|
1872
|
+
return void 0;
|
|
1873
|
+
}
|
|
1874
|
+
const environment = {};
|
|
1875
|
+
for (const [key, raw] of Object.entries(candidate.environment)) {
|
|
1876
|
+
if (!CHROME_DEVTOOLS_RELAY_RUNTIME_ENV_KEYS.includes(key) || typeof raw !== "string") {
|
|
1877
|
+
return void 0;
|
|
1878
|
+
}
|
|
1879
|
+
environment[key] = raw;
|
|
1880
|
+
}
|
|
1881
|
+
return {
|
|
1882
|
+
credentialPath: candidate.credentialPath,
|
|
1883
|
+
environment,
|
|
1884
|
+
configuredPolicy: candidate.configuredPolicy
|
|
1885
|
+
};
|
|
1886
|
+
} catch {
|
|
1887
|
+
return void 0;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
function referencedEnvironmentVariables(value) {
|
|
1891
|
+
const names = /* @__PURE__ */ new Set();
|
|
1892
|
+
if (value.startsWith("$env:"))
|
|
1893
|
+
names.add(value.slice("$env:".length));
|
|
1894
|
+
for (const match of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)/gu)) {
|
|
1895
|
+
if (match[1])
|
|
1896
|
+
names.add(match[1]);
|
|
1897
|
+
}
|
|
1898
|
+
return [...names];
|
|
1899
|
+
}
|
|
1900
|
+
function resolveRelayDefinitionEnvOverrides(overrides, env) {
|
|
1901
|
+
const resolved = {};
|
|
1902
|
+
for (const [key, raw] of Object.entries(overrides ?? {})) {
|
|
1903
|
+
const value = resolveEnvValue(raw, env);
|
|
1904
|
+
if (value !== "")
|
|
1905
|
+
resolved[key] = value;
|
|
1906
|
+
}
|
|
1907
|
+
return resolved;
|
|
1908
|
+
}
|
|
1909
|
+
function resolveRelayIdentityEnvOverrides(overrides, env) {
|
|
1910
|
+
const relevant = Object.fromEntries(CHROME_DEVTOOLS_RELAY_RUNTIME_ENV_KEYS.flatMap((key) => {
|
|
1911
|
+
const value = overrides?.[key];
|
|
1912
|
+
return value === void 0 ? [] : [[key, value]];
|
|
1913
|
+
}));
|
|
1914
|
+
return resolveRelayDefinitionEnvOverrides(relevant, env);
|
|
1915
|
+
}
|
|
1916
|
+
function isErrno(error, code) {
|
|
1917
|
+
return Boolean(error && typeof error === "object" && error.code === code);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
export {
|
|
1921
|
+
resolveChromeDevtoolsAutoConnectCommand,
|
|
1922
|
+
CHROME_DEVTOOLS_RELAY_RUNTIME_IDENTITY_VERSION,
|
|
1923
|
+
ChromeDevtoolsRelayRequiredError,
|
|
1924
|
+
recordChromeDevtoolsRelayDecision,
|
|
1925
|
+
getChromeDevtoolsRelayDecision,
|
|
1926
|
+
formatChromeDevtoolsRelayDecision,
|
|
1927
|
+
rewriteChromeDevtoolsArgsForRelay,
|
|
1928
|
+
resolveChromeDevtoolsRelayEnvironment,
|
|
1929
|
+
chromeDevtoolsRelayEnvironmentKeys,
|
|
1930
|
+
resolveChromeDevtoolsRelayRuntimeIdentity
|
|
1931
|
+
};
|