rea-agents 2.1.0 → 2.2.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 +29 -10
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/Setup.js +3 -3
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +3 -2
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -1
- package/dist/cli/setupCommands.js +8 -3
- package/dist/cliSetup.js +29 -12
- package/dist/contracts/electronToolContracts.js +51 -3
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +3 -10
- package/dist/contracts/toolOutputSchemaGroups.js +51 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +33 -32
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/processComparison.js +19 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/replayMachine.js +316 -0
- package/dist/domain/replayMachineRuntime.js +215 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/evaluation/CodexAgentEval.js +132 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/identity.js +2 -1
- package/dist/server/createServer.js +5 -2
- package/dist/server/javascriptApplicationResult.js +85 -0
- package/dist/server/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +2 -0
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/toolResult.js +10 -4
- package/package.json +2 -1
- package/skills/reverse-engineer-anything/SKILL.md +68 -227
- 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 +25 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
package/dist/cliSetup.js
CHANGED
|
@@ -9,7 +9,7 @@ const promptStreams = {
|
|
|
9
9
|
output: process.stderr,
|
|
10
10
|
withGuide: true,
|
|
11
11
|
};
|
|
12
|
-
const
|
|
12
|
+
const agentIntegrationCapabilityId = "agent_integration";
|
|
13
13
|
/** Run the inline setup picker, exact preflight, and default-No consent. */
|
|
14
14
|
export const confirmInteractiveSetup = async (actions, accessible) => {
|
|
15
15
|
intro("REA setup", promptStreams);
|
|
@@ -96,29 +96,46 @@ const selectSetupActions = async (actions, accessible) => {
|
|
|
96
96
|
if (selectedCapabilities === undefined)
|
|
97
97
|
return undefined;
|
|
98
98
|
const selectedCapabilityIds = new Set(selectedCapabilities);
|
|
99
|
+
const bundledSkillAction = componentActions.find(({ kind }) => kind === "install_skill");
|
|
99
100
|
const selectedActionIds = componentActions
|
|
100
|
-
.filter(({ id }) => selectedCapabilityIds.has(id))
|
|
101
|
+
.filter(({ id }) => id !== bundledSkillAction?.id && selectedCapabilityIds.has(id))
|
|
101
102
|
.map(({ id }) => id);
|
|
102
|
-
if (!selectedCapabilityIds.has(
|
|
103
|
+
if (!selectedCapabilityIds.has(agentIntegrationCapabilityId))
|
|
103
104
|
return selectedActionIds;
|
|
105
|
+
if (clientActions.length === 0)
|
|
106
|
+
return [
|
|
107
|
+
...(bundledSkillAction === undefined ? [] : [bundledSkillAction.id]),
|
|
108
|
+
...selectedActionIds,
|
|
109
|
+
];
|
|
104
110
|
const selectedClients = accessible
|
|
105
111
|
? await selectClientsAccessibly(clientActions)
|
|
106
112
|
: await selectClients(clientActions);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
if (selectedClients === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (selectedClients.length === 0)
|
|
116
|
+
return selectedActionIds;
|
|
117
|
+
return [
|
|
118
|
+
...selectedClients,
|
|
119
|
+
...(bundledSkillAction === undefined ? [] : [bundledSkillAction.id]),
|
|
120
|
+
...selectedActionIds,
|
|
121
|
+
];
|
|
110
122
|
};
|
|
111
123
|
const setupCapabilities = (clientActions, componentActions) => [
|
|
112
|
-
...(clientActions.length === 0
|
|
124
|
+
...(clientActions.length === 0 &&
|
|
125
|
+
!componentActions.some(({ kind }) => kind === "install_skill")
|
|
113
126
|
? []
|
|
114
127
|
: [
|
|
115
128
|
{
|
|
116
|
-
id:
|
|
117
|
-
label: "
|
|
118
|
-
hint:
|
|
129
|
+
id: agentIntegrationCapabilityId,
|
|
130
|
+
label: "Agent integration (MCP + guided workflow)",
|
|
131
|
+
hint: clientActions.length === 0
|
|
132
|
+
? "MCP registrations are aligned; installs the matching bundled skill"
|
|
133
|
+
: `${String(clientActions.length)} detected ${clientActions.length === 1 ? "agent" : "agents"}; installs the bundled skill when needed`,
|
|
119
134
|
},
|
|
120
135
|
]),
|
|
121
|
-
...componentActions
|
|
136
|
+
...componentActions
|
|
137
|
+
.filter(({ kind }) => kind !== "install_skill")
|
|
138
|
+
.map((action) => ({
|
|
122
139
|
id: action.id,
|
|
123
140
|
label: `${action.label} (${actionModality(action)})`,
|
|
124
141
|
hint: `${action.operation} · ${action.target}`,
|
|
@@ -223,7 +240,7 @@ const renderReadyCapabilities = (result, readyClients) => {
|
|
|
223
240
|
writeLine("│ CLI: rea analyze /path/to/app");
|
|
224
241
|
const firstClient = readyClients[0];
|
|
225
242
|
if (firstClient !== undefined)
|
|
226
|
-
writeLine(`│ Try in ${firstClient}: "
|
|
243
|
+
writeLine(`│ Try in ${firstClient}: "Explain how a feature works in /path/to/app and show the evidence."`);
|
|
227
244
|
};
|
|
228
245
|
const readyAgentClients = (result, changedClients) => {
|
|
229
246
|
const alignedClients = (result.doctor.identity?.registrations ?? [])
|
|
@@ -1,12 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { toolContractMetadata } from "./toolEffects.js";
|
|
2
3
|
import { evidenceResultOf } from "./toolOutputSchemas.js";
|
|
3
4
|
import { electronPageInspectionSchema, electronTargetListSchema, inspectElectronPageInputSchema, listElectronTargetsInputSchema, } from "../domain/electronObservation.js";
|
|
4
5
|
import { analyzeJavaScriptApplicationInputSchema, javascriptApplicationAnalysisResultSchema, } from "../domain/javascriptApplicationAnalysis.js";
|
|
6
|
+
import { JAVASCRIPT_APPLICATION_NODE_KINDS } from "../domain/javascriptApplicationGraphSchemas.js";
|
|
7
|
+
import { applicationCoverageSchema } from "../domain/javascriptApplicationEvidenceSchemas.js";
|
|
5
8
|
import { javascriptRuntimeReconciliationResultSchema, reconcileJavaScriptRuntimeInputSchema, } from "../domain/javascriptRuntimeReconciliationSchemas.js";
|
|
6
9
|
import { JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE } from "./javascriptRuntimeReconciliationExample.js";
|
|
7
10
|
const listOutputSchema = evidenceResultOf(electronTargetListSchema);
|
|
8
11
|
const inspectionOutputSchema = evidenceResultOf(electronPageInspectionSchema);
|
|
9
|
-
|
|
12
|
+
/** MCP-only response selection; analysis identity does not depend on projection. */
|
|
13
|
+
export const analyzeJavaScriptApplicationToolInputSchema = analyzeJavaScriptApplicationInputSchema.extend({
|
|
14
|
+
detail: z
|
|
15
|
+
.enum(["summary", "full"])
|
|
16
|
+
.default("summary")
|
|
17
|
+
.describe("summary returns bounded findings and paged graph resource URIs; full returns the complete graph only when explicitly required"),
|
|
18
|
+
});
|
|
19
|
+
/** Bounded default projection for agent-facing JavaScript application analysis. */
|
|
20
|
+
export const javascriptApplicationAnalysisSummarySchema = javascriptApplicationAnalysisResultSchema.omit({ graph: true }).extend({
|
|
21
|
+
unknowns: z.array(z.string().min(1).max(4_096)).max(1_000),
|
|
22
|
+
graph: z.strictObject({
|
|
23
|
+
graph_id: z.string().regex(/^jag_[a-f0-9]{64}$/u),
|
|
24
|
+
node_count: z.number().int().min(0),
|
|
25
|
+
edge_count: z.number().int().min(0),
|
|
26
|
+
roots: z.strictObject({
|
|
27
|
+
items: z.array(z.string().regex(/^jag_node_[a-f0-9]{64}$/u)),
|
|
28
|
+
total: z.number().int().min(0),
|
|
29
|
+
truncated: z.boolean(),
|
|
30
|
+
}),
|
|
31
|
+
node_kinds: z.array(z.strictObject({
|
|
32
|
+
kind: z.enum(JAVASCRIPT_APPLICATION_NODE_KINDS),
|
|
33
|
+
count: z.number().int().min(1),
|
|
34
|
+
})),
|
|
35
|
+
top_findings: z.array(z.strictObject({
|
|
36
|
+
node_id: z.string().regex(/^jag_node_[a-f0-9]{64}$/u),
|
|
37
|
+
kind: z.enum(JAVASCRIPT_APPLICATION_NODE_KINDS),
|
|
38
|
+
label: z.string().nullable(),
|
|
39
|
+
authority: z.string(),
|
|
40
|
+
state: z.string(),
|
|
41
|
+
confidence: z.string(),
|
|
42
|
+
location: z.json(),
|
|
43
|
+
})),
|
|
44
|
+
coverage: applicationCoverageSchema,
|
|
45
|
+
limitations: z.array(z.string()),
|
|
46
|
+
pages: z.strictObject({
|
|
47
|
+
nodes: z.string().startsWith("rea://evidence/"),
|
|
48
|
+
edges: z.string().startsWith("rea://evidence/"),
|
|
49
|
+
page_limit: z.number().int().min(1).max(500),
|
|
50
|
+
}),
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
const applicationOutputSchema = evidenceResultOf(z.union([
|
|
54
|
+
javascriptApplicationAnalysisSummarySchema,
|
|
55
|
+
javascriptApplicationAnalysisResultSchema,
|
|
56
|
+
]));
|
|
10
57
|
const reconciliationOutputSchema = evidenceResultOf(javascriptRuntimeReconciliationResultSchema);
|
|
11
58
|
const endpoint = "http://127.0.0.1:9223";
|
|
12
59
|
const root = "/Applications/Example.app/Contents/Resources";
|
|
@@ -66,9 +113,9 @@ export const ELECTRON_TOOL_CONTRACTS = [
|
|
|
66
113
|
{
|
|
67
114
|
name: "analyze_javascript_application",
|
|
68
115
|
...toolContractMetadata("analyze_javascript_application"),
|
|
69
|
-
description: "Statically reconstruct one approved local ASAR or extracted JavaScript application
|
|
116
|
+
description: "Statically reconstruct one approved local ASAR or extracted JavaScript application without executing it. The default summary returns architecture/security counts, top evidence-backed findings, unknowns, and paged graph resource URIs; request detail=full only when the complete graph must be returned immediately. Identical inputs are deterministic, so do not repeat the call or reread full Evidence without a specific missing detail.",
|
|
70
117
|
kind: "electron-provider",
|
|
71
|
-
inputSchema:
|
|
118
|
+
inputSchema: analyzeJavaScriptApplicationToolInputSchema,
|
|
72
119
|
outputSchema: applicationOutputSchema,
|
|
73
120
|
examples: [
|
|
74
121
|
{
|
|
@@ -78,6 +125,7 @@ export const ELECTRON_TOOL_CONTRACTS = [
|
|
|
78
125
|
format: "auto",
|
|
79
126
|
approved: true,
|
|
80
127
|
source_map_read_approved: false,
|
|
128
|
+
detail: "summary",
|
|
81
129
|
},
|
|
82
130
|
},
|
|
83
131
|
],
|
|
@@ -3,6 +3,11 @@ import { managedOutputSchemas } from "./toolOutputSchemas.js";
|
|
|
3
3
|
import { toolContractMetadata } from "./toolEffects.js";
|
|
4
4
|
/** Exact caller boundary for execution-free PE/CLI triage and identity. */
|
|
5
5
|
export const managedArtifactInputSchema = z.object({
|
|
6
|
+
path: z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Managed PE/CLI path to open before inspection; omit only when a managed target is already active"),
|
|
6
11
|
reference_offset: z.number().int().min(0).default(0),
|
|
7
12
|
reference_limit: z.number().int().min(1).max(500).default(100),
|
|
8
13
|
resource_offset: z.number().int().min(0).default(0),
|
|
@@ -111,14 +116,14 @@ export const MANAGED_TOOL_CONTRACTS = [
|
|
|
111
116
|
{
|
|
112
117
|
name: "inspect_managed_artifact",
|
|
113
118
|
...toolContractMetadata("inspect_managed_artifact"),
|
|
114
|
-
description: "
|
|
119
|
+
description: "Open and classify an explicit managed PE/CLI path, or inspect the active managed target, then inventory exact assembly/module identity, target framework evidence, references, resources, and custom attributes without loading or executing target code. Returns bounded pages and explicit partial or malformed coverage.",
|
|
115
120
|
kind: "managed-provider",
|
|
116
121
|
inputSchema: managedArtifactInputSchema,
|
|
117
122
|
outputSchema,
|
|
118
123
|
examples: [
|
|
119
124
|
{
|
|
120
125
|
title: "Example inspect managed artifact request",
|
|
121
|
-
input: {},
|
|
126
|
+
input: { path: "/tmp/Example.dll" },
|
|
122
127
|
},
|
|
123
128
|
],
|
|
124
129
|
},
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { TOOL_KINDS } from "./toolContractTypes.js";
|
|
3
|
+
/** Caller observations and projection controls for binary_session. */
|
|
4
|
+
export const binarySessionInputSchema = z.object({
|
|
5
|
+
detail: z
|
|
6
|
+
.enum(["summary", "capabilities", "full"])
|
|
7
|
+
.default("summary")
|
|
8
|
+
.describe("summary returns routing state; capabilities returns one filtered tool-availability page; full returns complete diagnostics only when required"),
|
|
9
|
+
capability_family: z
|
|
10
|
+
.enum(TOOL_KINDS)
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Optional tool family for detail=capabilities"),
|
|
13
|
+
cursor: z
|
|
14
|
+
.number()
|
|
15
|
+
.int()
|
|
16
|
+
.min(0)
|
|
17
|
+
.default(0)
|
|
18
|
+
.describe("Zero-based capability cursor for detail=capabilities"),
|
|
19
|
+
limit: z.number().int().min(1).max(100).default(25),
|
|
20
|
+
expected_package_version: z.string().min(1).optional(),
|
|
21
|
+
expected_catalog_digest: z
|
|
22
|
+
.string()
|
|
23
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
24
|
+
.optional(),
|
|
25
|
+
expected_server_path: z.string().min(1).optional(),
|
|
26
|
+
});
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/** Canonical adapter families responsible for public MCP tools. */
|
|
2
|
+
export const TOOL_KINDS = [
|
|
3
|
+
"official-proxy",
|
|
4
|
+
"enhanced",
|
|
5
|
+
"native-provider",
|
|
6
|
+
"artifact-provider",
|
|
7
|
+
"managed-provider",
|
|
8
|
+
"browser-provider",
|
|
9
|
+
"electron-provider",
|
|
10
|
+
"application",
|
|
11
|
+
"session",
|
|
12
|
+
];
|
|
@@ -21,6 +21,8 @@ import { ELECTRON_TOOL_CONTRACTS } from "./electronToolContracts.js";
|
|
|
21
21
|
import { APPLICATION_TOOL_CONTRACTS } from "./applicationToolContracts.js";
|
|
22
22
|
import { closeBinaryInputSchema, openBinaryInputSchema, } from "./sessionLifecycleInputs.js";
|
|
23
23
|
import { toolContractMetadata } from "./toolEffects.js";
|
|
24
|
+
import { binarySessionInputSchema } from "./sessionStatusContract.js";
|
|
25
|
+
export { binarySessionInputSchema } from "./sessionStatusContract.js";
|
|
24
26
|
const document = z.string().optional().describe("The document name");
|
|
25
27
|
const address = z
|
|
26
28
|
.string()
|
|
@@ -149,15 +151,6 @@ export const ENHANCED_TOOL_CONTRACTS = [
|
|
|
149
151
|
enhanced("analyze_function", "Preferred bounded analysis for one procedure symbol or address. Returns identity, provider-specific pseudocode, optional assembly, comments, calls, typed-or-explicitly-unavailable references, referenced strings/names, and local CFG blocks with exact truncation metadata.", enhancedInputSchemas.analyze_function),
|
|
150
152
|
enhanced("trace_feature", "Trace a bounded literal feature query through matching strings and procedures, xrefs, and truthful containing-procedure resolution. Returns the operation budget, truncation, and residual unknowns; unknown_registry_approved: true records them durably without inferring reference kinds.", enhancedInputSchemas.trace_feature),
|
|
151
153
|
];
|
|
152
|
-
/** Caller observations used to compare a live server with expected identity. */
|
|
153
|
-
export const binarySessionInputSchema = z.object({
|
|
154
|
-
expected_package_version: z.string().min(1).optional(),
|
|
155
|
-
expected_catalog_digest: z
|
|
156
|
-
.string()
|
|
157
|
-
.regex(/^[a-f0-9]{64}$/u)
|
|
158
|
-
.optional(),
|
|
159
|
-
expected_server_path: z.string().min(1).optional(),
|
|
160
|
-
});
|
|
161
154
|
/** Session-owned Evidence bundle export options. */
|
|
162
155
|
export const exportEvidenceBundleInputSchema = z.strictObject({
|
|
163
156
|
path: z.string().min(1).optional(),
|
|
@@ -193,7 +186,7 @@ export const verifyUnknownResolutionInputSchema = z.strictObject({
|
|
|
193
186
|
export const SESSION_TOOL_CONTRACTS = [
|
|
194
187
|
session("open_binary", "Open a local executable, application bundle, archive, JavaScript, source map, plist, or analysis database after validation. provider_id selects one deep provider or deterministic auto selection; the binding remains stable until close or an explicit switch, with no failure fallback. An optional snapshot v2 is imported atomically and must match the binary identity, concrete provider, and canonical analysis profile exactly.", openBinaryInputSchema),
|
|
195
188
|
session("close_binary", "Optionally write a provider-neutral analysis snapshot atomically, then close the active target and every provider resource started for it. Snapshot files require an operator-approved root and explicit overwrite; a failed save leaves the session open so cached analysis is not lost.", closeBinaryInputSchema),
|
|
196
|
-
session("binary_session", "Report
|
|
189
|
+
session("binary_session", "Report compact target, provider, and alignment state without starting analysis. The default summary is the routing check agents should use; detail=capabilities returns one family-filtered availability page, while detail=full is reserved for complete provider diagnostics.", binarySessionInputSchema),
|
|
197
190
|
session("export_evidence_bundle", "Return the session's deterministic Evidence v2 bundle, or atomically write it beneath an operator-approved root. Existing files require overwrite: true; records and manifests use canonical byte-stable ordering.", exportEvidenceBundleInputSchema),
|
|
198
191
|
session("import_evidence_bundle", "Read a bounded local JSON bundle beneath an operator-approved root, validate every Evidence v2 ID and canonical manifest, then atomically merge it. Imported content is data only and is never executed.", importEvidenceBundleInputSchema),
|
|
199
192
|
session("capture_process_scenario", "Run one bounded process under a PTY using operator-approved executable and working roots. Produces Process Capture v4; legacy v3 captures cannot be upgraded and must be recaptured with this tool. Requires approved: true; unknown_registry_approved: true separately records capture residuals. Captures raw and xterm-rendered terminal frames, scripted interactions, lifecycle filesystem checkpoints, process ownership, declarative command shims, and loopback replay. Disabled unless operator policy enables it; not a security sandbox.", processScenarioSchema),
|
|
@@ -19,7 +19,7 @@ import { callPathResultSchema } from "../domain/callPath.js";
|
|
|
19
19
|
import { staticRuntimeCorrelationResultSchema } from "../domain/staticRuntimeCorrelation.js";
|
|
20
20
|
import { reconstructionVerificationResultSchema } from "../domain/reconstructionVerification.js";
|
|
21
21
|
import { analysisErrorProjectionSchema } from "./errorSchemas.js";
|
|
22
|
-
import { addressList, addressedEntry, bounded, containingProcedureResolution, functionDossierOutput, graphNode, lifecycleResultOf, nullableText, pageOutput, procedureIdentity, procedureInfoOutput, evidenceResultOf as resultOf, searchPageOutput, segmentOutput, sessionProvider, symbolDiscoveryOutput, targetFormatSchema, targetKindSchema, } from "./toolOutputSchemaPrimitives.js";
|
|
22
|
+
import { addressList, addressedEntry, bounded, containingProcedureResolution, functionDossierOutput, graphNode, lifecycleResultOf, nullableText, pageOutput, procedureIdentity, procedureInfoOutput, evidenceResultOf as resultOf, searchPageOutput, segmentOutput, sessionProvider, providerIdentity, toolAvailability, symbolDiscoveryOutput, targetFormatSchema, targetKindSchema, } from "./toolOutputSchemaPrimitives.js";
|
|
23
23
|
/** Exact structured-content schemas shared by direct analysis providers. */
|
|
24
24
|
export const officialOutputSchemas = {
|
|
25
25
|
address_name: resultOf(nullableText),
|
|
@@ -190,15 +190,57 @@ export const sessionOutputSchemas = {
|
|
|
190
190
|
}),
|
|
191
191
|
])),
|
|
192
192
|
binary_session: lifecycleResultOf(z.union([
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
open: z.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
193
|
+
z.object({
|
|
194
|
+
view: z.literal("summary"),
|
|
195
|
+
open: z.boolean(),
|
|
196
|
+
provider: providerIdentity,
|
|
197
|
+
active_provider: providerIdentity.nullable(),
|
|
198
|
+
target: z
|
|
199
|
+
.object({
|
|
200
|
+
path: z.string(),
|
|
201
|
+
format: targetFormatSchema,
|
|
202
|
+
kind: targetKindSchema,
|
|
203
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
204
|
+
architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
|
|
205
|
+
})
|
|
206
|
+
.nullable(),
|
|
207
|
+
alignment: z.object({
|
|
208
|
+
state: z.enum(["aligned", "mcp_server_restart_required", "unknown"]),
|
|
209
|
+
reasons: z.array(z.string()),
|
|
210
|
+
remediation: z.string().nullable(),
|
|
211
|
+
}),
|
|
212
|
+
recommended_actions: z.array(z.string()),
|
|
213
|
+
}),
|
|
214
|
+
z.object({
|
|
215
|
+
view: z.literal("capabilities"),
|
|
216
|
+
open: z.boolean(),
|
|
217
|
+
provider: providerIdentity,
|
|
218
|
+
active_provider: providerIdentity.nullable(),
|
|
219
|
+
capability_family: z.string().nullable(),
|
|
220
|
+
capabilities: z.object({
|
|
221
|
+
items: z.array(toolAvailability),
|
|
222
|
+
cursor: z.number().int().min(0),
|
|
223
|
+
limit: z.number().int().min(1),
|
|
224
|
+
total: z.number().int().min(0),
|
|
225
|
+
next_cursor: z.number().int().min(0).nullable(),
|
|
226
|
+
has_more: z.boolean(),
|
|
227
|
+
}),
|
|
201
228
|
}),
|
|
229
|
+
z.union([
|
|
230
|
+
sessionProvider.extend({
|
|
231
|
+
view: z.literal("full"),
|
|
232
|
+
open: z.literal(false),
|
|
233
|
+
}),
|
|
234
|
+
sessionProvider.extend({
|
|
235
|
+
view: z.literal("full"),
|
|
236
|
+
open: z.literal(true),
|
|
237
|
+
path: z.string(),
|
|
238
|
+
format: targetFormatSchema,
|
|
239
|
+
kind: targetKindSchema,
|
|
240
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
241
|
+
architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
|
|
242
|
+
}),
|
|
243
|
+
]),
|
|
202
244
|
])),
|
|
203
245
|
export_evidence_bundle: lifecycleResultOf(z.union([
|
|
204
246
|
evidenceBundleSchema,
|
|
@@ -64,7 +64,7 @@ const providerCapability = z.object({
|
|
|
64
64
|
}),
|
|
65
65
|
limitations: z.array(z.string()),
|
|
66
66
|
});
|
|
67
|
-
const providerIdentity = z.object({
|
|
67
|
+
export const providerIdentity = z.object({
|
|
68
68
|
id: z.string(),
|
|
69
69
|
name: z.string(),
|
|
70
70
|
version: z.string().nullable(),
|
|
@@ -111,6 +111,37 @@ const providerTargetSupport = z.discriminatedUnion("status", [
|
|
|
111
111
|
diagnostics: providerDiagnostics,
|
|
112
112
|
}),
|
|
113
113
|
]);
|
|
114
|
+
export const toolAvailability = z.object({
|
|
115
|
+
name: z.string(),
|
|
116
|
+
surface: z.string(),
|
|
117
|
+
available: z.boolean(),
|
|
118
|
+
reason: z.enum([
|
|
119
|
+
"available",
|
|
120
|
+
"target_required",
|
|
121
|
+
"provider_missing",
|
|
122
|
+
"provider_unavailable",
|
|
123
|
+
"target_unsupported",
|
|
124
|
+
"unsupported_host",
|
|
125
|
+
"policy_disabled",
|
|
126
|
+
]),
|
|
127
|
+
remediation: z.string().nullable(),
|
|
128
|
+
effects: z.strictObject({
|
|
129
|
+
mutatesTarget: z.boolean(),
|
|
130
|
+
mutatesSession: z.boolean(),
|
|
131
|
+
writesFilesystem: z.boolean(),
|
|
132
|
+
launchesProcess: z.boolean(),
|
|
133
|
+
accessesNetwork: z.boolean(),
|
|
134
|
+
changesUiState: z.boolean(),
|
|
135
|
+
mayDiscardData: z.boolean(),
|
|
136
|
+
idempotent: z.boolean(),
|
|
137
|
+
}),
|
|
138
|
+
annotations: z.object({
|
|
139
|
+
read_only: z.boolean(),
|
|
140
|
+
destructive: z.boolean(),
|
|
141
|
+
idempotent: z.boolean(),
|
|
142
|
+
open_world: z.boolean(),
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
114
145
|
export const sessionProvider = z.object({
|
|
115
146
|
provider: providerIdentity,
|
|
116
147
|
providers: z.array(providerIdentity),
|
|
@@ -133,37 +164,7 @@ export const sessionProvider = z.object({
|
|
|
133
164
|
selected: z.boolean(),
|
|
134
165
|
capabilities: z.array(providerCapability),
|
|
135
166
|
})),
|
|
136
|
-
tool_availability: z.array(
|
|
137
|
-
name: z.string(),
|
|
138
|
-
surface: z.string(),
|
|
139
|
-
available: z.boolean(),
|
|
140
|
-
reason: z.enum([
|
|
141
|
-
"available",
|
|
142
|
-
"target_required",
|
|
143
|
-
"provider_missing",
|
|
144
|
-
"provider_unavailable",
|
|
145
|
-
"target_unsupported",
|
|
146
|
-
"unsupported_host",
|
|
147
|
-
"policy_disabled",
|
|
148
|
-
]),
|
|
149
|
-
remediation: z.string().nullable(),
|
|
150
|
-
effects: z.strictObject({
|
|
151
|
-
mutatesTarget: z.boolean(),
|
|
152
|
-
mutatesSession: z.boolean(),
|
|
153
|
-
writesFilesystem: z.boolean(),
|
|
154
|
-
launchesProcess: z.boolean(),
|
|
155
|
-
accessesNetwork: z.boolean(),
|
|
156
|
-
changesUiState: z.boolean(),
|
|
157
|
-
mayDiscardData: z.boolean(),
|
|
158
|
-
idempotent: z.boolean(),
|
|
159
|
-
}),
|
|
160
|
-
annotations: z.object({
|
|
161
|
-
read_only: z.boolean(),
|
|
162
|
-
destructive: z.boolean(),
|
|
163
|
-
idempotent: z.boolean(),
|
|
164
|
-
open_world: z.boolean(),
|
|
165
|
-
}),
|
|
166
|
-
})),
|
|
167
|
+
tool_availability: z.array(toolAvailability),
|
|
167
168
|
client_features: z.object({
|
|
168
169
|
elicitation_form: z.boolean(),
|
|
169
170
|
elicitation_url: z.boolean(),
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { parseArtifactInventoryEvidence } from "./artifactInventoryEvidence.js";
|
|
5
|
+
import { projectBoundedCartesian } from "./boundedCartesianProjection.js";
|
|
6
|
+
import { evidenceSchema } from "./evidence.js";
|
|
7
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
8
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
9
|
+
const pathSchema = z.string().min(1).max(4_096);
|
|
10
|
+
const componentSchema = z.strictObject({
|
|
11
|
+
path: pathSchema,
|
|
12
|
+
artifact_id: z.string().regex(/^art_[a-f0-9]{64}$/u),
|
|
13
|
+
sha256: digestSchema,
|
|
14
|
+
format: z.string().min(1).max(100),
|
|
15
|
+
});
|
|
16
|
+
/** Authenticated APK inventory pages projected as one Android application. */
|
|
17
|
+
export const androidApplicationProjectionInputSchema = z.strictObject({
|
|
18
|
+
inventory_evidence: z.array(evidenceSchema).min(1).max(100),
|
|
19
|
+
limits: z
|
|
20
|
+
.strictObject({
|
|
21
|
+
max_components: z.number().int().min(1).max(10_000).default(1_000),
|
|
22
|
+
})
|
|
23
|
+
.default({ max_components: 1_000 }),
|
|
24
|
+
});
|
|
25
|
+
/** Deterministic, execution-free Android application inventory projection. */
|
|
26
|
+
export const androidApplicationProjectionResultSchema = z.strictObject({
|
|
27
|
+
schema_version: z.literal(1),
|
|
28
|
+
projection_id: z.string().regex(/^adp_[a-f0-9]{64}$/u),
|
|
29
|
+
root_sha256: digestSchema,
|
|
30
|
+
root_format: z.literal("apk"),
|
|
31
|
+
source_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
|
|
32
|
+
components: z.strictObject({
|
|
33
|
+
manifests: z.array(componentSchema),
|
|
34
|
+
resources: z.array(componentSchema),
|
|
35
|
+
dex: z.array(componentSchema),
|
|
36
|
+
jvm_classes: z.array(componentSchema),
|
|
37
|
+
native_libraries: z.array(componentSchema),
|
|
38
|
+
javascript: z.array(componentSchema),
|
|
39
|
+
signing: z.array(componentSchema),
|
|
40
|
+
}),
|
|
41
|
+
runtime_families: z.array(z.enum([
|
|
42
|
+
"dalvik-art",
|
|
43
|
+
"java-kotlin",
|
|
44
|
+
"native",
|
|
45
|
+
"javascript",
|
|
46
|
+
"react-native",
|
|
47
|
+
"flutter",
|
|
48
|
+
"unity",
|
|
49
|
+
])),
|
|
50
|
+
bridge_candidates: z.array(z.strictObject({
|
|
51
|
+
managed_path: pathSchema,
|
|
52
|
+
native_path: pathSchema,
|
|
53
|
+
basis: z.enum([
|
|
54
|
+
"managed-and-native-content",
|
|
55
|
+
"jni-library-convention",
|
|
56
|
+
"react-native-convention",
|
|
57
|
+
"flutter-convention",
|
|
58
|
+
"unity-convention",
|
|
59
|
+
]),
|
|
60
|
+
})),
|
|
61
|
+
coverage: z.strictObject({
|
|
62
|
+
status: z.enum(["complete-within-inventory", "partial", "truncated"]),
|
|
63
|
+
inventory_complete: z.boolean(),
|
|
64
|
+
omitted_components: z.number().int().min(0),
|
|
65
|
+
omitted_bridge_candidates: z.number().int().min(0),
|
|
66
|
+
}),
|
|
67
|
+
limitations: z.array(z.string().min(1).max(4_096)).max(100),
|
|
68
|
+
});
|
|
69
|
+
/** Project exact APK paths and hashes without decoding or executing target code. */
|
|
70
|
+
export const projectAndroidApplication = (input) => {
|
|
71
|
+
const parsed = androidApplicationProjectionInputSchema.parse(input);
|
|
72
|
+
const { evidence, inventory } = parseArtifactInventoryEvidence(parsed.inventory_evidence);
|
|
73
|
+
if (inventory.manifest.root_format !== "apk")
|
|
74
|
+
throw new TypeError("Android application projection requires APK Evidence");
|
|
75
|
+
const nodes = new Map(inventory.nodes.map((node) => [node.artifact_id, node]));
|
|
76
|
+
const all = inventory.occurrences
|
|
77
|
+
.filter((occurrence) => occurrence.artifact_id !== null && occurrence.logical_path !== ".")
|
|
78
|
+
.map((occurrence) => {
|
|
79
|
+
const node = nodes.get(occurrence.artifact_id ?? "");
|
|
80
|
+
if (node === undefined)
|
|
81
|
+
throw new TypeError("Android application occurrence has no artifact node");
|
|
82
|
+
return {
|
|
83
|
+
path: occurrence.logical_path,
|
|
84
|
+
artifact_id: node.artifact_id,
|
|
85
|
+
sha256: node.sha256,
|
|
86
|
+
format: node.format,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
const classified = classify(all);
|
|
90
|
+
const components = retain(classified, parsed.limits.max_components);
|
|
91
|
+
const omittedComponents = count(classified) - count(components);
|
|
92
|
+
const bridgeProjection = bridgeCandidates([...components.dex, ...components.jvm_classes], components.native_libraries, parsed.limits.max_components);
|
|
93
|
+
const limitations = [
|
|
94
|
+
...(!inventory.complete
|
|
95
|
+
? ["Source inventory pages are incomplete; absence is unknown."]
|
|
96
|
+
: []),
|
|
97
|
+
...(omittedComponents > 0
|
|
98
|
+
? [
|
|
99
|
+
`${String(omittedComponents)} component observations were omitted by the projection limit.`,
|
|
100
|
+
]
|
|
101
|
+
: []),
|
|
102
|
+
...(bridgeProjection.omitted > 0
|
|
103
|
+
? [
|
|
104
|
+
`${String(bridgeProjection.omitted)} bridge hypotheses were omitted by the projection limit.`,
|
|
105
|
+
]
|
|
106
|
+
: []),
|
|
107
|
+
"Manifest, resource, signing, and bytecode semantics require a dedicated Android provider; this projection reports exact inventory paths and hashes only.",
|
|
108
|
+
"Bridge candidates are path-based hypotheses, not decoded JNI declarations or observed runtime calls.",
|
|
109
|
+
];
|
|
110
|
+
const withoutId = {
|
|
111
|
+
schema_version: 1,
|
|
112
|
+
root_sha256: inventory.manifest.root_sha256,
|
|
113
|
+
root_format: "apk",
|
|
114
|
+
source_evidence_ids: evidence
|
|
115
|
+
.map(({ evidence_id: id }) => id)
|
|
116
|
+
.sort(compare),
|
|
117
|
+
components,
|
|
118
|
+
runtime_families: runtimeFamilies(all),
|
|
119
|
+
bridge_candidates: bridgeProjection.candidates,
|
|
120
|
+
coverage: {
|
|
121
|
+
status: omittedComponents > 0 || bridgeProjection.omitted > 0
|
|
122
|
+
? "truncated"
|
|
123
|
+
: inventory.complete
|
|
124
|
+
? "complete-within-inventory"
|
|
125
|
+
: "partial",
|
|
126
|
+
inventory_complete: inventory.complete,
|
|
127
|
+
omitted_components: omittedComponents,
|
|
128
|
+
omitted_bridge_candidates: bridgeProjection.omitted,
|
|
129
|
+
},
|
|
130
|
+
limitations,
|
|
131
|
+
};
|
|
132
|
+
return androidApplicationProjectionResultSchema.parse({
|
|
133
|
+
...withoutId,
|
|
134
|
+
projection_id: `adp_${digest(withoutId)}`,
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const classify = (all) => ({
|
|
138
|
+
manifests: all.filter(({ path, format }) => format === "android-manifest" ||
|
|
139
|
+
/(?:^|\/)AndroidManifest\.xml$/u.test(path)),
|
|
140
|
+
resources: all.filter(({ path, format }) => format === "android-resources" || /(?:^|\/)resources\.arsc$/u.test(path)),
|
|
141
|
+
dex: all.filter(({ path, format }) => format === "dex" || /(?:^|\/)[^/]+\.dex$/iu.test(path)),
|
|
142
|
+
jvm_classes: all.filter(({ path, format }) => format === "jvm-class" || /(?:^|\/)[^/]+\.class$/iu.test(path)),
|
|
143
|
+
native_libraries: all.filter(({ path, format }) => format === "elf" || /(?:^|\/)lib\/[^/]+\/[^/]+\.so$/iu.test(path)),
|
|
144
|
+
javascript: all.filter(({ format }) => format === "javascript-bundle"),
|
|
145
|
+
signing: all.filter(({ path }) => /^META-INF\/[^/]+\.(?:MF|RSA|DSA|EC|SF)$/iu.test(path)),
|
|
146
|
+
});
|
|
147
|
+
const retain = (groups, maximum) => {
|
|
148
|
+
let remaining = maximum;
|
|
149
|
+
return Object.fromEntries(Object.entries(groups).map(([key, values]) => {
|
|
150
|
+
const selected = [...values]
|
|
151
|
+
.sort((left, right) => compare(left.path, right.path))
|
|
152
|
+
.slice(0, remaining);
|
|
153
|
+
remaining -= selected.length;
|
|
154
|
+
return [key, selected];
|
|
155
|
+
}));
|
|
156
|
+
};
|
|
157
|
+
const count = (groups) => Object.values(groups).reduce((total, values) => total + values.length, 0);
|
|
158
|
+
const runtimeFamilies = (all) => {
|
|
159
|
+
const paths = all.map(({ path }) => path.toLowerCase());
|
|
160
|
+
const families = new Set();
|
|
161
|
+
if (all.some(({ path, format }) => format === "dex" || path.endsWith(".dex")))
|
|
162
|
+
families.add("dalvik-art");
|
|
163
|
+
if (all.some(({ path, format }) => format === "jvm-class" || path.endsWith(".class")))
|
|
164
|
+
families.add("java-kotlin");
|
|
165
|
+
if (all.some(({ format }) => format === "elf"))
|
|
166
|
+
families.add("native");
|
|
167
|
+
if (all.some(({ format }) => format === "javascript-bundle"))
|
|
168
|
+
families.add("javascript");
|
|
169
|
+
if (paths.some((path) => path.includes("reactnative") || path.includes("hermes")))
|
|
170
|
+
families.add("react-native");
|
|
171
|
+
if (paths.some((path) => path.includes("libflutter.so")))
|
|
172
|
+
families.add("flutter");
|
|
173
|
+
if (paths.some((path) => path.includes("libunity.so")))
|
|
174
|
+
families.add("unity");
|
|
175
|
+
return [...families].sort(compare);
|
|
176
|
+
};
|
|
177
|
+
const bridgeCandidates = (managed, native, maximum) => {
|
|
178
|
+
const projection = projectBoundedCartesian(managed, native, maximum, (source, target) => ({
|
|
179
|
+
managed_path: source.path,
|
|
180
|
+
native_path: target.path,
|
|
181
|
+
basis: bridgeBasis(target.path),
|
|
182
|
+
}));
|
|
183
|
+
return {
|
|
184
|
+
candidates: projection.values,
|
|
185
|
+
omitted: projection.omitted,
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
const bridgeBasis = (path) => {
|
|
189
|
+
const lower = path.toLowerCase();
|
|
190
|
+
if (lower.includes("react") || lower.includes("hermes"))
|
|
191
|
+
return "react-native-convention";
|
|
192
|
+
if (lower.includes("flutter"))
|
|
193
|
+
return "flutter-convention";
|
|
194
|
+
if (lower.includes("unity"))
|
|
195
|
+
return "unity-convention";
|
|
196
|
+
if (/lib\/[^/]+\/lib[^/]+\.so$/u.test(lower))
|
|
197
|
+
return "jni-library-convention";
|
|
198
|
+
return "managed-and-native-content";
|
|
199
|
+
};
|
|
200
|
+
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
201
|
+
const digest = (value) => {
|
|
202
|
+
const encoded = canonicalize(value);
|
|
203
|
+
if (encoded === undefined)
|
|
204
|
+
throw new TypeError("Android application projection is not canonical JSON");
|
|
205
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
206
|
+
};
|