rea-agents 1.3.0 → 1.5.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 (110) hide show
  1. package/README.md +55 -12
  2. package/bridge/hopper_bridge.py +12 -3
  3. package/dist/application/AnalysisSnapshotCache.js +8 -6
  4. package/dist/application/ArtifactGraphConstruction.js +12 -3
  5. package/dist/application/ArtifactInventory.js +100 -27
  6. package/dist/application/AuthorizedArtifactInventory.js +4 -3
  7. package/dist/application/BinarySession.js +92 -4
  8. package/dist/application/BoundedJsonFiles.js +6 -29
  9. package/dist/application/BrowserEvidence.js +35 -0
  10. package/dist/application/BrowserObservationPort.js +1 -0
  11. package/dist/application/BrowserObservationService.js +52 -0
  12. package/dist/application/CapabilityInventory.js +134 -0
  13. package/dist/application/ClientRegistrationStatus.js +69 -0
  14. package/dist/application/ConfiguredRoots.js +19 -0
  15. package/dist/application/CrossVersionInventory.js +19 -8
  16. package/dist/application/CrossVersionInvestigation.js +74 -5
  17. package/dist/application/DeferredFileAuthorization.js +61 -0
  18. package/dist/application/DirectAnalysis.js +117 -9
  19. package/dist/application/Doctor.js +74 -2
  20. package/dist/application/EvidenceLedger.js +6 -5
  21. package/dist/application/InvestigationWorkspaceStore.js +2 -26
  22. package/dist/application/PermissionAuthority.js +183 -0
  23. package/dist/application/PermissionConfiguration.js +26 -0
  24. package/dist/application/ProcessCaptureAuthority.js +3 -2
  25. package/dist/application/ProcessCaptureError.js +13 -0
  26. package/dist/application/ProcessCaptureLifecycle.js +21 -9
  27. package/dist/application/ProcessCheckpoints.js +1 -1
  28. package/dist/application/ProcessCli.js +40 -15
  29. package/dist/application/ProcessHarness.js +10 -20
  30. package/dist/application/ProcessNormalization.js +17 -2
  31. package/dist/application/ProcessOwnership.js +4 -3
  32. package/dist/application/ProcessSampling.js +52 -17
  33. package/dist/application/ProgressReporter.js +30 -0
  34. package/dist/application/ProjectPermissionStore.js +213 -0
  35. package/dist/application/ReferenceSourceImportPolicy.js +3 -2
  36. package/dist/application/Setup.js +2 -41
  37. package/dist/application/SupportedClients.js +41 -0
  38. package/dist/application/TerminalRenderer.js +1 -1
  39. package/dist/application/Uninstall.js +1 -1
  40. package/dist/application/Upgrade.js +2 -0
  41. package/dist/application/runtime.js +1 -1
  42. package/dist/artifacts/ArtifactProvider.js +13 -3
  43. package/dist/artifacts/MachOSliceArtifactReader.js +1 -1
  44. package/dist/artifacts/NativeDmgArtifactReader.js +1 -1
  45. package/dist/artifacts/SafeOutputTree.js +3 -3
  46. package/dist/browser/CdpBrowserProvider.js +158 -0
  47. package/dist/browser/CdpCaptureDocuments.js +140 -0
  48. package/dist/browser/CdpCaptureEvents.js +248 -0
  49. package/dist/browser/CdpCaptureStorage.js +53 -0
  50. package/dist/browser/CdpCaptureValues.js +70 -0
  51. package/dist/browser/CdpConnection.js +218 -0
  52. package/dist/browser/CdpEndpoint.js +114 -0
  53. package/dist/browser/CdpOptionalCommand.js +15 -0
  54. package/dist/browser/CdpPageCapture.js +273 -0
  55. package/dist/catalogIdentity.js +123 -0
  56. package/dist/cli.js +48 -3
  57. package/dist/cliBrowserCommands.js +136 -0
  58. package/dist/cliEvidenceCommands.js +42 -1
  59. package/dist/cliInvestigationCommands.js +42 -6
  60. package/dist/cliLogging.js +20 -3
  61. package/dist/cliPolicyCommands.js +118 -0
  62. package/dist/cliProcessCommands.js +4 -14
  63. package/dist/config.js +107 -3
  64. package/dist/contracts/artifactToolContracts.js +14 -1
  65. package/dist/contracts/browserToolContracts.js +80 -0
  66. package/dist/contracts/errorSchemas.js +100 -0
  67. package/dist/contracts/toolContracts.js +13 -6
  68. package/dist/contracts/toolOutputSchemas.js +62 -30
  69. package/dist/domain/artifactComparison.js +38 -11
  70. package/dist/domain/artifactGraph.js +25 -1
  71. package/dist/domain/artifactInventoryEvidence.js +18 -0
  72. package/dist/domain/browserObservation.js +361 -0
  73. package/dist/domain/changedBehavior.js +9 -18
  74. package/dist/domain/errors.js +257 -3
  75. package/dist/domain/investigationWorkspace.js +131 -4
  76. package/dist/domain/jsonLimits.js +26 -0
  77. package/dist/domain/permissionPolicy.js +161 -0
  78. package/dist/domain/processCapture.js +3 -1
  79. package/dist/domain/processComparison.js +23 -34
  80. package/dist/domain/reconstructionVerification.js +3 -16
  81. package/dist/domain/reconstructionVerificationSchemas.js +3 -0
  82. package/dist/domain/staticRuntimeCorrelation.js +7 -1
  83. package/dist/domain/symbolAnalysis.js +1 -1
  84. package/dist/generatedPackageMetadata.js +9 -0
  85. package/dist/hopper/BridgeLauncher.js +7 -3
  86. package/dist/hopper/HopperClient.js +32 -5
  87. package/dist/identity.js +10 -3
  88. package/dist/logger.js +2 -2
  89. package/dist/main.js +101 -7
  90. package/dist/native/CommandRunner.js +11 -4
  91. package/dist/server/createServer.js +59 -5
  92. package/dist/server/mcpProgress.js +23 -0
  93. package/dist/server/registerArtifactComparisonTool.js +6 -2
  94. package/dist/server/registerBrowserTools.js +36 -0
  95. package/dist/server/registerBundleComparisonTool.js +6 -2
  96. package/dist/server/registerEnhancedTools.js +18 -1
  97. package/dist/server/registerEvidenceResources.js +302 -0
  98. package/dist/server/registerEvidenceTools.js +58 -1
  99. package/dist/server/registerFunctionComparisonTool.js +6 -2
  100. package/dist/server/registerInvestigationTools.js +78 -10
  101. package/dist/server/registerOfficialTools.js +19 -1
  102. package/dist/server/registerProcessComparisonTool.js +9 -3
  103. package/dist/server/registerSessionStatusTool.js +44 -3
  104. package/dist/server/registerSessionTools.js +144 -9
  105. package/dist/server/runDerivedOperation.js +43 -0
  106. package/dist/server/toolResult.js +27 -3
  107. package/dist/serverIdentity.js +70 -0
  108. package/package.json +7 -4
  109. package/scripts/rea.mjs +1 -1
  110. package/skills/rea-analysis/SKILL.md +18 -3
@@ -1,22 +1,12 @@
1
- import { Cli, Errors, z } from "incur";
2
- import { captureProcessScenarioFile, compareProcessEvidenceFiles, isProcessCliErrorOutput, } from "./application/ProcessCli.js";
1
+ import { Cli, z } from "incur";
2
+ import { captureProcessScenarioFile, compareProcessEvidenceFiles, } from "./application/ProcessCli.js";
3
3
  import { logCliCommand } from "./cliLogging.js";
4
- const requireProcessCommandSuccess = async (execute) => {
5
- const value = await execute();
6
- if (isProcessCliErrorOutput(value))
7
- throw new Errors.IncurError({
8
- code: "PROCESS_COMMAND_FAILED",
9
- message: value.message,
10
- retryable: false,
11
- });
12
- return value;
13
- };
14
4
  /** Register Process Capture v4 one-shot commands through shared application services. */
15
5
  export const registerProcessCommands = (cli, logger) => {
16
6
  cli.command("capture-process", {
17
7
  description: "Capture one approved Process Capture v4 JSON scenario",
18
8
  args: z.object({ scenario: z.string().describe("Scenario JSON path") }),
19
- run: ({ args }) => logCliCommand(logger, "capture-process", () => requireProcessCommandSuccess(() => captureProcessScenarioFile(args.scenario))),
9
+ run: ({ args }) => logCliCommand(logger, "capture-process", () => captureProcessScenarioFile(args.scenario)),
20
10
  });
21
11
  cli.command("compare-process-captures", {
22
12
  description: "Compare two Process Capture v4 Evidence JSON files",
@@ -24,6 +14,6 @@ export const registerProcessCommands = (cli, logger) => {
24
14
  left: z.string().describe("Left capture Evidence JSON path"),
25
15
  right: z.string().describe("Right capture Evidence JSON path"),
26
16
  }),
27
- run: ({ args }) => logCliCommand(logger, "compare-process-captures", () => requireProcessCommandSuccess(() => compareProcessEvidenceFiles(args.left, args.right))),
17
+ run: ({ args }) => logCliCommand(logger, "compare-process-captures", () => compareProcessEvidenceFiles(args.left, args.right)),
28
18
  });
29
19
  };
package/dist/config.js CHANGED
@@ -1,20 +1,29 @@
1
1
  import { z } from "zod";
2
2
  import { ConfigurationError } from "./domain/errors.js";
3
3
  import { err, ok } from "./domain/result.js";
4
+ import { browserEndpointSchema, browserOriginSchema, } from "./domain/browserObservation.js";
4
5
  const DEFAULT_HOPPER_LAUNCHER_PATH = "/Applications/Hopper Disassembler.app/Contents/MacOS/hopper";
5
6
  const defaultHopperLauncherPath = () => process.platform === "linux"
6
7
  ? "/opt/hopper/bin/Hopper"
7
8
  : DEFAULT_HOPPER_LAUNCHER_PATH;
8
- const environmentSchema = z.object({
9
+ const environmentSchema = z
10
+ .object({
9
11
  HOPPER_LAUNCHER_PATH: z.string().min(1).optional(),
10
12
  HOPPER_TARGET_PATH: z.string().min(1).optional(),
11
- HOPPER_TARGET_KIND: z.enum(["executable", "database"]).default("executable"),
13
+ HOPPER_TARGET_KIND: z
14
+ .enum(["executable", "database"])
15
+ .default("executable"),
12
16
  HOPPER_LOADER_ARGS_JSON: z.string().optional(),
13
17
  REA_LOG_LEVEL: z
14
18
  .enum(["trace", "debug", "info", "warn", "error", "fatal", "silent"])
15
19
  .default("info"),
16
20
  REA_PROCESS_CAPTURE_ENABLED: z.enum(["true", "false"]).default("false"),
17
- REA_ARTIFACT_NATIVE_MOUNT_ENABLED: z.enum(["true", "false"]).default("false"),
21
+ REA_ARTIFACT_NATIVE_MOUNT_ENABLED: z
22
+ .enum(["true", "false"])
23
+ .default("false"),
24
+ REA_ARTIFACT_INTEGRITY_CONTINUE_ENABLED: z
25
+ .enum(["true", "false"])
26
+ .default("false"),
18
27
  REA_PROCESS_ALLOW_EXTERNAL_NETWORK: z
19
28
  .enum(["true", "false"])
20
29
  .default("false"),
@@ -26,6 +35,15 @@ const environmentSchema = z.object({
26
35
  REA_ANALYSIS_SNAPSHOT_ROOTS_JSON: z.string().default("[]"),
27
36
  REA_REFERENCE_ROOTS_JSON: z.string().default("[]"),
28
37
  REA_REFERENCE_SECRET_PATTERNS_JSON: z.string().default("[]"),
38
+ REA_BROWSER_OBSERVE_ENABLED: z.enum(["true", "false"]).default("false"),
39
+ REA_BROWSER_CDP_ENDPOINTS_JSON: z.string().default("[]"),
40
+ REA_BROWSER_ALLOWED_ORIGINS_JSON: z.string().default("[]"),
41
+ REA_PERMISSION_PROJECT_ROOT: z.string().min(1).optional(),
42
+ REA_PERMISSION_PROJECT_STORE: z.string().min(1).optional(),
43
+ })
44
+ .refine((value) => (value.REA_PERMISSION_PROJECT_ROOT === undefined) ===
45
+ (value.REA_PERMISSION_PROJECT_STORE === undefined), {
46
+ message: "REA_PERMISSION_PROJECT_ROOT and REA_PERMISSION_PROJECT_STORE must be configured together",
29
47
  });
30
48
  const parseStringArray = (encoded, name) => {
31
49
  try {
@@ -43,6 +61,22 @@ const parseStringArray = (encoded, name) => {
43
61
  return err(new ConfigurationError(`${name} must be valid JSON`, { cause }));
44
62
  }
45
63
  };
64
+ const parseBrowserArray = (encoded, name, itemSchema, maximum) => {
65
+ try {
66
+ const parsed = z
67
+ .array(itemSchema)
68
+ .max(maximum)
69
+ .safeParse(JSON.parse(encoded));
70
+ return parsed.success
71
+ ? ok([...new Set(parsed.data)].sort())
72
+ : err(new ConfigurationError(`${name} must encode valid browser scopes`, {
73
+ cause: parsed.error,
74
+ }));
75
+ }
76
+ catch (cause) {
77
+ return err(new ConfigurationError(`${name} must be valid JSON`, { cause }));
78
+ }
79
+ };
46
80
  const filePolicy = (roots) => ({
47
81
  roots,
48
82
  maxBytes: 64 * 1024 * 1024,
@@ -50,6 +84,22 @@ const filePolicy = (roots) => ({
50
84
  maxStringLength: 1024 * 1024,
51
85
  maxNodes: 1_000_000,
52
86
  });
87
+ const permissionScope = (capability, roots, options = {}) => ({
88
+ capability,
89
+ roots,
90
+ executables: options.executables ?? [],
91
+ environment_names: options.environment_names ?? [],
92
+ ...(options.origins === undefined ? {} : { origins: options.origins }),
93
+ network: options.network ?? "none",
94
+ mount: options.mount ?? false,
95
+ });
96
+ const administratorGrants = (ceilings) => ceilings.map((ceiling) => ({
97
+ ...ceiling,
98
+ grant_id: `administrator:${ceiling.capability}`,
99
+ lifetime: "administrator",
100
+ operation_identity: null,
101
+ expires_at: null,
102
+ }));
53
103
  const parseLoaderArgs = (encoded) => {
54
104
  if (encoded === undefined)
55
105
  return ok([]);
@@ -107,6 +157,46 @@ export const parseConfig = (environment) => {
107
157
  const secretPatterns = parseStringArray(parsedEnvironment.data.REA_REFERENCE_SECRET_PATTERNS_JSON, "REA_REFERENCE_SECRET_PATTERNS_JSON");
108
158
  if (!secretPatterns.ok)
109
159
  return secretPatterns;
160
+ const browserEndpoints = parseBrowserArray(parsedEnvironment.data.REA_BROWSER_CDP_ENDPOINTS_JSON, "REA_BROWSER_CDP_ENDPOINTS_JSON", browserEndpointSchema, 16);
161
+ if (!browserEndpoints.ok)
162
+ return browserEndpoints;
163
+ const browserOrigins = parseBrowserArray(parsedEnvironment.data.REA_BROWSER_ALLOWED_ORIGINS_JSON, "REA_BROWSER_ALLOWED_ORIGINS_JSON", browserOriginSchema, 32);
164
+ if (!browserOrigins.ok)
165
+ return browserOrigins;
166
+ const permissionCeilings = [
167
+ ...(parsedEnvironment.data.REA_PROCESS_CAPTURE_ENABLED === "true"
168
+ ? [
169
+ permissionScope("process_capture", workingRoots.value, {
170
+ executables: executableRoots.value,
171
+ environment_names: allowedEnvironment.value,
172
+ network: parsedEnvironment.data.REA_PROCESS_ALLOW_EXTERNAL_NETWORK ===
173
+ "true"
174
+ ? "external"
175
+ : "none",
176
+ }),
177
+ ]
178
+ : []),
179
+ permissionScope("evidence_read", evidenceRoots.value),
180
+ permissionScope("evidence_write", evidenceRoots.value),
181
+ permissionScope("investigation_input", investigationInputRoots.value),
182
+ permissionScope("investigation_workspace_read", evidenceRoots.value),
183
+ permissionScope("investigation_workspace_write", evidenceRoots.value),
184
+ permissionScope("snapshot_read", analysisSnapshotRoots.value),
185
+ permissionScope("snapshot_write", analysisSnapshotRoots.value),
186
+ permissionScope("artifact_extract", ["/"]),
187
+ permissionScope("reference_read", referenceRoots.value),
188
+ ...(parsedEnvironment.data.REA_BROWSER_OBSERVE_ENABLED === "true"
189
+ ? [
190
+ permissionScope("browser_observe", [], {
191
+ origins: [...browserEndpoints.value, ...browserOrigins.value],
192
+ network: browserNetworkScope(browserOrigins.value),
193
+ }),
194
+ ]
195
+ : []),
196
+ ...(parsedEnvironment.data.REA_ARTIFACT_NATIVE_MOUNT_ENABLED === "true"
197
+ ? [permissionScope("native_mount", [], { mount: true })]
198
+ : []),
199
+ ];
110
200
  return ok({
111
201
  hopperLauncherPath: parsedEnvironment.data.HOPPER_LAUNCHER_PATH ??
112
202
  defaultHopperLauncherPath(),
@@ -122,6 +212,7 @@ export const parseConfig = (environment) => {
122
212
  allowExternalNetwork: parsedEnvironment.data.REA_PROCESS_ALLOW_EXTERNAL_NETWORK === "true",
123
213
  },
124
214
  artifactNativeMountEnabled: parsedEnvironment.data.REA_ARTIFACT_NATIVE_MOUNT_ENABLED === "true",
215
+ artifactIntegrityContinueEnabled: parsedEnvironment.data.REA_ARTIFACT_INTEGRITY_CONTINUE_ENABLED === "true",
125
216
  evidenceFilePolicy: filePolicy(evidenceRoots.value),
126
217
  investigationInputRoots: investigationInputRoots.value,
127
218
  analysisSnapshotFilePolicy: filePolicy(analysisSnapshotRoots.value),
@@ -133,5 +224,18 @@ export const parseConfig = (environment) => {
133
224
  maxDepth: 32,
134
225
  maxPathBytes: 4_096,
135
226
  },
227
+ browserObservationEnabled: parsedEnvironment.data.REA_BROWSER_OBSERVE_ENABLED === "true",
228
+ browserCdpEndpoints: browserEndpoints.value,
229
+ browserAllowedOrigins: browserOrigins.value,
230
+ permissionCeilings,
231
+ administratorPermissionGrants: administratorGrants(permissionCeilings),
232
+ permissionProjectRoot: parsedEnvironment.data.REA_PERMISSION_PROJECT_ROOT,
233
+ permissionProjectStore: parsedEnvironment.data.REA_PERMISSION_PROJECT_STORE,
136
234
  });
137
235
  };
236
+ const browserNetworkScope = (origins) => origins.every((origin) => {
237
+ const hostname = new URL(origin).hostname;
238
+ return hostname === "127.0.0.1" || hostname === "[::1]";
239
+ })
240
+ ? "loopback"
241
+ : "external";
@@ -28,10 +28,23 @@ const traversalLimitsInput = {
28
28
  max_path_bytes: z.number().int().min(1).max(65_535).default(4_096),
29
29
  };
30
30
  /** Exact caller boundary for deterministic artifact inventory. */
31
- export const artifactInventoryInputSchema = z.object({
31
+ export const artifactInventoryInputSchema = z
32
+ .object({
32
33
  native_mount_approved: z.boolean().default(false),
34
+ integrity_policy: z.enum(["fail", "record-and-continue"]).default("fail"),
35
+ integrity_continue_approved: z.boolean().default(false),
36
+ max_integrity_mismatches: z.number().int().min(1).max(100).default(10),
33
37
  ...pageInput,
34
38
  ...traversalLimitsInput,
39
+ })
40
+ .superRefine((input, context) => {
41
+ if (input.integrity_policy === "record-and-continue" &&
42
+ input.integrity_continue_approved !== true)
43
+ context.addIssue({
44
+ code: "custom",
45
+ path: ["integrity_continue_approved"],
46
+ message: "record-and-continue requires explicit approval",
47
+ });
35
48
  });
36
49
  /** Exact caller boundary for approved artifact extraction. */
37
50
  export const artifactExtractionInputSchema = z.object({
@@ -0,0 +1,80 @@
1
+ import { evidenceSchema } from "../domain/evidence.js";
2
+ import { browserTargetListSchema, inspectWebPageInputSchema, listBrowserTargetsInputSchema, webPageInspectionSchema, } from "../domain/browserObservation.js";
3
+ const evidenceResult = (schema) => evidenceSchema
4
+ .omit({ normalized_result: true })
5
+ .extend({ normalized_result: schema });
6
+ const listOutputSchema = evidenceResult(browserTargetListSchema);
7
+ const inspectionOutputSchema = evidenceSchema
8
+ .omit({ normalized_result: true })
9
+ .extend({ normalized_result: webPageInspectionSchema });
10
+ const endpoint = "http://127.0.0.1:9222";
11
+ const origin = "https://app.example.test";
12
+ /** Origin-scoped, passive browser reverse-engineering contracts. */
13
+ export const BROWSER_TOOL_CONTRACTS = [
14
+ {
15
+ name: "list_browser_targets",
16
+ description: "List bounded page targets from an approved user-owned loopback Chrome DevTools Protocol endpoint. Only targets whose current URL matches an approved exact origin are returned; URL credentials, query values, and fragments are redacted.",
17
+ kind: "browser-provider",
18
+ inputSchema: listBrowserTargetsInputSchema,
19
+ outputSchema: listOutputSchema,
20
+ annotations: {
21
+ readOnlyHint: true,
22
+ destructiveHint: false,
23
+ idempotentHint: true,
24
+ openWorldHint: true,
25
+ },
26
+ examples: [
27
+ {
28
+ title: "List approved browser page targets",
29
+ input: {
30
+ cdp_endpoint: endpoint,
31
+ allowed_origins: [origin],
32
+ approved: true,
33
+ offset: 0,
34
+ limit: 100,
35
+ },
36
+ },
37
+ ],
38
+ },
39
+ {
40
+ name: "inspect_web_page",
41
+ description: "Passively inspect one approved page target through CDP without evaluating JavaScript, navigating, clicking, closing, or mutating the page. Returns bounded DOM structure, accessibility, scripts, resources, attach-window network and console metadata, workers, and redacted storage inventory as Evidence v2.",
42
+ kind: "browser-provider",
43
+ inputSchema: inspectWebPageInputSchema,
44
+ outputSchema: inspectionOutputSchema,
45
+ annotations: {
46
+ readOnlyHint: true,
47
+ destructiveHint: false,
48
+ idempotentHint: true,
49
+ openWorldHint: true,
50
+ },
51
+ examples: [
52
+ {
53
+ title: "Inspect one approved browser page",
54
+ input: {
55
+ cdp_endpoint: endpoint,
56
+ allowed_origins: [origin],
57
+ approved: true,
58
+ target_id: "TARGET_ID_FROM_LIST_BROWSER_TARGETS",
59
+ observation_ms: 500,
60
+ include_script_sources: false,
61
+ include_storage_keys: false,
62
+ limits: {
63
+ max_frames: 200,
64
+ max_dom_nodes: 2_000,
65
+ max_ax_nodes: 2_000,
66
+ max_scripts: 200,
67
+ max_resources: 2_000,
68
+ max_workers: 500,
69
+ max_storage_keys: 1_000,
70
+ max_script_source_bytes: 1_048_576,
71
+ max_total_script_source_bytes: 4_194_304,
72
+ max_network_events: 1_000,
73
+ max_console_events: 200,
74
+ max_websocket_events: 500,
75
+ },
76
+ },
77
+ },
78
+ ],
79
+ },
80
+ ];
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ const categorySchema = z.enum([
3
+ "invalid_input",
4
+ "permission_required",
5
+ "unsupported_provider",
6
+ "integrity_mismatch",
7
+ "truncated",
8
+ "cancelled",
9
+ "timeout",
10
+ "unavailable",
11
+ "execution_failure",
12
+ ]);
13
+ const remediationSchema = z
14
+ .object({
15
+ action: z.string().min(1),
16
+ restart_required: z.boolean(),
17
+ elicitation_supported: z.boolean().optional(),
18
+ })
19
+ .strict();
20
+ const common = {
21
+ category: categorySchema,
22
+ message: z.string().min(1),
23
+ retryable: z.boolean(),
24
+ remediation: remediationSchema,
25
+ };
26
+ const genericDetails = z.record(z.string(), z.json()).optional();
27
+ const generic = (code) => z
28
+ .object({ code: z.literal(code), ...common, details: genericDetails })
29
+ .strict();
30
+ const scopeSchema = z
31
+ .object({
32
+ capability: z.string(),
33
+ roots: z.array(z.string()),
34
+ executables: z.array(z.string()),
35
+ environment_names: z.array(z.string()),
36
+ origins: z.array(z.string()).optional(),
37
+ network: z.enum(["none", "loopback", "external"]),
38
+ mount: z.boolean(),
39
+ })
40
+ .strict();
41
+ const missingScopeSchema = z
42
+ .object({
43
+ roots: z.array(z.string()).optional(),
44
+ executables: z.array(z.string()).optional(),
45
+ environment_names: z.array(z.string()).optional(),
46
+ origins: z.array(z.string()).optional(),
47
+ network: z.enum(["none", "loopback", "external"]).optional(),
48
+ mount: z.literal(true).optional(),
49
+ })
50
+ .strict();
51
+ /** Stable discriminated schema shared by every CLI and MCP error surface. */
52
+ export const analysisErrorProjectionSchema = z.discriminatedUnion("code", [
53
+ generic("invalid_request"),
54
+ generic("unreadable_output"),
55
+ generic("capability_unavailable"),
56
+ generic("provider_unavailable"),
57
+ generic("provider_timeout"),
58
+ generic("cancelled"),
59
+ z
60
+ .object({
61
+ code: z.literal("artifact_integrity_mismatch"),
62
+ ...common,
63
+ details: z
64
+ .object({
65
+ logical_path: z.string(),
66
+ declared_sha256: z.string().nullable(),
67
+ calculated_sha256: z.string().nullable(),
68
+ unpacked: z.boolean(),
69
+ })
70
+ .strict(),
71
+ })
72
+ .strict(),
73
+ generic("artifact_operation_failed"),
74
+ generic("evidence_integrity_mismatch"),
75
+ generic("truncated"),
76
+ z
77
+ .object({
78
+ code: z.literal("permission_required"),
79
+ ...common,
80
+ details: z
81
+ .object({
82
+ capability: z.string(),
83
+ requested: scopeSchema,
84
+ missing: missingScopeSchema,
85
+ ceiling: scopeSchema.nullable(),
86
+ })
87
+ .strict()
88
+ .optional(),
89
+ })
90
+ .strict(),
91
+ generic("process_capture_failed"),
92
+ generic("cleanup_incomplete"),
93
+ generic("revision_conflict"),
94
+ generic("outside_approved_root"),
95
+ generic("configuration_invalid"),
96
+ generic("target_unavailable"),
97
+ generic("execution_failure"),
98
+ ]);
99
+ /** JSON Schema document used by generated API documentation and clients. */
100
+ export const analysisErrorJsonSchema = z.toJSONSchema(analysisErrorProjectionSchema);
@@ -14,6 +14,7 @@ import { enhancedOutputSchemas, officialOutputSchemas, requireOutputSchema, sess
14
14
  import { TOOL_EXAMPLE_OVERRIDES } from "./toolContractExamples.js";
15
15
  import { NATIVE_TOOL_CONTRACTS } from "./nativeToolContracts.js";
16
16
  import { ARTIFACT_TOOL_CONTRACTS } from "./artifactToolContracts.js";
17
+ import { BROWSER_TOOL_CONTRACTS } from "./browserToolContracts.js";
17
18
  import { closeBinaryInputSchema, openBinaryInputSchema, } from "./sessionLifecycleInputs.js";
18
19
  const document = z.string().optional().describe("The document name");
19
20
  const address = z.string().describe("A Hopper address");
@@ -158,11 +159,20 @@ export const ENHANCED_TOOL_CONTRACTS = [
158
159
  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),
159
160
  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),
160
161
  ];
162
+ /** Caller observations used to compare a live server with expected identity. */
163
+ export const binarySessionInputSchema = z.object({
164
+ expected_package_version: z.string().min(1).optional(),
165
+ expected_catalog_digest: z
166
+ .string()
167
+ .regex(/^[a-f0-9]{64}$/u)
168
+ .optional(),
169
+ expected_server_path: z.string().min(1).optional(),
170
+ });
161
171
  /** Target lifecycle tools available only on the long-lived MCP adapter. */
162
172
  export const SESSION_TOOL_CONTRACTS = [
163
173
  session("open_binary", "Open a local executable, application bundle, archive, JavaScript, source map, plist, or Hopper database after validation. An optional provider-neutral snapshot is imported atomically and must match the binary digest, format, and architecture exactly; provider resources may still start before an MCP cache replay.", openBinaryInputSchema),
164
174
  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),
165
- session("binary_session", "Report provider identity, deterministic capability descriptors, and whether a target is open; open targets include canonical path, format, and kind. Use availability, effects, limits, and limitations before selecting analysis operations.", z.object({})),
175
+ session("binary_session", "Report provider identity, deterministic capability descriptors, and whether a target is open; open targets include canonical path, format, and kind. Use availability, effects, limits, and limitations before selecting analysis operations.", binarySessionInputSchema),
166
176
  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.", z.object({
167
177
  path: z.string().min(1).optional(),
168
178
  overwrite: z.boolean().default(false),
@@ -198,15 +208,12 @@ export const SESSION_TOOL_CONTRACTS = [
198
208
  session("update_unknown", "Append one immutable full-state revision and mutation evidence. Requires approved: true and exact expected_revision; stale concurrent writers fail instead of overwriting newer analysis.", updateUnknownInputSchema),
199
209
  session("verify_unknown_resolution", "Revalidate the current residual-unknown head against live bundled evidence, exact authority/confidence/environment requirements, and revision integrity. Withdrawn and out-of-scope dispositions are not truth claims.", z.object({ unknown_id: z.string().regex(/^unk_[a-f0-9]{64}$/u) })),
200
210
  ];
201
- /**
202
- * Complete ordered public inventory used by registration and verification.
203
- * Keep this collection at 68 tools unless a deliberate contract change updates
204
- * snapshots, package verification, and real-Hopper verification together.
205
- */
211
+ /** Complete ordered public inventory used by registration and verification. */
206
212
  export const TOOL_CONTRACTS = [
207
213
  ...OFFICIAL_TOOL_CONTRACTS,
208
214
  ...ENHANCED_TOOL_CONTRACTS,
209
215
  ...NATIVE_TOOL_CONTRACTS,
210
216
  ...ARTIFACT_TOOL_CONTRACTS,
217
+ ...BROWSER_TOOL_CONTRACTS,
211
218
  ...SESSION_TOOL_CONTRACTS,
212
219
  ];
@@ -13,6 +13,7 @@ import { changedBehaviorResultSchema } from "../domain/changedBehavior.js";
13
13
  import { callPathResultSchema } from "../domain/callPath.js";
14
14
  import { staticRuntimeCorrelationResultSchema } from "../domain/staticRuntimeCorrelation.js";
15
15
  import { reconstructionVerificationResultSchema } from "../domain/reconstructionVerification.js";
16
+ import { analysisErrorProjectionSchema } from "./errorSchemas.js";
16
17
  const resultOf = (schema) => evidenceSchema
17
18
  .omit({ normalized_result: true })
18
19
  .extend({ normalized_result: schema });
@@ -78,6 +79,67 @@ const sessionProvider = z.object({
78
79
  provider: providerIdentity,
79
80
  providers: z.array(providerIdentity),
80
81
  capabilities: z.array(providerCapability),
82
+ tool_availability: z.array(z.object({
83
+ name: z.string(),
84
+ surface: z.string(),
85
+ available: z.boolean(),
86
+ reason: z.enum([
87
+ "available",
88
+ "target_required",
89
+ "provider_missing",
90
+ "provider_unavailable",
91
+ "target_unsupported",
92
+ "unsupported_host",
93
+ "policy_disabled",
94
+ ]),
95
+ remediation: z.string().nullable(),
96
+ annotations: z.object({
97
+ read_only: z.boolean(),
98
+ destructive: z.boolean(),
99
+ idempotent: z.boolean(),
100
+ open_world: z.boolean(),
101
+ }),
102
+ })),
103
+ client_features: z.object({
104
+ elicitation_form: z.boolean(),
105
+ elicitation_url: z.boolean(),
106
+ roots: z.boolean(),
107
+ sampling: z.boolean(),
108
+ }),
109
+ server_identity: z.object({
110
+ package: z.object({
111
+ name: z.string(),
112
+ version: z.string(),
113
+ root_path: z.string(),
114
+ build_commit: z.string().nullable(),
115
+ }),
116
+ server: z.object({
117
+ name: z.string(),
118
+ version: z.string(),
119
+ started_at: z.string(),
120
+ command_path: z.string(),
121
+ }),
122
+ sdk: z.object({
123
+ server: z.string(),
124
+ client_test: z.string(),
125
+ core: z.string(),
126
+ }),
127
+ negotiated_protocol_version: z.string().nullable(),
128
+ client: z.object({ name: z.string(), version: z.string() }).nullable(),
129
+ skill: z.object({ name: z.string(), expected_version: z.string() }),
130
+ catalog: z.record(z.string(), z.json()),
131
+ protocol_features: z.object({
132
+ progress: z.boolean(),
133
+ cancellation: z.boolean(),
134
+ evidence_resources: z.boolean(),
135
+ elicitation: z.boolean(),
136
+ }),
137
+ alignment: z.object({
138
+ state: z.enum(["aligned", "mcp_server_restart_required", "unknown"]),
139
+ reasons: z.array(z.string()),
140
+ remediation: z.string().nullable(),
141
+ }),
142
+ }),
81
143
  });
82
144
  const nullableText = z.string().nullable();
83
145
  const addressList = z.array(z.string());
@@ -149,36 +211,6 @@ const symbolDiscoveryOutput = (property) => resultOf(z.object({
149
211
  count: z.number().int().min(0),
150
212
  [property]: z.array(addressedEntry),
151
213
  }));
152
- const analysisErrorProjectionSchema = z
153
- .object({
154
- category: z.enum([
155
- "invalid_input",
156
- "permission_required",
157
- "unsupported_provider",
158
- "integrity_mismatch",
159
- "truncated",
160
- "cancelled",
161
- "timeout",
162
- "unavailable",
163
- "execution_failure",
164
- ]),
165
- message: z.string(),
166
- code: z
167
- .enum([
168
- "private_display_unavailable",
169
- "x11_authorization_failed",
170
- "unsupported_hopper_build",
171
- "invalid_launch_command",
172
- "process_ownership_mismatch",
173
- "hopper_exited_during_startup",
174
- "unsupported_demo_dialog",
175
- "unexpected_display_geometry",
176
- "x11_input_failed",
177
- "runtime_dependency_unavailable",
178
- ])
179
- .optional(),
180
- })
181
- .strict();
182
214
  const graphNode = z.discriminatedUnion("status", [
183
215
  z.object({
184
216
  address: z.string(),