rea-agents 1.3.0 → 1.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.
- package/README.md +21 -0
- package/bridge/hopper_bridge.py +12 -3
- package/dist/application/ArtifactGraphConstruction.js +1 -1
- package/dist/application/ArtifactInventory.js +98 -26
- package/dist/application/AuthorizedArtifactInventory.js +2 -2
- package/dist/application/BinarySession.js +83 -0
- package/dist/application/CapabilityInventory.js +127 -0
- package/dist/application/ClientRegistrationStatus.js +69 -0
- package/dist/application/CrossVersionInventory.js +19 -8
- package/dist/application/CrossVersionInvestigation.js +68 -5
- package/dist/application/DirectAnalysis.js +97 -9
- package/dist/application/Doctor.js +74 -2
- package/dist/application/PermissionAuthority.js +183 -0
- package/dist/application/PermissionConfiguration.js +26 -0
- package/dist/application/ProcessCaptureError.js +13 -0
- package/dist/application/ProcessCaptureLifecycle.js +3 -1
- package/dist/application/ProcessCli.js +23 -10
- package/dist/application/ProcessHarness.js +4 -1
- package/dist/application/ProgressReporter.js +30 -0
- package/dist/application/ProjectPermissionStore.js +110 -0
- package/dist/application/Setup.js +2 -41
- package/dist/application/SupportedClients.js +41 -0
- package/dist/application/Uninstall.js +1 -1
- package/dist/application/Upgrade.js +2 -0
- package/dist/application/runtime.js +1 -1
- package/dist/artifacts/ArtifactProvider.js +13 -3
- package/dist/catalogIdentity.js +121 -0
- package/dist/cli.js +46 -3
- package/dist/cliEvidenceCommands.js +42 -1
- package/dist/cliInvestigationCommands.js +57 -6
- package/dist/cliLogging.js +20 -3
- package/dist/cliPolicyCommands.js +115 -0
- package/dist/cliProcessCommands.js +4 -14
- package/dist/config.js +63 -3
- package/dist/contracts/artifactToolContracts.js +14 -1
- package/dist/contracts/errorSchemas.js +98 -0
- package/dist/contracts/toolContracts.js +10 -1
- package/dist/contracts/toolOutputSchemas.js +62 -30
- package/dist/domain/artifactComparison.js +38 -11
- package/dist/domain/artifactGraph.js +25 -1
- package/dist/domain/artifactInventoryEvidence.js +18 -0
- package/dist/domain/changedBehavior.js +5 -3
- package/dist/domain/errors.js +216 -3
- package/dist/domain/investigationWorkspace.js +131 -4
- package/dist/domain/permissionPolicy.js +157 -0
- package/dist/domain/processComparison.js +1 -0
- package/dist/domain/reconstructionVerification.js +1 -1
- package/dist/domain/reconstructionVerificationSchemas.js +1 -0
- package/dist/domain/staticRuntimeCorrelation.js +5 -1
- package/dist/generatedPackageMetadata.js +9 -0
- package/dist/hopper/BridgeLauncher.js +7 -3
- package/dist/hopper/HopperClient.js +32 -5
- package/dist/identity.js +10 -3
- package/dist/logger.js +2 -2
- package/dist/main.js +95 -7
- package/dist/server/createServer.js +52 -5
- package/dist/server/mcpProgress.js +23 -0
- package/dist/server/registerArtifactComparisonTool.js +6 -2
- package/dist/server/registerBundleComparisonTool.js +6 -2
- package/dist/server/registerEnhancedTools.js +18 -1
- package/dist/server/registerEvidenceResources.js +302 -0
- package/dist/server/registerEvidenceTools.js +58 -1
- package/dist/server/registerFunctionComparisonTool.js +6 -2
- package/dist/server/registerInvestigationTools.js +83 -10
- package/dist/server/registerOfficialTools.js +19 -1
- package/dist/server/registerProcessComparisonTool.js +7 -3
- package/dist/server/registerSessionStatusTool.js +44 -3
- package/dist/server/registerSessionTools.js +129 -7
- package/dist/server/runDerivedOperation.js +35 -0
- package/dist/server/toolResult.js +27 -3
- package/dist/serverIdentity.js +70 -0
- package/package.json +6 -4
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ConfigurationError } from "../domain/errors.js";
|
|
2
|
+
import { err, ok } from "../domain/result.js";
|
|
3
|
+
import { createPermissionAuthority, } from "./PermissionAuthority.js";
|
|
4
|
+
import { readProjectPermissionStore } from "./ProjectPermissionStore.js";
|
|
5
|
+
/** Load the same administrator and optional project policy for CLI and MCP. */
|
|
6
|
+
export const loadConfiguredPermissionAuthority = async (config) => {
|
|
7
|
+
const authority = await createPermissionAuthority(config.permissionCeilings, config.administratorPermissionGrants);
|
|
8
|
+
if (!authority.ok)
|
|
9
|
+
return err(new ConfigurationError("Permission policy could not be loaded", {
|
|
10
|
+
cause: authority.error,
|
|
11
|
+
}));
|
|
12
|
+
if (config.permissionProjectRoot === undefined ||
|
|
13
|
+
config.permissionProjectStore === undefined)
|
|
14
|
+
return ok(authority.value);
|
|
15
|
+
const project = await readProjectPermissionStore(config.permissionProjectStore, config.permissionProjectRoot);
|
|
16
|
+
if (!project.ok)
|
|
17
|
+
return err(new ConfigurationError("Project permission policy could not be loaded", {
|
|
18
|
+
cause: project.error,
|
|
19
|
+
}));
|
|
20
|
+
const replaced = await authority.value.replaceProjectGrants(project.value?.grants ?? []);
|
|
21
|
+
return replaced.ok
|
|
22
|
+
? ok(authority.value)
|
|
23
|
+
: err(new ConfigurationError("Project permission policy could not be applied", {
|
|
24
|
+
cause: replaced.error,
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
@@ -4,14 +4,27 @@ export class ProcessCaptureError extends AnalysisError {
|
|
|
4
4
|
_tag = "ProcessCaptureError";
|
|
5
5
|
userMessage;
|
|
6
6
|
userCategory;
|
|
7
|
+
reason;
|
|
8
|
+
cleanupIncomplete;
|
|
9
|
+
cleanupResources;
|
|
7
10
|
constructor(message, options) {
|
|
8
11
|
super(message, options);
|
|
9
12
|
this.userMessage = options?.userMessage;
|
|
10
13
|
this.userCategory = options?.userCategory;
|
|
14
|
+
this.reason =
|
|
15
|
+
options?.reason ??
|
|
16
|
+
(options?.userCategory === "permission_required"
|
|
17
|
+
? "permission_required"
|
|
18
|
+
: options?.userCategory === "cancelled"
|
|
19
|
+
? "cancelled"
|
|
20
|
+
: "capture_failed");
|
|
21
|
+
this.cleanupIncomplete = this.reason === "cleanup_incomplete";
|
|
22
|
+
this.cleanupResources = options?.cleanupResources ?? [];
|
|
11
23
|
}
|
|
12
24
|
}
|
|
13
25
|
/** Caller-visible cancellation without exposing capture implementation state. */
|
|
14
26
|
export const processCaptureCancelled = () => new ProcessCaptureError("process capture was cancelled", {
|
|
15
27
|
userCategory: "cancelled",
|
|
28
|
+
reason: "cancelled",
|
|
16
29
|
userMessage: "Process capture was cancelled. Start it again when ready.",
|
|
17
30
|
});
|
|
@@ -249,7 +249,9 @@ export const resolveProcessResult = (capture, executionFailure, cleanupFailure)
|
|
|
249
249
|
cause: executionFailure,
|
|
250
250
|
});
|
|
251
251
|
if (cleanupFailure !== undefined)
|
|
252
|
-
throw new ProcessCaptureError(cleanupFailure
|
|
252
|
+
throw new ProcessCaptureError(cleanupFailure, {
|
|
253
|
+
reason: "cleanup_incomplete",
|
|
254
|
+
});
|
|
253
255
|
if (capture === undefined)
|
|
254
256
|
throw new ProcessCaptureError("process capture produced no result");
|
|
255
257
|
return capture;
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { parseConfig } from "../config.js";
|
|
3
|
-
import { AnalysisError, projectAnalysisError } from "../domain/errors.js";
|
|
3
|
+
import { AnalysisError, AnalysisProtocolError, PermissionRequiredError, projectAnalysisError, } from "../domain/errors.js";
|
|
4
4
|
import { createEvidence, parseEvidence } from "../domain/evidence.js";
|
|
5
5
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
6
6
|
import { compareProcessCaptures, LEGACY_PROCESS_CAPTURE_MESSAGE, parseProcessScenario, parseProcessCapture, } from "../domain/processCapture.js";
|
|
7
7
|
import { captureProcessScenario } from "./ProcessHarness.js";
|
|
8
8
|
import { PROCESS_PROVIDER, createProcessCaptureEvidence, } from "./ProcessEvidence.js";
|
|
9
|
+
import { loadConfiguredPermissionAuthority } from "./PermissionConfiguration.js";
|
|
9
10
|
const MAX_INPUT_BYTES = 64 * 1024 * 1024;
|
|
10
|
-
/** Distinguish a safe process-command failure from successful Evidence. */
|
|
11
|
-
export const isProcessCliErrorOutput = (value) => typeof value === "object" &&
|
|
12
|
-
value !== null &&
|
|
13
|
-
"error" in value &&
|
|
14
|
-
value.error === "Process command failed" &&
|
|
15
|
-
"category" in value &&
|
|
16
|
-
typeof value.category === "string" &&
|
|
17
|
-
"message" in value &&
|
|
18
|
-
typeof value.message === "string";
|
|
19
11
|
/** Capture one JSON scenario through the same policy and evidence contract as MCP. */
|
|
20
12
|
export const captureProcessScenarioFile = async (path) => {
|
|
21
13
|
try {
|
|
@@ -30,6 +22,27 @@ export const captureProcessScenarioFile = async (path) => {
|
|
|
30
22
|
const config = parseConfig(process.env);
|
|
31
23
|
if (!config.ok)
|
|
32
24
|
return cliAnalysisError(config.error);
|
|
25
|
+
const authority = await loadConfiguredPermissionAuthority(config.value);
|
|
26
|
+
if (!authority.ok)
|
|
27
|
+
return cliAnalysisError(authority.error);
|
|
28
|
+
const authorized = await authority.value.authorize({
|
|
29
|
+
capability: "process_capture",
|
|
30
|
+
roots: [scenario.working_directory, ...scenario.filesystem_roots],
|
|
31
|
+
executables: [scenario.executable],
|
|
32
|
+
environment_names: [
|
|
33
|
+
...Object.keys(scenario.environment),
|
|
34
|
+
...scenario.inherit_environment,
|
|
35
|
+
],
|
|
36
|
+
network: scenario.network_access === "host" ? "external" : "none",
|
|
37
|
+
mount: false,
|
|
38
|
+
operation_identity: `capture_process_scenario:${scenario.executable}`,
|
|
39
|
+
}, "read");
|
|
40
|
+
if (!authorized.ok)
|
|
41
|
+
return cliAnalysisError(authorized.error instanceof PermissionRequiredError
|
|
42
|
+
? authorized.error
|
|
43
|
+
: new AnalysisProtocolError(authorized.error.message, {
|
|
44
|
+
cause: authorized.error,
|
|
45
|
+
}));
|
|
33
46
|
const captured = await captureProcessScenario(scenario, config.value.processExecutionPolicy);
|
|
34
47
|
if (!captured.ok)
|
|
35
48
|
return cliAnalysisError(captured.error);
|
|
@@ -98,7 +98,10 @@ const cleanupFailedStartup = async (options) => {
|
|
|
98
98
|
sampledProcessGroupIds: [],
|
|
99
99
|
});
|
|
100
100
|
if (cleanupFailure !== undefined)
|
|
101
|
-
throw new ProcessCaptureError(cleanupFailure, {
|
|
101
|
+
throw new ProcessCaptureError(cleanupFailure, {
|
|
102
|
+
cause: options.cause,
|
|
103
|
+
reason: "cleanup_incomplete",
|
|
104
|
+
});
|
|
102
105
|
throw options.cause;
|
|
103
106
|
};
|
|
104
107
|
const createTerminalRenderer = (scenario, temporaryRoot, terminalPid) => new TerminalRenderer({
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Create a monotonic, rate-bounded reporter around a surface-specific sink. */
|
|
2
|
+
export const createProgressReporter = (sink, options = {}) => {
|
|
3
|
+
const minimumIntervalMs = options.minimumIntervalMs ?? 100;
|
|
4
|
+
const now = options.now ?? Date.now;
|
|
5
|
+
let lastCompleted = -1;
|
|
6
|
+
let lastSentAt = Number.NEGATIVE_INFINITY;
|
|
7
|
+
let sequence = 0;
|
|
8
|
+
return {
|
|
9
|
+
async report(update) {
|
|
10
|
+
if (!Number.isFinite(update.completed) || update.completed < 0)
|
|
11
|
+
throw new TypeError("Progress completed must be a nonnegative number");
|
|
12
|
+
if (update.total !== null && update.total < update.completed)
|
|
13
|
+
throw new TypeError("Progress total cannot be less than completed");
|
|
14
|
+
if (update.completed < lastCompleted)
|
|
15
|
+
throw new TypeError("Progress cannot move backwards");
|
|
16
|
+
lastCompleted = update.completed;
|
|
17
|
+
const observedAt = now();
|
|
18
|
+
if (update.terminal !== true &&
|
|
19
|
+
observedAt - lastSentAt < minimumIntervalMs)
|
|
20
|
+
return;
|
|
21
|
+
lastSentAt = observedAt;
|
|
22
|
+
sequence += 1;
|
|
23
|
+
await sink({ ...update, sequence });
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/** Surface that deliberately ignores progress while preserving one workflow API. */
|
|
28
|
+
export const silentProgressReporter = {
|
|
29
|
+
report: () => Promise.resolve(),
|
|
30
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import writeFileAtomic from "write-file-atomic";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { err, ok } from "../domain/result.js";
|
|
7
|
+
const grantSchema = z.object({
|
|
8
|
+
grant_id: z.string().min(1),
|
|
9
|
+
capability: z.enum([
|
|
10
|
+
"process_capture",
|
|
11
|
+
"evidence_read",
|
|
12
|
+
"evidence_write",
|
|
13
|
+
"investigation_input",
|
|
14
|
+
"investigation_workspace_read",
|
|
15
|
+
"investigation_workspace_write",
|
|
16
|
+
"snapshot_read",
|
|
17
|
+
"snapshot_write",
|
|
18
|
+
"artifact_extract",
|
|
19
|
+
"native_mount",
|
|
20
|
+
"reference_read",
|
|
21
|
+
]),
|
|
22
|
+
roots: z.array(z.string()),
|
|
23
|
+
executables: z.array(z.string()),
|
|
24
|
+
environment_names: z.array(z.string()),
|
|
25
|
+
network: z.enum(["none", "loopback", "external"]),
|
|
26
|
+
mount: z.boolean(),
|
|
27
|
+
lifetime: z.literal("project"),
|
|
28
|
+
operation_identity: z.string().nullable(),
|
|
29
|
+
expires_at: z.iso.datetime().nullable(),
|
|
30
|
+
});
|
|
31
|
+
const storeSchema = z.object({
|
|
32
|
+
schema_version: z.literal(1),
|
|
33
|
+
project_id: z.string().regex(/^project_[a-f0-9]{64}$/u),
|
|
34
|
+
project_root: z.string(),
|
|
35
|
+
grants: z.array(grantSchema).max(1_000),
|
|
36
|
+
});
|
|
37
|
+
/** Owner-only project policy persistence failure. */
|
|
38
|
+
export class ProjectPermissionStoreError extends Error {
|
|
39
|
+
reason;
|
|
40
|
+
_tag = "ProjectPermissionStoreError";
|
|
41
|
+
constructor(reason, options) {
|
|
42
|
+
super(`Project permission store failed: ${reason}`, options);
|
|
43
|
+
this.reason = reason;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Derive relocation-explicit project identity from its canonical root. */
|
|
47
|
+
const identifyPermissionProject = async (projectRoot) => {
|
|
48
|
+
try {
|
|
49
|
+
const root = await realpath(projectRoot);
|
|
50
|
+
return ok({
|
|
51
|
+
id: `project_${createHash("sha256").update(root).digest("hex")}`,
|
|
52
|
+
root,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (cause) {
|
|
56
|
+
return err(new ProjectPermissionStoreError("project_not_found", { cause }));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/** Read and validate an owner-only store bound to the requested project. */
|
|
60
|
+
export const readProjectPermissionStore = async (path, projectRoot) => {
|
|
61
|
+
const project = await identifyPermissionProject(projectRoot);
|
|
62
|
+
if (!project.ok)
|
|
63
|
+
return project;
|
|
64
|
+
try {
|
|
65
|
+
const metadata = await stat(path);
|
|
66
|
+
if ((metadata.mode & 0o077) !== 0)
|
|
67
|
+
return err(new ProjectPermissionStoreError("not_owner_only"));
|
|
68
|
+
const parsed = storeSchema.safeParse(JSON.parse(await readFile(path, "utf8")));
|
|
69
|
+
if (!parsed.success ||
|
|
70
|
+
parsed.data.project_id !== project.value.id ||
|
|
71
|
+
parsed.data.project_root !== project.value.root)
|
|
72
|
+
return err(new ProjectPermissionStoreError("invalid"));
|
|
73
|
+
return ok(parsed.data);
|
|
74
|
+
}
|
|
75
|
+
catch (cause) {
|
|
76
|
+
if (isNotFound(cause))
|
|
77
|
+
return ok(null);
|
|
78
|
+
return err(new ProjectPermissionStoreError("io", { cause }));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** Atomically replace explicit project grants with owner-only permissions. */
|
|
82
|
+
export const writeProjectPermissionStore = async (path, projectRoot, grants) => {
|
|
83
|
+
const project = await identifyPermissionProject(projectRoot);
|
|
84
|
+
if (!project.ok)
|
|
85
|
+
return project;
|
|
86
|
+
const candidate = storeSchema.safeParse({
|
|
87
|
+
schema_version: 1,
|
|
88
|
+
project_id: project.value.id,
|
|
89
|
+
project_root: project.value.root,
|
|
90
|
+
grants,
|
|
91
|
+
});
|
|
92
|
+
if (!candidate.success)
|
|
93
|
+
return err(new ProjectPermissionStoreError("invalid", { cause: candidate.error }));
|
|
94
|
+
try {
|
|
95
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
96
|
+
await writeFileAtomic(path, `${JSON.stringify(candidate.data, null, 2)}\n`, {
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
mode: 0o600,
|
|
99
|
+
});
|
|
100
|
+
const metadata = await stat(path);
|
|
101
|
+
if ((metadata.mode & 0o077) !== 0) {
|
|
102
|
+
return err(new ProjectPermissionStoreError("not_owner_only"));
|
|
103
|
+
}
|
|
104
|
+
return ok(candidate.data);
|
|
105
|
+
}
|
|
106
|
+
catch (cause) {
|
|
107
|
+
return err(new ProjectPermissionStoreError("io", { cause }));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const isNotFound = (cause) => cause instanceof Error && "code" in cause && cause.code === "ENOENT";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { access, copyFile, mkdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname,
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import writeFileAtomic from "write-file-atomic";
|
|
6
6
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
@@ -10,6 +10,7 @@ import { runDoctor, systemDoctorHost } from "./Doctor.js";
|
|
|
10
10
|
import { installLinuxHopper, readLinuxDistribution, } from "./LinuxHopper.js";
|
|
11
11
|
import { installMacHopper } from "./MacHopper.js";
|
|
12
12
|
import { configureDetectedClients } from "./SetupClients.js";
|
|
13
|
+
import { supportedClients } from "./SupportedClients.js";
|
|
13
14
|
import { installCanonicalSkill } from "./SetupSkill.js";
|
|
14
15
|
import { setupPlan } from "./SetupPlan.js";
|
|
15
16
|
import { setupInstallFailure, } from "./SetupInstallFailure.js";
|
|
@@ -160,46 +161,6 @@ export const detectClients = async (home) => {
|
|
|
160
161
|
detected.push(candidate);
|
|
161
162
|
return detected;
|
|
162
163
|
};
|
|
163
|
-
/** Describe every client location that setup or uninstall may own. */
|
|
164
|
-
export const supportedClients = (home) => [
|
|
165
|
-
{
|
|
166
|
-
name: "claude_code",
|
|
167
|
-
configPath: join(home, ".claude.json"),
|
|
168
|
-
markerPath: join(home, ".claude"),
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: "claude_desktop",
|
|
172
|
-
configPath: join(home, "Library/Application Support/Claude/claude_desktop_config.json"),
|
|
173
|
-
markerPath: join(home, "Library/Application Support/Claude"),
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: "codex",
|
|
177
|
-
configPath: join(home, ".codex/config.toml"),
|
|
178
|
-
markerPath: join(home, ".codex"),
|
|
179
|
-
format: "toml",
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
name: "cursor",
|
|
183
|
-
configPath: join(home, ".cursor/mcp.json"),
|
|
184
|
-
markerPath: join(home, ".cursor"),
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: "gemini_cli",
|
|
188
|
-
configPath: join(home, ".gemini/settings.json"),
|
|
189
|
-
markerPath: join(home, ".gemini"),
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
name: "windsurf",
|
|
193
|
-
configPath: join(home, ".codeium/windsurf/mcp_config.json"),
|
|
194
|
-
markerPath: join(home, ".codeium/windsurf"),
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
name: "devin",
|
|
198
|
-
configPath: join(home, ".devin"),
|
|
199
|
-
markerPath: join(home, ".devin"),
|
|
200
|
-
format: "unsupported",
|
|
201
|
-
},
|
|
202
|
-
];
|
|
203
164
|
/** Back up, atomically update, and semantically read back one JSON MCP configuration. */
|
|
204
165
|
export const configureJsonClient = async (client, hopperPath, command = [
|
|
205
166
|
"npx",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
/** Describe every client location that setup, doctor, or uninstall may inspect. */
|
|
3
|
+
export const supportedClients = (home) => [
|
|
4
|
+
{
|
|
5
|
+
name: "claude_code",
|
|
6
|
+
configPath: join(home, ".claude.json"),
|
|
7
|
+
markerPath: join(home, ".claude"),
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: "claude_desktop",
|
|
11
|
+
configPath: join(home, "Library/Application Support/Claude/claude_desktop_config.json"),
|
|
12
|
+
markerPath: join(home, "Library/Application Support/Claude"),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "codex",
|
|
16
|
+
configPath: join(home, ".codex/config.toml"),
|
|
17
|
+
markerPath: join(home, ".codex"),
|
|
18
|
+
format: "toml",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "cursor",
|
|
22
|
+
configPath: join(home, ".cursor/mcp.json"),
|
|
23
|
+
markerPath: join(home, ".cursor"),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "gemini_cli",
|
|
27
|
+
configPath: join(home, ".gemini/settings.json"),
|
|
28
|
+
markerPath: join(home, ".gemini"),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "windsurf",
|
|
32
|
+
configPath: join(home, ".codeium/windsurf/mcp_config.json"),
|
|
33
|
+
markerPath: join(home, ".codeium/windsurf"),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "devin",
|
|
37
|
+
configPath: join(home, ".devin"),
|
|
38
|
+
markerPath: join(home, ".devin"),
|
|
39
|
+
format: "unsupported",
|
|
40
|
+
},
|
|
41
|
+
];
|
|
@@ -5,7 +5,7 @@ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
|
5
5
|
import writeFileAtomic from "write-file-atomic";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
8
|
-
import { supportedClients } from "./
|
|
8
|
+
import { supportedClients } from "./SupportedClients.js";
|
|
9
9
|
const systemFileSystem = {
|
|
10
10
|
readText: (path) => readFile(path, "utf8"),
|
|
11
11
|
copy: (source, destination) => copyFile(source, destination),
|
|
@@ -42,6 +42,8 @@ export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), out
|
|
|
42
42
|
versionCheck: latestVersion === undefined ? "unavailable" : "available",
|
|
43
43
|
installMethod: "npm",
|
|
44
44
|
command: UPGRADE_COMMAND,
|
|
45
|
+
clientRestartRequired: true,
|
|
46
|
+
remediation: "Rerun rea setup to refresh registrations and the skill, then restart clients that may retain an older MCP server.",
|
|
45
47
|
};
|
|
46
48
|
};
|
|
47
49
|
/** Create the npm registry and subprocess effects for a production upgrade. */
|
|
@@ -11,7 +11,7 @@ import { silentLogger } from "../logger.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export const createBinarySession = (config, logger = silentLogger) => {
|
|
13
13
|
return new BinarySession(new CompositeProvider([
|
|
14
|
-
new ArtifactProvider(config.artifactNativeMountEnabled),
|
|
14
|
+
new ArtifactProvider(config.artifactNativeMountEnabled, config.artifactIntegrityContinueEnabled),
|
|
15
15
|
new NativeMacOSProvider(),
|
|
16
16
|
new HopperProvider(config, logger),
|
|
17
17
|
]), { snapshotsEnabled: config.hopperLoaderArgs.length === 0 });
|
|
@@ -10,8 +10,10 @@ const IDENTITY = Object.freeze(ARTIFACT_GRAPH_PROVIDER);
|
|
|
10
10
|
/** Read-only inventory and exclusively owned extraction provider. */
|
|
11
11
|
export class ArtifactProvider {
|
|
12
12
|
nativeMountEnabled;
|
|
13
|
-
|
|
13
|
+
integrityContinueEnabled;
|
|
14
|
+
constructor(nativeMountEnabled = false, integrityContinueEnabled = false) {
|
|
14
15
|
this.nativeMountEnabled = nativeMountEnabled;
|
|
16
|
+
this.integrityContinueEnabled = integrityContinueEnabled;
|
|
15
17
|
}
|
|
16
18
|
#capabilities = Object.freeze(ARTIFACT_TOOL_CONTRACTS.map((contract) => Object.freeze({
|
|
17
19
|
provider: IDENTITY,
|
|
@@ -48,15 +50,17 @@ export class ArtifactProvider {
|
|
|
48
50
|
return this.#capabilities;
|
|
49
51
|
}
|
|
50
52
|
createClient(target) {
|
|
51
|
-
return new ArtifactClient(target, this.nativeMountEnabled);
|
|
53
|
+
return new ArtifactClient(target, this.nativeMountEnabled, this.integrityContinueEnabled);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
class ArtifactClient {
|
|
55
57
|
target;
|
|
56
58
|
nativeMountEnabled;
|
|
57
|
-
|
|
59
|
+
integrityContinueEnabled;
|
|
60
|
+
constructor(target, nativeMountEnabled, integrityContinueEnabled) {
|
|
58
61
|
this.target = target;
|
|
59
62
|
this.nativeMountEnabled = nativeMountEnabled;
|
|
63
|
+
this.integrityContinueEnabled = integrityContinueEnabled;
|
|
60
64
|
}
|
|
61
65
|
async execute(operation, parameters, options) {
|
|
62
66
|
if (operation === "health")
|
|
@@ -99,6 +103,12 @@ class ArtifactClient {
|
|
|
99
103
|
nativeMountApproved: parsed.native_mount_approved === true,
|
|
100
104
|
nativeMountEnabled: this.nativeMountEnabled,
|
|
101
105
|
},
|
|
106
|
+
integrity: {
|
|
107
|
+
mode: parsed.integrity_policy,
|
|
108
|
+
approved: parsed.integrity_continue_approved === true,
|
|
109
|
+
enabled: this.integrityContinueEnabled,
|
|
110
|
+
maxMismatches: parsed.max_integrity_mismatches,
|
|
111
|
+
},
|
|
102
112
|
});
|
|
103
113
|
return ok(createAnalysisExecution(result, IDENTITY, {
|
|
104
114
|
rawResult: null,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
|
|
5
|
+
import { TOOL_CONTRACTS } from "./contracts/toolContracts.js";
|
|
6
|
+
/** Explicit CLI command inventory, kept separate from MCP tool counts. */
|
|
7
|
+
export const CLI_COMMAND_NAMES = [
|
|
8
|
+
"analyze",
|
|
9
|
+
"inspect",
|
|
10
|
+
"decompile",
|
|
11
|
+
"setup",
|
|
12
|
+
"doctor",
|
|
13
|
+
"uninstall",
|
|
14
|
+
"upgrade",
|
|
15
|
+
"xrefs",
|
|
16
|
+
"trace",
|
|
17
|
+
"capabilities",
|
|
18
|
+
"providers",
|
|
19
|
+
"function",
|
|
20
|
+
"search",
|
|
21
|
+
"import-reference-source",
|
|
22
|
+
"inventory-artifact",
|
|
23
|
+
"extract-artifact",
|
|
24
|
+
"inspect-macho",
|
|
25
|
+
"inspect-signature",
|
|
26
|
+
"list-architectures",
|
|
27
|
+
"inspect-plist",
|
|
28
|
+
"demangle-swift",
|
|
29
|
+
"evidence-import",
|
|
30
|
+
"evidence-export",
|
|
31
|
+
"compare",
|
|
32
|
+
"investigate-versions",
|
|
33
|
+
"capture-process",
|
|
34
|
+
"compare-process-captures",
|
|
35
|
+
"policy",
|
|
36
|
+
];
|
|
37
|
+
const toolCatalog = TOOL_CONTRACTS.map((contract) => ({
|
|
38
|
+
name: contract.name,
|
|
39
|
+
surface: contract.kind,
|
|
40
|
+
description: contract.description,
|
|
41
|
+
annotations: contract.annotations,
|
|
42
|
+
input_schema: z.toJSONSchema(contract.inputSchema, {
|
|
43
|
+
unrepresentable: "any",
|
|
44
|
+
}),
|
|
45
|
+
output_schema: z.toJSONSchema(contract.outputSchema, {
|
|
46
|
+
unrepresentable: "any",
|
|
47
|
+
}),
|
|
48
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
49
|
+
const promptCatalog = PROMPT_CONTRACTS.map((contract) => ({
|
|
50
|
+
name: contract.name,
|
|
51
|
+
title: contract.title,
|
|
52
|
+
description: contract.description,
|
|
53
|
+
arguments: contract.arguments,
|
|
54
|
+
steps: contract.steps,
|
|
55
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
56
|
+
const resourceCatalog = [
|
|
57
|
+
{ name: "server-identity", uri: "rea://server/identity" },
|
|
58
|
+
{ name: "active-residual-unknowns", uri: "rea://unknowns/active" },
|
|
59
|
+
];
|
|
60
|
+
const resourceTemplateCatalog = [
|
|
61
|
+
{ name: "session-evidence", uri_template: "rea://evidence/{evidenceId}" },
|
|
62
|
+
{
|
|
63
|
+
name: "evidence-section",
|
|
64
|
+
uri_template: "rea://evidence/{evidenceId}/section/{section}",
|
|
65
|
+
},
|
|
66
|
+
{ name: "residual-unknown", uri_template: "rea://unknown/{unknownId}" },
|
|
67
|
+
{
|
|
68
|
+
name: "analysis-snapshot",
|
|
69
|
+
uri_template: "rea://snapshot/{snapshotDigest}",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "artifact-page",
|
|
73
|
+
uri_template: "rea://artifact/{manifestId}/{collection}",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "function-dossier",
|
|
77
|
+
uri_template: "rea://function/{targetSha256}/{address}",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "investigation-workspace-revision",
|
|
81
|
+
uri_template: "rea://workspace/{workspaceId}/revision/{revision}",
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
const digest = (value) => {
|
|
85
|
+
const encoded = canonicalize(value);
|
|
86
|
+
if (encoded === undefined)
|
|
87
|
+
throw new TypeError("Catalog is not canonical JSON");
|
|
88
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
89
|
+
};
|
|
90
|
+
/** Stable, schema-sensitive identity for every public REA surface. */
|
|
91
|
+
export const CATALOG_IDENTITY = {
|
|
92
|
+
counts: {
|
|
93
|
+
cli_commands: CLI_COMMAND_NAMES.length,
|
|
94
|
+
mcp_tools: toolCatalog.length,
|
|
95
|
+
mcp_prompts: promptCatalog.length,
|
|
96
|
+
mcp_resources: resourceCatalog.length,
|
|
97
|
+
mcp_resource_templates: resourceTemplateCatalog.length,
|
|
98
|
+
},
|
|
99
|
+
digests: {
|
|
100
|
+
tools_sha256: digest(toolCatalog),
|
|
101
|
+
prompts_sha256: digest(promptCatalog),
|
|
102
|
+
resources_sha256: digest({ resourceCatalog, resourceTemplateCatalog }),
|
|
103
|
+
combined_sha256: digest({
|
|
104
|
+
cli: CLI_COMMAND_NAMES,
|
|
105
|
+
tools: toolCatalog,
|
|
106
|
+
prompts: promptCatalog,
|
|
107
|
+
resources: resourceCatalog,
|
|
108
|
+
resource_templates: resourceTemplateCatalog,
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
tools: toolCatalog.map(({ name, surface, annotations }) => ({
|
|
112
|
+
name,
|
|
113
|
+
surface,
|
|
114
|
+
annotations: {
|
|
115
|
+
read_only: annotations.readOnlyHint ?? false,
|
|
116
|
+
destructive: annotations.destructiveHint ?? false,
|
|
117
|
+
idempotent: annotations.idempotentHint ?? false,
|
|
118
|
+
open_world: annotations.openWorldHint ?? true,
|
|
119
|
+
},
|
|
120
|
+
})),
|
|
121
|
+
};
|