fix(inspector): route file locations through URL semantics

This commit is contained in:
morluto committed 2026-10-26 16:02:14 +08:00
1 parent 1a1121c61f
commit c994870f6b
2 files changed
+132 -1

No files matched your search

+1 -1
View File
@@ -58,7 +58,7 @@ export const authorizeRuntimeLocation = async (
asarHeaderAdmission: AsarHeaderAdmission = DEFAULT_ASAR_HEADER_ADMISSION,
): Promise<RuntimeLocationDecision> => {
throwIfRuntimeObservationCancelled(signal, operation);
if (value.startsWith("file:")) {
if (URL.parse(value)?.protocol === "file:") {
const filePath = await authorizedElectronFile(
value,
signal,
@@ -0,0 +1,131 @@
import { spawn } from "node:child_process";
import { once } from "node:events";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { expect, it, onTestFinished } from "vitest";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it("preserves file URL scheme spelling and script identity after real Inspector state changes", async () => {
const root = await createTestTempDirectory("rea-inspector-file-url-");
const source = join(root, "source #参数%.cjs");
const runner = join(root, "runner.cjs");
const sourceText = "globalThis.fileUrlProbe = true;\n";
await writeFile(source, sourceText);
await writeFile(
runner,
`
const { readFileSync } = require("node:fs");
const vm = require("node:vm");
const scripts = [];
process.on("message", (urls) => {
for (const filename of urls) {
const script = new vm.Script(readFileSync(process.argv[2], "utf8"), { filename });
scripts.push(script);
script.runInThisContext();
}
process.send({ loaded: scripts.length });
});
setInterval(() => {}, 1000);
`,
);
const child = spawn(
process.execPath,
["--inspect=127.0.0.1:0", runner, source],
{
stdio: ["ignore", "ignore", "pipe", "ipc"],
},
);
onTestFinished(async () => {
if (child.exitCode === null && child.signalCode === null) {
const exited = once(child, "exit");
child.kill("SIGTERM");
await exited;
}
});
let stderr = "";
if (child.stderr === null) throw new Error("Expected piped Inspector stderr");
child.stderr.on("data", (chunk: Buffer) => {
stderr += chunk.toString();
});
const address = /ws:\/\/127\.0\.0\.1:\d+\/[^\s]+/u;
await expect.poll(() => stderr, { timeout: 5000 }).toMatch(address);
const socket = address.exec(stderr)?.[0];
if (socket === undefined) throw new Error("Node omitted its Inspector URL");
const endpoint = `http://127.0.0.1:${new URL(socket).port}`;
const load = async (urls: readonly string[]) => {
const loaded = once(child, "message");
child.send(urls);
await loaded;
};
const originalUrl = pathToFileURL(source).href;
await load([originalUrl]);
const { call } = await connectLocalToolsMcp();
const listed = await call("list_javascript_runtime_targets", {
inspector_endpoint: endpoint,
});
expect(listed.isError, JSON.stringify(listed.content)).not.toBe(true);
const discovery = toolContract(
"list_javascript_runtime_targets",
).outputSchema.parse(listed.structuredContent);
const targetId = discovery.normalized_result.targets[0]?.target_id;
if (targetId === undefined) throw new Error("Node target was not listed");
const request = {
inspector_endpoint: endpoint,
target_id: targetId,
observation_ms: 25,
};
const observe = async () => {
const response = await call("observe_javascript_runtime", request);
expect(response.isError, JSON.stringify(response.content)).not.toBe(true);
return toolContract("observe_javascript_runtime").outputSchema.parse(
response.structuredContent,
);
};
const first = await observe();
const initialScript = first.normalized_result.scripts.items.find(
(script) => script.reported_url === originalUrl,
);
expect(initialScript).toMatchObject({
location: { kind: "file", file_path: source },
});
const addedUrls = [
originalUrl.replace(/^file:/u, "FILE:"),
`${originalUrl.replace(/^file:/u, "FiLe:")}?instance=2#selected`,
` \t${originalUrl}\t `,
];
await load(addedUrls);
const second = await observe();
const expectedUrls = [originalUrl, ...addedUrls];
const scripts = second.normalized_result.scripts.items.filter((script) =>
expectedUrls.includes(script.reported_url ?? ""),
);
expect(scripts.map((script) => script.reported_url)).toEqual(
expect.arrayContaining(expectedUrls),
);
expect(scripts).toHaveLength(4);
expect(new Set(scripts.map((script) => script.script_key)).size).toBe(4);
for (const script of scripts)
expect(script.location).toEqual({ kind: "file", file_path: source });
expect(
scripts.find((script) => script.reported_url === originalUrl)?.script_key,
).toBe(initialScript?.script_key);
const retained = await call("get_evidence_bundle", {});
expect(retained.isError, JSON.stringify(retained.content)).not.toBe(true);
const records = toolContract("get_evidence_bundle").outputSchema.parse(
retained.structuredContent,
).result.records;
const original = records.find(
(record) => record.evidence_id === first.evidence_id,
);
if (original === undefined || !("normalized_result" in original))
throw new Error("Original Inspector Evidence was not retained");
expect(original.predicate_type).toBe(first.predicate_type);
expect(original.normalized_result).toEqual(first.normalized_result);
expect(await readFile(source, "utf8")).toBe(sourceText);
}, 30_000);