rea-agents 2.0.0 → 2.1.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 +78 -32
- package/bridge/ghidra/ReaGhidraBridge.java +96 -11
- package/dist/application/ArtifactExtraction.js +1 -1
- package/dist/application/ArtifactInventory/classify.js +42 -0
- package/dist/application/ArtifactInventory/hash.js +43 -0
- package/dist/application/ArtifactInventory/reader.js +41 -0
- package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
- package/dist/application/ArtifactInventory/scanReader.js +124 -0
- package/dist/application/ArtifactInventory/types.js +10 -0
- package/dist/application/ArtifactInventory.js +9 -351
- package/dist/application/AuthorizedArtifactInventory.js +2 -2
- package/dist/application/BinarySession.js +47 -270
- package/dist/application/BinarySessionExecution.js +28 -0
- package/dist/application/BinarySessionOpen.js +39 -0
- package/dist/application/BinarySessionRecords.js +149 -0
- package/dist/application/BinarySessionStatus.js +75 -0
- package/dist/application/CapabilityInventory.js +24 -2
- package/dist/application/ClientRegistrationStatus.js +1 -1
- package/dist/application/CrossVersionInventory.js +8 -2
- package/dist/application/CrossVersionInvestigation.js +1 -23
- package/dist/application/CrossVersionInvestigationCollections.js +23 -0
- package/dist/application/DirectAnalysis.js +47 -44
- package/dist/application/Doctor.js +156 -238
- package/dist/application/DoctorDiagnostics.js +180 -0
- package/dist/application/ElectronBoundaryAnalysis.js +23 -7
- package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
- package/dist/application/EnhancedToolPagination.js +22 -0
- package/dist/application/EnhancedToolTypes.js +4 -0
- package/dist/application/EnhancedTools.js +20 -37
- package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
- package/dist/application/InvestigationWorkspaceStore.js +24 -10
- package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
- package/dist/application/JavaScriptArtifactFiles.js +6 -2
- package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
- package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
- package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
- package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
- package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
- package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
- package/dist/application/JavaScriptJsonModules.js +39 -0
- package/dist/application/JavaScriptModuleRelationships.js +262 -0
- package/dist/application/JavaScriptReplayPermission.js +16 -0
- package/dist/application/JavaScriptReplayService.js +182 -156
- package/dist/application/LinuxHopper.js +10 -0
- package/dist/application/MacHopper.js +22 -11
- package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
- package/dist/application/PermissionAuthority.js +106 -63
- package/dist/application/ProcessCaptureCapability.js +1 -1
- package/dist/application/ProcessCapturePermission.js +13 -0
- package/dist/application/ProcessCli.js +2 -12
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProjectPermissionStore.js +48 -9
- package/dist/application/RealHopperAssertions.js +76 -0
- package/dist/application/ReconstructionCoverageService.js +69 -0
- package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
- package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
- package/dist/application/Setup.js +147 -259
- package/dist/application/SetupClientConfiguration.js +220 -0
- package/dist/application/SetupClients.js +24 -2
- package/dist/application/SetupPlan.js +123 -12
- package/dist/application/SetupSkill.js +26 -12
- package/dist/application/SupportedClients.js +1 -0
- package/dist/application/Uninstall.js +6 -3
- package/dist/application/WindowsCapabilities.js +86 -0
- package/dist/artifacts/AsarArtifactReader.js +11 -0
- package/dist/browser/BrowserProcessStartup.js +88 -0
- package/dist/browser/CdpCaptureDocuments.js +12 -8
- package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
- package/dist/browser/CdpCaptureEventHandlers.js +336 -0
- package/dist/browser/CdpCaptureEventHelpers.js +71 -0
- package/dist/browser/CdpCaptureEventState.js +1 -0
- package/dist/browser/CdpCaptureEventTypes.js +1 -0
- package/dist/browser/CdpCaptureEvents.js +29 -496
- package/dist/browser/CdpObservationSession.js +51 -8
- package/dist/browser/CdpPageCapture.js +12 -133
- package/dist/browser/CdpPageCaptureScripts.js +97 -0
- package/dist/browser/CdpPageCaptureWorkers.js +46 -0
- package/dist/browser/CdpWebMcpDiscovery.js +38 -14
- package/dist/browser/PngVisualDiff.js +9 -2
- package/dist/catalogIdentity.js +4 -0
- package/dist/cli/artifactCommands.js +82 -0
- package/dist/cli/coreAnalysisCommands.js +232 -0
- package/dist/cli/managedCommands.js +397 -0
- package/dist/cli/managedProjectGraphCommand.js +24 -0
- package/dist/cli/options.js +9 -0
- package/dist/cli/setupCommands.js +108 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utilityCommands.js +122 -0
- package/dist/cli.js +10 -768
- package/dist/cliApplicationCommands.js +118 -4
- package/dist/cliBrowserAdvancedCommands.js +73 -16
- package/dist/cliBrowserCommands.js +39 -115
- package/dist/cliCommandNames.js +4 -0
- package/dist/cliElectronCommands.js +33 -62
- package/dist/cliEvidenceCommands.js +13 -2
- package/dist/cliInvestigationCommands.js +53 -10
- package/dist/cliObservationOptions.js +137 -0
- package/dist/cliPolicyCommands.js +80 -46
- package/dist/cliSetup.js +289 -0
- package/dist/config/environment.js +107 -0
- package/dist/config/parseConfig.js +228 -0
- package/dist/config/parsers.js +68 -0
- package/dist/config/permissions.js +30 -0
- package/dist/config/types.js +1 -0
- package/dist/config.js +1 -375
- package/dist/contracts/applicationToolContracts.js +134 -0
- package/dist/contracts/managedToolContracts.js +1 -1
- package/dist/contracts/managedWorkflowToolContracts.js +3 -3
- package/dist/contracts/providerSelection.js +2 -0
- package/dist/contracts/toolContracts.js +2 -2
- package/dist/contracts/toolEffects.js +14 -0
- package/dist/contracts/toolOutputSchemaGroups.js +232 -0
- package/dist/contracts/toolOutputSchemaPrimitives.js +327 -0
- package/dist/contracts/toolOutputSchemas.js +2 -550
- package/dist/domain/analysisErrorPresentation.js +234 -0
- package/dist/domain/analysisErrorProjection.js +284 -0
- package/dist/domain/artifactComparison.js +34 -15
- package/dist/domain/binaryTarget.js +87 -37
- package/dist/domain/browserObservation.js +1 -285
- package/dist/domain/browserObservationSchemas.js +286 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
- package/dist/domain/errors.js +4 -486
- package/dist/domain/functionComparison.js +4 -165
- package/dist/domain/functionComparisonDimensions.js +164 -0
- package/dist/domain/javascriptApplicationVersionItems.js +18 -6
- package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
- package/dist/domain/javascriptExportInstrumentation.js +97 -0
- package/dist/domain/javascriptFeatureTraversal.js +19 -7
- package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
- package/dist/domain/javascriptSemanticAnalysis.js +386 -0
- package/dist/domain/javascriptSemanticIr.js +30 -0
- package/dist/domain/javascriptSemanticProjection.js +366 -0
- package/dist/domain/javascriptSemanticState.js +40 -0
- package/dist/domain/javascriptSemanticTraversal.js +22 -0
- package/dist/domain/javascriptSemanticValues.js +352 -0
- package/dist/domain/javascriptStaticAnalysis.js +5 -221
- package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
- package/dist/domain/knownPageCoverage.js +21 -0
- package/dist/domain/managedApplicationGraph.js +14 -364
- package/dist/domain/managedApplicationGraphCoverage.js +170 -0
- package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
- package/dist/domain/managedApplicationGraphNodes.js +336 -0
- package/dist/domain/managedArtifact.js +16 -8
- package/dist/domain/managedMemberComparison.js +37 -391
- package/dist/domain/managedMemberComparisonCoverage.js +72 -0
- package/dist/domain/managedMemberComparisonMatch.js +356 -0
- package/dist/domain/managedNativeVerification.js +27 -389
- package/dist/domain/managedNativeVerificationMatch.js +272 -0
- package/dist/domain/managedNativeVerificationSchemas.js +141 -0
- package/dist/domain/managedReconstruction.js +82 -69
- package/dist/domain/managedRuntimeCorrelation.js +12 -3
- package/dist/domain/nodeRuntimeCharacterization.js +66 -0
- package/dist/domain/permissionPolicy.js +2 -0
- package/dist/domain/reconstructionCoverage.js +387 -0
- package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
- package/dist/domain/reconstructionVerificationSchemas.js +8 -0
- package/dist/domain/runtimeCharacterization.js +85 -0
- package/dist/domain/webBundleAnalyzer.js +3 -409
- package/dist/domain/webBundleAnalyzerAst.js +77 -0
- package/dist/domain/webBundleAnalyzerInspection.js +307 -0
- package/dist/domain/webBundleAnalyzerResult.js +120 -0
- package/dist/dotnet/ManagedArtifactInspector.js +50 -29
- package/dist/dotnet/ManagedMemberInspector.js +51 -785
- package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
- package/dist/dotnet/ManagedMemberRows.js +120 -0
- package/dist/dotnet/ManagedMetadataInventory.js +42 -306
- package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
- package/dist/dotnet/ManagedMetadataLayout.js +48 -111
- package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
- package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
- package/dist/dotnet/ManagedPeReader.js +59 -28
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +76 -91
- package/dist/ghidra/GhidraClientStartup.js +29 -0
- package/dist/ghidra/GhidraClientWire.js +34 -0
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraDiagnostics.js +6 -1
- package/dist/ghidra/GhidraInstallation.js +20 -15
- package/dist/ghidra/GhidraLauncher.js +93 -29
- package/dist/ghidra/GhidraProvider.js +61 -161
- package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
- package/dist/ghidra/GhidraSessionValues.js +2 -0
- package/dist/ghidra/GhidraSocketConnection.js +44 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
- package/dist/ghidra/GhidraTransport.js +71 -0
- package/dist/identity.js +3 -2
- package/dist/main/messages.js +5 -0
- package/dist/main/reload.js +49 -0
- package/dist/main/shutdown.js +25 -0
- package/dist/main/startup.js +14 -0
- package/dist/main/state.js +26 -0
- package/dist/main/transport.js +61 -0
- package/dist/main/types.js +1 -0
- package/dist/main.js +35 -146
- package/dist/process/PrivateRuntimeRoot.js +3 -2
- package/dist/process/ProcessOwnership.js +35 -0
- package/dist/process/ProviderProcess.js +2 -0
- package/dist/replay/JavaScriptReplayWorker.js +64 -50
- package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
- package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
- package/dist/replay/ReplayOutcome.js +95 -0
- package/dist/replay/ReplayProcessLifecycle.js +71 -0
- package/dist/server/ProcessCaptureElicitation.js +157 -0
- package/dist/server/createServer.js +119 -54
- package/dist/server/registerApplicationTools/characterization.js +57 -0
- package/dist/server/registerApplicationTools/compareVersions.js +34 -0
- package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
- package/dist/server/registerApplicationTools/coverage.js +84 -0
- package/dist/server/registerApplicationTools/helpers.js +42 -0
- package/dist/server/registerApplicationTools/traceFeature.js +26 -0
- package/dist/server/registerApplicationTools/types.js +1 -0
- package/dist/server/registerApplicationTools.js +10 -112
- package/dist/server/registerBrowserTools.js +59 -83
- package/dist/server/registerElectronTools.js +31 -46
- package/dist/server/registerEnhancedTools.js +34 -42
- package/dist/server/registerEvidenceResources.js +54 -0
- package/dist/server/registerInvestigationTools/helpers.js +74 -0
- package/dist/server/registerInvestigationTools/types.js +1 -0
- package/dist/server/registerInvestigationTools.js +83 -116
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
- package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
- package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
- package/dist/server/registerManagedWorkflowTools/types.js +1 -0
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
- package/dist/server/registerManagedWorkflowTools.js +10 -274
- package/dist/server/registerOfficialTools.js +73 -59
- package/dist/server/registerSessionRecordTools.js +137 -0
- package/dist/server/registerSessionStatusTool.js +2 -1
- package/dist/server/registerSessionTools.js +60 -164
- package/dist/server/sessionAvailabilityPolicy.js +10 -0
- package/dist/server/toolRegistrationOptions.js +101 -3
- package/dist/server/toolResult.js +5 -0
- package/package.json +10 -11
- package/skills/{rea-analysis → reverse-engineer-anything}/SKILL.md +20 -7
- package/scripts/prepare-node-pty.mjs +0 -35
- package/scripts/rebuild-native.mjs +0 -22
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import writeFileAtomic from "write-file-atomic";
|
|
6
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
7
|
+
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
8
|
+
import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
|
|
9
|
+
const defaultCommand = () => [
|
|
10
|
+
"npx",
|
|
11
|
+
"-y",
|
|
12
|
+
PRODUCT_IDENTITY.packageSpecifier,
|
|
13
|
+
"mcp",
|
|
14
|
+
];
|
|
15
|
+
/** Back up, atomically update, and semantically read back one JSON MCP configuration. */
|
|
16
|
+
export const configureJsonClient = async (client, environment = {}, command = defaultCommand()) => {
|
|
17
|
+
const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
|
|
18
|
+
if (transactionPath === undefined)
|
|
19
|
+
return { status: "failed", reason: "path" };
|
|
20
|
+
let document = {};
|
|
21
|
+
let original;
|
|
22
|
+
try {
|
|
23
|
+
original = await readFile(transactionPath, "utf8");
|
|
24
|
+
document = parseObject(original);
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
if (!isMissing(cause))
|
|
28
|
+
return { status: "failed", reason: "readback" };
|
|
29
|
+
}
|
|
30
|
+
let servers;
|
|
31
|
+
try {
|
|
32
|
+
servers = parseOptionalObject(document.mcpServers);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { status: "failed", reason: "readback" };
|
|
36
|
+
}
|
|
37
|
+
const desired = clientConfigurationDesired(normalizeProviderEnvironment(environment), command);
|
|
38
|
+
if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
|
|
39
|
+
return { status: "unchanged" };
|
|
40
|
+
let backupPath;
|
|
41
|
+
if (original !== undefined) {
|
|
42
|
+
backupPath = `${client.configPath}.rea.backup`;
|
|
43
|
+
if (!(await preserveConfigBackup(transactionPath, backupPath)))
|
|
44
|
+
return { status: "failed", reason: "backup" };
|
|
45
|
+
}
|
|
46
|
+
document.mcpServers = {
|
|
47
|
+
...servers,
|
|
48
|
+
[PRODUCT_IDENTITY.mcpServerKey]: desired,
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
await mkdir(dirname(client.configPath), { recursive: true });
|
|
52
|
+
await writeFileAtomic(transactionPath, `${JSON.stringify(document, null, 2)}\n`, {
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
mode: 0o600,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return { status: "failed", reason: "write" };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const readback = parseObject(await readFile(transactionPath, "utf8"));
|
|
62
|
+
if (!sameConfiguration(parseOptionalObject(readback.mcpServers)[PRODUCT_IDENTITY.mcpServerKey], desired)) {
|
|
63
|
+
await restoreConfig(transactionPath, original);
|
|
64
|
+
return { status: "failed", reason: "readback" };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
await restoreConfig(transactionPath, original);
|
|
69
|
+
return { status: "failed", reason: "readback" };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
status: "configured",
|
|
73
|
+
...(backupPath === undefined ? {} : { backupPath }),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
/** Back up, atomically update, and semantically read back Codex TOML configuration. */
|
|
77
|
+
export const configureTomlClient = async (client, environment = {}, command = defaultCommand()) => {
|
|
78
|
+
const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
|
|
79
|
+
if (transactionPath === undefined)
|
|
80
|
+
return { status: "failed", reason: "path" };
|
|
81
|
+
let document = {};
|
|
82
|
+
let original;
|
|
83
|
+
try {
|
|
84
|
+
original = await readFile(transactionPath, "utf8");
|
|
85
|
+
document = objectSchema.parse(parseToml(original));
|
|
86
|
+
}
|
|
87
|
+
catch (cause) {
|
|
88
|
+
if (!isMissing(cause))
|
|
89
|
+
return { status: "failed", reason: "readback" };
|
|
90
|
+
}
|
|
91
|
+
let servers;
|
|
92
|
+
try {
|
|
93
|
+
servers = parseOptionalObject(document.mcp_servers);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { status: "failed", reason: "readback" };
|
|
97
|
+
}
|
|
98
|
+
const desired = clientConfigurationDesired(normalizeProviderEnvironment(environment), command);
|
|
99
|
+
if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
|
|
100
|
+
return { status: "unchanged" };
|
|
101
|
+
const backupPath = original === undefined ? undefined : `${client.configPath}.rea.backup`;
|
|
102
|
+
if (backupPath !== undefined &&
|
|
103
|
+
!(await preserveConfigBackup(transactionPath, backupPath)))
|
|
104
|
+
return { status: "failed", reason: "backup" };
|
|
105
|
+
document.mcp_servers = {
|
|
106
|
+
...servers,
|
|
107
|
+
[PRODUCT_IDENTITY.mcpServerKey]: desired,
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
await mkdir(dirname(client.configPath), { recursive: true });
|
|
111
|
+
await writeFileAtomic(transactionPath, stringifyToml(document), {
|
|
112
|
+
encoding: "utf8",
|
|
113
|
+
mode: 0o600,
|
|
114
|
+
});
|
|
115
|
+
const readback = objectSchema.parse(parseToml(await readFile(transactionPath, "utf8")));
|
|
116
|
+
if (!sameConfiguration(parseOptionalObject(readback.mcp_servers)[PRODUCT_IDENTITY.mcpServerKey], desired))
|
|
117
|
+
throw new Error("TOML readback mismatch");
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
await restoreConfig(transactionPath, original);
|
|
121
|
+
return { status: "failed", reason: "readback" };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
status: "configured",
|
|
125
|
+
...(backupPath === undefined ? {} : { backupPath }),
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/** Determine whether an existing client configuration matches the desired registration. */
|
|
129
|
+
export const clientConfigurationAligned = async (client, providerEnvironment, command) => {
|
|
130
|
+
const desired = clientConfigurationDesired(providerEnvironment, command);
|
|
131
|
+
try {
|
|
132
|
+
const original = await readFile(client.configPath, "utf8");
|
|
133
|
+
const document = client.format === "toml"
|
|
134
|
+
? objectSchema.parse(parseToml(original))
|
|
135
|
+
: parseObject(original);
|
|
136
|
+
const servers = parseOptionalObject(client.format === "toml" ? document.mcp_servers : document.mcpServers);
|
|
137
|
+
return sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/** Preflight one client configuration before setup presents any mutations. */
|
|
144
|
+
export const inspectClientConfiguration = async (client, providerEnvironment, command) => {
|
|
145
|
+
if (client.format === "unsupported")
|
|
146
|
+
return { status: "already_current" };
|
|
147
|
+
const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
|
|
148
|
+
if (transactionPath === undefined)
|
|
149
|
+
return {
|
|
150
|
+
status: "invalid",
|
|
151
|
+
remediation: "The configuration path is unsafe or unresolved. Check ownership and symbolic links before rerunning setup.",
|
|
152
|
+
};
|
|
153
|
+
let original;
|
|
154
|
+
try {
|
|
155
|
+
original = await readFile(transactionPath, "utf8");
|
|
156
|
+
}
|
|
157
|
+
catch (cause) {
|
|
158
|
+
if (isMissing(cause))
|
|
159
|
+
return { status: "create" };
|
|
160
|
+
return {
|
|
161
|
+
status: "invalid",
|
|
162
|
+
remediation: "The configuration file could not be read. Check its permissions before rerunning setup.",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const desired = clientConfigurationDesired(providerEnvironment, command);
|
|
166
|
+
try {
|
|
167
|
+
const document = client.format === "toml"
|
|
168
|
+
? objectSchema.parse(parseToml(original))
|
|
169
|
+
: parseObject(original);
|
|
170
|
+
const servers = parseOptionalObject(client.format === "toml" ? document.mcp_servers : document.mcpServers);
|
|
171
|
+
if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
|
|
172
|
+
return { status: "already_current" };
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return {
|
|
176
|
+
status: "invalid",
|
|
177
|
+
remediation: "The existing configuration is malformed. Repair it before setup applies any other changes.",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
status: "update",
|
|
182
|
+
backupPath: `${client.configPath}.rea.backup`,
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const objectSchema = z.record(z.string(), z.unknown());
|
|
186
|
+
const parseOptionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
|
|
187
|
+
const parseObject = (text) => objectSchema.parse(JSON.parse(text));
|
|
188
|
+
const isMissing = (cause) => cause instanceof Error && "code" in cause && cause.code === "ENOENT";
|
|
189
|
+
const sameConfiguration = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
190
|
+
const preserveConfigBackup = async (source, destination) => {
|
|
191
|
+
try {
|
|
192
|
+
await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
catch (cause) {
|
|
196
|
+
return cause instanceof Error && "code" in cause && cause.code === "EEXIST";
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const restoreConfig = async (path, original) => {
|
|
200
|
+
try {
|
|
201
|
+
if (original === undefined)
|
|
202
|
+
await rm(path, { force: true });
|
|
203
|
+
else
|
|
204
|
+
await writeFile(path, original, { encoding: "utf8", mode: 0o600 });
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// The backup remains available for the remediation reported by setup.
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const clientConfigurationDesired = (providerEnvironment, command) => {
|
|
211
|
+
const environment = Object.fromEntries(Object.entries(providerEnvironment).sort(([left], [right]) => left.localeCompare(right)));
|
|
212
|
+
return {
|
|
213
|
+
command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
|
|
214
|
+
args: command.slice(1),
|
|
215
|
+
...(Object.keys(environment).length === 0 ? {} : { env: environment }),
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
const normalizeProviderEnvironment = (environment) => typeof environment === "string"
|
|
219
|
+
? { HOPPER_LAUNCHER_PATH: environment }
|
|
220
|
+
: environment;
|
|
@@ -11,12 +11,34 @@ const failedConfigurationMessage = (reason) => {
|
|
|
11
11
|
export const configureDetectedClients = async (options) => {
|
|
12
12
|
let failure;
|
|
13
13
|
for (const client of options.detectedClients) {
|
|
14
|
+
const actionId = `configure_client:${client.name}`;
|
|
15
|
+
const label = client.displayName ?? client.name;
|
|
16
|
+
options.onProgress?.({ actionId, label, state: "started" });
|
|
14
17
|
const result = await options.host.configureClient(client, options.providerEnvironment, options.command);
|
|
15
18
|
options.clients[client.name] = result;
|
|
16
|
-
if (result.status === "failed")
|
|
19
|
+
if (result.status === "failed") {
|
|
17
20
|
failure ??= failedConfigurationMessage(result.reason);
|
|
18
|
-
|
|
21
|
+
options.onProgress?.({
|
|
22
|
+
actionId,
|
|
23
|
+
label,
|
|
24
|
+
state: "failed",
|
|
25
|
+
detail: failedConfigurationMessage(result.reason),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (result.status === "configured") {
|
|
19
29
|
options.appliedActions.push(`configured_${client.name}`);
|
|
30
|
+
options.onProgress?.({ actionId, label, state: "completed" });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
options.onProgress?.({
|
|
34
|
+
actionId,
|
|
35
|
+
label,
|
|
36
|
+
state: "warning",
|
|
37
|
+
detail: result.status === "unchanged"
|
|
38
|
+
? "Already current"
|
|
39
|
+
: "Manual configuration required",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
20
42
|
}
|
|
21
43
|
return failure;
|
|
22
44
|
};
|
|
@@ -1,38 +1,149 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
4
|
+
import { linuxHopperInstallDisclosure, } from "./LinuxHopper.js";
|
|
5
|
+
import { macHopperInstallDisclosure } from "./MacHopper.js";
|
|
1
6
|
/** Build the complete setup mutation plan before approval. */
|
|
2
|
-
|
|
3
|
-
...(installHopper
|
|
7
|
+
const setupPlan = (input) => [
|
|
8
|
+
...(input.installHopper
|
|
4
9
|
? [
|
|
5
10
|
{
|
|
11
|
+
id: "install_hopper",
|
|
6
12
|
kind: "install_hopper",
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
label: "Hopper deep-analysis provider",
|
|
14
|
+
target: input.platform === "darwin"
|
|
15
|
+
? join(homedir(), "Applications/Hopper Disassembler.app")
|
|
9
16
|
: "system package manager",
|
|
10
|
-
detail: platform === "linux"
|
|
17
|
+
detail: input.platform === "linux"
|
|
11
18
|
? "Download, verify, and install Hopper plus its Xvfb demo-session dependencies. For the supported demo build, REA uses a private display and selects Hopper's offered demo mode for each analysis session."
|
|
12
19
|
: "Download the official Hopper package, verify it, and install it. Hopper may show its demo or license prompt when first opened.",
|
|
13
20
|
external: true,
|
|
21
|
+
operation: "install",
|
|
22
|
+
...(input.platform === "linux"
|
|
23
|
+
? input.linuxPackageFamily === undefined
|
|
24
|
+
? {}
|
|
25
|
+
: linuxDisclosure(input.linuxPackageFamily)
|
|
26
|
+
: {
|
|
27
|
+
networkOrigins: macHopperInstallDisclosure.networkOrigins,
|
|
28
|
+
commands: macHopperInstallDisclosure.commands,
|
|
29
|
+
}),
|
|
14
30
|
},
|
|
15
31
|
]
|
|
16
32
|
: []),
|
|
17
|
-
...clients
|
|
18
|
-
.filter(({
|
|
19
|
-
.map((client) => ({
|
|
33
|
+
...input.clients
|
|
34
|
+
.filter(({ client }) => client.format !== "unsupported")
|
|
35
|
+
.map(({ client, operation, backupPath }) => ({
|
|
36
|
+
id: `configure_client:${client.name}`,
|
|
20
37
|
kind: "configure_client",
|
|
38
|
+
label: client.displayName ?? client.name,
|
|
21
39
|
target: client.configPath,
|
|
22
|
-
detail: clientConfigurationDetail(client.name, providerEnvironment),
|
|
40
|
+
detail: clientConfigurationDetail(client.displayName ?? client.name, input.providerEnvironment),
|
|
23
41
|
external: false,
|
|
42
|
+
operation,
|
|
43
|
+
...(backupPath === undefined ? {} : { backupPath }),
|
|
24
44
|
})),
|
|
25
|
-
...(installSkill
|
|
45
|
+
...(input.installSkill
|
|
26
46
|
? [
|
|
27
47
|
{
|
|
48
|
+
id: "install_skill",
|
|
28
49
|
kind: "install_skill",
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
label: "REA reverse-engineering skill",
|
|
51
|
+
target: join(homedir(), ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md"),
|
|
52
|
+
detail: "Install or update the bundled REA reverse-engineering skill.",
|
|
31
53
|
external: false,
|
|
54
|
+
operation: "install",
|
|
32
55
|
},
|
|
33
56
|
]
|
|
34
57
|
: []),
|
|
35
58
|
];
|
|
59
|
+
/** Discover and preflight the complete set of selectable setup actions. */
|
|
60
|
+
export const discoverSetupActions = async (input) => {
|
|
61
|
+
const { host, providerEnvironment, command } = input;
|
|
62
|
+
const initialDoctor = await host.doctor();
|
|
63
|
+
const linuxHopperRepairNeeded = initialDoctor.checks.some(({ name, ok, detail }) => !ok &&
|
|
64
|
+
(name === "hopper-demo-runtime" ||
|
|
65
|
+
(name === "hopper-version" && detail === "/opt/hopper/bin/Hopper")));
|
|
66
|
+
const installHopper = input.forceHopperInstall ||
|
|
67
|
+
(input.proposeHopper &&
|
|
68
|
+
(Object.keys(providerEnvironment).length === 0 ||
|
|
69
|
+
linuxHopperRepairNeeded));
|
|
70
|
+
const [allDetectedClients, skillNeedsInstall] = await Promise.all([
|
|
71
|
+
host.detectedClients(),
|
|
72
|
+
host.skillNeedsInstall(),
|
|
73
|
+
]);
|
|
74
|
+
const selectedClientIdSet = input.selectedClientIds === undefined
|
|
75
|
+
? undefined
|
|
76
|
+
: new Set(input.selectedClientIds);
|
|
77
|
+
const detectedClients = allDetectedClients.filter(({ name }) => selectedClientIdSet === undefined || selectedClientIdSet.has(name));
|
|
78
|
+
const installSkill = input.installSkillSelection === false ? false : skillNeedsInstall;
|
|
79
|
+
const clients = installHopper
|
|
80
|
+
? detectedClients
|
|
81
|
+
: await filterClientsNeedingConfigure({
|
|
82
|
+
host,
|
|
83
|
+
clients: detectedClients,
|
|
84
|
+
providerEnvironment,
|
|
85
|
+
command,
|
|
86
|
+
});
|
|
87
|
+
const inspections = await inspectClients({
|
|
88
|
+
host,
|
|
89
|
+
clients,
|
|
90
|
+
providerEnvironment,
|
|
91
|
+
command,
|
|
92
|
+
});
|
|
93
|
+
const invalid = inspections.find(({ inspection }) => inspection.status === "invalid");
|
|
94
|
+
const blocker = invalid?.inspection.status === "invalid"
|
|
95
|
+
? `${invalid.client.displayName ?? invalid.client.name}: ${invalid.inspection.remediation}`
|
|
96
|
+
: undefined;
|
|
97
|
+
const clientPlans = inspections.flatMap(({ client, inspection }) => inspection.status === "create" ||
|
|
98
|
+
inspection.status === "update" ||
|
|
99
|
+
(installHopper && inspection.status === "already_current")
|
|
100
|
+
? [
|
|
101
|
+
{
|
|
102
|
+
client,
|
|
103
|
+
operation: inspection.status === "already_current"
|
|
104
|
+
? "update"
|
|
105
|
+
: inspection.status,
|
|
106
|
+
...(inspection.status !== "already_current" &&
|
|
107
|
+
inspection.backupPath !== undefined
|
|
108
|
+
? { backupPath: inspection.backupPath }
|
|
109
|
+
: {}),
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
: []);
|
|
113
|
+
const linuxDistribution = host.platform === "linux" ? await host.linuxDistribution() : undefined;
|
|
114
|
+
return {
|
|
115
|
+
installHopper,
|
|
116
|
+
installSkill,
|
|
117
|
+
detectedClients: clientPlans.map(({ client }) => client),
|
|
118
|
+
plannedActions: setupPlan({
|
|
119
|
+
platform: host.platform,
|
|
120
|
+
installHopper,
|
|
121
|
+
installSkill,
|
|
122
|
+
clients: clientPlans,
|
|
123
|
+
providerEnvironment,
|
|
124
|
+
...(linuxDistribution?.packageFamily === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: { linuxPackageFamily: linuxDistribution.packageFamily }),
|
|
127
|
+
}),
|
|
128
|
+
blocker,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
const inspectClients = async (input) => Promise.all(input.clients.map(async (client) => ({
|
|
132
|
+
client,
|
|
133
|
+
inspection: (await input.host.inspectClientConfiguration?.(client, input.providerEnvironment, input.command)) ?? { status: "update" },
|
|
134
|
+
})));
|
|
135
|
+
const filterClientsNeedingConfigure = async (input) => {
|
|
136
|
+
const needs = await Promise.all(input.clients.map((client) => input.host.clientNeedsConfigure(client, input.providerEnvironment, input.command)));
|
|
137
|
+
return input.clients.filter((_, index) => needs[index]);
|
|
138
|
+
};
|
|
139
|
+
const linuxDisclosure = (family) => {
|
|
140
|
+
const disclosure = linuxHopperInstallDisclosure(family, process.getuid?.() === 0);
|
|
141
|
+
return {
|
|
142
|
+
networkOrigins: [disclosure.downloadUrl],
|
|
143
|
+
commands: disclosure.commands,
|
|
144
|
+
integrity: `${String(disclosure.expectedBytes)} bytes · SHA-1 ${disclosure.expectedSha1}`,
|
|
145
|
+
};
|
|
146
|
+
};
|
|
36
147
|
const clientConfigurationDetail = (client, environment) => {
|
|
37
148
|
const entries = Object.entries(environment)
|
|
38
149
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
@@ -2,15 +2,26 @@ import { mkdir, readFile, rm } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import writeFileAtomic from "write-file-atomic";
|
|
4
4
|
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
5
|
+
const skillDestination = (home, name) => join(home, ".agents/skills", name, "SKILL.md");
|
|
6
|
+
const readOptionalText = async (path) => {
|
|
7
|
+
try {
|
|
8
|
+
return await readFile(path, "utf8");
|
|
9
|
+
}
|
|
10
|
+
catch (cause) {
|
|
11
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
12
|
+
return undefined;
|
|
13
|
+
throw cause;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
5
16
|
const canonicalSkillFiles = async (home) => ({
|
|
6
|
-
destination:
|
|
17
|
+
destination: skillDestination(home, PRODUCT_IDENTITY.skillName),
|
|
7
18
|
content: await readFile(new URL(`../../skills/${PRODUCT_IDENTITY.skillName}/SKILL.md`, import.meta.url), "utf8"),
|
|
8
19
|
});
|
|
9
20
|
/** Report whether setup would change the managed REA skill. */
|
|
10
21
|
export const canonicalSkillNeedsInstall = async (home) => {
|
|
11
22
|
try {
|
|
12
23
|
const { content, destination } = await canonicalSkillFiles(home);
|
|
13
|
-
return (await
|
|
24
|
+
return (await readOptionalText(destination)) !== content;
|
|
14
25
|
}
|
|
15
26
|
catch {
|
|
16
27
|
return true;
|
|
@@ -18,43 +29,46 @@ export const canonicalSkillNeedsInstall = async (home) => {
|
|
|
18
29
|
};
|
|
19
30
|
/** Transactionally install or upgrade the versioned canonical REA skill. */
|
|
20
31
|
export const installCanonicalSkill = async (home) => {
|
|
21
|
-
let destination =
|
|
32
|
+
let destination = skillDestination(home, PRODUCT_IDENTITY.skillName);
|
|
22
33
|
let backup = `${destination}.rea.backup`;
|
|
23
34
|
let original;
|
|
35
|
+
let canonicalChanged = false;
|
|
24
36
|
try {
|
|
25
37
|
const canonical = await canonicalSkillFiles(home);
|
|
26
38
|
destination = canonical.destination;
|
|
27
39
|
backup = `${destination}.rea.backup`;
|
|
28
40
|
const { content } = canonical;
|
|
29
|
-
original = await
|
|
41
|
+
original = await readOptionalText(destination);
|
|
30
42
|
if (original === content)
|
|
31
43
|
return "unchanged";
|
|
32
44
|
await mkdir(dirname(destination), { recursive: true });
|
|
33
|
-
if (original !== undefined)
|
|
45
|
+
if (original !== undefined && original !== content)
|
|
34
46
|
await writeFileAtomic(backup, original, {
|
|
35
47
|
encoding: "utf8",
|
|
36
48
|
mode: 0o600,
|
|
37
49
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
canonicalChanged = original !== content;
|
|
51
|
+
if (canonicalChanged)
|
|
52
|
+
await writeFileAtomic(destination, content, {
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
mode: 0o600,
|
|
55
|
+
});
|
|
42
56
|
if ((await readFile(destination, "utf8")) !== content)
|
|
43
57
|
throw new Error("skill readback mismatch");
|
|
44
58
|
return "installed";
|
|
45
59
|
}
|
|
46
60
|
catch {
|
|
47
61
|
try {
|
|
48
|
-
if (original === undefined)
|
|
62
|
+
if (canonicalChanged && original === undefined)
|
|
49
63
|
await rm(destination, { force: true });
|
|
50
|
-
else
|
|
64
|
+
else if (canonicalChanged && original !== undefined)
|
|
51
65
|
await writeFileAtomic(destination, original, {
|
|
52
66
|
encoding: "utf8",
|
|
53
67
|
mode: 0o600,
|
|
54
68
|
});
|
|
55
69
|
}
|
|
56
70
|
catch {
|
|
57
|
-
// The backup remains beside the skill for
|
|
71
|
+
// The canonical backup remains beside the skill for operator recovery.
|
|
58
72
|
}
|
|
59
73
|
return "failed";
|
|
60
74
|
}
|
|
@@ -59,6 +59,7 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [
|
|
|
59
59
|
/** Describe every client location that setup, doctor, or uninstall may inspect. */
|
|
60
60
|
export const supportedClients = (home) => SUPPORTED_CLIENT_DEFINITIONS.map((definition) => ({
|
|
61
61
|
name: definition.name,
|
|
62
|
+
displayName: definition.displayName,
|
|
62
63
|
configPath: join(home, ...definition.configPath),
|
|
63
64
|
markerPath: join(home, ...definition.markerPath),
|
|
64
65
|
format: definition.format,
|
|
@@ -39,7 +39,7 @@ export const runUninstall = async (purgeData, host = systemUninstallHost()) => {
|
|
|
39
39
|
export const systemUninstallHost = (home = homedir(), fileSystem = systemFileSystem) => ({
|
|
40
40
|
clients: () => Promise.resolve(supportedClients(home)),
|
|
41
41
|
removeClient: (client) => removeClient(client, fileSystem),
|
|
42
|
-
removeSkill: () =>
|
|
42
|
+
removeSkill: () => removeManagedSkills(home, fileSystem),
|
|
43
43
|
purgeData: async () => [
|
|
44
44
|
await removeManagedPath(join(home, ".rea/cache"), "cache", fileSystem),
|
|
45
45
|
await removeManagedPath(join(home, ".rea/state"), "state", fileSystem),
|
|
@@ -127,8 +127,10 @@ const isOwnedRegistration = (value) => {
|
|
|
127
127
|
args.length === 1 &&
|
|
128
128
|
args[0] === "mcp") ||
|
|
129
129
|
(command === "npx" &&
|
|
130
|
-
JSON.stringify(args) ===
|
|
131
|
-
JSON.stringify(["-y", PRODUCT_IDENTITY.
|
|
130
|
+
(JSON.stringify(args) ===
|
|
131
|
+
JSON.stringify(["-y", PRODUCT_IDENTITY.packageSpecifier, "mcp"]) ||
|
|
132
|
+
JSON.stringify(args) ===
|
|
133
|
+
JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"]))));
|
|
132
134
|
};
|
|
133
135
|
const removeManagedPath = async (path, name, fileSystem) => {
|
|
134
136
|
try {
|
|
@@ -144,6 +146,7 @@ const removeManagedPath = async (path, name, fileSystem) => {
|
|
|
144
146
|
: item(name, "failed", "This item could not be removed. Check file permissions, then rerun uninstall.");
|
|
145
147
|
}
|
|
146
148
|
};
|
|
149
|
+
const removeManagedSkills = async (home, fileSystem) => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill", fileSystem);
|
|
147
150
|
const item = (name, status, detail) => ({ name, status, detail });
|
|
148
151
|
const objectSchema = z.record(z.string(), z.unknown());
|
|
149
152
|
const optionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { probeProcessCaptureCapability, } from "./ProcessCaptureCapability.js";
|
|
6
|
+
/** Probe named prerequisites without converting missing controls into support. */
|
|
7
|
+
export const probeWindowsCapabilities = async (dependencies = systemDependencies()) => {
|
|
8
|
+
const [symlinkCreation, pty] = await Promise.all([
|
|
9
|
+
dependencies.probeSymlinkCreation(),
|
|
10
|
+
dependencies.probePty(),
|
|
11
|
+
]);
|
|
12
|
+
return {
|
|
13
|
+
platform: dependencies.platform,
|
|
14
|
+
architecture: dependencies.architecture,
|
|
15
|
+
capabilities: {
|
|
16
|
+
symlink_creation: symlinkCreation,
|
|
17
|
+
no_follow_open: dependencies.probeNoFollowOpen(),
|
|
18
|
+
private_acl: dependencies.probePrivateAcl(),
|
|
19
|
+
unix_domain_socket: dependencies.probeUnixDomainSocket(),
|
|
20
|
+
pty: {
|
|
21
|
+
available: pty.available,
|
|
22
|
+
reason: pty.available ? null : pty.reason,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const systemDependencies = () => ({
|
|
28
|
+
platform: process.platform,
|
|
29
|
+
architecture: process.arch,
|
|
30
|
+
probeSymlinkCreation,
|
|
31
|
+
probeNoFollowOpen: () => systemNoFollowOpenCapability(process.platform, constants.O_NOFOLLOW),
|
|
32
|
+
probePrivateAcl: () => ({
|
|
33
|
+
available: false,
|
|
34
|
+
reason: "Windows ACL enforcement is not implemented by this REA build",
|
|
35
|
+
}),
|
|
36
|
+
probeUnixDomainSocket: () => process.platform === "win32"
|
|
37
|
+
? {
|
|
38
|
+
available: false,
|
|
39
|
+
reason: "Node path-based IPC uses named pipes on Windows",
|
|
40
|
+
}
|
|
41
|
+
: { available: true, reason: null },
|
|
42
|
+
probePty: probeProcessCaptureCapability,
|
|
43
|
+
});
|
|
44
|
+
/** Report pathname no-follow support without treating it as Windows authority. */
|
|
45
|
+
export const systemNoFollowOpenCapability = (platform, noFollow) => {
|
|
46
|
+
if (platform === "win32")
|
|
47
|
+
return {
|
|
48
|
+
available: false,
|
|
49
|
+
reason: "Windows reparse-safe handle admission is not implemented",
|
|
50
|
+
};
|
|
51
|
+
return typeof noFollow === "number"
|
|
52
|
+
? { available: true, reason: null }
|
|
53
|
+
: {
|
|
54
|
+
available: false,
|
|
55
|
+
reason: "O_NOFOLLOW is unavailable in this Node runtime",
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const probeSymlinkCreation = async () => {
|
|
59
|
+
const root = await mkdtemp(join(tmpdir(), "rea-symlink-probe-"));
|
|
60
|
+
const target = join(root, "target");
|
|
61
|
+
const link = join(root, "link");
|
|
62
|
+
try {
|
|
63
|
+
await writeFile(target, "probe");
|
|
64
|
+
await symlink(target, link, "file");
|
|
65
|
+
return { available: true, reason: null };
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
return {
|
|
69
|
+
available: false,
|
|
70
|
+
reason: cause instanceof Error &&
|
|
71
|
+
"code" in cause &&
|
|
72
|
+
typeof cause.code === "string"
|
|
73
|
+
? cause.code
|
|
74
|
+
: "symlink creation failed",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
try {
|
|
79
|
+
await rm(root, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Cleanup failure must not replace the machine-readable probe outcome.
|
|
83
|
+
// The probe root lives beneath the OS temporary directory and is unique.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -70,6 +70,13 @@ export class AsarArtifactReader {
|
|
|
70
70
|
return Promise.resolve(Readable.from(extractFile(this.path, entry.adapterKey, false)));
|
|
71
71
|
}
|
|
72
72
|
catch (cause) {
|
|
73
|
+
if (entry.unpacked && isMissingFile(cause))
|
|
74
|
+
return Promise.reject(new ArtifactReaderFailure("unavailable", `ASAR unpacked entry bytes are unavailable: ${entry.path}`, { cause }, {
|
|
75
|
+
logicalPath: entry.path,
|
|
76
|
+
declaredSha256: entry.declaredSha256,
|
|
77
|
+
calculatedSha256: null,
|
|
78
|
+
unpacked: true,
|
|
79
|
+
}));
|
|
73
80
|
return Promise.reject(asarFailure(this.path, `read ${entry.path}`, cause));
|
|
74
81
|
}
|
|
75
82
|
}
|
|
@@ -87,3 +94,7 @@ const abortIfNeeded = (signal) => {
|
|
|
87
94
|
const asarFailure = (path, operation, cause) => cause instanceof ArtifactReaderFailure
|
|
88
95
|
? cause
|
|
89
96
|
: new ArtifactReaderFailure("format", `Malformed or unreadable ASAR during ${operation}: ${path}`, { cause });
|
|
97
|
+
const isMissingFile = (cause) => typeof cause === "object" &&
|
|
98
|
+
cause !== null &&
|
|
99
|
+
"code" in cause &&
|
|
100
|
+
cause.code === "ENOENT";
|