jev-layer 0.1.0 → 0.3.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/CHANGELOG.md +73 -34
- package/CONTRIBUTING.md +146 -146
- package/LICENSE +21 -21
- package/README.md +125 -126
- package/README.ru.md +140 -117
- package/README.zh-CN.md +139 -117
- package/RELEASE.md +53 -53
- package/SECURITY.md +64 -56
- package/bin/jev.mjs +215 -213
- package/config/codex.mcp.toml +8 -8
- package/config/generic-mcp.json +13 -13
- package/config/hermes.mcp.yaml +9 -9
- package/config/jev.example.json +23 -22
- package/config/omp.mcp.json +14 -14
- package/config/providers.env.example +13 -13
- package/docs/AGENT-IMPLEMENTATION.md +306 -0
- package/docs/COMPATIBILITY.md +24 -15
- package/docs/HERMES-LOCAL-AGENT-HANDOFF.ru.md +187 -0
- package/docs/PROVIDERS.md +109 -0
- package/docs/SCHEMA-VERSIONING.md +101 -94
- package/docs/architecture.svg +77 -0
- package/docs/jev-layer-banner.svg +38 -0
- package/examples/capabilities.json +39 -39
- package/examples/route-request.json +28 -28
- package/integrations/codex/.codex-plugin/plugin.json +19 -19
- package/integrations/codex/.mcp.json +11 -11
- package/integrations/codex/AGENTS.md +1 -1
- package/integrations/codex/run-mcp.mjs +9 -9
- package/integrations/codex/skills/jev-route/SKILL.md +17 -17
- package/integrations/hermes/__init__.py +156 -51
- package/integrations/hermes/plugin.yaml +11 -5
- package/integrations/hermes/schemas.py +120 -12
- package/integrations/omp/extension.js +106 -105
- package/integrations/template/README.md +10 -10
- package/integrations/template/adapter.mjs +87 -87
- package/package.json +60 -59
- package/scripts/benchmark.mjs +35 -35
- package/scripts/browser-benchmark.mjs +92 -92
- package/scripts/browser-e2e.mjs +117 -117
- package/scripts/capability-e2e.mjs +31 -31
- package/scripts/clean-install-smoke.mjs +176 -162
- package/scripts/codex-mcp-smoke.mjs +119 -119
- package/scripts/context-filter-e2e.mjs +45 -45
- package/scripts/fail-open-smoke.mjs +120 -120
- package/scripts/feature-flags-smoke.mjs +46 -46
- package/scripts/mcp-receipt-smoke.mjs +115 -98
- package/scripts/model-route-report.mjs +15 -0
- package/scripts/openrouter-choice.mjs +59 -59
- package/scripts/replay-eval.mjs +124 -124
- package/scripts/smoke.mjs +34 -34
- package/scripts/supervision-e2e.mjs +138 -109
- package/src/browser.mjs +583 -569
- package/src/cli.mjs +33 -33
- package/src/computer.mjs +196 -0
- package/src/config.mjs +86 -75
- package/src/context-filter.mjs +56 -56
- package/src/contract.mjs +74 -72
- package/src/discovery.mjs +65 -65
- package/src/hermes-adapter.mjs +134 -0
- package/src/images.mjs +98 -0
- package/src/mcp-server.mjs +313 -210
- package/src/model-route-metrics.mjs +89 -0
- package/src/model-routing.mjs +87 -0
- package/src/providers/demo.mjs +52 -52
- package/src/providers/index.mjs +12 -0
- package/src/providers/typesafe.mjs +206 -126
- package/src/receipts.mjs +228 -226
- package/src/registry.mjs +109 -109
- package/src/relevance-filter.mjs +99 -99
- package/src/route.mjs +228 -221
- package/src/shadow-compaction.mjs +170 -0
- package/src/supervision.mjs +268 -244
- package/test/browser.test.mjs +222 -199
- package/test/capability.test.mjs +54 -54
- package/test/computer.test.mjs +195 -0
- package/test/context-filter.test.mjs +65 -65
- package/test/hermes-adapter.test.mjs +63 -0
- package/test/image-fixture.mjs +2 -0
- package/test/images.test.mjs +85 -0
- package/test/model-route-metrics.test.mjs +61 -0
- package/test/model-routing.test.mjs +59 -0
- package/test/openrouter-provider.test.mjs +55 -55
- package/test/provider-transports.test.mjs +205 -0
- package/test/providers.test.mjs +125 -0
- package/test/receipts.test.mjs +71 -71
- package/test/route.test.mjs +86 -74
- package/test/shadow-compaction.test.mjs +67 -0
- package/test/supervision.test.mjs +117 -99
|
@@ -1,162 +1,176 @@
|
|
|
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
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
await
|
|
20
|
-
await
|
|
21
|
-
await
|
|
22
|
-
await
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
assert.equal(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
assert.
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
mcp.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
assert.
|
|
116
|
-
assert.equal(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
assert.equal(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 npmCli = process.env.npm_execpath;
|
|
12
|
+
assert.ok(npmCli, "Run this smoke with npm run clean-install-smoke so npm_execpath identifies the npm CLI.");
|
|
13
|
+
const stage = await mkdtemp(join(tmpdir(), "jev-clean-install-"));
|
|
14
|
+
const packageRoot = join(stage, "package");
|
|
15
|
+
const workspace = join(stage, "workspace");
|
|
16
|
+
const installedRoot = join(workspace, "node_modules", "jev-layer");
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await copyDistribution(packageRoot);
|
|
20
|
+
await runNpm(packageRoot, ["install", "--ignore-scripts", "--no-audit", "--no-fund"]);
|
|
21
|
+
await mkdir(workspace, { recursive: true });
|
|
22
|
+
await writeFile(join(workspace, "package.json"), JSON.stringify({ name: "clean-install-workspace", private: true }, null, 2) + "\n", "utf8");
|
|
23
|
+
await runNpm(workspace, ["install", "--ignore-scripts", "--no-audit", "--no-fund", "--no-save", "--install-links", packageRoot]);
|
|
24
|
+
await runJev(workspace, ["install", "--project", workspace]);
|
|
25
|
+
await runJev(workspace, ["add", "generic", "--project", workspace]);
|
|
26
|
+
const doctor = await runJev(workspace, ["doctor", "--project", workspace]);
|
|
27
|
+
const doctorReport = JSON.parse(doctor.stdout);
|
|
28
|
+
assert.equal(doctorReport.ok, true);
|
|
29
|
+
assert.equal(doctorReport.package_root, installedRoot);
|
|
30
|
+
|
|
31
|
+
const cli = await runJev(workspace, ["cli", "--provider", "demo", "--input", join(installedRoot, "examples", "route-request.json")]);
|
|
32
|
+
const cliDecision = JSON.parse(cli.stdout.trim().split(/\r?\n/).at(-1));
|
|
33
|
+
assert.equal(cliDecision.execution.enabled, false);
|
|
34
|
+
assert.ok(cliDecision.correlation_id);
|
|
35
|
+
|
|
36
|
+
const genericConfig = JSON.parse(await readFile(join(workspace, ".jev", "adapters", "generic.json"), "utf8"));
|
|
37
|
+
const genericServer = genericConfig.mcpServers.jev;
|
|
38
|
+
if (process.platform === "win32") {
|
|
39
|
+
assert.equal(genericServer.command, process.execPath);
|
|
40
|
+
assert.deepEqual(genericServer.args, [join(installedRoot, "bin", "jev.mjs"), "mcp"]);
|
|
41
|
+
} else {
|
|
42
|
+
assert.equal(genericServer.command, "jev");
|
|
43
|
+
assert.deepEqual(genericServer.args, ["mcp"]);
|
|
44
|
+
}
|
|
45
|
+
const mcpEnv = localEnv(workspace);
|
|
46
|
+
const mcp = spawn(genericServer.command, genericServer.args, {
|
|
47
|
+
cwd: workspace,
|
|
48
|
+
env: { ...mcpEnv, JEV_LAYER_PROVIDER: "demo", JEV_REPLAY_CASES: join(workspace, ".jev", "replay", "cases.jsonl") },
|
|
49
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
50
|
+
});
|
|
51
|
+
const pending = new Map();
|
|
52
|
+
let nextId = 1;
|
|
53
|
+
const stderr = [];
|
|
54
|
+
let buffer = "";
|
|
55
|
+
mcp.stderr.on("data", (chunk) => stderr.push(String(chunk)));
|
|
56
|
+
mcp.stdout.on("data", (chunk) => {
|
|
57
|
+
buffer += String(chunk);
|
|
58
|
+
for (const line of buffer.split(/\r?\n/).slice(0, -1)) {
|
|
59
|
+
if (!line.trim()) continue;
|
|
60
|
+
const message = JSON.parse(line);
|
|
61
|
+
pending.get(message.id)?.(message);
|
|
62
|
+
pending.delete(message.id);
|
|
63
|
+
}
|
|
64
|
+
buffer = buffer.split(/\r?\n/).at(-1) ?? "";
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const request = (method, params) => new Promise((resolveRequest, reject) => {
|
|
68
|
+
const id = nextId++;
|
|
69
|
+
pending.set(id, resolveRequest);
|
|
70
|
+
mcp.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
if (pending.delete(id)) reject(new Error(`timeout waiting for ${method}`));
|
|
73
|
+
}, 10_000).unref();
|
|
74
|
+
});
|
|
75
|
+
const notify = (method, params) => mcp.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
76
|
+
|
|
77
|
+
const initialized = await request("initialize", {});
|
|
78
|
+
assert.equal(initialized.result.serverInfo.name, "jev-layer");
|
|
79
|
+
notify("notifications/initialized", {});
|
|
80
|
+
const listed = await request("tools/list", {});
|
|
81
|
+
assert.ok(listed.result.tools.some((tool) => tool.name === "jev_route"));
|
|
82
|
+
assert.ok(listed.result.tools.some((tool) => tool.name === "jev_record_execution"));
|
|
83
|
+
const routed = await request("tools/call", {
|
|
84
|
+
name: "jev_route",
|
|
85
|
+
arguments: {
|
|
86
|
+
harness: "clean-install",
|
|
87
|
+
intent: "read the installed package manifest",
|
|
88
|
+
actor_permissions: ["read"],
|
|
89
|
+
capabilities: [{
|
|
90
|
+
id: "read_manifest",
|
|
91
|
+
kind: "tool",
|
|
92
|
+
name: "Read package manifest",
|
|
93
|
+
description: "Read package.json without changing files",
|
|
94
|
+
permissions: ["read"],
|
|
95
|
+
risk: "low",
|
|
96
|
+
}],
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
const decision = routed.result.structuredContent;
|
|
100
|
+
assert.equal(decision.status, "selected");
|
|
101
|
+
const hostResult = await readFile(join(installedRoot, "package.json"), "utf8");
|
|
102
|
+
const recorded = await request("tools/call", {
|
|
103
|
+
name: "jev_record_execution",
|
|
104
|
+
arguments: {
|
|
105
|
+
correlation_id: decision.correlation_id,
|
|
106
|
+
harness: "clean-install",
|
|
107
|
+
capability_id: decision.selected,
|
|
108
|
+
status: "completed",
|
|
109
|
+
result: { bytes: Buffer.byteLength(hostResult) },
|
|
110
|
+
exit_status: 0,
|
|
111
|
+
duration_ms: 1,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const receipt = recorded.result.structuredContent;
|
|
115
|
+
assert.equal(receipt.correlation_id, decision.correlation_id);
|
|
116
|
+
assert.equal(receipt.host.exit_status, 0);
|
|
117
|
+
assert.equal(receipt.host.duration_ms, 1);
|
|
118
|
+
mcp.stdin.end();
|
|
119
|
+
await onceExit(mcp);
|
|
120
|
+
assert.equal(stderr.join(""), "");
|
|
121
|
+
|
|
122
|
+
const records = (await readFile(join(workspace, ".jev", "replay", "cases.jsonl"), "utf8"))
|
|
123
|
+
.trim().split(/\r?\n/).map((line) => JSON.parse(line));
|
|
124
|
+
assert.equal(records.length, 2);
|
|
125
|
+
assert.deepEqual(records.map((record) => record.record_type), ["routing_case", "execution_receipt"]);
|
|
126
|
+
assert.equal(records[0].correlation_id, records[1].correlation_id);
|
|
127
|
+
const replay = await runNode(installedRoot, ["scripts/replay-eval.mjs", "--provider", "demo", "--input", join(workspace, ".jev", "replay", "cases.jsonl"), "--limit", "1"]);
|
|
128
|
+
const replayReport = JSON.parse(replay.stdout);
|
|
129
|
+
assert.equal(replayReport.replayed_cases, 1);
|
|
130
|
+
assert.equal(replayReport.host_execution_calls, 0);
|
|
131
|
+
console.log(JSON.stringify({
|
|
132
|
+
ok: true,
|
|
133
|
+
stage,
|
|
134
|
+
package_root: packageRoot,
|
|
135
|
+
workspace,
|
|
136
|
+
install: "npm install",
|
|
137
|
+
cli: { status: cliDecision.status, correlation_id: cliDecision.correlation_id },
|
|
138
|
+
mcp: { tools: listed.result.tools.map((tool) => tool.name), selected: decision.selected, host_exit_status: receipt.host.exit_status },
|
|
139
|
+
receipts: records.length,
|
|
140
|
+
replay_cases: records.filter((record) => record.record_type === "routing_case").length,
|
|
141
|
+
replay: { replayed_cases: replayReport.replayed_cases, host_execution_calls: replayReport.host_execution_calls },
|
|
142
|
+
}, null, 2));
|
|
143
|
+
} finally {
|
|
144
|
+
await rm(stage, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function copyDistribution(destination) {
|
|
148
|
+
const entries = ["bin", "config", "examples", "integrations", "scripts", "src", "test", "README.md", "package.json", "package-lock.json"];
|
|
149
|
+
for (const entry of entries) await cp(join(sourceRoot, entry), join(destination, entry), { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function runNode(cwd, args) {
|
|
153
|
+
return execFileAsync(process.execPath, args, { cwd, env: process.env, maxBuffer: 2 * 1024 * 1024 });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function runNpm(cwd, args) {
|
|
157
|
+
return runNode(cwd, [npmCli, ...args]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function runJev(cwd, args) {
|
|
161
|
+
return execFileAsync(process.execPath, [join(installedRoot, "bin", "jev.mjs"), ...args], { cwd, env: localEnv(cwd), maxBuffer: 2 * 1024 * 1024 });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function localEnv(workspaceRoot) {
|
|
165
|
+
return {
|
|
166
|
+
...process.env,
|
|
167
|
+
PATH: `${join(workspaceRoot, "node_modules", ".bin")}${delimiter}${process.env.PATH ?? ""}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onceExit(child) {
|
|
172
|
+
return new Promise((resolveExit, reject) => {
|
|
173
|
+
child.once("error", reject);
|
|
174
|
+
child.once("close", (code) => code === 0 ? resolveExit() : reject(new Error(`MCP exited with ${code}`)));
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { 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, resolve } from "node:path";
|
|
7
|
-
|
|
8
|
-
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
-
const runner = resolve(root, "integrations/codex/run-mcp.mjs");
|
|
10
|
-
const child = spawn(process.env.JEV_NODE ?? "node", [runner], {
|
|
11
|
-
cwd: root,
|
|
12
|
-
env: {
|
|
13
|
-
...process.env,
|
|
14
|
-
JEV_LAYER_ROOT: root,
|
|
15
|
-
JEV_LAYER_PROVIDER: "openrouter",
|
|
16
|
-
},
|
|
17
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
18
|
-
});
|
|
19
|
-
const lines = createInterface({ input: child.stdout });
|
|
20
|
-
const pending = new Map();
|
|
21
|
-
const stderr = [];
|
|
22
|
-
child.stderr.on("data", (chunk) => stderr.push(String(chunk)));
|
|
23
|
-
lines.on("line", (line) => {
|
|
24
|
-
if (!line.trim()) return;
|
|
25
|
-
const message = JSON.parse(line);
|
|
26
|
-
const waiter = pending.get(message.id);
|
|
27
|
-
if (waiter) {
|
|
28
|
-
pending.delete(message.id);
|
|
29
|
-
waiter(message);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const initialized = await request(1, "initialize", {});
|
|
35
|
-
assert.equal(initialized.result.serverInfo.name, "jev-layer");
|
|
36
|
-
await notification("notifications/initialized", {});
|
|
37
|
-
const listed = await request(2, "tools/list", {});
|
|
38
|
-
assert.ok(listed.result.tools.some((tool) => tool.name === "jev_route"));
|
|
39
|
-
|
|
40
|
-
const response = await request(3, "tools/call", {
|
|
41
|
-
name: "jev_route",
|
|
42
|
-
arguments: {
|
|
43
|
-
intent: "inspect the routing implementation",
|
|
44
|
-
harness: "codex",
|
|
45
|
-
context: { workspace: "jev-layer", operation: "read-only" },
|
|
46
|
-
actor_permissions: ["read"],
|
|
47
|
-
capabilities: [
|
|
48
|
-
{
|
|
49
|
-
id: "read_route",
|
|
50
|
-
name: "read",
|
|
51
|
-
kind: "tool",
|
|
52
|
-
description: "Read src/route.mjs without modifying it.",
|
|
53
|
-
execution: { mode: "host", target: "src/route.mjs" },
|
|
54
|
-
permissions: ["read"],
|
|
55
|
-
risk: "low",
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: "read_manifest",
|
|
59
|
-
name: "read",
|
|
60
|
-
kind: "tool",
|
|
61
|
-
description: "Read package.json without modifying it.",
|
|
62
|
-
execution: { mode: "host", target: "package.json" },
|
|
63
|
-
permissions: ["read"],
|
|
64
|
-
risk: "low",
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
const decision = response.result.structuredContent;
|
|
70
|
-
assert.equal(decision.status, "selected");
|
|
71
|
-
assert.ok(decision.selected);
|
|
72
|
-
|
|
73
|
-
const target = decision.selected === "read_route" ? "src/route.mjs" : "package.json";
|
|
74
|
-
const content = await readFile(resolve(root, target), "utf8");
|
|
75
|
-
console.log(JSON.stringify({
|
|
76
|
-
ok: true,
|
|
77
|
-
transport: "codex-mcp-runner",
|
|
78
|
-
tool: "jev_route",
|
|
79
|
-
decision: {
|
|
80
|
-
selected: decision.selected,
|
|
81
|
-
probabilities: decision.probabilities,
|
|
82
|
-
confidence: decision.confidence,
|
|
83
|
-
status: decision.status,
|
|
84
|
-
latency_ms: decision.receipt?.latency_ms ?? null,
|
|
85
|
-
cost_usd: decision.raw_jev?.usage?.cost ?? null,
|
|
86
|
-
fallback: decision.fallback,
|
|
87
|
-
provider: decision.receipt?.provider ?? null,
|
|
88
|
-
},
|
|
89
|
-
host_execution: {
|
|
90
|
-
mode: "adapter-transport-fixture",
|
|
91
|
-
target,
|
|
92
|
-
bytes: Buffer.byteLength(content),
|
|
93
|
-
note: "Codex CLI model execution was not exercised by this fixture.",
|
|
94
|
-
},
|
|
95
|
-
}, null, 2));
|
|
96
|
-
} finally {
|
|
97
|
-
child.kill();
|
|
98
|
-
await new Promise((resolveExit) => child.once("close", resolveExit));
|
|
99
|
-
if (stderr.length > 0) process.stderr.write(stderr.join(""));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function request(id, method, params) {
|
|
103
|
-
return new Promise((resolveResponse, reject) => {
|
|
104
|
-
const timer = setTimeout(() => {
|
|
105
|
-
pending.delete(id);
|
|
106
|
-
reject(new Error(`MCP request timed out: ${method}`));
|
|
107
|
-
}, 15_000);
|
|
108
|
-
pending.set(id, (message) => {
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
resolveResponse(message);
|
|
111
|
-
});
|
|
112
|
-
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function notification(method, params) {
|
|
117
|
-
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
118
|
-
return new Promise((resolveNotification) => setImmediate(resolveNotification));
|
|
119
|
-
}
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { 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, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const runner = resolve(root, "integrations/codex/run-mcp.mjs");
|
|
10
|
+
const child = spawn(process.env.JEV_NODE ?? "node", [runner], {
|
|
11
|
+
cwd: root,
|
|
12
|
+
env: {
|
|
13
|
+
...process.env,
|
|
14
|
+
JEV_LAYER_ROOT: root,
|
|
15
|
+
JEV_LAYER_PROVIDER: "openrouter",
|
|
16
|
+
},
|
|
17
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
18
|
+
});
|
|
19
|
+
const lines = createInterface({ input: child.stdout });
|
|
20
|
+
const pending = new Map();
|
|
21
|
+
const stderr = [];
|
|
22
|
+
child.stderr.on("data", (chunk) => stderr.push(String(chunk)));
|
|
23
|
+
lines.on("line", (line) => {
|
|
24
|
+
if (!line.trim()) return;
|
|
25
|
+
const message = JSON.parse(line);
|
|
26
|
+
const waiter = pending.get(message.id);
|
|
27
|
+
if (waiter) {
|
|
28
|
+
pending.delete(message.id);
|
|
29
|
+
waiter(message);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const initialized = await request(1, "initialize", {});
|
|
35
|
+
assert.equal(initialized.result.serverInfo.name, "jev-layer");
|
|
36
|
+
await notification("notifications/initialized", {});
|
|
37
|
+
const listed = await request(2, "tools/list", {});
|
|
38
|
+
assert.ok(listed.result.tools.some((tool) => tool.name === "jev_route"));
|
|
39
|
+
|
|
40
|
+
const response = await request(3, "tools/call", {
|
|
41
|
+
name: "jev_route",
|
|
42
|
+
arguments: {
|
|
43
|
+
intent: "inspect the routing implementation",
|
|
44
|
+
harness: "codex",
|
|
45
|
+
context: { workspace: "jev-layer", operation: "read-only" },
|
|
46
|
+
actor_permissions: ["read"],
|
|
47
|
+
capabilities: [
|
|
48
|
+
{
|
|
49
|
+
id: "read_route",
|
|
50
|
+
name: "read",
|
|
51
|
+
kind: "tool",
|
|
52
|
+
description: "Read src/route.mjs without modifying it.",
|
|
53
|
+
execution: { mode: "host", target: "src/route.mjs" },
|
|
54
|
+
permissions: ["read"],
|
|
55
|
+
risk: "low",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "read_manifest",
|
|
59
|
+
name: "read",
|
|
60
|
+
kind: "tool",
|
|
61
|
+
description: "Read package.json without modifying it.",
|
|
62
|
+
execution: { mode: "host", target: "package.json" },
|
|
63
|
+
permissions: ["read"],
|
|
64
|
+
risk: "low",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
const decision = response.result.structuredContent;
|
|
70
|
+
assert.equal(decision.status, "selected");
|
|
71
|
+
assert.ok(decision.selected);
|
|
72
|
+
|
|
73
|
+
const target = decision.selected === "read_route" ? "src/route.mjs" : "package.json";
|
|
74
|
+
const content = await readFile(resolve(root, target), "utf8");
|
|
75
|
+
console.log(JSON.stringify({
|
|
76
|
+
ok: true,
|
|
77
|
+
transport: "codex-mcp-runner",
|
|
78
|
+
tool: "jev_route",
|
|
79
|
+
decision: {
|
|
80
|
+
selected: decision.selected,
|
|
81
|
+
probabilities: decision.probabilities,
|
|
82
|
+
confidence: decision.confidence,
|
|
83
|
+
status: decision.status,
|
|
84
|
+
latency_ms: decision.receipt?.latency_ms ?? null,
|
|
85
|
+
cost_usd: decision.raw_jev?.usage?.cost ?? null,
|
|
86
|
+
fallback: decision.fallback,
|
|
87
|
+
provider: decision.receipt?.provider ?? null,
|
|
88
|
+
},
|
|
89
|
+
host_execution: {
|
|
90
|
+
mode: "adapter-transport-fixture",
|
|
91
|
+
target,
|
|
92
|
+
bytes: Buffer.byteLength(content),
|
|
93
|
+
note: "Codex CLI model execution was not exercised by this fixture.",
|
|
94
|
+
},
|
|
95
|
+
}, null, 2));
|
|
96
|
+
} finally {
|
|
97
|
+
child.kill();
|
|
98
|
+
await new Promise((resolveExit) => child.once("close", resolveExit));
|
|
99
|
+
if (stderr.length > 0) process.stderr.write(stderr.join(""));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function request(id, method, params) {
|
|
103
|
+
return new Promise((resolveResponse, reject) => {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
pending.delete(id);
|
|
106
|
+
reject(new Error(`MCP request timed out: ${method}`));
|
|
107
|
+
}, 15_000);
|
|
108
|
+
pending.set(id, (message) => {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
resolveResponse(message);
|
|
111
|
+
});
|
|
112
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function notification(method, params) {
|
|
117
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
118
|
+
return new Promise((resolveNotification) => setImmediate(resolveNotification));
|
|
119
|
+
}
|