rea-agents 2.3.0 → 2.4.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.
Files changed (34) hide show
  1. package/README.md +2 -2
  2. package/dist/application/ArtifactExtraction.js +5 -1
  3. package/dist/application/ArtifactGraphConstruction.js +3 -15
  4. package/dist/application/ArtifactInventory/classify.js +5 -1
  5. package/dist/application/ArtifactInventory/reader.js +2 -0
  6. package/dist/application/BinarySessionStatus.js +1 -0
  7. package/dist/application/CapabilityInventory.js +11 -3
  8. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  9. package/dist/application/ProcessCaptureLifecycle.js +1 -0
  10. package/dist/application/ProcessCheckpoints.js +5 -0
  11. package/dist/application/ProcessHarness.js +45 -34
  12. package/dist/application/ProcessNormalization.js +6 -0
  13. package/dist/application/ProcessReactiveCoordinator.js +10 -3
  14. package/dist/application/ProcessReactiveEffects.js +9 -1
  15. package/dist/application/ProcessReactiveHarness.js +85 -0
  16. package/dist/application/ProcessReactiveObservations.js +101 -16
  17. package/dist/contracts/toolOutputSchemaPrimitives.js +3 -0
  18. package/dist/domain/analysisSnapshot.js +2 -0
  19. package/dist/domain/artifactGraph.js +2 -0
  20. package/dist/domain/binaryTarget.js +3 -6
  21. package/dist/domain/evidence.js +2 -0
  22. package/dist/domain/processCapture.js +64 -0
  23. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  24. package/dist/domain/processCaptureValidation.js +2 -0
  25. package/dist/domain/processObservation.js +15 -6
  26. package/dist/domain/processReactiveRuntime.js +21 -10
  27. package/dist/domain/processReactiveTransition.js +8 -6
  28. package/dist/domain/processScenario.js +21 -0
  29. package/dist/domain/zipPackageFormat.js +20 -0
  30. package/dist/generatedPackageMetadata.js +1 -1
  31. package/dist/native/NativeMacOSProvider.js +1 -0
  32. package/package.json +1 -1
  33. package/skills/reverse-engineer-anything/SKILL.md +3 -2
  34. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
@@ -27,6 +27,8 @@ export const targetFormatSchema = z.enum([
27
27
  "zip",
28
28
  "ipa",
29
29
  "apk",
30
+ "msix",
31
+ "appx",
30
32
  "asar",
31
33
  "dmg",
32
34
  "pkg",
@@ -44,6 +46,7 @@ const providerCapability = z.object({
44
46
  operation: z.string(),
45
47
  available: z.boolean(),
46
48
  reason: z.string().nullable(),
49
+ availability_code: z.enum(PROVIDER_REJECTION_CODES).nullable(),
47
50
  input_contract_version: z.number().int().min(1),
48
51
  output_contract_version: z.number().int().min(1),
49
52
  pagination: z.enum(["none", "offset", "cursor"]),
@@ -15,6 +15,8 @@ const formatSchema = z.enum([
15
15
  "zip",
16
16
  "ipa",
17
17
  "apk",
18
+ "msix",
19
+ "appx",
18
20
  "asar",
19
21
  "dmg",
20
22
  "pkg",
@@ -45,6 +45,8 @@ const artifactFormatSchema = z.enum([
45
45
  "analysis-database",
46
46
  "ipa",
47
47
  "apk",
48
+ "msix",
49
+ "appx",
48
50
  "plist",
49
51
  "entitlements",
50
52
  "directory",
@@ -6,6 +6,7 @@ import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import { promisify } from "node:util";
7
7
  import { BinaryTargetError } from "./errors.js";
8
8
  import { err, ok } from "./result.js";
9
+ import { zipPackageFormatForPath } from "./zipPackageFormat.js";
9
10
  const execFileAsync = promisify(execFile);
10
11
  /**
11
12
  * Resolve and classify a readable local target before a provider is selected.
@@ -71,11 +72,7 @@ const detectArtifactFormat = async (path, handle) => {
71
72
  magic[1] === 0x4b &&
72
73
  [0x03, 0x05, 0x07].includes(magic[2] ?? -1) &&
73
74
  [0x04, 0x06, 0x08].includes(magic[3] ?? -1)) {
74
- return lower.endsWith(".ipa")
75
- ? "ipa"
76
- : lower.endsWith(".apk")
77
- ? "apk"
78
- : "zip";
75
+ return zipPackageFormatForPath(lower) ?? "zip";
79
76
  }
80
77
  const named = namedArtifactFormat(lower);
81
78
  if (named !== undefined)
@@ -104,7 +101,7 @@ const namedArtifactFormat = (lowerPath) => {
104
101
  return "source-map";
105
102
  return /\.(?:m?js|cjs)$/u.test(lowerPath) ? "javascript" : undefined;
106
103
  };
107
- const isArchiveFormat = (format) => ["zip", "ipa", "apk", "asar", "dmg", "pkg"].includes(format);
104
+ const isArchiveFormat = (format) => ["zip", "ipa", "apk", "msix", "appx", "asar", "dmg", "pkg"].includes(format);
108
105
  const sha256Handle = async (handle) => {
109
106
  const hash = createHash("sha256");
110
107
  const chunk = Buffer.allocUnsafe(64 * 1024);
@@ -22,6 +22,8 @@ const subjectSchema = z.object({
22
22
  "zip",
23
23
  "ipa",
24
24
  "apk",
25
+ "msix",
26
+ "appx",
25
27
  "asar",
26
28
  "dmg",
27
29
  "pkg",
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { jsonValueSchema } from "./jsonValue.js";
3
3
  import { LEGACY_PROCESS_CAPTURE_MESSAGE, normalizationSchema, } from "./processScenario.js";
4
+ import { PROCESS_REACTIVE_LIMITS } from "./processReactiveScenario.js";
4
5
  export * from "./processScenario.js";
5
6
  /** Capture collection whose members participate in global observation order. */
6
7
  export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [
@@ -145,6 +146,69 @@ export const processCaptureSchema = z.object({
145
146
  sensitive_aliases: z.array(z.string().min(1).max(64)).max(32),
146
147
  }))
147
148
  .default([]),
149
+ reactive_run: z
150
+ .object({
151
+ status: z.enum(["running", "finished"]),
152
+ outcome: z
153
+ .enum([
154
+ "passed",
155
+ "predicate_timeout",
156
+ "scenario_deadline",
157
+ "ambiguous_match",
158
+ "action_rejected",
159
+ "target_lost",
160
+ "capture_incomplete",
161
+ "cancelled",
162
+ "cleanup_failed",
163
+ ])
164
+ .nullable(),
165
+ active_state: z.string().min(1).max(64),
166
+ controls: z
167
+ .array(z.object({
168
+ sequence: z.number().int().nonnegative(),
169
+ kind: z.enum([
170
+ "state_deadline",
171
+ "scenario_deadline",
172
+ "target_lost",
173
+ "cancelled",
174
+ "cleanup_failed",
175
+ ]),
176
+ after_capture_order: z.number().int().min(-1),
177
+ }))
178
+ .default([]),
179
+ transitions: z
180
+ .array(z.object({
181
+ sequence: z.number().int().nonnegative(),
182
+ transition_id: z.string().min(1).max(64),
183
+ state_before: z.string().min(1).max(64),
184
+ state_after: z.string().min(1).max(64).nullable(),
185
+ outcome: z
186
+ .enum([
187
+ "passed",
188
+ "predicate_timeout",
189
+ "scenario_deadline",
190
+ "ambiguous_match",
191
+ "action_rejected",
192
+ "target_lost",
193
+ "capture_incomplete",
194
+ "cancelled",
195
+ "cleanup_failed",
196
+ ])
197
+ .nullable(),
198
+ trigger_event_ids: z
199
+ .array(z.string().min(1))
200
+ .max(PROCESS_REACTIVE_LIMITS.predicates),
201
+ action_event_ids: z
202
+ .array(z.string().min(1))
203
+ .max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
204
+ action_types: z
205
+ .array(z.enum(["send_input", "resize", "send_signal", "checkpoint"]))
206
+ .max(PROCESS_REACTIVE_LIMITS.actionsPerTransition),
207
+ }))
208
+ .max(PROCESS_REACTIVE_LIMITS.transitions),
209
+ })
210
+ .nullable()
211
+ .default(null),
148
212
  event_journal: z
149
213
  .array(z.object({
150
214
  capture_order: z.number().int().nonnegative(),
@@ -0,0 +1,175 @@
1
+ import canonicalize from "canonicalize";
2
+ import { projectProcessObservation, } from "./processObservation.js";
3
+ import { commitProcessReactiveProposal, createProcessReactiveSnapshot, reduceProcessReactiveScenario, } from "./processReactiveRuntime.js";
4
+ import { processReactiveScenarioSchema, } from "./processReactiveScenario.js";
5
+ const admittedCollection = (collection) => [
6
+ "frames",
7
+ "interaction_events",
8
+ "process_samples",
9
+ "filesystem_checkpoints",
10
+ "shim_events",
11
+ "protocol_events",
12
+ ].includes(collection);
13
+ const reactiveObservations = (capture) => (capture.event_journal ?? []).flatMap((location) => {
14
+ if (!admittedCollection(location.collection))
15
+ return [];
16
+ const observation = projectProcessObservation(capture, location);
17
+ return observation === null ? [] : [observation];
18
+ });
19
+ const validateTransitionDeclaration = (transition, next, context) => {
20
+ const { index, scenario, require } = context;
21
+ const path = `reactive_run.transitions[${String(index)}]`;
22
+ const declared = scenario.states
23
+ .find(({ id }) => id === transition.state_before)
24
+ ?.on.find(({ id }) => id === transition.transition_id);
25
+ require(declared !==
26
+ undefined, path, "reactive transition is not declared by its recorded source state");
27
+ if (declared !== undefined) {
28
+ const stateAfter = declared.target.kind === "goto" ? declared.target.state : null;
29
+ const outcome = declared.target.kind === "finish" ? declared.target.outcome : null;
30
+ require(transition.state_after === stateAfter &&
31
+ transition.outcome ===
32
+ outcome, path, "reactive transition target or outcome differs from the committed graph");
33
+ require(canonicalize(transition.action_types) ===
34
+ canonicalize(declared.actions.map(({ type }) => type)), `${path}.action_types`, "reactive transition actions differ from the committed graph");
35
+ }
36
+ require(transition.sequence ===
37
+ index, `${path}.sequence`, "reactive transition sequence must be contiguous from zero");
38
+ if (next !== undefined)
39
+ require(transition.state_after ===
40
+ next.state_before, `reactive_run.transitions[${String(index + 1)}].state_before`, "reactive transition states must be contiguous");
41
+ };
42
+ const validateTransitionDeclarations = (run, scenario, require) => {
43
+ require(run.transitions[0]?.state_before === scenario.initial_state ||
44
+ run.transitions.length ===
45
+ 0, "reactive_run.transitions[0].state_before", "reactive transition journal must start at the declared initial state");
46
+ for (const [index, transition] of run.transitions.entries())
47
+ validateTransitionDeclaration(transition, run.transitions[index + 1], {
48
+ index,
49
+ scenario,
50
+ require,
51
+ });
52
+ };
53
+ const commitRecordedEffects = (options) => commitProcessReactiveProposal(options.scenario, options.proposal, options.recordedFailure === undefined
54
+ ? options.proposal.effects.map((_, index) => {
55
+ const eventId = options.expected?.action_event_ids[index];
56
+ const observation = eventId === undefined
57
+ ? undefined
58
+ : options.observations.get(eventId);
59
+ return observation === undefined
60
+ ? { status: "rejected" }
61
+ : { status: "succeeded", observation };
62
+ })
63
+ : [
64
+ options.recordedFailure === "target_lost"
65
+ ? { status: "target_lost" }
66
+ : { status: "rejected" },
67
+ ], { committedSensitiveInputs: true });
68
+ const replayTransitions = (scenario, run, observations, require) => {
69
+ const observationsById = new Map(observations.map((observation) => [observation.event_id, observation]));
70
+ let snapshot = createProcessReactiveSnapshot(scenario);
71
+ let transitionIndex = 0;
72
+ const advance = (input) => {
73
+ let decision = reduceProcessReactiveScenario(scenario, snapshot, input);
74
+ if (decision.kind === "proposal") {
75
+ const proposal = decision;
76
+ decision = commitRecordedEffects({
77
+ scenario,
78
+ proposal: decision,
79
+ expected: run.transitions[transitionIndex],
80
+ observations: observationsById,
81
+ recordedFailure: run.transitions[transitionIndex] === undefined &&
82
+ decision.effects.length > 0 &&
83
+ (run.outcome === "action_rejected" || run.outcome === "target_lost")
84
+ ? run.outcome
85
+ : undefined,
86
+ });
87
+ require(decision.kind !== "finished" ||
88
+ decision.outcome !== "action_rejected" ||
89
+ run.outcome ===
90
+ "action_rejected", `reactive_run.transitions[${String(transitionIndex)}].action_event_ids`, `recorded action observations do not match proposed effects: ${canonicalize({
91
+ effects: proposal.effects,
92
+ observations: run.transitions[transitionIndex]?.action_event_ids.map((eventId) => observationsById.get(eventId)),
93
+ })}`);
94
+ }
95
+ if (decision.kind === "transition") {
96
+ const expected = run.transitions[transitionIndex];
97
+ require(expected !== undefined &&
98
+ canonicalize(decision.record) ===
99
+ canonicalize(expected), `reactive_run.transitions[${String(transitionIndex)}]`, "reactive transition differs from deterministic journal replay");
100
+ transitionIndex += 1;
101
+ }
102
+ snapshot = decision.snapshot;
103
+ };
104
+ const advanceControl = (control, index) => {
105
+ require(snapshot.status !== "finished" ||
106
+ control.kind ===
107
+ "cleanup_failed", `reactive_run.controls[${String(index)}]`, "reactive control cannot follow a finished run");
108
+ advance(control.kind === "state_deadline"
109
+ ? {
110
+ kind: control.kind,
111
+ state_id: snapshot.active_state,
112
+ state_entry_capture_order: snapshot.state_entry_capture_order,
113
+ }
114
+ : { kind: control.kind });
115
+ };
116
+ let controlIndex = 0;
117
+ for (const observation of observations) {
118
+ while ((run.controls[controlIndex]?.after_capture_order ??
119
+ Number.MAX_SAFE_INTEGER) < observation.capture_order) {
120
+ const control = run.controls[controlIndex];
121
+ advanceControl(control, controlIndex);
122
+ controlIndex += 1;
123
+ }
124
+ advance({ kind: "observation", observation });
125
+ }
126
+ for (; controlIndex < run.controls.length; controlIndex += 1) {
127
+ const control = run.controls[controlIndex];
128
+ advanceControl(control, controlIndex);
129
+ }
130
+ require(transitionIndex ===
131
+ run.transitions
132
+ .length, "reactive_run.transitions", `reactive transition journal is not reproduced by deterministic replay (replayed ${String(transitionIndex)} of ${String(run.transitions.length)}; outcome ${snapshot.outcome})`);
133
+ return snapshot;
134
+ };
135
+ const validateControls = (run, observations, require) => {
136
+ const admittedOrders = new Set([
137
+ -1,
138
+ ...observations.map(({ capture_order }) => capture_order),
139
+ ]);
140
+ for (const [index, control] of run.controls.entries()) {
141
+ require(control.sequence ===
142
+ index, `reactive_run.controls[${String(index)}].sequence`, "reactive control sequence must be contiguous");
143
+ require(admittedOrders.has(control.after_capture_order), `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoff must identify the last admitted observation");
144
+ require(index === 0 ||
145
+ control.after_capture_order >=
146
+ run.controls[index - 1]
147
+ .after_capture_order, `reactive_run.controls[${String(index)}].after_capture_order`, "reactive control cutoffs must be ordered");
148
+ }
149
+ };
150
+ /** Validate one capture's reactive journal by replaying the production reducer. */
151
+ export const validateProcessCaptureReactiveRun = (capture, require) => {
152
+ const declared = capture.manifest.scenario["reactive"];
153
+ const run = capture.reactive_run;
154
+ const declaredReactive = declared !== undefined && declared !== null;
155
+ require(declaredReactive ===
156
+ (run !==
157
+ null), "reactive_run", "reactive result presence must match the committed scenario");
158
+ if (run === null)
159
+ return;
160
+ const parsed = processReactiveScenarioSchema.safeParse(declared);
161
+ require(parsed.success, "manifest.scenario.reactive", "committed reactive scenario does not satisfy its declared schema");
162
+ if (!parsed.success)
163
+ return;
164
+ require(run.status === "finished" &&
165
+ run.outcome !==
166
+ null, "reactive_run.outcome", "completed capture requires a finished reactive outcome");
167
+ validateTransitionDeclarations(run, parsed.data, require);
168
+ const observations = reactiveObservations(capture);
169
+ validateControls(run, observations, require);
170
+ const snapshot = replayTransitions(parsed.data, run, observations, require);
171
+ require(snapshot.outcome ===
172
+ run.outcome, "reactive_run.outcome", "reactive outcome differs from deterministic journal replay");
173
+ require(snapshot.active_state ===
174
+ run.active_state, "reactive_run.active_state", "reactive active state differs from deterministic journal replay");
175
+ };
@@ -1,3 +1,4 @@
1
+ import { validateProcessCaptureReactiveRun } from "./processCaptureReactiveValidation.js";
1
2
  import { replayMachineSchema } from "./replayMachine.js";
2
3
  import { digestProcessCommitment } from "./processScenario.js";
3
4
  const orderedTimestamps = (values) => values.every((value, index) => index === 0 || value.at_ms >= values[index - 1].at_ms);
@@ -209,5 +210,6 @@ export const validateProcessCapture = (capture) => {
209
210
  validateLifecycle(capture, require);
210
211
  validateShimEvents(capture, require);
211
212
  validateReplayEvents(capture, require);
213
+ validateProcessCaptureReactiveRun(capture, require);
212
214
  return issues;
213
215
  };
@@ -60,13 +60,22 @@ const atMilliseconds = (value) => {
60
60
  }
61
61
  return null;
62
62
  };
63
- const subjectFor = (source, value) => {
63
+ /** Derive the stable capture-local identity for one normalized observation. */
64
+ export const processObservationSubject = (source, value) => {
64
65
  if (typeof value !== "object" || value === null || Array.isArray(value))
65
66
  return null;
66
- if (source === "process" && typeof value["pid"] === "number")
67
- return `process:${String(value["pid"])}`;
68
- if (source === "filesystem" && typeof value["name"] === "string")
69
- return `checkpoint:${value["name"]}`;
67
+ if (source === "process" && "pid" in value && typeof value.pid === "number")
68
+ return `process:${String(value.pid)}`;
69
+ if (source === "filesystem" &&
70
+ "name" in value &&
71
+ typeof value.name === "string")
72
+ return `checkpoint:${value.name}`;
73
+ if (source === "shim" &&
74
+ "command" in value &&
75
+ typeof value.command === "string")
76
+ return "route_index" in value && typeof value.route_index === "number"
77
+ ? `shim:${value.command}:route:${String(value.route_index)}`
78
+ : `shim:${value.command}:unmatched`;
70
79
  return null;
71
80
  };
72
81
  const projectedRecord = (capture, location) => {
@@ -133,7 +142,7 @@ const projectedRecord = (capture, location) => {
133
142
  return {
134
143
  source: projected.source,
135
144
  captured_at_ms: atMilliseconds(payload),
136
- subject_id: subjectFor(projected.source, payload),
145
+ subject_id: processObservationSubject(projected.source, payload),
137
146
  payload,
138
147
  };
139
148
  };
@@ -2,6 +2,22 @@ import { matchProcessReactiveTrigger } from "./processReactiveMatching.js";
2
2
  import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
3
3
  export { commitProcessReactiveProposal } from "./processReactiveTransition.js";
4
4
  const counterValue = (counters, id) => counters.find((counter) => counter.id === id)?.count ?? 0;
5
+ const observeInput = (snapshot, observation) => {
6
+ const duplicate = snapshot.observations.find(({ event_id }) => event_id === observation.event_id);
7
+ if (duplicate !== undefined)
8
+ return { kind: "invalid" };
9
+ const prior = snapshot.observations.at(-1);
10
+ if ((prior !== undefined && observation.capture_order <= prior.capture_order) ||
11
+ snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
12
+ return { kind: "invalid" };
13
+ return {
14
+ kind: "new",
15
+ snapshot: {
16
+ ...snapshot,
17
+ observations: [...snapshot.observations, observation],
18
+ },
19
+ };
20
+ };
5
21
  /** Create the initial immutable reducer state for one validated scenario. */
6
22
  export const createProcessReactiveSnapshot = (scenario) => ({
7
23
  status: "running",
@@ -35,6 +51,8 @@ const reduceControlInput = (snapshot, input) => {
35
51
  : { kind: "waiting", snapshot, evaluations: [] };
36
52
  if (input.kind === "scenario_deadline")
37
53
  return finishedDecision(snapshot, "scenario_deadline");
54
+ if (input.kind === "target_lost")
55
+ return finishedDecision(snapshot, "target_lost");
38
56
  return finishedDecision(snapshot, "cancelled");
39
57
  };
40
58
  /** Advance one validated scenario using only an observation or explicit deadline. */
@@ -44,17 +62,10 @@ export const reduceProcessReactiveScenario = (scenario, snapshot, input) => {
44
62
  return controlDecision;
45
63
  if (input.kind !== "observation")
46
64
  return finishedDecision(snapshot, "capture_incomplete");
47
- const prior = snapshot.observations.at(-1);
48
- if ((prior !== undefined &&
49
- input.observation.capture_order <= prior.capture_order) ||
50
- snapshot.observations.some(({ event_id }) => event_id === input.observation.event_id))
51
- return finishedDecision(snapshot, "capture_incomplete");
52
- if (snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
65
+ const admitted = observeInput(snapshot, input.observation);
66
+ if (admitted.kind === "invalid")
53
67
  return finishedDecision(snapshot, "capture_incomplete");
54
- const observed = {
55
- ...snapshot,
56
- observations: [...snapshot.observations, input.observation],
57
- };
68
+ const observed = admitted.snapshot;
58
69
  const state = scenario.states.find(({ id }) => id === observed.active_state);
59
70
  if (state === undefined)
60
71
  return finishedDecision(observed, "target_lost");
@@ -75,7 +75,7 @@ const applyProcessReactiveTransition = (input) => {
75
75
  record,
76
76
  };
77
77
  };
78
- const effectObservationMatchesAction = (action, observation) => {
78
+ const effectObservationMatchesAction = (action, observation, committedSensitiveInputs) => {
79
79
  const payload = typeof observation.payload === "object" &&
80
80
  observation.payload !== null &&
81
81
  !Array.isArray(observation.payload)
@@ -98,14 +98,16 @@ const effectObservationMatchesAction = (action, observation) => {
98
98
  if (action.type === "send_input")
99
99
  return (payload["type"] === "input" &&
100
100
  payload["data"] ===
101
- (action.sensitive
102
- ? `<redacted-input:${String(Buffer.byteLength(action.data))}-bytes>`
103
- : action.data));
101
+ expectedInputEvidence(action.data, action.sensitive, committedSensitiveInputs));
104
102
  if (action.type === "resize")
105
103
  return (payload["type"] === "resize" &&
106
104
  payload["data"] === `${String(action.columns)}x${String(action.rows)}`);
107
105
  return payload["type"] === "signal" && payload["data"] === action.signal;
108
106
  };
107
+ const expectedInputEvidence = (data, sensitive, committedSensitiveInput) => !sensitive ||
108
+ (committedSensitiveInput && /^<redacted-input:\d+-bytes>$/.test(data))
109
+ ? data
110
+ : `<redacted-input:${String(Buffer.byteLength(data))}-bytes>`;
109
111
  const failedProposal = (proposed, outcome) => ({
110
112
  kind: "finished",
111
113
  outcome,
@@ -113,7 +115,7 @@ const failedProposal = (proposed, outcome) => ({
113
115
  snapshot: { ...proposed.snapshot, status: "finished", outcome },
114
116
  });
115
117
  /** Commit a proposal only after the host reports every effect as successful. */
116
- export const commitProcessReactiveProposal = (scenario, proposed, results) => {
118
+ export const commitProcessReactiveProposal = (scenario, proposed, results, options = {}) => {
117
119
  if (results.some(({ status }) => status === "target_lost"))
118
120
  return failedProposal(proposed, "target_lost");
119
121
  const state = scenario.states.find(({ id }) => id === proposed.snapshot.active_state);
@@ -137,7 +139,7 @@ export const commitProcessReactiveProposal = (scenario, proposed, results) => {
137
139
  const action = transition.actions[index];
138
140
  const effectObservation = result.observation;
139
141
  if (action === undefined ||
140
- !effectObservationMatchesAction(action, effectObservation) ||
142
+ !effectObservationMatchesAction(action, effectObservation, options.committedSensitiveInputs ?? false) ||
141
143
  !Number.isSafeInteger(effectObservation.capture_order) ||
142
144
  effectObservation.capture_order <= previousOrder ||
143
145
  knownEventIds.has(effectObservation.event_id))
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { resolve } from "node:path";
3
3
  import canonicalize from "canonicalize";
4
4
  import { z } from "zod";
5
+ import { processReactiveScenarioSchema, } from "./processReactiveScenario.js";
5
6
  import { replayMachineSchema } from "./replayMachine.js";
6
7
  const positiveBudget = z.number().int().positive();
7
8
  const timedEventBase = { at_ms: z.number().int().nonnegative() };
@@ -44,6 +45,23 @@ export const digestProcessCommitment = (value) => {
44
45
  throw new TypeError("Process capture commitment is not canonical JSON");
45
46
  return createHash("sha256").update(serialized).digest("hex");
46
47
  };
48
+ const reactiveScenarioCommitment = (scenario) => scenario === null
49
+ ? null
50
+ : {
51
+ ...scenario,
52
+ states: scenario.states.map((state) => ({
53
+ ...state,
54
+ on: state.on.map((transition) => ({
55
+ ...transition,
56
+ actions: transition.actions.map((action) => action.type === "send_input" && action.sensitive
57
+ ? {
58
+ ...action,
59
+ data: `<redacted-input:${String(Buffer.byteLength(action.data))}-bytes>`,
60
+ }
61
+ : action),
62
+ })),
63
+ })),
64
+ };
47
65
  /** Build the secret-safe scenario projection committed by Process Capture v4. */
48
66
  export const processScenarioCommitment = (scenario, executableSha256) => ({
49
67
  ...scenario,
@@ -51,6 +69,7 @@ export const processScenarioCommitment = (scenario, executableSha256) => ({
51
69
  name,
52
70
  scenario.secret_aliases.includes(name) ? "<redacted-secret>" : value,
53
71
  ])),
72
+ reactive: reactiveScenarioCommitment(scenario.reactive),
54
73
  unknown_registry_approved: scenario.unknown_registry_approved === true,
55
74
  executable_sha256: executableSha256 ?? null,
56
75
  });
@@ -71,6 +90,7 @@ export const processComparisonContract = (scenario) => ({
71
90
  normalization: scenario.normalization,
72
91
  command_shims: scenario.command_shims,
73
92
  replay: scenario.replay,
93
+ reactive: reactiveScenarioCommitment(scenario.reactive),
74
94
  });
75
95
  /**
76
96
  * Boundary schema for one explicitly approved, bounded process experiment.
@@ -258,6 +278,7 @@ export const processScenarioSchema = z
258
278
  websocket_messages: [],
259
279
  websocket_connections: [],
260
280
  }),
281
+ reactive: processReactiveScenarioSchema.nullable().default(null),
261
282
  })
262
283
  .strict()
263
284
  .superRefine((scenario, context) => {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Classify a ZIP-backed package by its complete lower-cased path suffix.
3
+ *
4
+ * This is deliberately extension-only. Callers must separately verify ZIP
5
+ * magic before trusting the classification for a root input.
6
+ */
7
+ export const zipPackageFormatForPath = (path) => {
8
+ const lower = path.toLowerCase();
9
+ if (lower.endsWith(".zip"))
10
+ return "zip";
11
+ if (lower.endsWith(".ipa"))
12
+ return "ipa";
13
+ if (lower.endsWith(".apk") || lower.endsWith(".aab"))
14
+ return "apk";
15
+ if (lower.endsWith(".msix") || lower.endsWith(".msixbundle"))
16
+ return "msix";
17
+ if (lower.endsWith(".appx") || lower.endsWith(".appxbundle"))
18
+ return "appx";
19
+ return undefined;
20
+ };
@@ -1,7 +1,7 @@
1
1
  /** Generated from package.json and package-lock.json; do not edit. */
2
2
  export const PACKAGE_METADATA = {
3
3
  name: "rea-agents",
4
- version: "2.3.0", // x-release-please-version
4
+ version: "2.4.0", // x-release-please-version
5
5
  serverSdkVersion: "2.0.0-beta.4",
6
6
  clientSdkVersion: "2.0.0-beta.4",
7
7
  coreSdkVersion: "2.0.0-beta.4",
@@ -35,6 +35,7 @@ export class NativeMacOSProvider {
35
35
  ? { available: true, reason: null }
36
36
  : {
37
37
  available: false,
38
+ availabilityCode: "unsupported_host",
38
39
  reason: "Native macOS utilities require macOS.",
39
40
  };
40
41
  return Object.freeze({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rea-agents",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -4,7 +4,7 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, an
4
4
  metadata:
5
5
  version: "23"
6
6
  tool_count: 98
7
- catalog_digest: "71de8a7174176823fd82c2666130233b5a1b4a1613583270d268774775b410bc"
7
+ catalog_digest: "aa413204625f3aeb62732d0f732b9b8b24832eea4520214b1e6bd204e599bcd6"
8
8
  ---
9
9
 
10
10
  # REA
@@ -22,7 +22,8 @@ Choose the first tool from the target the user supplied. Do not call
22
22
 
23
23
  - ASAR or extracted JavaScript/Electron tree:
24
24
  `analyze_javascript_application`.
25
- - Archive, application package, ZIP/APK/IPA, or DMG: `inventory_artifact`.
25
+ - Archive, application package, ZIP/APK/IPA/MSIX/AppX, or DMG:
26
+ `inventory_artifact`.
26
27
  - Managed PE/CLI assembly: `inspect_managed_artifact`.
27
28
  - User-owned browser page already open: `list_browser_targets`.
28
29
  - User-owned Electron runtime already open: `list_electron_targets`.
@@ -24,8 +24,8 @@ parser and must not become an implicit setup dependency.
24
24
  ## Packages and extraction
25
25
 
26
26
  Call `inventory_artifact` before extraction for application bundles, archives,
27
- ZIP/APK/IPA, ASAR, or DMG inputs. Continue deterministic occurrence pages from
28
- the returned offset only when needed. Cite graph manifest IDs.
27
+ ZIP/APK/IPA/MSIX/AppX, ASAR, or DMG inputs. Continue deterministic occurrence
28
+ pages from the returned offset only when needed. Cite graph manifest IDs.
29
29
 
30
30
  `extract_artifact` requires explicit approval, an absent absolute output root,
31
31
  and selected occurrence IDs. Never extract every entry implicitly. Symlinks and