rea-agents 2.1.0 → 2.3.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 +84 -18
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- package/dist/application/BinarySession.js +9 -2
- package/dist/application/BinarySessionStatus.js +39 -1
- package/dist/application/CapabilityInventory.js +15 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/CommandShimReplay.js +6 -3
- package/dist/application/CompositeProvider.js +5 -2
- package/dist/application/DirectAnalysis.js +1 -20
- package/dist/application/DirectAnalysisStatus.js +83 -0
- package/dist/application/Doctor.js +34 -25
- package/dist/application/DoctorDiagnostics.js +1 -4
- package/dist/application/DoctorProjection.js +43 -0
- package/dist/application/DoctorScope.js +102 -0
- package/dist/application/EvidenceReferenceResolver.js +26 -0
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/JavaScriptApplicationEvidence.js +1 -1
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
- package/dist/application/JavaScriptApplicationService.js +3 -2
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
- package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
- package/dist/application/JavaScriptModuleRelationships.js +25 -1
- package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
- package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
- package/dist/application/JavaScriptSemanticTraceService.js +45 -0
- package/dist/application/LoopbackReplay.js +277 -133
- package/dist/application/LoopbackReplayRecorder.js +101 -0
- package/dist/application/PermissionAuthority.js +7 -3
- package/dist/application/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +24 -13
- package/dist/application/ProcessCheckpoints.js +30 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +20 -50
- package/dist/application/ProcessNormalization.js +3 -2
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProcessReactiveCoordinator.js +218 -0
- package/dist/application/ProcessReactiveEffects.js +133 -0
- package/dist/application/ProcessReactiveObservations.js +20 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +14 -13
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +4 -3
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -1
- package/dist/browser/CdpEndpoint.js +37 -4
- package/dist/catalogIdentity.js +16 -7
- package/dist/cli/coreAnalysisCommands.js +34 -2
- package/dist/cli/javascriptApplicationAnalysis.js +19 -0
- package/dist/cli/setupCommands.js +84 -30
- package/dist/cli/utilityCommands.js +10 -2
- package/dist/cliApplicationCommands.js +79 -16
- package/dist/cliCommandNames.js +3 -0
- package/dist/cliElectronCommands.js +23 -33
- package/dist/cliObservationOptions.js +1 -1
- package/dist/cliOutput.js +56 -0
- package/dist/cliProcessCommands.js +32 -1
- package/dist/cliSetup.js +55 -29
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +73 -4
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +8 -11
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +53 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +73 -32
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- package/dist/domain/evidenceCompletionLedger.js +113 -0
- package/dist/domain/hopperStartupFailure.js +9 -0
- package/dist/domain/javascriptApplicationAnalysis.js +44 -2
- package/dist/domain/javascriptExportShapeComparison.js +145 -0
- package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
- package/dist/domain/javascriptExportShapeSelection.js +132 -0
- package/dist/domain/javascriptExportShapeVariants.js +272 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
- package/dist/domain/javascriptSemanticAnalysis.js +17 -36
- package/dist/domain/javascriptSemanticCallResolution.js +129 -0
- package/dist/domain/javascriptSemanticCalls.js +269 -0
- package/dist/domain/javascriptSemanticGraph.js +356 -0
- package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
- package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
- package/dist/domain/javascriptSemanticIr.js +14 -1
- package/dist/domain/javascriptSemanticPrimitives.js +36 -0
- package/dist/domain/javascriptSemanticProjection.js +36 -4
- package/dist/domain/javascriptSemanticProvenance.js +29 -0
- package/dist/domain/javascriptSemanticQuery.js +294 -0
- package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
- package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
- package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
- package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
- package/dist/domain/javascriptSemanticReturns.js +187 -0
- package/dist/domain/javascriptSemanticState.js +32 -0
- package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
- package/dist/domain/javascriptSemanticValues.js +54 -70
- package/dist/domain/processCapture.js +36 -0
- package/dist/domain/processCaptureValidation.js +101 -12
- package/dist/domain/processComparison.js +96 -24
- package/dist/domain/processObservation.js +153 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +109 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +159 -0
- package/dist/domain/processScenario.js +12 -0
- package/dist/domain/processTraceComparison.js +43 -0
- package/dist/domain/processTraceDimensionProjection.js +50 -0
- package/dist/domain/processTraceEvaluation.js +312 -0
- package/dist/domain/processTraceSpecification.js +373 -0
- package/dist/domain/reconstructionVerification.js +5 -4
- package/dist/domain/replayMachine.js +332 -0
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +294 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/evaluation/CodexAgentEval.js +156 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +13 -2
- package/dist/ghidra/GhidraDiagnostics.js +1 -0
- package/dist/ghidra/GhidraLauncher.js +7 -3
- package/dist/ghidra/GhidraProvider.js +8 -1
- package/dist/hopper/BridgeLauncher.js +56 -14
- package/dist/hopper/HopperClient.js +23 -10
- package/dist/hopper/HopperDiagnostics.js +14 -0
- package/dist/hopper/HopperProcessDiagnostic.js +14 -0
- package/dist/hopper/HopperProvider.js +8 -1
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
- package/dist/identity.js +2 -1
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +8 -2
- package/dist/server/javascriptApplicationResult.js +106 -0
- package/dist/server/registerApplicationTools/characterization.js +3 -3
- package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
- package/dist/server/registerApplicationTools/compareVersions.js +12 -6
- package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
- package/dist/server/registerApplicationTools/coverage.js +3 -3
- package/dist/server/registerApplicationTools/helpers.js +37 -25
- package/dist/server/registerApplicationTools/traceFeature.js +9 -5
- package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
- package/dist/server/registerApplicationTools.js +4 -0
- package/dist/server/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +7 -1
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/toolResult.js +10 -4
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -29
- package/scripts/hopper-demo-x11.py +459 -61
- package/scripts/prepack.mjs +55 -0
- package/scripts/prepare.mjs +28 -0
- package/scripts/rea.mjs +26 -5
- package/skills/reverse-engineer-anything/SKILL.md +69 -228
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +35 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
|
@@ -6,6 +6,7 @@ import { silentLogger } from "../logger.js";
|
|
|
6
6
|
import { PendingOperations } from "../process/PendingOperations.js";
|
|
7
7
|
import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
|
|
8
8
|
import { ProviderStartupDeadline } from "../process/ProviderDeadline.js";
|
|
9
|
+
import { ProviderRunLineage } from "../process/ProviderRunLineage.js";
|
|
9
10
|
import { ProviderProcessSupervisor, } from "../process/ProviderProcess.js";
|
|
10
11
|
import { GHIDRA_MAX_LINE_BYTES, GHIDRA_MAX_QUEUED_REQUESTS, GHIDRA_REQUEST_TIMEOUT_MS, GHIDRA_STARTUP_TIMEOUT_MS, } from "./GhidraDefaults.js";
|
|
11
12
|
import { createGhidraDiagnostics } from "./GhidraDiagnostics.js";
|
|
@@ -33,6 +34,7 @@ export class GhidraClient {
|
|
|
33
34
|
#snapshotPath;
|
|
34
35
|
#token;
|
|
35
36
|
#runId;
|
|
37
|
+
#lineage = new ProviderRunLineage();
|
|
36
38
|
#nextId = 1;
|
|
37
39
|
#closing = false;
|
|
38
40
|
#processSnapshot;
|
|
@@ -145,6 +147,10 @@ export class GhidraClient {
|
|
|
145
147
|
diagnostics() {
|
|
146
148
|
return structuredClone(this.#diagnostics());
|
|
147
149
|
}
|
|
150
|
+
/** Latest token-verified launcher lineage for the active run. */
|
|
151
|
+
runtimeLineage() {
|
|
152
|
+
return this.#lineage.snapshot();
|
|
153
|
+
}
|
|
148
154
|
async #start(signal) {
|
|
149
155
|
if (signal.aborted)
|
|
150
156
|
return err(this.#failure("cancelled", "Ghidra startup was cancelled"));
|
|
@@ -188,7 +194,8 @@ export class GhidraClient {
|
|
|
188
194
|
if (deadline.signal.aborted)
|
|
189
195
|
return this.#startupInterrupted(deadline);
|
|
190
196
|
this.#token = randomBytes(32).toString("hex");
|
|
191
|
-
this.#
|
|
197
|
+
this.#lineage.reset();
|
|
198
|
+
this.#runId = this.#options.runId ?? randomUUID();
|
|
192
199
|
const launched = await this.#options.launcher
|
|
193
200
|
.launch({
|
|
194
201
|
runtimeRoot: this.#runtimeRoot.path,
|
|
@@ -221,7 +228,7 @@ export class GhidraClient {
|
|
|
221
228
|
await this.#cleanup();
|
|
222
229
|
return err(failure);
|
|
223
230
|
}
|
|
224
|
-
|
|
231
|
+
const completed = await completeGhidraStartupHandshake({
|
|
225
232
|
deadline,
|
|
226
233
|
request: (method, params, requestOptions) => this.#wire.request(method, params, requestOptions),
|
|
227
234
|
parseSessionInfo: (value) => this.#parseSessionInfo(value),
|
|
@@ -229,6 +236,9 @@ export class GhidraClient {
|
|
|
229
236
|
failure: this.#failure,
|
|
230
237
|
startupTimeoutMs: this.#options.startupTimeoutMs,
|
|
231
238
|
});
|
|
239
|
+
if (completed.ok)
|
|
240
|
+
await this.#lineage.observe(this.#launch);
|
|
241
|
+
return completed;
|
|
232
242
|
}
|
|
233
243
|
async #connect(endpoint, deadline) {
|
|
234
244
|
const connected = await connectGhidraSocket({
|
|
@@ -373,6 +383,7 @@ export class GhidraClient {
|
|
|
373
383
|
transport: this.#options.transport,
|
|
374
384
|
providerVersion: this.#options.providerVersion,
|
|
375
385
|
profileDigest: this.#options.profileDigest,
|
|
386
|
+
...(this.#runId === undefined ? {} : { runId: this.#runId }),
|
|
376
387
|
...(this.#runtimeRoot === undefined
|
|
377
388
|
? {}
|
|
378
389
|
: { runtimeRoot: this.#runtimeRoot.path }),
|
|
@@ -14,6 +14,7 @@ export const createGhidraDiagnostics = (options) => {
|
|
|
14
14
|
transport: options.transport,
|
|
15
15
|
provider_version: options.providerVersion,
|
|
16
16
|
profile_digest: options.profileDigest,
|
|
17
|
+
...(options.runId === undefined ? {} : { run_id: options.runId }),
|
|
17
18
|
...(options.runtimeRoot === undefined
|
|
18
19
|
? {}
|
|
19
20
|
: {
|
|
@@ -91,9 +91,7 @@ export class GhidraHeadlessLauncher {
|
|
|
91
91
|
}
|
|
92
92
|
const spawned = started;
|
|
93
93
|
return ok({
|
|
94
|
-
|
|
95
|
-
ownsProcessLifetime: true,
|
|
96
|
-
cleanup: () => cleanupStartedProcess(spawned, platform),
|
|
94
|
+
...ownedGhidraProcess(spawned, platform),
|
|
97
95
|
projectRoot: paths.projectRoot,
|
|
98
96
|
ghidraLogPath: paths.ghidraLogPath,
|
|
99
97
|
scriptLogPath: paths.scriptLogPath,
|
|
@@ -108,6 +106,12 @@ export class GhidraHeadlessLauncher {
|
|
|
108
106
|
}
|
|
109
107
|
}
|
|
110
108
|
}
|
|
109
|
+
const ownedGhidraProcess = (spawned, platform) => ({
|
|
110
|
+
process: spawned.process,
|
|
111
|
+
ownsProcessLifetime: true,
|
|
112
|
+
ownership: spawned.ownership,
|
|
113
|
+
cleanup: () => cleanupStartedProcess(spawned, platform),
|
|
114
|
+
});
|
|
111
115
|
/** Build a direct POSIX launch or a conservatively quoted Windows batch call. */
|
|
112
116
|
export const ghidraHeadlessCommand = (options) => {
|
|
113
117
|
if (options.platform !== "win32")
|
|
@@ -95,7 +95,7 @@ export class GhidraProvider {
|
|
|
95
95
|
resolveAnalysisProfile(target, options) {
|
|
96
96
|
return resolveGhidraAnalysisProfile(target, GHIDRA_PROVIDER_IDENTITY, this.#inspectInstallation(), options?.signal);
|
|
97
97
|
}
|
|
98
|
-
createClient(target, profile) {
|
|
98
|
+
createClient(target, profile, context) {
|
|
99
99
|
const installation = this.#inspectInstallation();
|
|
100
100
|
const prerequisites = ghidraClientPrerequisites(target, profile, installation);
|
|
101
101
|
if (!prerequisites.ok)
|
|
@@ -118,6 +118,7 @@ export class GhidraProvider {
|
|
|
118
118
|
: "unix-socket",
|
|
119
119
|
providerVersion: prerequisites.value.providerVersion,
|
|
120
120
|
profileDigest: committedProfile.digest,
|
|
121
|
+
...(context === undefined ? {} : { runId: context.runId }),
|
|
121
122
|
logger: this.logger.child({ layer: "ghidra-bridge" }),
|
|
122
123
|
});
|
|
123
124
|
return {
|
|
@@ -160,6 +161,12 @@ export class GhidraProvider {
|
|
|
160
161
|
limitations: [...limitationsFor(operation), ...providerLimitations],
|
|
161
162
|
}));
|
|
162
163
|
},
|
|
164
|
+
runtimeLineageSnapshots: () => {
|
|
165
|
+
const observation = client.runtimeLineage?.() ?? null;
|
|
166
|
+
return observation === null
|
|
167
|
+
? []
|
|
168
|
+
: [{ provider: committedProfile.provider, observation }];
|
|
169
|
+
},
|
|
163
170
|
close: () => client.close(),
|
|
164
171
|
};
|
|
165
172
|
}
|
|
@@ -2,11 +2,12 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { chmod, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, extname, join } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
|
-
import { HopperCancelledError, HopperStartError } from "../domain/errors.js";
|
|
5
|
+
import { HopperCancelledError, HopperProcessError, HopperStartError, } from "../domain/errors.js";
|
|
6
6
|
import { err, ok } from "../domain/result.js";
|
|
7
7
|
import { cleanupOwnedProcessGroup } from "../process/ProcessOwnership.js";
|
|
8
8
|
import { spawnOwnedProviderProcess, } from "../process/ProviderProcess.js";
|
|
9
9
|
import { waitForAbortableDelay } from "../process/ProviderDeadline.js";
|
|
10
|
+
import { selectLinuxPrivateDisplayStrategy, } from "./LinuxPrivateDisplayProbe.js";
|
|
10
11
|
import writeFileAtomic from "write-file-atomic";
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
const HOPPER_BACKGROUND_STARTUP_MS = 5_000;
|
|
@@ -26,8 +27,10 @@ const HOPPER_BACKGROUND_STARTUP_MS = 5_000;
|
|
|
26
27
|
*/
|
|
27
28
|
export class HopperApplicationLauncher {
|
|
28
29
|
options;
|
|
29
|
-
|
|
30
|
+
dependencies;
|
|
31
|
+
constructor(options, dependencies = {}) {
|
|
30
32
|
this.options = options;
|
|
33
|
+
this.dependencies = dependencies;
|
|
31
34
|
}
|
|
32
35
|
async launch(session, options = {}) {
|
|
33
36
|
const bootstrapPath = `${session.directory}/bootstrap.py`;
|
|
@@ -60,10 +63,13 @@ export class HopperApplicationLauncher {
|
|
|
60
63
|
try {
|
|
61
64
|
if (options.signal?.aborted === true)
|
|
62
65
|
return err(new HopperCancelledError());
|
|
66
|
+
const display = await this.#privateDisplay(options.signal);
|
|
67
|
+
if (!display.ok)
|
|
68
|
+
return err(display.error);
|
|
63
69
|
const prepared = await prepareHopperApplication(this.options.launcherPath, options.signal);
|
|
64
70
|
if (!prepared)
|
|
65
71
|
return err(new HopperCancelledError());
|
|
66
|
-
const linuxDemo = linuxDemoLaunch(this.options, session, args);
|
|
72
|
+
const linuxDemo = linuxDemoLaunch(this.options, session, args, display.strategy);
|
|
67
73
|
const ownershipCommand = linuxDemo?.ownershipCommand ?? this.options.launcherPath;
|
|
68
74
|
const started = await spawnOwnedProviderProcess({
|
|
69
75
|
command: linuxDemo?.command ?? this.options.launcherPath,
|
|
@@ -92,6 +98,7 @@ export class HopperApplicationLauncher {
|
|
|
92
98
|
return ok({
|
|
93
99
|
process: child,
|
|
94
100
|
ownsProcessLifetime: true,
|
|
101
|
+
ownership: started.ownership,
|
|
95
102
|
shutdownByCleanup: ownsProcessLifetime,
|
|
96
103
|
cleanup: () => cleanupOwnedProcessGroup(started.ownership),
|
|
97
104
|
});
|
|
@@ -100,23 +107,58 @@ export class HopperApplicationLauncher {
|
|
|
100
107
|
return err(hopperLaunchFailure(cause, options.signal));
|
|
101
108
|
}
|
|
102
109
|
}
|
|
110
|
+
async #privateDisplay(signal) {
|
|
111
|
+
if (!usesLinuxDemo(this.options))
|
|
112
|
+
return { ok: true, strategy: undefined };
|
|
113
|
+
const selection = await (this.dependencies.selectPrivateDisplay ??
|
|
114
|
+
selectLinuxPrivateDisplayStrategy)({
|
|
115
|
+
helperPath: this.options.demoHelperPath,
|
|
116
|
+
...(signal === undefined ? {} : { signal }),
|
|
117
|
+
});
|
|
118
|
+
if (signal?.aborted === true)
|
|
119
|
+
return { ok: false, error: new HopperCancelledError() };
|
|
120
|
+
return selection.ok
|
|
121
|
+
? { ok: true, strategy: selection.strategy }
|
|
122
|
+
: {
|
|
123
|
+
ok: false,
|
|
124
|
+
error: new HopperProcessError(selection.exitCode, selection.diagnostic),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
103
127
|
}
|
|
104
|
-
const linuxDemoLaunch = (options, session, hopperArgs) => {
|
|
128
|
+
export const linuxDemoLaunch = (options, session, hopperArgs, strategy) => {
|
|
105
129
|
if (!usesLinuxDemo(options) || options.demoHelperPath === undefined)
|
|
106
130
|
return undefined;
|
|
131
|
+
const helperArguments = [
|
|
132
|
+
options.demoHelperPath,
|
|
133
|
+
"--strategy",
|
|
134
|
+
strategy ?? "direct",
|
|
135
|
+
...(strategy === "user-mount-namespace" ? ["--mount-private-x11"] : []),
|
|
136
|
+
"--hopper",
|
|
137
|
+
options.launcherPath,
|
|
138
|
+
"--socket",
|
|
139
|
+
session.socketPath,
|
|
140
|
+
"--",
|
|
141
|
+
options.launcherPath,
|
|
142
|
+
...hopperArgs,
|
|
143
|
+
];
|
|
144
|
+
if (strategy === "user-mount-namespace")
|
|
145
|
+
return {
|
|
146
|
+
command: "/usr/bin/unshare",
|
|
147
|
+
ownershipCommand: "/usr/bin/python3",
|
|
148
|
+
args: [
|
|
149
|
+
"--user",
|
|
150
|
+
"--map-root-user",
|
|
151
|
+
"--mount",
|
|
152
|
+
"--propagation",
|
|
153
|
+
"private",
|
|
154
|
+
"/usr/bin/python3",
|
|
155
|
+
...helperArguments,
|
|
156
|
+
],
|
|
157
|
+
};
|
|
107
158
|
return {
|
|
108
159
|
command: "/usr/bin/python3",
|
|
109
160
|
ownershipCommand: "/usr/bin/python3",
|
|
110
|
-
args:
|
|
111
|
-
options.demoHelperPath,
|
|
112
|
-
"--hopper",
|
|
113
|
-
options.launcherPath,
|
|
114
|
-
"--socket",
|
|
115
|
-
session.socketPath,
|
|
116
|
-
"--",
|
|
117
|
-
options.launcherPath,
|
|
118
|
-
...hopperArgs,
|
|
119
|
-
],
|
|
161
|
+
args: helperArguments,
|
|
120
162
|
};
|
|
121
163
|
};
|
|
122
164
|
const hopperLaunchFailure = (cause, signal) => signal?.aborted === true
|
|
@@ -8,13 +8,17 @@ import { silentLogger } from "../logger.js";
|
|
|
8
8
|
import { PendingOperations } from "../process/PendingOperations.js";
|
|
9
9
|
import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
|
|
10
10
|
import { ProviderStartupDeadline } from "../process/ProviderDeadline.js";
|
|
11
|
+
import { ProviderRunLineage } from "../process/ProviderRunLineage.js";
|
|
11
12
|
import { ProviderProcessSupervisor, } from "../process/ProviderProcess.js";
|
|
13
|
+
import { createOwnedHopperShutdownDiagnostic, providerCleanupFailure, } from "./HopperDiagnostics.js";
|
|
12
14
|
import { isHopperCleanupRequired, isHopperShutdownAcknowledgement, parseHopperServerInfo, } from "./HopperSessionValues.js";
|
|
13
15
|
import { connectHopperSocketOnce } from "./HopperSocketConnection.js";
|
|
16
|
+
import { hopperLauncherFailureDiagnostic } from "./HopperProcessDiagnostic.js";
|
|
14
17
|
import { parseResponseLine, responseResult } from "./protocol.js";
|
|
15
18
|
const MAX_LINE_BYTES = 10 * 1024 * 1024;
|
|
16
19
|
const SHUTDOWN_TIMEOUT_MS = 30_000;
|
|
17
20
|
const SESSION_ROOT = process.platform === "darwin" ? "/tmp" : tmpdir();
|
|
21
|
+
/** Safe launcher telemetry; stderr content is intentionally never exposed. */
|
|
18
22
|
/**
|
|
19
23
|
* Owns one authenticated NDJSON-over-Unix-socket bridge session.
|
|
20
24
|
*
|
|
@@ -32,10 +36,12 @@ export class HopperClient {
|
|
|
32
36
|
#process;
|
|
33
37
|
#runtimeRoot;
|
|
34
38
|
#token;
|
|
39
|
+
#lineage = new ProviderRunLineage();
|
|
35
40
|
#nextId = 1;
|
|
36
41
|
#buffer = "";
|
|
37
42
|
#closing = false;
|
|
38
43
|
#launcherExitCode;
|
|
44
|
+
#launcherFailureDiagnostic;
|
|
39
45
|
#startupController;
|
|
40
46
|
#startPromise;
|
|
41
47
|
#closePromise;
|
|
@@ -57,6 +63,10 @@ export class HopperClient {
|
|
|
57
63
|
startupTimeoutMs: options.startupTimeoutMs ?? 120_000,
|
|
58
64
|
};
|
|
59
65
|
}
|
|
66
|
+
/** Latest token-verified launcher lineage for the active run. */
|
|
67
|
+
runtimeLineage() {
|
|
68
|
+
return this.#lineage.snapshot();
|
|
69
|
+
}
|
|
60
70
|
/** Launch the bridge once and complete its authenticated health handshake. */
|
|
61
71
|
start(signal) {
|
|
62
72
|
if (this.#closePromise !== undefined) {
|
|
@@ -124,8 +134,10 @@ export class HopperClient {
|
|
|
124
134
|
}
|
|
125
135
|
const socketPath = join(this.#runtimeRoot.path, "bridge.sock");
|
|
126
136
|
this.#token = randomBytes(32).toString("hex");
|
|
137
|
+
this.#lineage.reset();
|
|
127
138
|
this.#launcherExitCode = undefined;
|
|
128
|
-
|
|
139
|
+
this.#launcherFailureDiagnostic = undefined;
|
|
140
|
+
const runId = this.#options.runId ?? randomUUID();
|
|
129
141
|
const launched = await this.#options.launcher
|
|
130
142
|
.launch({
|
|
131
143
|
directory: this.#runtimeRoot.path,
|
|
@@ -163,7 +175,9 @@ export class HopperClient {
|
|
|
163
175
|
: health;
|
|
164
176
|
}
|
|
165
177
|
const parsed = parseHopperServerInfo(health.value, runId);
|
|
166
|
-
if (
|
|
178
|
+
if (parsed.ok)
|
|
179
|
+
await this.#lineage.observe(this.#launch);
|
|
180
|
+
else
|
|
167
181
|
await this.#cleanup();
|
|
168
182
|
return parsed;
|
|
169
183
|
}
|
|
@@ -228,6 +242,9 @@ export class HopperClient {
|
|
|
228
242
|
this.#socket = undefined;
|
|
229
243
|
if (this.#process !== undefined) {
|
|
230
244
|
const stopped = await this.#process.stop(cleanupResult === undefined ? {} : { cleanupResult });
|
|
245
|
+
const diagnostic = createOwnedHopperShutdownDiagnostic(this.#process.launch, stopped, cleanupResult);
|
|
246
|
+
this.#options.onDiagnostic?.(diagnostic);
|
|
247
|
+
this.#logger.info(diagnostic, "Owned Hopper launcher shutdown completed");
|
|
231
248
|
if (stopped.status === "incomplete")
|
|
232
249
|
this.#logger.warn({ reason: stopped.reason }, "Owned launcher cleanup failed closed");
|
|
233
250
|
}
|
|
@@ -239,6 +256,7 @@ export class HopperClient {
|
|
|
239
256
|
this.#token = undefined;
|
|
240
257
|
this.#buffer = "";
|
|
241
258
|
this.#launcherExitCode = undefined;
|
|
259
|
+
this.#launcherFailureDiagnostic = undefined;
|
|
242
260
|
}
|
|
243
261
|
finally {
|
|
244
262
|
this.#closing = false;
|
|
@@ -257,7 +275,7 @@ export class HopperClient {
|
|
|
257
275
|
return err(new HopperProcessError(null));
|
|
258
276
|
if (this.#launcherExitCode !== undefined &&
|
|
259
277
|
hopperStartupFailure(this.#launcherExitCode) !== undefined)
|
|
260
|
-
return err(new HopperProcessError(this.#launcherExitCode));
|
|
278
|
+
return err(new HopperProcessError(this.#launcherExitCode, this.#launcherFailureDiagnostic));
|
|
261
279
|
try {
|
|
262
280
|
await access(socketPath);
|
|
263
281
|
}
|
|
@@ -338,12 +356,13 @@ export class HopperClient {
|
|
|
338
356
|
if (event.type !== "exit")
|
|
339
357
|
return;
|
|
340
358
|
this.#launcherExitCode = event.code;
|
|
359
|
+
this.#launcherFailureDiagnostic = hopperLauncherFailureDiagnostic(event);
|
|
341
360
|
this.#options.onDiagnostic?.({
|
|
342
361
|
type: "launcher-exit",
|
|
343
362
|
code: event.code,
|
|
344
363
|
});
|
|
345
364
|
if (launch.ownsProcessLifetime && !this.#closing)
|
|
346
|
-
this.#failAll(new HopperProcessError(event.code));
|
|
365
|
+
this.#failAll(new HopperProcessError(event.code, this.#launcherFailureDiagnostic));
|
|
347
366
|
}
|
|
348
367
|
#onData(chunk) {
|
|
349
368
|
this.#buffer += chunk;
|
|
@@ -389,9 +408,3 @@ const isAborted = (signal) => signal?.aborted === true;
|
|
|
389
408
|
const startupInterruption = (deadline) => deadline.cancelled
|
|
390
409
|
? new HopperCancelledError()
|
|
391
410
|
: new HopperTimeoutError(deadline.timeoutMs);
|
|
392
|
-
const providerCleanupFailure = (cause) => ({
|
|
393
|
-
cleaned: false,
|
|
394
|
-
reason: cause instanceof Error
|
|
395
|
-
? `owned provider cleanup failed: ${cause.message}`
|
|
396
|
-
: "owned provider cleanup failed",
|
|
397
|
-
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Retain actionable ownership coordinates without exposing the run token. */
|
|
2
|
+
export const createOwnedHopperShutdownDiagnostic = (launch, stopped, cleanup) => ({
|
|
3
|
+
type: "owned-shutdown",
|
|
4
|
+
status: stopped.status,
|
|
5
|
+
launcher_pid: launch.ownership?.leaderPid ?? launch.process.pid ?? null,
|
|
6
|
+
process_group_id: launch.ownership?.processGroupId ?? null,
|
|
7
|
+
cleanup_signaled: cleanup?.cleaned === true ? cleanup.signaled : null,
|
|
8
|
+
reason: stopped.status === "incomplete" ? stopped.reason : null,
|
|
9
|
+
});
|
|
10
|
+
/** Convert an unexpected cleanup rejection without exposing a stack or token. */
|
|
11
|
+
export const providerCleanupFailure = (_cause) => ({
|
|
12
|
+
cleaned: false,
|
|
13
|
+
reason: "owned provider cleanup failed",
|
|
14
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { hopperStartupFailure, } from "../domain/errors.js";
|
|
2
|
+
import { parseLinuxPrivateDisplayDiagnostic } from "./LinuxPrivateDisplayDiagnostic.js";
|
|
3
|
+
/** Extract only a matching, versioned Linux adapter failure record. */
|
|
4
|
+
export const hopperLauncherFailureDiagnostic = (event) => {
|
|
5
|
+
const parsed = parseLinuxPrivateDisplayDiagnostic(event.snapshot.stderr.text, event.snapshot.stderr.truncated);
|
|
6
|
+
if (!parsed.ok)
|
|
7
|
+
return undefined;
|
|
8
|
+
const expected = hopperStartupFailure(event.code)?.code;
|
|
9
|
+
return parsed.value.operation === "launch" &&
|
|
10
|
+
parsed.value.status === "error" &&
|
|
11
|
+
parsed.value.failure_code === expected
|
|
12
|
+
? parsed.value
|
|
13
|
+
: undefined;
|
|
14
|
+
};
|
|
@@ -147,7 +147,7 @@ export class HopperProvider {
|
|
|
147
147
|
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
|
-
createClient(target, profile) {
|
|
150
|
+
createClient(target, profile, context) {
|
|
151
151
|
if (target.kind !== "executable" && target.kind !== "database")
|
|
152
152
|
return {
|
|
153
153
|
execute: (operation) => Promise.resolve(err(new AnalysisCapabilityUnavailableError(IDENTITY.id, operation, `Hopper cannot open ${target.kind} targets directly. Inventory or extract the artifact first.`))),
|
|
@@ -176,6 +176,7 @@ export class HopperProvider {
|
|
|
176
176
|
}
|
|
177
177
|
: { launchMode: "native" }),
|
|
178
178
|
}),
|
|
179
|
+
...(context === undefined ? {} : { runId: context.runId }),
|
|
179
180
|
logger: this.logger.child({ layer: "bridge" }),
|
|
180
181
|
});
|
|
181
182
|
return {
|
|
@@ -193,6 +194,12 @@ export class HopperProvider {
|
|
|
193
194
|
}
|
|
194
195
|
: result;
|
|
195
196
|
},
|
|
197
|
+
runtimeLineageSnapshots: () => {
|
|
198
|
+
const observation = client.runtimeLineage();
|
|
199
|
+
return observation === null
|
|
200
|
+
? []
|
|
201
|
+
: [{ provider: executionProvider, observation }];
|
|
202
|
+
},
|
|
196
203
|
close: () => client.close(),
|
|
197
204
|
};
|
|
198
205
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isHopperStartupFailureCode, } from "../domain/hopperStartupFailure.js";
|
|
3
|
+
export const LINUX_PRIVATE_DISPLAY_DIAGNOSTIC_PREFIX = "REA_X11_DIAGNOSTIC_V1=";
|
|
4
|
+
const diagnosticSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
schema_version: z.literal(1),
|
|
7
|
+
component: z.literal("hopper_private_display"),
|
|
8
|
+
operation: z.enum(["probe", "launch"]),
|
|
9
|
+
status: z.enum(["ready", "error"]),
|
|
10
|
+
failure_code: z.string().max(64).nullable(),
|
|
11
|
+
reason: z
|
|
12
|
+
.string()
|
|
13
|
+
.regex(/^[a-z0-9_]+$/u)
|
|
14
|
+
.max(64),
|
|
15
|
+
socket_directory: z.literal("/tmp/.X11-unix"),
|
|
16
|
+
socket_directory_mode: z
|
|
17
|
+
.string()
|
|
18
|
+
.regex(/^[0-7]{4}$/u)
|
|
19
|
+
.nullable(),
|
|
20
|
+
mount_read_only: z.boolean().nullable(),
|
|
21
|
+
effective_socket_directory_mode: z
|
|
22
|
+
.string()
|
|
23
|
+
.regex(/^[0-7]{4}$/u)
|
|
24
|
+
.nullable(),
|
|
25
|
+
effective_mount_read_only: z.boolean().nullable(),
|
|
26
|
+
wsl: z.boolean(),
|
|
27
|
+
strategy: z.enum(["direct", "user-mount-namespace", "unavailable"]),
|
|
28
|
+
fallback_reason: z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^[a-z0-9_]+$/u)
|
|
31
|
+
.max(64)
|
|
32
|
+
.nullable(),
|
|
33
|
+
xvfb_stderr_bytes: z.number().int().nonnegative().safe(),
|
|
34
|
+
xvfb_stderr_truncated: z.boolean(),
|
|
35
|
+
})
|
|
36
|
+
.strict();
|
|
37
|
+
/** Parse exactly one bounded, versioned helper diagnostic from stderr. */
|
|
38
|
+
export const parseLinuxPrivateDisplayDiagnostic = (stderr, truncated) => {
|
|
39
|
+
if (truncated)
|
|
40
|
+
return { ok: false, reason: "diagnostic_truncated" };
|
|
41
|
+
const records = stderr
|
|
42
|
+
.split("\n")
|
|
43
|
+
.filter((line) => line.startsWith(LINUX_PRIVATE_DISPLAY_DIAGNOSTIC_PREFIX));
|
|
44
|
+
if (records.length === 0)
|
|
45
|
+
return { ok: false, reason: "diagnostic_missing" };
|
|
46
|
+
if (records.length !== 1)
|
|
47
|
+
return { ok: false, reason: "diagnostic_multiple" };
|
|
48
|
+
const record = records[0];
|
|
49
|
+
if (record === undefined)
|
|
50
|
+
return { ok: false, reason: "diagnostic_missing" };
|
|
51
|
+
try {
|
|
52
|
+
const parsed = diagnosticSchema.safeParse(JSON.parse(record.slice(LINUX_PRIVATE_DISPLAY_DIAGNOSTIC_PREFIX.length)));
|
|
53
|
+
if (!parsed.success)
|
|
54
|
+
return { ok: false, reason: "diagnostic_malformed" };
|
|
55
|
+
const code = parsed.data.failure_code;
|
|
56
|
+
if (code !== null && !isHopperStartupFailureCode(code))
|
|
57
|
+
return { ok: false, reason: "diagnostic_malformed" };
|
|
58
|
+
if ((parsed.data.status === "ready" && code !== null) ||
|
|
59
|
+
(parsed.data.status === "error" && code === null))
|
|
60
|
+
return { ok: false, reason: "diagnostic_malformed" };
|
|
61
|
+
return {
|
|
62
|
+
ok: true,
|
|
63
|
+
value: {
|
|
64
|
+
...parsed.data,
|
|
65
|
+
failure_code: code,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { ok: false, reason: "diagnostic_malformed" };
|
|
71
|
+
}
|
|
72
|
+
};
|