rea-agents 0.2.1
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/LICENSE +21 -0
- package/README.md +312 -0
- package/bridge/hopper_bridge.py +557 -0
- package/dist/application/AnalysisProvider.js +1 -0
- package/dist/application/BinarySession.js +173 -0
- package/dist/application/DirectAnalysis.js +31 -0
- package/dist/application/Doctor.js +110 -0
- package/dist/application/EnhancedTools.js +357 -0
- package/dist/application/EvidenceLedger.js +65 -0
- package/dist/application/HopperToolPort.js +1 -0
- package/dist/application/LoopbackReplay.js +84 -0
- package/dist/application/ProcessHarness.js +347 -0
- package/dist/application/Setup.js +240 -0
- package/dist/application/homebrew.js +19 -0
- package/dist/application/runtime.js +11 -0
- package/dist/cli.js +88 -0
- package/dist/config.js +86 -0
- package/dist/contracts/enhancedInputs.js +73 -0
- package/dist/contracts/toolContracts.js +144 -0
- package/dist/contracts/toolOutputSchemas.js +267 -0
- package/dist/domain/binaryTarget.js +321 -0
- package/dist/domain/errors.js +81 -0
- package/dist/domain/evidence.js +146 -0
- package/dist/domain/evidenceBundle.js +11 -0
- package/dist/domain/hopperValues.js +253 -0
- package/dist/domain/jsonValue.js +3 -0
- package/dist/domain/processCapture.js +252 -0
- package/dist/domain/result.js +4 -0
- package/dist/domain/symbolAnalysis.js +68 -0
- package/dist/hopper/BridgeLauncher.js +130 -0
- package/dist/hopper/HopperClient.js +314 -0
- package/dist/hopper/HopperProvider.js +66 -0
- package/dist/hopper/protocol.js +32 -0
- package/dist/identity.js +14 -0
- package/dist/logger.js +27 -0
- package/dist/main.js +81 -0
- package/dist/server/createServer.js +30 -0
- package/dist/server/registerEnhancedTools.js +53 -0
- package/dist/server/registerOfficialTools.js +59 -0
- package/dist/server/registerSessionTools.js +159 -0
- package/dist/server/toolLogging.js +11 -0
- package/dist/server/toolResult.js +44 -0
- package/package.json +109 -0
- package/scripts/rea.mjs +15 -0
- package/scripts/rebuild-native.mjs +22 -0
- package/skills/rea-analysis/SKILL.md +44 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { evidenceBundleSchema } from "../domain/evidenceBundle.js";
|
|
3
|
+
import { processCaptureSchema, processScenarioSchema, } from "../domain/processCapture.js";
|
|
4
|
+
import { enhancedInputSchemas } from "./enhancedInputs.js";
|
|
5
|
+
import { enhancedOutputSchemas, officialOutputSchemas, sessionOutputSchemas, } from "./toolOutputSchemas.js";
|
|
6
|
+
const document = z.string().optional().describe("The document name");
|
|
7
|
+
const address = z.string().describe("A Hopper address");
|
|
8
|
+
const optionalAddress = address.optional();
|
|
9
|
+
const procedure = z.string().describe("The procedure name or address");
|
|
10
|
+
const pattern = z.string().describe("The regex pattern to search for");
|
|
11
|
+
const caseSensitive = z
|
|
12
|
+
.boolean()
|
|
13
|
+
.default(false)
|
|
14
|
+
.describe("Whether to match case");
|
|
15
|
+
const pagination = {
|
|
16
|
+
offset: z.number().int().min(0).default(0),
|
|
17
|
+
limit: z.number().int().min(1).max(500).default(100),
|
|
18
|
+
};
|
|
19
|
+
const annotations = (name, kind) => ({
|
|
20
|
+
readOnlyHint: kind === "enhanced" ||
|
|
21
|
+
name === "export_evidence_bundle" ||
|
|
22
|
+
(!name.startsWith("set_") &&
|
|
23
|
+
name !== "unset_bookmark" &&
|
|
24
|
+
name !== "goto_address" &&
|
|
25
|
+
kind !== "session"),
|
|
26
|
+
destructiveHint: name === "unset_bookmark" ||
|
|
27
|
+
name === "set_address_name" ||
|
|
28
|
+
name === "set_addresses_names" ||
|
|
29
|
+
name === "set_comment" ||
|
|
30
|
+
name === "set_inline_comment",
|
|
31
|
+
idempotentHint: true,
|
|
32
|
+
openWorldHint: false,
|
|
33
|
+
});
|
|
34
|
+
const official = (name, description, inputSchema) => ({
|
|
35
|
+
name,
|
|
36
|
+
description,
|
|
37
|
+
kind: "official-proxy",
|
|
38
|
+
inputSchema,
|
|
39
|
+
outputSchema: requireOutputSchema(officialOutputSchemas, name),
|
|
40
|
+
annotations: annotations(name, "official-proxy"),
|
|
41
|
+
});
|
|
42
|
+
const enhanced = (name, description, inputSchema) => ({
|
|
43
|
+
name,
|
|
44
|
+
description,
|
|
45
|
+
kind: "enhanced",
|
|
46
|
+
inputSchema,
|
|
47
|
+
outputSchema: requireOutputSchema(enhancedOutputSchemas, name),
|
|
48
|
+
annotations: annotations(name, "enhanced"),
|
|
49
|
+
});
|
|
50
|
+
const session = (name, description, inputSchema) => ({
|
|
51
|
+
name,
|
|
52
|
+
description,
|
|
53
|
+
kind: "session",
|
|
54
|
+
inputSchema,
|
|
55
|
+
outputSchema: requireOutputSchema(sessionOutputSchemas, name),
|
|
56
|
+
annotations: annotations(name, "session"),
|
|
57
|
+
});
|
|
58
|
+
const requireOutputSchema = (schemas, name) => {
|
|
59
|
+
const schema = schemas[name];
|
|
60
|
+
if (schema === undefined)
|
|
61
|
+
throw new Error(`Missing output schema for ${name}`);
|
|
62
|
+
return schema;
|
|
63
|
+
};
|
|
64
|
+
/** Bridge operations exposed without additional application composition. */
|
|
65
|
+
export const OFFICIAL_TOOL_CONTRACTS = [
|
|
66
|
+
official("address_name", "Resolve the analyzed name at a code or data address, defaulting to Hopper's current cursor. Use before following a symbol into xrefs; null means Hopper has no name at that address.", z.object({ document, address: optionalAddress })),
|
|
67
|
+
official("comment", "Read the regular analysis comment at an address, defaulting to the current cursor. This is read-only and returns null when no comment exists; use set_comment to persist a finding.", z.object({ document, address: optionalAddress })),
|
|
68
|
+
official("current_address", "Return Hopper's current cursor address for the selected document. Use only for interactive navigation state; prefer explicit addresses in reproducible investigations.", z.object({ document })),
|
|
69
|
+
official("current_procedure", "Return the analyzed procedure containing Hopper's current cursor. Use procedure_info or analyze_function next; this depends on GUI cursor state and is not an exhaustive lookup.", z.object({ document })),
|
|
70
|
+
official("current_document", "Return the document currently selected by REA's Hopper bridge. Use list_documents before switching when several documents are open.", z.object({})),
|
|
71
|
+
official("goto_address", "Move Hopper's GUI cursor to a hexadecimal address and return the resolved address. This changes navigation state but not analysis data; use explicit-address tools for headless workflows.", z.object({ address, document })),
|
|
72
|
+
official("inline_comment", "Read the inline instruction comment at an address, defaulting to the current cursor. Returns null when absent; use set_inline_comment to write one.", z.object({ document, address: optionalAddress })),
|
|
73
|
+
official("list_bookmarks", "List every bookmark in the selected Hopper document as address and name pairs. Use bookmarks as analyst-authored navigation aids; this does not discover code references.", z.object({ document })),
|
|
74
|
+
official("list_documents", "List all Hopper documents visible to the bridge. Use before set_current_document when a provider session contains multiple documents.", z.object({})),
|
|
75
|
+
official("list_names", "Page through analyzed names as address/value pairs. Results are bounded by offset and limit; follow next_offset until has_more is false before claiming exhaustive symbol coverage.", z.object({ document, address: optionalAddress, ...pagination })),
|
|
76
|
+
official("list_procedures", "Page through analyzed procedures as address/value pairs. Wait for Hopper analysis first, then follow next_offset for exhaustive coverage; use returned addresses with analyze_function or procedure_pseudo_code.", z.object({ document, ...pagination })),
|
|
77
|
+
official("list_segments", "List all segments and sections in the selected document. Hopper's public API does not expose permissions, so writable and executable are null with explicit capability metadata.", z.object({ document })),
|
|
78
|
+
official("list_strings", "Page through analyzed strings, or filter to one address, as address/value pairs. Follow next_offset for exhaustive results and use xrefs on interesting string addresses.", z.object({ document, address: optionalAddress, ...pagination })),
|
|
79
|
+
official("next_address", "Return the next analyzed object address after an explicit address or current cursor. This is a navigation primitive, not instruction-flow or CFG analysis.", z.object({ document, address: optionalAddress })),
|
|
80
|
+
official("prev_address", "Return the previous analyzed instruction start before an explicit address or current cursor. This is a navigation primitive and may fail at document boundaries.", z.object({ document, address: optionalAddress })),
|
|
81
|
+
official("procedure_address", "Resolve a procedure symbol name or hexadecimal address to its entry address. Use this to canonicalize user-supplied identifiers before xrefs, assembly, or decompilation.", z.object({ procedure, document })),
|
|
82
|
+
official("procedure_assembly", "Return assembly for one analyzed procedure identified by symbol or hexadecimal address. Use when pseudocode loses calling-convention or instruction-level detail; output is currently returned as one unpaginated string.", z.object({ procedure, document })),
|
|
83
|
+
official("procedure_callees", "Return Hopper's analyzed direct callees for one procedure identified by symbol or address. Indirect calls may be absent; use analyze_function and xrefs to corroborate the call path.", z.object({ procedure, document })),
|
|
84
|
+
official("procedure_callers", "Return Hopper's analyzed direct callers for one procedure identified by symbol or address. Results reflect completed static analysis and may omit indirect references.", z.object({ procedure, document })),
|
|
85
|
+
official("procedure_info", "Return bounded metadata for one procedure identified by symbol or address: entrypoint, signature, locals, size, and block count. Follow with decompilation or assembly for behavior.", z.object({ procedure, document })),
|
|
86
|
+
official("procedure_references", "Return a bounded set of raw incoming or outgoing reference edges for one procedure. Endpoint procedures are resolved only when Hopper reports containment; reference kinds remain explicitly unavailable.", z.object({
|
|
87
|
+
procedure,
|
|
88
|
+
direction: z.enum(["incoming", "outgoing"]).default("outgoing"),
|
|
89
|
+
offset: z.number().int().min(0).default(0),
|
|
90
|
+
limit: z.number().int().min(1).max(500).default(100),
|
|
91
|
+
max_instructions: z.number().int().min(1).max(5000).default(500),
|
|
92
|
+
document,
|
|
93
|
+
})),
|
|
94
|
+
official("procedure_pseudo_code", "Decompile one analyzed procedure by symbol name or hexadecimal address. Returns Hopper pseudocode, not original source, and may return null; request procedure_assembly when instruction precision matters.", z.object({ procedure, document })),
|
|
95
|
+
official("resolve_containing_procedure", "Resolve an arbitrary address, including an xref source or interior instruction, to its Hopper-analyzed containing procedure. A negative result includes an explicit reason and is not guessed from nearby symbols.", z.object({ address, document })),
|
|
96
|
+
official("search_procedures", "Regex-search all analyzed procedure names with optional case sensitivity. The current bridge returns an unpaginated address/name map, so constrain patterns and use list_procedures for controlled exhaustive traversal.", z.object({ pattern, case_sensitive: caseSensitive, document })),
|
|
97
|
+
official("search_strings", "Regex-search all analyzed strings with optional case sensitivity. The current bridge is unpaginated and evaluates Python regex, so use narrow patterns and follow matches with xrefs.", z.object({ pattern, case_sensitive: caseSensitive, document })),
|
|
98
|
+
official("set_address_name", "Assign an analyst name to one hexadecimal address and report Hopper's boolean result. This mutates analysis metadata; read it back with address_name before relying on it.", z.object({ address, name: z.string(), document })),
|
|
99
|
+
official("set_addresses_names", "Assign analyst names to an address/name map and return per-address success booleans. This mutates analysis metadata; use for bounded batches and verify failures individually.", z.object({ names: z.record(z.string(), z.string()), document })),
|
|
100
|
+
official("set_bookmark", "Create or replace a bookmark at a hexadecimal address. This mutates navigation metadata; verify with list_bookmarks and do not treat bookmarks as binary evidence.", z.object({ address, name: z.string().optional(), document })),
|
|
101
|
+
official("set_comment", "Write a regular analysis comment at a hexadecimal address and return whether readback matched. This mutates the Hopper document; use comments to record evidence IDs or reasoning.", z.object({ address, comment: z.string(), document })),
|
|
102
|
+
official("set_current_document", "Select an already-open Hopper document by exact document name. This changes subsequent default-document routing; call list_documents first and prefer explicit document inputs where reproducibility matters.", z.object({ document: z.string() })),
|
|
103
|
+
official("set_inline_comment", "Write an inline instruction comment at a hexadecimal address and return whether readback matched. This mutates analysis metadata; confirm with inline_comment.", z.object({ address, comment: z.string(), document })),
|
|
104
|
+
official("unset_bookmark", "Remove the bookmark at a hexadecimal address and return whether it is absent. This mutates navigation metadata and does not alter binary bytes.", z.object({ address, document })),
|
|
105
|
+
official("xrefs", "Return analyzed references to a code or data address, defaulting to the current cursor. Use to connect strings, globals, selectors, and functions; bare addresses are untyped and indirect references may be incomplete.", z.object({ document, address: optionalAddress })),
|
|
106
|
+
];
|
|
107
|
+
/** Bounded workflows composed from one or more bridge operations. */
|
|
108
|
+
export const ENHANCED_TOOL_CONTRACTS = [
|
|
109
|
+
enhanced("swift_classes", "Discover legacy-mangled Swift class procedures after exhaustively paging analyzed procedures. Returns at most 100 entries and scans at most 5,000 symbols; use analyze_swift_types for other Swift kinds.", enhancedInputSchemas.swift_classes),
|
|
110
|
+
enhanced("get_objc_classes", "Discover and deduplicate Objective-C class labels after exhaustively paging names, optionally filtering by literal substring. Returns at most 100 classes; inspect matching metadata and references next.", enhancedInputSchemas.get_objc_classes),
|
|
111
|
+
enhanced("get_objc_protocols", "Discover and deduplicate Objective-C and Swift protocol labels after exhaustively paging names. Returns at most 100 entries; use xrefs or analyze_function to connect a protocol to implementations.", enhancedInputSchemas.get_objc_protocols),
|
|
112
|
+
enhanced("batch_decompile", "Decompile up to 20 explicit procedure symbols or addresses concurrently. Per-item strings may contain errors or no-output markers, so validate each result; use analyze_function for a richer single-function dossier.", enhancedInputSchemas.batch_decompile),
|
|
113
|
+
enhanced("get_call_graph", "Traverse Hopper's caller or callee relationships from one symbol or address for at most five levels. Nodes preserve per-procedure errors; indirect calls may be missing and results are not a whole-program CFG.", enhancedInputSchemas.get_call_graph),
|
|
114
|
+
enhanced("analyze_swift_types", "Categorize exhaustively paged procedure names into Swift classes, structs, enums, protocols, extensions, and other symbols. Scans at most 5,000 names and returns at most 50 entries per category.", enhancedInputSchemas.analyze_swift_types),
|
|
115
|
+
enhanced("find_xrefs_to_name", "Resolve a name through Hopper and return analyzed references to its address. Use when starting from a selector or symbol; resolution failure is returned explicitly and xrefs remain untyped.", enhancedInputSchemas.find_xrefs_to_name),
|
|
116
|
+
enhanced("binary_overview", "Use immediately after opening a target to summarize document, exhaustive procedure/string counts, and a bounded segment sample. detail controls segment fields and limit controls only the returned segment sample.", enhancedInputSchemas.binary_overview),
|
|
117
|
+
enhanced("analyze_function", "Preferred bounded analysis for one procedure symbol or address. Returns identity, pseudocode, optional assembly, comments, calls, incoming references, and blocks; unsupported outgoing references and CFG edges carry explicit unavailable metadata.", enhancedInputSchemas.analyze_function),
|
|
118
|
+
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; it does not infer reference kinds.", enhancedInputSchemas.trace_feature),
|
|
119
|
+
];
|
|
120
|
+
/** Target lifecycle tools available only on the long-lived MCP adapter. */
|
|
121
|
+
export const SESSION_TOOL_CONTRACTS = [
|
|
122
|
+
session("open_binary", "Open a local executable, application bundle, or Hopper database, replacing the active target only after validation. This launches Hopper and may show UI; call binary_overview after success.", z.object({ path: z.string().min(1) })),
|
|
123
|
+
session("close_binary", "Close the active Hopper-backed target and release its provider process. The operation is idempotent; call binary_session to verify the session is closed.", z.object({})),
|
|
124
|
+
session("binary_session", "Report whether a target is open and, when open, its canonical path, format, and kind. Use before analysis calls or target switches; this performs no analysis.", z.object({})),
|
|
125
|
+
session("export_evidence_bundle", "Return the session's deterministic Evidence v2 bundle without clearing it. Records are sorted by evidence ID, and array order carries no investigative meaning.", z.object({})),
|
|
126
|
+
session("import_evidence_bundle", "Validate and atomically merge a local Evidence v2 bundle supplied as data. Semantic IDs are recomputed; tampering, unsupported versions, conflicts, and ledger overflow reject the entire import.", z.object({ bundle: evidenceBundleSchema })),
|
|
127
|
+
session("capture_process_scenario", "Run one bounded process under a PTY using operator-approved executable and working roots. Requires approved: true on every call. Captures normalized terminal frames, sampled descendants, filesystem snapshots, and loopback HTTP/WebSocket replay. This launches a process and is disabled unless operator policy enables it; it is not a security sandbox.", processScenarioSchema),
|
|
128
|
+
session("compare_process_captures", "Compare two bounded process captures across terminal, exit, sampled process, filesystem, HTTP, and WebSocket evidence. Missing or truncated observations are never treated as equivalent.", z.object({
|
|
129
|
+
left_evidence_id: z.string().regex(/^ev_[a-f0-9]{64}$/u),
|
|
130
|
+
left: processCaptureSchema,
|
|
131
|
+
right_evidence_id: z.string().regex(/^ev_[a-f0-9]{64}$/u),
|
|
132
|
+
right: processCaptureSchema,
|
|
133
|
+
})),
|
|
134
|
+
];
|
|
135
|
+
/**
|
|
136
|
+
* Complete ordered public inventory used by registration and verification.
|
|
137
|
+
* Keep this collection at 50 tools unless a deliberate contract change updates
|
|
138
|
+
* snapshots, package verification, and real-Hopper verification together.
|
|
139
|
+
*/
|
|
140
|
+
export const TOOL_CONTRACTS = [
|
|
141
|
+
...OFFICIAL_TOOL_CONTRACTS,
|
|
142
|
+
...ENHANCED_TOOL_CONTRACTS,
|
|
143
|
+
...SESSION_TOOL_CONTRACTS,
|
|
144
|
+
];
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { evidenceSchema } from "../domain/evidence.js";
|
|
3
|
+
import { processCaptureSchema } from "../domain/processCapture.js";
|
|
4
|
+
import { evidenceBundleSchema } from "../domain/evidenceBundle.js";
|
|
5
|
+
const resultOf = (schema) => evidenceSchema.omit({ result: true }).extend({ result: schema });
|
|
6
|
+
const lifecycleResultOf = (schema) => z.object({ result: schema });
|
|
7
|
+
const comparisonStatus = z.enum([
|
|
8
|
+
"unchanged",
|
|
9
|
+
"added",
|
|
10
|
+
"removed",
|
|
11
|
+
"changed",
|
|
12
|
+
"truncated",
|
|
13
|
+
"unknown",
|
|
14
|
+
]);
|
|
15
|
+
const nullableText = z.string().nullable();
|
|
16
|
+
const addressList = z.array(z.string());
|
|
17
|
+
const addressedEntry = z.object({ address: z.string(), name: z.string() });
|
|
18
|
+
const procedureIdentity = z.object({ address: z.string(), name: z.string() });
|
|
19
|
+
const containingProcedureResolution = z.discriminatedUnion("found", [
|
|
20
|
+
z.object({
|
|
21
|
+
query_address: z.string(),
|
|
22
|
+
found: z.literal(true),
|
|
23
|
+
procedure: procedureIdentity,
|
|
24
|
+
}),
|
|
25
|
+
z.object({
|
|
26
|
+
query_address: z.string(),
|
|
27
|
+
found: z.literal(false),
|
|
28
|
+
procedure: z.null(),
|
|
29
|
+
reason: z.enum(["outside_segments", "not_in_procedure"]),
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
const unavailable = z.object({
|
|
33
|
+
available: z.literal(false),
|
|
34
|
+
reason: z.string(),
|
|
35
|
+
});
|
|
36
|
+
const bounded = (item) => z.object({
|
|
37
|
+
items: z.array(item),
|
|
38
|
+
total: z.number().int().min(0).nullable(),
|
|
39
|
+
returned: z.number().int().min(0),
|
|
40
|
+
truncated: z.boolean(),
|
|
41
|
+
next_offset: z.number().int().min(0).nullable(),
|
|
42
|
+
});
|
|
43
|
+
const pageOutput = z.object({
|
|
44
|
+
items: z.array(z.object({ address: z.string(), value: z.string() })),
|
|
45
|
+
offset: z.number().int().min(0),
|
|
46
|
+
limit: z.number().int().min(1),
|
|
47
|
+
total: z.number().int().min(0),
|
|
48
|
+
next_offset: z.number().int().min(0).nullable(),
|
|
49
|
+
has_more: z.boolean(),
|
|
50
|
+
});
|
|
51
|
+
const segmentOutput = resultOf(z.array(z.object({
|
|
52
|
+
name: z.string(),
|
|
53
|
+
start: z.string(),
|
|
54
|
+
end: z.string(),
|
|
55
|
+
writable: z.boolean().nullable(),
|
|
56
|
+
executable: z.boolean().nullable(),
|
|
57
|
+
permissions: unavailable,
|
|
58
|
+
sections: z.array(z.object({ name: z.string(), start: z.string(), end: z.string() })),
|
|
59
|
+
})));
|
|
60
|
+
const procedureInfoOutput = resultOf(z.object({
|
|
61
|
+
name: z.string(),
|
|
62
|
+
entrypoint: z.string(),
|
|
63
|
+
basicblock_count: z.number().int().min(0),
|
|
64
|
+
length: z.number().min(0),
|
|
65
|
+
signature: nullableText,
|
|
66
|
+
locals: z.array(z.json()),
|
|
67
|
+
}));
|
|
68
|
+
const symbolDiscoveryOutput = (property) => resultOf(z.object({
|
|
69
|
+
count: z.number().int().min(0),
|
|
70
|
+
[property]: z.array(addressedEntry),
|
|
71
|
+
}));
|
|
72
|
+
const graphNode = z.union([
|
|
73
|
+
z.object({ address: z.string(), calls: z.array(z.string()) }),
|
|
74
|
+
z.object({ address: z.string(), error: z.string() }),
|
|
75
|
+
]);
|
|
76
|
+
const referenceEdge = z.object({
|
|
77
|
+
source_address: z.string(),
|
|
78
|
+
target_address: z.string(),
|
|
79
|
+
source_procedure: procedureIdentity.nullable(),
|
|
80
|
+
target_procedure: procedureIdentity.nullable(),
|
|
81
|
+
kind: unavailable,
|
|
82
|
+
});
|
|
83
|
+
const referencedValue = z.object({
|
|
84
|
+
address: z.string(),
|
|
85
|
+
value: z.string(),
|
|
86
|
+
source_address: z.string(),
|
|
87
|
+
});
|
|
88
|
+
const functionDossierOutput = resultOf(z.object({
|
|
89
|
+
procedure: z.object({
|
|
90
|
+
address: z.string(),
|
|
91
|
+
name: z.string(),
|
|
92
|
+
signature: nullableText,
|
|
93
|
+
locals: z.array(z.json()),
|
|
94
|
+
}),
|
|
95
|
+
pseudocode: z.object({
|
|
96
|
+
text: z.string(),
|
|
97
|
+
total_chars: z.number().int().min(0),
|
|
98
|
+
returned_chars: z.number().int().min(0),
|
|
99
|
+
truncated: z.boolean(),
|
|
100
|
+
next_offset: z.number().int().min(0).nullable(),
|
|
101
|
+
}),
|
|
102
|
+
assembly: bounded(z.string()),
|
|
103
|
+
comments: bounded(z.object({
|
|
104
|
+
address: z.string(),
|
|
105
|
+
kind: z.enum(["comment", "inline"]),
|
|
106
|
+
text: z.string(),
|
|
107
|
+
})),
|
|
108
|
+
callers: bounded(procedureIdentity),
|
|
109
|
+
callees: bounded(procedureIdentity),
|
|
110
|
+
incoming_references: bounded(referenceEdge),
|
|
111
|
+
outgoing_references: bounded(referenceEdge),
|
|
112
|
+
referenced_strings: bounded(referencedValue),
|
|
113
|
+
referenced_names: bounded(referencedValue),
|
|
114
|
+
basic_blocks: bounded(z.object({
|
|
115
|
+
start: z.string(),
|
|
116
|
+
end: z.string(),
|
|
117
|
+
successors: z.array(z.string()),
|
|
118
|
+
})),
|
|
119
|
+
instruction_scan: z.object({
|
|
120
|
+
scanned: z.number().int().min(0),
|
|
121
|
+
truncated: z.boolean(),
|
|
122
|
+
}),
|
|
123
|
+
}));
|
|
124
|
+
/** Exact structured-content schemas for the direct Hopper operations. */
|
|
125
|
+
export const officialOutputSchemas = {
|
|
126
|
+
address_name: resultOf(nullableText),
|
|
127
|
+
comment: resultOf(nullableText),
|
|
128
|
+
current_address: resultOf(z.string()),
|
|
129
|
+
current_procedure: resultOf(z.string()),
|
|
130
|
+
current_document: resultOf(z.string()),
|
|
131
|
+
goto_address: resultOf(z.string()),
|
|
132
|
+
inline_comment: resultOf(nullableText),
|
|
133
|
+
list_bookmarks: resultOf(z.array(addressedEntry)),
|
|
134
|
+
list_documents: resultOf(z.array(z.string())),
|
|
135
|
+
list_names: resultOf(pageOutput),
|
|
136
|
+
list_procedures: resultOf(pageOutput),
|
|
137
|
+
list_segments: segmentOutput,
|
|
138
|
+
list_strings: resultOf(pageOutput),
|
|
139
|
+
next_address: resultOf(z.string()),
|
|
140
|
+
prev_address: resultOf(z.string()),
|
|
141
|
+
procedure_address: resultOf(z.string()),
|
|
142
|
+
procedure_assembly: resultOf(z.string()),
|
|
143
|
+
procedure_callees: resultOf(addressList),
|
|
144
|
+
procedure_callers: resultOf(addressList),
|
|
145
|
+
procedure_info: procedureInfoOutput,
|
|
146
|
+
procedure_references: resultOf(z.object({
|
|
147
|
+
procedure: procedureIdentity,
|
|
148
|
+
direction: z.enum(["incoming", "outgoing"]),
|
|
149
|
+
references: bounded(z.object({
|
|
150
|
+
source_address: z.string(),
|
|
151
|
+
target_address: z.string(),
|
|
152
|
+
source_procedure: procedureIdentity.nullable(),
|
|
153
|
+
target_procedure: procedureIdentity.nullable(),
|
|
154
|
+
kind: unavailable,
|
|
155
|
+
})),
|
|
156
|
+
instructions_scanned: z.number().int().min(0),
|
|
157
|
+
instruction_scan_truncated: z.boolean(),
|
|
158
|
+
})),
|
|
159
|
+
procedure_pseudo_code: resultOf(nullableText),
|
|
160
|
+
resolve_containing_procedure: resultOf(containingProcedureResolution),
|
|
161
|
+
search_procedures: resultOf(z.record(z.string(), z.string())),
|
|
162
|
+
search_strings: resultOf(z.record(z.string(), z.string())),
|
|
163
|
+
set_address_name: resultOf(z.boolean()),
|
|
164
|
+
set_addresses_names: resultOf(z.record(z.string(), z.boolean())),
|
|
165
|
+
set_bookmark: resultOf(z.boolean()),
|
|
166
|
+
set_comment: resultOf(z.boolean()),
|
|
167
|
+
set_current_document: resultOf(z.string()),
|
|
168
|
+
set_inline_comment: resultOf(z.boolean()),
|
|
169
|
+
unset_bookmark: resultOf(z.boolean()),
|
|
170
|
+
xrefs: resultOf(addressList),
|
|
171
|
+
};
|
|
172
|
+
/** Exact structured-content schemas for composed analysis workflows. */
|
|
173
|
+
export const enhancedOutputSchemas = {
|
|
174
|
+
swift_classes: symbolDiscoveryOutput("classes"),
|
|
175
|
+
get_objc_classes: symbolDiscoveryOutput("classes"),
|
|
176
|
+
get_objc_protocols: symbolDiscoveryOutput("protocols"),
|
|
177
|
+
batch_decompile: resultOf(z.union([
|
|
178
|
+
z.object({ error: z.literal("No addresses provided") }),
|
|
179
|
+
z.record(z.string(), z.string()),
|
|
180
|
+
])),
|
|
181
|
+
get_call_graph: resultOf(z.record(z.string(), z.array(graphNode))),
|
|
182
|
+
analyze_swift_types: resultOf(z.object({
|
|
183
|
+
total: z.number().int().min(0),
|
|
184
|
+
categories: z.record(z.string(), z.object({
|
|
185
|
+
count: z.number().int().min(0),
|
|
186
|
+
items: z.array(addressedEntry),
|
|
187
|
+
})),
|
|
188
|
+
})),
|
|
189
|
+
find_xrefs_to_name: resultOf(z.union([
|
|
190
|
+
z.object({ xrefs: addressList }),
|
|
191
|
+
z.object({ error: z.string() }),
|
|
192
|
+
])),
|
|
193
|
+
binary_overview: resultOf(z.object({
|
|
194
|
+
document: z.string(),
|
|
195
|
+
detail: z.enum(["concise", "detailed"]),
|
|
196
|
+
segments: z.array(z.object({
|
|
197
|
+
name: z.string(),
|
|
198
|
+
start: z.string(),
|
|
199
|
+
end: z.string(),
|
|
200
|
+
length: z.number().min(0).optional(),
|
|
201
|
+
})),
|
|
202
|
+
segment_count: z.number().int().min(0),
|
|
203
|
+
procedure_count: z.number().int().min(0),
|
|
204
|
+
string_count: z.number().int().min(0),
|
|
205
|
+
})),
|
|
206
|
+
analyze_function: functionDossierOutput,
|
|
207
|
+
trace_feature: resultOf(z.object({
|
|
208
|
+
query: z.string(),
|
|
209
|
+
search_mode: z.literal("literal"),
|
|
210
|
+
operations_used: z.number().int().min(0),
|
|
211
|
+
operation_budget: z.number().int().min(1),
|
|
212
|
+
matches: z.array(z.object({
|
|
213
|
+
type: z.enum(["string", "procedure"]),
|
|
214
|
+
address: z.string(),
|
|
215
|
+
value: z.string(),
|
|
216
|
+
})),
|
|
217
|
+
references: z.array(z.object({
|
|
218
|
+
target_address: z.string(),
|
|
219
|
+
source_address: z.string(),
|
|
220
|
+
containing_procedure: containingProcedureResolution,
|
|
221
|
+
})),
|
|
222
|
+
truncated: z.boolean(),
|
|
223
|
+
residual_unknowns: z.array(z.string()),
|
|
224
|
+
})),
|
|
225
|
+
};
|
|
226
|
+
/** Exact structured-content schemas for target lifecycle operations. */
|
|
227
|
+
export const sessionOutputSchemas = {
|
|
228
|
+
open_binary: lifecycleResultOf(z.object({
|
|
229
|
+
path: z.string(),
|
|
230
|
+
format: z.enum(["hopper", "mach-o", "elf", "pe"]),
|
|
231
|
+
kind: z.enum(["executable", "database"]),
|
|
232
|
+
loaderArgs: z.array(z.string()),
|
|
233
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
234
|
+
architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
|
|
235
|
+
})),
|
|
236
|
+
close_binary: lifecycleResultOf(z.null()),
|
|
237
|
+
binary_session: lifecycleResultOf(z.union([
|
|
238
|
+
z.object({ open: z.literal(false) }),
|
|
239
|
+
z.object({
|
|
240
|
+
open: z.literal(true),
|
|
241
|
+
path: z.string(),
|
|
242
|
+
format: z.enum(["hopper", "mach-o", "elf", "pe"]),
|
|
243
|
+
kind: z.enum(["executable", "database"]),
|
|
244
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
245
|
+
architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
|
|
246
|
+
}),
|
|
247
|
+
])),
|
|
248
|
+
export_evidence_bundle: lifecycleResultOf(evidenceBundleSchema),
|
|
249
|
+
import_evidence_bundle: lifecycleResultOf(z.object({
|
|
250
|
+
imported: z.number().int().min(0),
|
|
251
|
+
total: z.number().int().min(0),
|
|
252
|
+
})),
|
|
253
|
+
capture_process_scenario: lifecycleResultOf(evidenceSchema
|
|
254
|
+
.omit({ result: true })
|
|
255
|
+
.extend({ result: processCaptureSchema })),
|
|
256
|
+
compare_process_captures: lifecycleResultOf(evidenceSchema.omit({ result: true }).extend({
|
|
257
|
+
result: z.object({
|
|
258
|
+
status: comparisonStatus,
|
|
259
|
+
terminal: comparisonStatus,
|
|
260
|
+
exit: comparisonStatus,
|
|
261
|
+
filesystem: comparisonStatus,
|
|
262
|
+
protocol: comparisonStatus,
|
|
263
|
+
process: comparisonStatus,
|
|
264
|
+
limitations: z.array(z.string()),
|
|
265
|
+
}),
|
|
266
|
+
})),
|
|
267
|
+
};
|