surf-cli 2.19.0 → 2.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/README.md +169 -3
- package/dist/content/index.js +4 -4
- package/dist/content/index.js.map +1 -1
- package/dist/options/options.html +1 -18
- package/dist/service-worker/index.js +44 -15
- package/dist/service-worker/index.js.map +1 -1
- package/native/cli.cjs +109 -6
- package/native/do-executor.cjs +35 -8
- package/native/doctor.cjs +200 -40
- package/native/host-helpers.cjs +95 -10
- package/native/host.cjs +24 -2
- package/native/native-host-launch-probe.cjs +69 -0
- package/native/private-state.cjs +25 -1
- package/native/semantic-cli.cjs +764 -0
- package/native/semantic-core.cjs +369 -0
- package/native/semantic-credentials.cjs +207 -0
- package/native/semantic-provider.cjs +61 -0
- package/native/semantic-workflow-executor.cjs +65 -0
- package/native/semantic-workflow-state.cjs +271 -0
- package/native/semantic-workflow.cjs +398 -0
- package/native/tool-scope.cjs +1 -0
- package/native/workflow-definition.cjs +125 -1
- package/native/workflow-runtime.cjs +71 -5
- package/package.json +8 -4
- package/scripts/install-native-host.cjs +103 -60
- package/scripts/uninstall-native-host.cjs +40 -38
- package/scripts/windows-interop.cjs +89 -0
- package/skills/surf/SKILL.md +78 -1
package/native/host-helpers.cjs
CHANGED
|
@@ -228,6 +228,21 @@ function formatToolContent(result, log = () => {}, options = {}) {
|
|
|
228
228
|
];
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
if (result.semanticObservation !== undefined) {
|
|
232
|
+
return text(JSON.stringify({
|
|
233
|
+
pageContent: result.pageContent,
|
|
234
|
+
viewport: result.viewport,
|
|
235
|
+
semanticObservation: result.semanticObservation,
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
result.identity !== undefined && result.matches !== undefined ||
|
|
241
|
+
result.scopeToken !== undefined && result.geometry !== undefined
|
|
242
|
+
) {
|
|
243
|
+
return text(JSON.stringify(result));
|
|
244
|
+
}
|
|
245
|
+
|
|
231
246
|
if (result.pageContent !== undefined) {
|
|
232
247
|
const content = result.pageContent || "No content";
|
|
233
248
|
let output = '';
|
|
@@ -442,6 +457,11 @@ function formatToolContent(result, log = () => {}, options = {}) {
|
|
|
442
457
|
|
|
443
458
|
// Bug fix: Handle success with metrics/frames/readyState/hint in one block
|
|
444
459
|
if (result.success) {
|
|
460
|
+
// Keep the response envelope intact so the CLI can print body bytes verbatim
|
|
461
|
+
// for text output while still producing structured output under --json.
|
|
462
|
+
if (typeof result.body === "string" && Object.hasOwn(result, "base64Encoded")) {
|
|
463
|
+
return text(JSON.stringify(result));
|
|
464
|
+
}
|
|
445
465
|
if (result.metrics) {
|
|
446
466
|
return text(JSON.stringify(result.metrics, null, 2));
|
|
447
467
|
}
|
|
@@ -501,7 +521,7 @@ function mapComputerAction(args, tabId) {
|
|
|
501
521
|
const { action, text, scroll_direction, scroll_amount,
|
|
502
522
|
start_coordinate, ref, duration, modifiers } = a;
|
|
503
523
|
const coordinate = a.coordinate || (a.x !== undefined && a.y !== undefined ? [a.x, a.y] : undefined);
|
|
504
|
-
const baseMsg = { tabId };
|
|
524
|
+
const baseMsg = { tabId, ...(Number.isInteger(a.semanticFrameId) ? { frameId: a.semanticFrameId } : {}) };
|
|
505
525
|
|
|
506
526
|
if (!action) {
|
|
507
527
|
return { type: "UNSUPPORTED_ACTION", action: null, message: "No action specified for computer tool" };
|
|
@@ -512,7 +532,7 @@ function mapComputerAction(args, tabId) {
|
|
|
512
532
|
return { type: "EXECUTE_SCREENSHOT", ...baseMsg };
|
|
513
533
|
|
|
514
534
|
case "left_click":
|
|
515
|
-
if (ref) return { type: "CLICK_REF", ref, button: "left", ...baseMsg };
|
|
535
|
+
if (ref) return { type: "CLICK_REF", ref, button: "left", expectedIdentity: a.semanticExpectedIdentity, ...baseMsg };
|
|
516
536
|
if (a.selector) return { type: "CLICK_SELECTOR", selector: a.selector, index: a.index || 0, button: "left", ...baseMsg };
|
|
517
537
|
return { type: "EXECUTE_CLICK", x: coordinate?.[0], y: coordinate?.[1], modifiers, ...baseMsg };
|
|
518
538
|
|
|
@@ -530,7 +550,7 @@ function mapComputerAction(args, tabId) {
|
|
|
530
550
|
|
|
531
551
|
case "type": {
|
|
532
552
|
if (ref) {
|
|
533
|
-
return { type: "FORM_FILL", data: [{ ref, value: text }], ...baseMsg };
|
|
553
|
+
return { type: "FORM_FILL", data: [{ ref, value: text }], expectedIdentity: a.semanticExpectedIdentity, ...baseMsg };
|
|
534
554
|
}
|
|
535
555
|
const typeSelector = a.selector || a.into;
|
|
536
556
|
if (typeSelector) {
|
|
@@ -570,7 +590,7 @@ function mapComputerAction(args, tabId) {
|
|
|
570
590
|
right: { deltaX: amount, deltaY: 0 },
|
|
571
591
|
};
|
|
572
592
|
const { deltaX, deltaY } = deltas[direction] || { deltaX: 0, deltaY: 0 };
|
|
573
|
-
return { type: "EXECUTE_SCROLL", deltaX, deltaY, x: coordinate?.[0], y: coordinate?.[1], ...baseMsg };
|
|
593
|
+
return { type: "EXECUTE_SCROLL", deltaX, deltaY, x: coordinate?.[0], y: coordinate?.[1], expectedIdentity: a.semanticExpectedIdentity, ...baseMsg };
|
|
574
594
|
}
|
|
575
595
|
|
|
576
596
|
case "scroll_to":
|
|
@@ -609,14 +629,14 @@ function mapComputerAction(args, tabId) {
|
|
|
609
629
|
* Map tool name and args to extension message
|
|
610
630
|
*/
|
|
611
631
|
function mapToolToMessage(tool, args, tabId) {
|
|
612
|
-
const baseMsg = { tabId };
|
|
613
632
|
const a = args || {};
|
|
633
|
+
const baseMsg = { tabId, ...(Number.isInteger(a.semanticFrameId) ? { frameId: a.semanticFrameId } : {}) };
|
|
614
634
|
|
|
615
635
|
switch (tool) {
|
|
616
636
|
case "computer":
|
|
617
637
|
return mapComputerAction(args, tabId);
|
|
618
638
|
case "navigate":
|
|
619
|
-
return { type: "EXECUTE_NAVIGATE", url: a.url, ...baseMsg };
|
|
639
|
+
return { type: "EXECUTE_NAVIGATE", url: a.url, expectedIdentity: a.semanticExpectedIdentity, ...baseMsg };
|
|
620
640
|
case "read_page":
|
|
621
641
|
return {
|
|
622
642
|
type: "READ_PAGE",
|
|
@@ -880,9 +900,9 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
880
900
|
case "js":
|
|
881
901
|
return { type: "EXECUTE_JAVASCRIPT", code: a.code, ...baseMsg };
|
|
882
902
|
case "scroll.top":
|
|
883
|
-
return { type: "SCROLL_TO_POSITION", position: "top", selector: a.selector, ...baseMsg };
|
|
903
|
+
return { type: "SCROLL_TO_POSITION", position: "top", selector: a.selector, expectedIdentity: a.semanticExpectedIdentity, ...baseMsg };
|
|
884
904
|
case "scroll.bottom":
|
|
885
|
-
return { type: "SCROLL_TO_POSITION", position: "bottom", selector: a.selector, ...baseMsg };
|
|
905
|
+
return { type: "SCROLL_TO_POSITION", position: "bottom", selector: a.selector, expectedIdentity: a.semanticExpectedIdentity, ...baseMsg };
|
|
886
906
|
case "scroll.info":
|
|
887
907
|
return { type: "GET_SCROLL_INFO", selector: a.selector, ...baseMsg };
|
|
888
908
|
case "scroll.to":
|
|
@@ -966,7 +986,7 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
966
986
|
if (typeof fillData === "string") {
|
|
967
987
|
try { fillData = JSON.parse(fillData); } catch (e) { throw new Error("invalid --data JSON"); }
|
|
968
988
|
}
|
|
969
|
-
return { type: "FORM_FILL", data: fillData, ...baseMsg };
|
|
989
|
+
return { type: "FORM_FILL", data: fillData, expectedIdentity: a.semanticExpectedIdentity, ...baseMsg };
|
|
970
990
|
case "perf.start":
|
|
971
991
|
return { type: "PERF_START", categories: a.categories ? a.categories.split(",") : undefined, ...baseMsg };
|
|
972
992
|
case "perf.stop":
|
|
@@ -998,10 +1018,30 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
998
1018
|
compact: a.compact || false,
|
|
999
1019
|
maxBytes,
|
|
1000
1020
|
forceFullSnapshot: a.compact === true || maxBytes !== undefined,
|
|
1021
|
+
...(a.semanticObservation === true ? { semanticObservation: true } : {}),
|
|
1001
1022
|
},
|
|
1002
1023
|
...baseMsg
|
|
1003
1024
|
};
|
|
1004
1025
|
}
|
|
1026
|
+
case "semantic.localCompare":
|
|
1027
|
+
return {
|
|
1028
|
+
type: "SEMANTIC_LOCAL_COMPARE",
|
|
1029
|
+
ref: a.ref,
|
|
1030
|
+
predicate: a.predicate,
|
|
1031
|
+
expectedIdentity: a.semanticExpectedIdentity,
|
|
1032
|
+
...baseMsg,
|
|
1033
|
+
};
|
|
1034
|
+
case "semantic.scrollScope":
|
|
1035
|
+
if (!["inspect", "top", "advance"].includes(a.action)) {
|
|
1036
|
+
throw new Error("semantic scroll scope action must be inspect, top, or advance");
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
type: "SEMANTIC_SCROLL_SCOPE",
|
|
1040
|
+
action: a.action,
|
|
1041
|
+
scopeToken: a.scopeToken,
|
|
1042
|
+
expectedIdentity: a.semanticExpectedIdentity,
|
|
1043
|
+
...baseMsg,
|
|
1044
|
+
};
|
|
1005
1045
|
case "page.text":
|
|
1006
1046
|
return { type: "GET_PAGE_TEXT", ...baseMsg };
|
|
1007
1047
|
case "page.html":
|
|
@@ -1317,4 +1357,49 @@ function mapToolToMessage(tool, args, tabId) {
|
|
|
1317
1357
|
}
|
|
1318
1358
|
}
|
|
1319
1359
|
|
|
1320
|
-
|
|
1360
|
+
function applySemanticExpectedIdentity(request, extensionMessage, args) {
|
|
1361
|
+
const fail = (code, message) => {
|
|
1362
|
+
const error = new Error(message);
|
|
1363
|
+
error.code = code;
|
|
1364
|
+
throw error;
|
|
1365
|
+
};
|
|
1366
|
+
const expected = args?.semanticExpectedIdentity;
|
|
1367
|
+
const identityRequired = ["SEMANTIC_LOCAL_COMPARE", "SEMANTIC_SCROLL_SCOPE"].includes(extensionMessage?.type);
|
|
1368
|
+
if (expected === undefined) {
|
|
1369
|
+
if (identityRequired) fail("invalid_expected_identity", "semantic expected identity is required for this action");
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const guardedTypes = [
|
|
1373
|
+
"CLICK_REF", "FORM_FILL", "EXECUTE_NAVIGATE", "EXECUTE_SCROLL", "SCROLL_TO_POSITION",
|
|
1374
|
+
"SEMANTIC_LOCAL_COMPARE", "SEMANTIC_SCROLL_SCOPE",
|
|
1375
|
+
];
|
|
1376
|
+
if (!extensionMessage || !guardedTypes.includes(extensionMessage.type)) {
|
|
1377
|
+
fail("invalid_expected_identity", "semantic expected identity is not valid for this action");
|
|
1378
|
+
}
|
|
1379
|
+
const domAction = ["CLICK_REF", "FORM_FILL", "SEMANTIC_LOCAL_COMPARE"].includes(extensionMessage.type);
|
|
1380
|
+
const stringFields = ["browserEpoch", "fullUrl", "documentToken", ...(domAction ? ["ref", "role", "name", "type"] : [])];
|
|
1381
|
+
if (!expected || typeof expected !== "object" || stringFields.some((field) => typeof expected[field] !== "string")) {
|
|
1382
|
+
fail("invalid_expected_identity", "invalid semantic expected identity");
|
|
1383
|
+
}
|
|
1384
|
+
if (!Number.isInteger(expected.tabId) || !Number.isInteger(expected.frameId)) {
|
|
1385
|
+
fail("invalid_expected_identity", "invalid semantic expected identity");
|
|
1386
|
+
}
|
|
1387
|
+
const actualFrameId = Number.isInteger(extensionMessage.frameId) ? extensionMessage.frameId : 0;
|
|
1388
|
+
if (
|
|
1389
|
+
request?.browserIdentity?.browserEpoch !== expected.browserEpoch ||
|
|
1390
|
+
request?.target?.tabId !== expected.tabId ||
|
|
1391
|
+
actualFrameId !== expected.frameId ||
|
|
1392
|
+
domAction && extensionMessage.ref && extensionMessage.ref !== expected.ref ||
|
|
1393
|
+
extensionMessage.type === "FORM_FILL" &&
|
|
1394
|
+
(!Array.isArray(extensionMessage.data) || extensionMessage.data.length !== 1 || extensionMessage.data[0]?.ref !== expected.ref)
|
|
1395
|
+
) {
|
|
1396
|
+
fail("stale_observation", "stale_observation");
|
|
1397
|
+
}
|
|
1398
|
+
extensionMessage.expectedIdentity = {
|
|
1399
|
+
fullUrl: expected.fullUrl,
|
|
1400
|
+
documentToken: expected.documentToken,
|
|
1401
|
+
...(domAction ? { ref: expected.ref, role: expected.role, name: expected.name, type: expected.type } : {}),
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
module.exports = { mapToolToMessage, mapComputerAction, formatToolContent, formatToolError, buildProviderUploadMessage, readinessExpectations, applySemanticExpectedIdentity };
|
package/native/host.cjs
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
const LAUNCH_PROBE_ARGUMENT = "--surf-native-host-launch-probe";
|
|
3
|
+
const LAUNCH_PROBE_MARKER = "SURF_NATIVE_HOST_LAUNCH_PROBE_OK";
|
|
4
|
+
|
|
5
|
+
if (process.argv.length === 3 && process.argv[2] === LAUNCH_PROBE_ARGUMENT) {
|
|
6
|
+
process.stdout.write(`${LAUNCH_PROBE_MARKER}\n`);
|
|
7
|
+
process.exit(0);
|
|
8
|
+
}
|
|
9
|
+
if (process.argv.length === 3 && process.argv[2] === `${LAUNCH_PROBE_ARGUMENT}-distro`) {
|
|
10
|
+
process.stdout.write(`${LAUNCH_PROBE_MARKER}:${JSON.stringify(process.env.WSL_DISTRO_NAME || null)}\n`);
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
2
14
|
const net = require("net");
|
|
3
15
|
const fs = require("fs");
|
|
4
16
|
const path = require("path");
|
|
@@ -15,7 +27,7 @@ const grokClient = require("./grok-client.cjs");
|
|
|
15
27
|
const kimiClient = require("./kimi-client.cjs");
|
|
16
28
|
const aistudioClient = require("./aistudio-client.cjs");
|
|
17
29
|
const aistudioBuild = require("./aistudio-build.cjs");
|
|
18
|
-
const { mapToolToMessage, mapComputerAction, formatToolContent, formatToolError, buildProviderUploadMessage } = require("./host-helpers.cjs");
|
|
30
|
+
const { mapToolToMessage, mapComputerAction, formatToolContent, formatToolError, buildProviderUploadMessage, applySemanticExpectedIdentity } = require("./host-helpers.cjs");
|
|
19
31
|
const { createOracleHost } = require("./oracle-host.cjs");
|
|
20
32
|
|
|
21
33
|
const IS_WIN = process.platform === "win32";
|
|
@@ -844,7 +856,7 @@ function currentFrameContext(request) {
|
|
|
844
856
|
function applyFrameContextToMessage(request, extensionMessage) {
|
|
845
857
|
if (!extensionMessage || !FRAME_CONTEXT_MESSAGE_TYPES.has(extensionMessage.type)) return;
|
|
846
858
|
const context = currentFrameContext(request);
|
|
847
|
-
if (context) extensionMessage.frameId = context.frameId;
|
|
859
|
+
if (context && !Number.isInteger(extensionMessage.frameId)) extensionMessage.frameId = context.frameId;
|
|
848
860
|
}
|
|
849
861
|
|
|
850
862
|
function persistFrameContext(request, frameId, url) {
|
|
@@ -1497,6 +1509,7 @@ async function executeMappedHostTool(request, tool, args, tabId) {
|
|
|
1497
1509
|
if (!extensionMsg) throw new Error(`Unknown tool: ${tool}`);
|
|
1498
1510
|
if (request.target?.strict) extensionMsg.strictTarget = true;
|
|
1499
1511
|
applyFrameContextToMessage(request, extensionMsg);
|
|
1512
|
+
applySemanticExpectedIdentity(request, extensionMsg, args);
|
|
1500
1513
|
if (extensionMsg.type === "UNSUPPORTED_ACTION") throw new Error(extensionMsg.message);
|
|
1501
1514
|
if (extensionMsg.type === "LOCAL_WAIT") {
|
|
1502
1515
|
await abortableDelay(extensionMsg.seconds * 1000, request.signal);
|
|
@@ -1720,6 +1733,9 @@ function sendToolResponse(socket, id, result, error) {
|
|
|
1720
1733
|
let output = result;
|
|
1721
1734
|
try {
|
|
1722
1735
|
if (!error) output = await sendRequestDownloads(context, request, result);
|
|
1736
|
+
if (!error && output?.semanticObservation?.identity && request?.target) {
|
|
1737
|
+
output.semanticObservation.identity.browserEpoch = request.target.browserEpoch;
|
|
1738
|
+
}
|
|
1723
1739
|
} catch (transferFailure) {
|
|
1724
1740
|
finalError = transferFailure.message;
|
|
1725
1741
|
}
|
|
@@ -1905,6 +1921,12 @@ function handleToolRequest(msg, socket, requestContext = requestStorage.getStore
|
|
|
1905
1921
|
}
|
|
1906
1922
|
if (requestContext.target?.strict) extensionMsg.strictTarget = true;
|
|
1907
1923
|
applyFrameContextToMessage(requestContext, extensionMsg);
|
|
1924
|
+
try {
|
|
1925
|
+
applySemanticExpectedIdentity(requestContext, extensionMsg, args);
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
sendToolResponse(socket, originalId, null, error);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1908
1930
|
|
|
1909
1931
|
if (extensionMsg.type === "UNSUPPORTED_ACTION") {
|
|
1910
1932
|
sendToolResponse(socket, originalId, null, extensionMsg.message);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const { execFileSync } = require("child_process");
|
|
2
|
+
const { runWindowsExecutable } = require("../scripts/windows-interop.cjs");
|
|
3
|
+
|
|
4
|
+
const LAUNCH_PROBE_ARGUMENT = "--surf-native-host-launch-probe";
|
|
5
|
+
const LAUNCH_PROBE_MARKER = "SURF_NATIVE_HOST_LAUNCH_PROBE_OK";
|
|
6
|
+
const WRAPPER_PROBE_CAPABILITY_MARKER = "rem SURF_NATIVE_HOST_LAUNCH_PROBE_V1";
|
|
7
|
+
const LAUNCH_PROBE_TIMEOUT_MS = 5000;
|
|
8
|
+
|
|
9
|
+
function renderWslWrapper(nodePath, hostPath, distro) {
|
|
10
|
+
const path = require("path");
|
|
11
|
+
for (const value of [nodePath, hostPath, distro].filter((value) => value !== undefined)) {
|
|
12
|
+
if (/["%!\r\n]/.test(value)) throw new Error("WSL wrapper path or distro contains unsupported batch characters");
|
|
13
|
+
}
|
|
14
|
+
const distroArg = distro ? ` -d "${distro}"` : "";
|
|
15
|
+
return `@echo off\r\n${WRAPPER_PROBE_CAPABILITY_MARKER}\r\nwsl.exe${distroArg} --cd "${path.dirname(hostPath)}" --exec "${nodePath}" "${hostPath}" %*\r\n`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function probeWindowsWrapper(wrapperPath, deps = {}) {
|
|
19
|
+
if (!/^[a-z]:\\[^"%\r\n]*\.cmd$/i.test(wrapperPath) || /[&|<>^!]/.test(wrapperPath)) {
|
|
20
|
+
throw new Error("Native host wrapper path cannot be safely passed to cmd.exe");
|
|
21
|
+
}
|
|
22
|
+
let output;
|
|
23
|
+
try {
|
|
24
|
+
output = runWindowsExecutable(
|
|
25
|
+
"cmd.exe",
|
|
26
|
+
["/d", "/s", "/c", `""${wrapperPath}" ${LAUNCH_PROBE_ARGUMENT}${deps.verifyDistro ? "-distro" : ""}"`],
|
|
27
|
+
{
|
|
28
|
+
execFileSync: deps.execFileSync || execFileSync,
|
|
29
|
+
allowWslFallback: true,
|
|
30
|
+
execOptions: {
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
timeout: deps.timeoutMs ?? LAUNCH_PROBE_TIMEOUT_MS,
|
|
33
|
+
maxBuffer: 64 * 1024,
|
|
34
|
+
windowsHide: true,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Native host wrapper launch probe failed: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const response = String(output).trim();
|
|
43
|
+
if (deps.verifyDistro) {
|
|
44
|
+
if (!response.startsWith(`${LAUNCH_PROBE_MARKER}:`)) {
|
|
45
|
+
throw new Error("Native host wrapper launch probe failed: host returned unexpected output");
|
|
46
|
+
}
|
|
47
|
+
let distro;
|
|
48
|
+
try {
|
|
49
|
+
distro = JSON.parse(response.slice(LAUNCH_PROBE_MARKER.length + 1));
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error("Native host wrapper launch probe failed: invalid distro identity");
|
|
52
|
+
}
|
|
53
|
+
if (distro !== null && (typeof distro !== "string" || !distro)) {
|
|
54
|
+
throw new Error("Native host wrapper launch probe failed: invalid distro identity");
|
|
55
|
+
}
|
|
56
|
+
return distro;
|
|
57
|
+
}
|
|
58
|
+
if (response !== LAUNCH_PROBE_MARKER) {
|
|
59
|
+
throw new Error("Native host wrapper launch probe failed: host returned unexpected output");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
LAUNCH_PROBE_ARGUMENT,
|
|
65
|
+
LAUNCH_PROBE_MARKER,
|
|
66
|
+
WRAPPER_PROBE_CAPABILITY_MARKER,
|
|
67
|
+
renderWslWrapper,
|
|
68
|
+
probeWindowsWrapper,
|
|
69
|
+
};
|
package/native/private-state.cjs
CHANGED
|
@@ -30,6 +30,19 @@ function assertWithin(root, targetPath) {
|
|
|
30
30
|
return resolvedTarget;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function assertPrivatePath(targetPath, root, allowMissing) {
|
|
34
|
+
const resolvedRoot = path.resolve(root);
|
|
35
|
+
const resolvedTarget = assertWithin(resolvedRoot, targetPath);
|
|
36
|
+
const relative = path.relative(resolvedRoot, resolvedTarget);
|
|
37
|
+
let current = resolvedRoot;
|
|
38
|
+
for (const segment of ["", ...relative.split(path.sep).filter(Boolean)]) {
|
|
39
|
+
if (segment) current = path.join(current, segment);
|
|
40
|
+
const stat = assertNotSymlink(current, allowMissing);
|
|
41
|
+
if (!stat) return null;
|
|
42
|
+
}
|
|
43
|
+
return fs.lstatSync(resolvedTarget);
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
function ensurePrivateDir(dirPath, root = getPrivateStateRoot()) {
|
|
34
47
|
const resolvedRoot = path.resolve(root);
|
|
35
48
|
const resolvedDir = assertWithin(resolvedRoot, dirPath);
|
|
@@ -129,13 +142,23 @@ function readPrivateFile(filePath, options = {}) {
|
|
|
129
142
|
const resolved = path.resolve(filePath);
|
|
130
143
|
const root = options.root || getPrivateStateRoot();
|
|
131
144
|
assertWithin(root, resolved);
|
|
132
|
-
const stat =
|
|
145
|
+
const stat = assertPrivatePath(resolved, root, options.allowMissing === true);
|
|
133
146
|
if (!stat) return options.fallback;
|
|
134
147
|
if (!stat.isFile()) throw new Error(`private state path is not a file: ${resolved}`);
|
|
135
148
|
if (process.platform !== "win32" && (stat.mode & 0o077) !== 0) throw new Error(`private state file permissions are too broad: ${resolved}`);
|
|
136
149
|
return fs.readFileSync(resolved, options.encoding || null);
|
|
137
150
|
}
|
|
138
151
|
|
|
152
|
+
function removePrivateFile(filePath, options = {}) {
|
|
153
|
+
const resolved = path.resolve(filePath);
|
|
154
|
+
const root = options.root || getPrivateStateRoot();
|
|
155
|
+
const stat = assertPrivatePath(resolved, root, true);
|
|
156
|
+
if (!stat) return false;
|
|
157
|
+
if (!stat.isFile()) throw new Error(`private state path is not a file: ${resolved}`);
|
|
158
|
+
fs.unlinkSync(resolved);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
139
162
|
function readPrivateJson(filePath, fallback = null, options = {}) {
|
|
140
163
|
const content = readPrivateFile(filePath, { ...options, allowMissing: true, fallback: null, encoding: "utf8" });
|
|
141
164
|
return content === null ? fallback : JSON.parse(content);
|
|
@@ -152,5 +175,6 @@ module.exports = {
|
|
|
152
175
|
privateStatePath,
|
|
153
176
|
readPrivateFile,
|
|
154
177
|
readPrivateJson,
|
|
178
|
+
removePrivateFile,
|
|
155
179
|
writePrivateFileExclusive,
|
|
156
180
|
};
|