jev-layer 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +45 -34
  2. package/CONTRIBUTING.md +146 -146
  3. package/LICENSE +21 -21
  4. package/README.md +123 -126
  5. package/README.ru.md +138 -117
  6. package/README.zh-CN.md +137 -117
  7. package/RELEASE.md +53 -53
  8. package/SECURITY.md +56 -56
  9. package/bin/jev.mjs +213 -213
  10. package/config/codex.mcp.toml +8 -8
  11. package/config/generic-mcp.json +13 -13
  12. package/config/hermes.mcp.yaml +9 -9
  13. package/config/jev.example.json +22 -22
  14. package/config/omp.mcp.json +14 -14
  15. package/config/providers.env.example +13 -13
  16. package/docs/AGENT-IMPLEMENTATION.md +250 -0
  17. package/docs/COMPATIBILITY.md +15 -15
  18. package/docs/HERMES-LOCAL-AGENT-HANDOFF.ru.md +177 -0
  19. package/docs/PROVIDERS.md +67 -0
  20. package/docs/SCHEMA-VERSIONING.md +96 -94
  21. package/docs/architecture.svg +77 -0
  22. package/docs/jev-layer-banner.svg +38 -0
  23. package/examples/capabilities.json +39 -39
  24. package/examples/route-request.json +28 -28
  25. package/integrations/codex/.codex-plugin/plugin.json +19 -19
  26. package/integrations/codex/.mcp.json +11 -11
  27. package/integrations/codex/AGENTS.md +1 -1
  28. package/integrations/codex/run-mcp.mjs +9 -9
  29. package/integrations/codex/skills/jev-route/SKILL.md +17 -17
  30. package/integrations/hermes/__init__.py +150 -51
  31. package/integrations/hermes/plugin.yaml +10 -5
  32. package/integrations/hermes/schemas.py +89 -12
  33. package/integrations/omp/extension.js +105 -105
  34. package/integrations/template/README.md +10 -10
  35. package/integrations/template/adapter.mjs +87 -87
  36. package/package.json +60 -59
  37. package/scripts/benchmark.mjs +35 -35
  38. package/scripts/browser-benchmark.mjs +92 -92
  39. package/scripts/browser-e2e.mjs +117 -117
  40. package/scripts/capability-e2e.mjs +31 -31
  41. package/scripts/clean-install-smoke.mjs +162 -162
  42. package/scripts/codex-mcp-smoke.mjs +119 -119
  43. package/scripts/context-filter-e2e.mjs +45 -45
  44. package/scripts/fail-open-smoke.mjs +120 -120
  45. package/scripts/feature-flags-smoke.mjs +46 -46
  46. package/scripts/mcp-receipt-smoke.mjs +113 -98
  47. package/scripts/model-route-report.mjs +15 -0
  48. package/scripts/openrouter-choice.mjs +59 -59
  49. package/scripts/replay-eval.mjs +124 -124
  50. package/scripts/smoke.mjs +34 -34
  51. package/scripts/supervision-e2e.mjs +138 -109
  52. package/src/browser.mjs +571 -569
  53. package/src/cli.mjs +33 -33
  54. package/src/config.mjs +85 -75
  55. package/src/context-filter.mjs +56 -56
  56. package/src/contract.mjs +72 -72
  57. package/src/discovery.mjs +65 -65
  58. package/src/hermes-adapter.mjs +123 -0
  59. package/src/mcp-server.mjs +267 -210
  60. package/src/model-route-metrics.mjs +89 -0
  61. package/src/model-routing.mjs +87 -0
  62. package/src/providers/demo.mjs +52 -52
  63. package/src/providers/index.mjs +12 -0
  64. package/src/providers/typesafe.mjs +126 -126
  65. package/src/receipts.mjs +227 -226
  66. package/src/registry.mjs +109 -109
  67. package/src/relevance-filter.mjs +99 -99
  68. package/src/route.mjs +219 -221
  69. package/src/shadow-compaction.mjs +170 -0
  70. package/src/supervision.mjs +268 -244
  71. package/test/browser.test.mjs +199 -199
  72. package/test/capability.test.mjs +54 -54
  73. package/test/context-filter.test.mjs +65 -65
  74. package/test/hermes-adapter.test.mjs +62 -0
  75. package/test/model-route-metrics.test.mjs +61 -0
  76. package/test/model-routing.test.mjs +59 -0
  77. package/test/openrouter-provider.test.mjs +55 -55
  78. package/test/provider-transports.test.mjs +205 -0
  79. package/test/providers.test.mjs +126 -0
  80. package/test/receipts.test.mjs +71 -71
  81. package/test/route.test.mjs +74 -74
  82. package/test/shadow-compaction.test.mjs +67 -0
  83. package/test/supervision.test.mjs +117 -99
@@ -1,117 +1,117 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtemp, readFile } from "node:fs/promises";
3
- import { spawn } from "node:child_process";
4
- import { createInterface } from "node:readline";
5
- import { fileURLToPath } from "node:url";
6
- import { dirname, join, resolve } from "node:path";
7
- import { tmpdir } from "node:os";
8
-
9
- const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
- const casesPath = join(await mkdtemp(join(tmpdir(), "jev-browser-e2e-")), "cases.jsonl");
11
- const child = spawn(process.env.JEV_NODE ?? "node", [resolve(root, "src/mcp-server.mjs")], {
12
- cwd: root,
13
- env: { ...process.env, JEV_BROWSER_FAST_PATH: "1", JEV_REPLAY_CASES: casesPath },
14
- stdio: ["pipe", "pipe", "pipe"],
15
- });
16
- const lines = createInterface({ input: child.stdout });
17
- const pending = new Map();
18
- lines.on("line", (line) => {
19
- if (!line.trim()) return;
20
- const message = JSON.parse(line);
21
- const waiter = pending.get(message.id);
22
- if (waiter) {
23
- pending.delete(message.id);
24
- waiter(message);
25
- }
26
- });
27
-
28
- try {
29
- const initialized = await request(1, "initialize", {});
30
- assert.equal(initialized.result.serverInfo.name, "jev-layer");
31
- await notification("notifications/initialized", {});
32
- const listed = await request(2, "tools/list", {});
33
- assert.ok(listed.result.tools.some((tool) => tool.name === "jev_browser_step"));
34
-
35
- const routed = await request(3, "tools/call", {
36
- name: "jev_browser_step",
37
- arguments: {
38
- provider: process.env.JEV_BROWSER_PROVIDER ?? "demo",
39
- enabled: true,
40
- harness: "browser-e2e",
41
- goal: "scroll the page downward",
42
- observation: {
43
- url: "https://example.test",
44
- title: "Fixture",
45
- visible_text: "Additional content is below the fold",
46
- scroll: { up: false, down: true },
47
- targets: [],
48
- tabs: [],
49
- },
50
- policy: { min_confidence: 0 },
51
- },
52
- });
53
- const decision = routed.result.structuredContent;
54
- assert.equal(decision.status, "selected");
55
- assert.equal(decision.browser_action.operation, "scroll");
56
- assert.equal(decision.browser_action.target_id, null);
57
-
58
- const started = performance.now();
59
- const hostResult = { operation: decision.browser_action.operation, direction: "down", fixture: true };
60
- const recorded = await request(4, "tools/call", {
61
- name: "jev_record_execution",
62
- arguments: {
63
- correlation_id: decision.correlation_id,
64
- harness: "browser-e2e",
65
- capability_id: decision.selected,
66
- status: "completed",
67
- result: hostResult,
68
- browser: { operation: "scroll", direction: "down", executed: true },
69
- exit_status: 0,
70
- duration_ms: Number((performance.now() - started).toFixed(3)),
71
- },
72
- });
73
- const receipt = recorded.result.structuredContent;
74
- assert.equal(receipt.correlation_id, decision.correlation_id);
75
- assert.equal(receipt.host.browser.operation, "scroll");
76
- assert.equal(receipt.host.exit_status, 0);
77
-
78
- const records = (await readFile(casesPath, "utf8")).trim().split(/\r?\n/).map(JSON.parse);
79
- assert.deepEqual(records.map((record) => record.record_type), ["routing_case", "execution_receipt"]);
80
- assert.equal(records[0].request.context.browser.goal, "scroll the page downward");
81
- console.log(JSON.stringify({
82
- ok: true,
83
- cases_path: casesPath,
84
- decision: {
85
- correlation_id: decision.correlation_id,
86
- selected: decision.selected,
87
- operation: decision.browser_action.operation,
88
- confidence: decision.confidence,
89
- latency_ms: decision.receipt.latency_ms,
90
- cost_usd: decision.receipt.cost_usd ?? null,
91
- },
92
- host_execution: { operation: hostResult.operation, exit_status: receipt.host.exit_status },
93
- receipts: records.length,
94
- }, null, 2));
95
- } finally {
96
- child.kill();
97
- await new Promise((resolveExit) => child.once("close", resolveExit));
98
- }
99
-
100
- function request(id, method, params) {
101
- return new Promise((resolveResponse, reject) => {
102
- const timer = setTimeout(() => {
103
- pending.delete(id);
104
- reject(new Error(`MCP request timed out: ${method}`));
105
- }, 15_000);
106
- pending.set(id, (message) => {
107
- clearTimeout(timer);
108
- resolveResponse(message);
109
- });
110
- child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
111
- });
112
- }
113
-
114
- function notification(method, params) {
115
- child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
116
- return new Promise((resolveNotification) => setImmediate(resolveNotification));
117
- }
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile } from "node:fs/promises";
3
+ import { spawn } from "node:child_process";
4
+ import { createInterface } from "node:readline";
5
+ import { fileURLToPath } from "node:url";
6
+ import { dirname, join, resolve } from "node:path";
7
+ import { tmpdir } from "node:os";
8
+
9
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
+ const casesPath = join(await mkdtemp(join(tmpdir(), "jev-browser-e2e-")), "cases.jsonl");
11
+ const child = spawn(process.env.JEV_NODE ?? "node", [resolve(root, "src/mcp-server.mjs")], {
12
+ cwd: root,
13
+ env: { ...process.env, JEV_BROWSER_FAST_PATH: "1", JEV_REPLAY_CASES: casesPath },
14
+ stdio: ["pipe", "pipe", "pipe"],
15
+ });
16
+ const lines = createInterface({ input: child.stdout });
17
+ const pending = new Map();
18
+ lines.on("line", (line) => {
19
+ if (!line.trim()) return;
20
+ const message = JSON.parse(line);
21
+ const waiter = pending.get(message.id);
22
+ if (waiter) {
23
+ pending.delete(message.id);
24
+ waiter(message);
25
+ }
26
+ });
27
+
28
+ try {
29
+ const initialized = await request(1, "initialize", {});
30
+ assert.equal(initialized.result.serverInfo.name, "jev-layer");
31
+ await notification("notifications/initialized", {});
32
+ const listed = await request(2, "tools/list", {});
33
+ assert.ok(listed.result.tools.some((tool) => tool.name === "jev_browser_step"));
34
+
35
+ const routed = await request(3, "tools/call", {
36
+ name: "jev_browser_step",
37
+ arguments: {
38
+ provider: process.env.JEV_BROWSER_PROVIDER ?? "demo",
39
+ enabled: true,
40
+ harness: "browser-e2e",
41
+ goal: "scroll the page downward",
42
+ observation: {
43
+ url: "https://example.test",
44
+ title: "Fixture",
45
+ visible_text: "Additional content is below the fold",
46
+ scroll: { up: false, down: true },
47
+ targets: [],
48
+ tabs: [],
49
+ },
50
+ policy: { min_confidence: 0 },
51
+ },
52
+ });
53
+ const decision = routed.result.structuredContent;
54
+ assert.equal(decision.status, "selected");
55
+ assert.equal(decision.browser_action.operation, "scroll");
56
+ assert.equal(decision.browser_action.target_id, null);
57
+
58
+ const started = performance.now();
59
+ const hostResult = { operation: decision.browser_action.operation, direction: "down", fixture: true };
60
+ const recorded = await request(4, "tools/call", {
61
+ name: "jev_record_execution",
62
+ arguments: {
63
+ correlation_id: decision.correlation_id,
64
+ harness: "browser-e2e",
65
+ capability_id: decision.selected,
66
+ status: "completed",
67
+ result: hostResult,
68
+ browser: { operation: "scroll", direction: "down", executed: true },
69
+ exit_status: 0,
70
+ duration_ms: Number((performance.now() - started).toFixed(3)),
71
+ },
72
+ });
73
+ const receipt = recorded.result.structuredContent;
74
+ assert.equal(receipt.correlation_id, decision.correlation_id);
75
+ assert.equal(receipt.host.browser.operation, "scroll");
76
+ assert.equal(receipt.host.exit_status, 0);
77
+
78
+ const records = (await readFile(casesPath, "utf8")).trim().split(/\r?\n/).map(JSON.parse);
79
+ assert.deepEqual(records.map((record) => record.record_type), ["routing_case", "execution_receipt"]);
80
+ assert.equal(records[0].request.context.browser.goal, "scroll the page downward");
81
+ console.log(JSON.stringify({
82
+ ok: true,
83
+ cases_path: casesPath,
84
+ decision: {
85
+ correlation_id: decision.correlation_id,
86
+ selected: decision.selected,
87
+ operation: decision.browser_action.operation,
88
+ confidence: decision.confidence,
89
+ latency_ms: decision.receipt.latency_ms,
90
+ cost_usd: decision.receipt.cost_usd ?? null,
91
+ },
92
+ host_execution: { operation: hostResult.operation, exit_status: receipt.host.exit_status },
93
+ receipts: records.length,
94
+ }, null, 2));
95
+ } finally {
96
+ child.kill();
97
+ await new Promise((resolveExit) => child.once("close", resolveExit));
98
+ }
99
+
100
+ function request(id, method, params) {
101
+ return new Promise((resolveResponse, reject) => {
102
+ const timer = setTimeout(() => {
103
+ pending.delete(id);
104
+ reject(new Error(`MCP request timed out: ${method}`));
105
+ }, 15_000);
106
+ pending.set(id, (message) => {
107
+ clearTimeout(timer);
108
+ resolveResponse(message);
109
+ });
110
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
111
+ });
112
+ }
113
+
114
+ function notification(method, params) {
115
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
116
+ return new Promise((resolveNotification) => setImmediate(resolveNotification));
117
+ }
@@ -1,31 +1,31 @@
1
- import assert from "node:assert/strict";
2
- import { routeRequest } from "../src/route.mjs";
3
-
4
- let providerCalls = 0;
5
- const decision = await routeRequest({
6
- harness: "capability-e2e",
7
- intent: "use the verified model for a bounded decision",
8
- context: { task: "model" },
9
- policy: { deterministic: true, prefer_kind: "model", prefer_verified: true },
10
- }, {
11
- discovery: {
12
- skills: [{ id: "skill:inspect", name: "Inspect skill", description: "Inspect context" }],
13
- models: [{ id: "model:verified", name: "Verified model", description: "Make bounded decisions", verified: true, risk: "low", source: "e2e-manifest" }],
14
- tools: [{ id: "tool:offline", name: "Offline tool", description: "Unavailable", available: false }],
15
- },
16
- provider: { name: "capability-e2e-provider", async decide() { providerCalls += 1; throw new Error("deterministic selection should not call Jev"); } },
17
- });
18
- assert.equal(providerCalls, 0);
19
- assert.equal(decision.status, "selected");
20
- assert.equal(decision.selected, "model:verified");
21
- assert.equal(decision.candidates.find((candidate) => candidate.id === "model:verified").source, "e2e-manifest");
22
- assert.equal(decision.candidates.find((candidate) => candidate.id === "model:verified").verified, true);
23
- console.log(JSON.stringify({
24
- ok: true,
25
- status: decision.status,
26
- selected: decision.selected,
27
- provider: decision.receipt.provider,
28
- deterministic: true,
29
- jev_calls: providerCalls,
30
- candidate_metadata: decision.candidates.find((candidate) => candidate.id === decision.selected),
31
- }, null, 2));
1
+ import assert from "node:assert/strict";
2
+ import { routeRequest } from "../src/route.mjs";
3
+
4
+ let providerCalls = 0;
5
+ const decision = await routeRequest({
6
+ harness: "capability-e2e",
7
+ intent: "use the verified model for a bounded decision",
8
+ context: { task: "model" },
9
+ policy: { deterministic: true, prefer_kind: "model", prefer_verified: true },
10
+ }, {
11
+ discovery: {
12
+ skills: [{ id: "skill:inspect", name: "Inspect skill", description: "Inspect context" }],
13
+ models: [{ id: "model:verified", name: "Verified model", description: "Make bounded decisions", verified: true, risk: "low", source: "e2e-manifest" }],
14
+ tools: [{ id: "tool:offline", name: "Offline tool", description: "Unavailable", available: false }],
15
+ },
16
+ provider: { name: "capability-e2e-provider", async decide() { providerCalls += 1; throw new Error("deterministic selection should not call Jev"); } },
17
+ });
18
+ assert.equal(providerCalls, 0);
19
+ assert.equal(decision.status, "selected");
20
+ assert.equal(decision.selected, "model:verified");
21
+ assert.equal(decision.candidates.find((candidate) => candidate.id === "model:verified").source, "e2e-manifest");
22
+ assert.equal(decision.candidates.find((candidate) => candidate.id === "model:verified").verified, true);
23
+ console.log(JSON.stringify({
24
+ ok: true,
25
+ status: decision.status,
26
+ selected: decision.selected,
27
+ provider: decision.receipt.provider,
28
+ deterministic: true,
29
+ jev_calls: providerCalls,
30
+ candidate_metadata: decision.candidates.find((candidate) => candidate.id === decision.selected),
31
+ }, null, 2));
@@ -1,162 +1,162 @@
1
- import assert from "node:assert/strict";
2
- import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
- import { execFile, spawn } from "node:child_process";
4
- import { fileURLToPath } from "node:url";
5
- import { delimiter, dirname, join, resolve } from "node:path";
6
- import { promisify } from "node:util";
7
- import { tmpdir } from "node:os";
8
-
9
- const execFileAsync = promisify(execFile);
10
- const sourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
- const stage = await mkdtemp(join(tmpdir(), "jev-clean-install-"));
12
- const packageRoot = join(stage, "package");
13
- const workspace = join(stage, "workspace");
14
-
15
- try {
16
- await copyDistribution(packageRoot);
17
- await execFileAsync("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], { cwd: packageRoot, env: process.env });
18
- await mkdir(workspace, { recursive: true });
19
- await writeFile(join(workspace, "package.json"), JSON.stringify({ name: "clean-install-workspace", private: true }, null, 2) + "\n", "utf8");
20
- await execFileAsync("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund", "--no-save", packageRoot], { cwd: workspace, env: process.env });
21
- await runNode(packageRoot, ["bin/jev.mjs", "install", "--project", workspace]);
22
- await runNode(packageRoot, ["bin/jev.mjs", "add", "generic", "--project", workspace]);
23
- const doctor = await runNode(packageRoot, ["bin/jev.mjs", "doctor", "--project", workspace]);
24
- const doctorReport = JSON.parse(doctor.stdout);
25
- assert.equal(doctorReport.ok, true);
26
-
27
- const cli = await runJev(workspace, ["cli", "--provider", "demo", "--input", join(packageRoot, "examples", "route-request.json")]);
28
- const cliDecision = JSON.parse(cli.stdout.trim().split(/\r?\n/).at(-1));
29
- assert.equal(cliDecision.execution.enabled, false);
30
- assert.ok(cliDecision.correlation_id);
31
-
32
- const genericConfig = JSON.parse(await readFile(join(workspace, ".jev", "adapters", "generic.json"), "utf8"));
33
- const genericServer = genericConfig.mcpServers.jev;
34
- assert.deepEqual(genericServer.args, ["mcp"]);
35
- const mcpEnv = localEnv(workspace);
36
- const mcp = spawn(genericServer.command, genericServer.args, {
37
- cwd: workspace,
38
- env: { ...mcpEnv, JEV_LAYER_PROVIDER: "demo", JEV_REPLAY_CASES: join(workspace, ".jev", "replay", "cases.jsonl") },
39
- stdio: ["pipe", "pipe", "pipe"],
40
- });
41
- const pending = new Map();
42
- let nextId = 1;
43
- const stderr = [];
44
- let buffer = "";
45
- mcp.stderr.on("data", (chunk) => stderr.push(String(chunk)));
46
- mcp.stdout.on("data", (chunk) => {
47
- buffer += String(chunk);
48
- for (const line of buffer.split(/\r?\n/).slice(0, -1)) {
49
- if (!line.trim()) continue;
50
- const message = JSON.parse(line);
51
- pending.get(message.id)?.(message);
52
- pending.delete(message.id);
53
- }
54
- buffer = buffer.split(/\r?\n/).at(-1) ?? "";
55
- });
56
-
57
- const request = (method, params) => new Promise((resolveRequest, reject) => {
58
- const id = nextId++;
59
- pending.set(id, resolveRequest);
60
- mcp.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
61
- setTimeout(() => {
62
- if (pending.delete(id)) reject(new Error(`timeout waiting for ${method}`));
63
- }, 10_000).unref();
64
- });
65
- const notify = (method, params) => mcp.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
66
-
67
- const initialized = await request("initialize", {});
68
- assert.equal(initialized.result.serverInfo.name, "jev-layer");
69
- notify("notifications/initialized", {});
70
- const listed = await request("tools/list", {});
71
- assert.ok(listed.result.tools.some((tool) => tool.name === "jev_route"));
72
- assert.ok(listed.result.tools.some((tool) => tool.name === "jev_record_execution"));
73
- const routed = await request("tools/call", {
74
- name: "jev_route",
75
- arguments: {
76
- harness: "clean-install",
77
- intent: "read the installed package manifest",
78
- actor_permissions: ["read"],
79
- capabilities: [{
80
- id: "read_manifest",
81
- kind: "tool",
82
- name: "Read package manifest",
83
- description: "Read package.json without changing files",
84
- permissions: ["read"],
85
- risk: "low",
86
- }],
87
- },
88
- });
89
- const decision = routed.result.structuredContent;
90
- assert.equal(decision.status, "selected");
91
- const hostResult = await readFile(join(packageRoot, "package.json"), "utf8");
92
- const recorded = await request("tools/call", {
93
- name: "jev_record_execution",
94
- arguments: {
95
- correlation_id: decision.correlation_id,
96
- harness: "clean-install",
97
- capability_id: decision.selected,
98
- status: "completed",
99
- result: { bytes: Buffer.byteLength(hostResult) },
100
- exit_status: 0,
101
- duration_ms: 1,
102
- },
103
- });
104
- const receipt = recorded.result.structuredContent;
105
- assert.equal(receipt.correlation_id, decision.correlation_id);
106
- assert.equal(receipt.host.exit_status, 0);
107
- assert.equal(receipt.host.duration_ms, 1);
108
- mcp.stdin.end();
109
- await onceExit(mcp);
110
- assert.equal(stderr.join(""), "");
111
-
112
- const records = (await readFile(join(workspace, ".jev", "replay", "cases.jsonl"), "utf8"))
113
- .trim().split(/\r?\n/).map((line) => JSON.parse(line));
114
- assert.equal(records.length, 2);
115
- assert.deepEqual(records.map((record) => record.record_type), ["routing_case", "execution_receipt"]);
116
- assert.equal(records[0].correlation_id, records[1].correlation_id);
117
- const replay = await runNode(packageRoot, ["scripts/replay-eval.mjs", "--provider", "demo", "--input", join(workspace, ".jev", "replay", "cases.jsonl"), "--limit", "1"]);
118
- const replayReport = JSON.parse(replay.stdout);
119
- assert.equal(replayReport.replayed_cases, 1);
120
- assert.equal(replayReport.host_execution_calls, 0);
121
- console.log(JSON.stringify({
122
- ok: true,
123
- stage,
124
- package_root: packageRoot,
125
- workspace,
126
- install: "npm install",
127
- cli: { status: cliDecision.status, correlation_id: cliDecision.correlation_id },
128
- mcp: { tools: listed.result.tools.map((tool) => tool.name), selected: decision.selected, host_exit_status: receipt.host.exit_status },
129
- receipts: records.length,
130
- replay_cases: records.filter((record) => record.record_type === "routing_case").length,
131
- replay: { replayed_cases: replayReport.replayed_cases, host_execution_calls: replayReport.host_execution_calls },
132
- }, null, 2));
133
- } finally {
134
- await rm(stage, { recursive: true, force: true });
135
- }
136
-
137
- async function copyDistribution(destination) {
138
- const entries = ["bin", "config", "examples", "integrations", "scripts", "src", "test", "README.md", "package.json", "package-lock.json"];
139
- for (const entry of entries) await cp(join(sourceRoot, entry), join(destination, entry), { recursive: true });
140
- }
141
-
142
- function runNode(cwd, args) {
143
- return execFileAsync(process.execPath, args, { cwd, env: process.env, maxBuffer: 2 * 1024 * 1024 });
144
- }
145
-
146
- function runJev(cwd, args) {
147
- return execFileAsync("jev", args, { cwd, env: localEnv(cwd), maxBuffer: 2 * 1024 * 1024 });
148
- }
149
-
150
- function localEnv(workspaceRoot) {
151
- return {
152
- ...process.env,
153
- PATH: `${join(workspaceRoot, "node_modules", ".bin")}${delimiter}${process.env.PATH ?? ""}`,
154
- };
155
- }
156
-
157
- function onceExit(child) {
158
- return new Promise((resolveExit, reject) => {
159
- child.once("error", reject);
160
- child.once("close", (code) => code === 0 ? resolveExit() : reject(new Error(`MCP exited with ${code}`)));
161
- });
162
- }
1
+ import assert from "node:assert/strict";
2
+ import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { execFile, spawn } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { delimiter, dirname, join, resolve } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { tmpdir } from "node:os";
8
+
9
+ const execFileAsync = promisify(execFile);
10
+ const sourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
+ const stage = await mkdtemp(join(tmpdir(), "jev-clean-install-"));
12
+ const packageRoot = join(stage, "package");
13
+ const workspace = join(stage, "workspace");
14
+
15
+ try {
16
+ await copyDistribution(packageRoot);
17
+ await execFileAsync("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], { cwd: packageRoot, env: process.env });
18
+ await mkdir(workspace, { recursive: true });
19
+ await writeFile(join(workspace, "package.json"), JSON.stringify({ name: "clean-install-workspace", private: true }, null, 2) + "\n", "utf8");
20
+ await execFileAsync("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund", "--no-save", packageRoot], { cwd: workspace, env: process.env });
21
+ await runNode(packageRoot, ["bin/jev.mjs", "install", "--project", workspace]);
22
+ await runNode(packageRoot, ["bin/jev.mjs", "add", "generic", "--project", workspace]);
23
+ const doctor = await runNode(packageRoot, ["bin/jev.mjs", "doctor", "--project", workspace]);
24
+ const doctorReport = JSON.parse(doctor.stdout);
25
+ assert.equal(doctorReport.ok, true);
26
+
27
+ const cli = await runJev(workspace, ["cli", "--provider", "demo", "--input", join(packageRoot, "examples", "route-request.json")]);
28
+ const cliDecision = JSON.parse(cli.stdout.trim().split(/\r?\n/).at(-1));
29
+ assert.equal(cliDecision.execution.enabled, false);
30
+ assert.ok(cliDecision.correlation_id);
31
+
32
+ const genericConfig = JSON.parse(await readFile(join(workspace, ".jev", "adapters", "generic.json"), "utf8"));
33
+ const genericServer = genericConfig.mcpServers.jev;
34
+ assert.deepEqual(genericServer.args, ["mcp"]);
35
+ const mcpEnv = localEnv(workspace);
36
+ const mcp = spawn(genericServer.command, genericServer.args, {
37
+ cwd: workspace,
38
+ env: { ...mcpEnv, JEV_LAYER_PROVIDER: "demo", JEV_REPLAY_CASES: join(workspace, ".jev", "replay", "cases.jsonl") },
39
+ stdio: ["pipe", "pipe", "pipe"],
40
+ });
41
+ const pending = new Map();
42
+ let nextId = 1;
43
+ const stderr = [];
44
+ let buffer = "";
45
+ mcp.stderr.on("data", (chunk) => stderr.push(String(chunk)));
46
+ mcp.stdout.on("data", (chunk) => {
47
+ buffer += String(chunk);
48
+ for (const line of buffer.split(/\r?\n/).slice(0, -1)) {
49
+ if (!line.trim()) continue;
50
+ const message = JSON.parse(line);
51
+ pending.get(message.id)?.(message);
52
+ pending.delete(message.id);
53
+ }
54
+ buffer = buffer.split(/\r?\n/).at(-1) ?? "";
55
+ });
56
+
57
+ const request = (method, params) => new Promise((resolveRequest, reject) => {
58
+ const id = nextId++;
59
+ pending.set(id, resolveRequest);
60
+ mcp.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
61
+ setTimeout(() => {
62
+ if (pending.delete(id)) reject(new Error(`timeout waiting for ${method}`));
63
+ }, 10_000).unref();
64
+ });
65
+ const notify = (method, params) => mcp.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
66
+
67
+ const initialized = await request("initialize", {});
68
+ assert.equal(initialized.result.serverInfo.name, "jev-layer");
69
+ notify("notifications/initialized", {});
70
+ const listed = await request("tools/list", {});
71
+ assert.ok(listed.result.tools.some((tool) => tool.name === "jev_route"));
72
+ assert.ok(listed.result.tools.some((tool) => tool.name === "jev_record_execution"));
73
+ const routed = await request("tools/call", {
74
+ name: "jev_route",
75
+ arguments: {
76
+ harness: "clean-install",
77
+ intent: "read the installed package manifest",
78
+ actor_permissions: ["read"],
79
+ capabilities: [{
80
+ id: "read_manifest",
81
+ kind: "tool",
82
+ name: "Read package manifest",
83
+ description: "Read package.json without changing files",
84
+ permissions: ["read"],
85
+ risk: "low",
86
+ }],
87
+ },
88
+ });
89
+ const decision = routed.result.structuredContent;
90
+ assert.equal(decision.status, "selected");
91
+ const hostResult = await readFile(join(packageRoot, "package.json"), "utf8");
92
+ const recorded = await request("tools/call", {
93
+ name: "jev_record_execution",
94
+ arguments: {
95
+ correlation_id: decision.correlation_id,
96
+ harness: "clean-install",
97
+ capability_id: decision.selected,
98
+ status: "completed",
99
+ result: { bytes: Buffer.byteLength(hostResult) },
100
+ exit_status: 0,
101
+ duration_ms: 1,
102
+ },
103
+ });
104
+ const receipt = recorded.result.structuredContent;
105
+ assert.equal(receipt.correlation_id, decision.correlation_id);
106
+ assert.equal(receipt.host.exit_status, 0);
107
+ assert.equal(receipt.host.duration_ms, 1);
108
+ mcp.stdin.end();
109
+ await onceExit(mcp);
110
+ assert.equal(stderr.join(""), "");
111
+
112
+ const records = (await readFile(join(workspace, ".jev", "replay", "cases.jsonl"), "utf8"))
113
+ .trim().split(/\r?\n/).map((line) => JSON.parse(line));
114
+ assert.equal(records.length, 2);
115
+ assert.deepEqual(records.map((record) => record.record_type), ["routing_case", "execution_receipt"]);
116
+ assert.equal(records[0].correlation_id, records[1].correlation_id);
117
+ const replay = await runNode(packageRoot, ["scripts/replay-eval.mjs", "--provider", "demo", "--input", join(workspace, ".jev", "replay", "cases.jsonl"), "--limit", "1"]);
118
+ const replayReport = JSON.parse(replay.stdout);
119
+ assert.equal(replayReport.replayed_cases, 1);
120
+ assert.equal(replayReport.host_execution_calls, 0);
121
+ console.log(JSON.stringify({
122
+ ok: true,
123
+ stage,
124
+ package_root: packageRoot,
125
+ workspace,
126
+ install: "npm install",
127
+ cli: { status: cliDecision.status, correlation_id: cliDecision.correlation_id },
128
+ mcp: { tools: listed.result.tools.map((tool) => tool.name), selected: decision.selected, host_exit_status: receipt.host.exit_status },
129
+ receipts: records.length,
130
+ replay_cases: records.filter((record) => record.record_type === "routing_case").length,
131
+ replay: { replayed_cases: replayReport.replayed_cases, host_execution_calls: replayReport.host_execution_calls },
132
+ }, null, 2));
133
+ } finally {
134
+ await rm(stage, { recursive: true, force: true });
135
+ }
136
+
137
+ async function copyDistribution(destination) {
138
+ const entries = ["bin", "config", "examples", "integrations", "scripts", "src", "test", "README.md", "package.json", "package-lock.json"];
139
+ for (const entry of entries) await cp(join(sourceRoot, entry), join(destination, entry), { recursive: true });
140
+ }
141
+
142
+ function runNode(cwd, args) {
143
+ return execFileAsync(process.execPath, args, { cwd, env: process.env, maxBuffer: 2 * 1024 * 1024 });
144
+ }
145
+
146
+ function runJev(cwd, args) {
147
+ return execFileAsync("jev", args, { cwd, env: localEnv(cwd), maxBuffer: 2 * 1024 * 1024 });
148
+ }
149
+
150
+ function localEnv(workspaceRoot) {
151
+ return {
152
+ ...process.env,
153
+ PATH: `${join(workspaceRoot, "node_modules", ".bin")}${delimiter}${process.env.PATH ?? ""}`,
154
+ };
155
+ }
156
+
157
+ function onceExit(child) {
158
+ return new Promise((resolveExit, reject) => {
159
+ child.once("error", reject);
160
+ child.once("close", (code) => code === 0 ? resolveExit() : reject(new Error(`MCP exited with ${code}`)));
161
+ });
162
+ }