jev-layer 0.1.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 +34 -0
- package/CONTRIBUTING.md +146 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/README.ru.md +117 -0
- package/README.zh-CN.md +117 -0
- package/RELEASE.md +53 -0
- package/SECURITY.md +56 -0
- package/bin/jev.mjs +214 -0
- package/config/codex.mcp.toml +8 -0
- package/config/generic-mcp.json +13 -0
- package/config/hermes.mcp.yaml +9 -0
- package/config/jev.example.json +22 -0
- package/config/omp.mcp.json +14 -0
- package/config/providers.env.example +13 -0
- package/docs/COMPATIBILITY.md +15 -0
- package/docs/SCHEMA-VERSIONING.md +94 -0
- package/examples/capabilities.json +39 -0
- package/examples/route-request.json +28 -0
- package/integrations/codex/.codex-plugin/plugin.json +19 -0
- package/integrations/codex/.mcp.json +11 -0
- package/integrations/codex/AGENTS.md +1 -0
- package/integrations/codex/run-mcp.mjs +9 -0
- package/integrations/codex/skills/jev-route/SKILL.md +17 -0
- package/integrations/hermes/__init__.py +51 -0
- package/integrations/hermes/plugin.yaml +5 -0
- package/integrations/hermes/schemas.py +12 -0
- package/integrations/omp/extension.js +105 -0
- package/integrations/template/README.md +10 -0
- package/integrations/template/adapter.mjs +87 -0
- package/package.json +59 -0
- package/scripts/benchmark.mjs +35 -0
- package/scripts/browser-benchmark.mjs +92 -0
- package/scripts/browser-e2e.mjs +117 -0
- package/scripts/capability-e2e.mjs +31 -0
- package/scripts/clean-install-smoke.mjs +162 -0
- package/scripts/codex-mcp-smoke.mjs +119 -0
- package/scripts/context-filter-e2e.mjs +45 -0
- package/scripts/fail-open-smoke.mjs +120 -0
- package/scripts/feature-flags-smoke.mjs +46 -0
- package/scripts/mcp-receipt-smoke.mjs +98 -0
- package/scripts/openrouter-choice.mjs +59 -0
- package/scripts/replay-eval.mjs +124 -0
- package/scripts/smoke.mjs +34 -0
- package/scripts/supervision-e2e.mjs +109 -0
- package/src/browser.mjs +569 -0
- package/src/cli.mjs +33 -0
- package/src/config.mjs +75 -0
- package/src/context-filter.mjs +56 -0
- package/src/contract.mjs +72 -0
- package/src/discovery.mjs +65 -0
- package/src/mcp-server.mjs +210 -0
- package/src/providers/demo.mjs +52 -0
- package/src/providers/typesafe.mjs +126 -0
- package/src/receipts.mjs +226 -0
- package/src/registry.mjs +109 -0
- package/src/relevance-filter.mjs +99 -0
- package/src/route.mjs +221 -0
- package/src/supervision.mjs +244 -0
- package/test/browser.test.mjs +199 -0
- package/test/capability.test.mjs +54 -0
- package/test/context-filter.test.mjs +65 -0
- package/test/openrouter-provider.test.mjs +55 -0
- package/test/receipts.test.mjs +71 -0
- package/test/route.test.mjs +74 -0
- package/test/supervision.test.mjs +99 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { readRoutingCases, readSupervisionCases, replayCasePath } from "../src/receipts.mjs";
|
|
4
|
+
import { deterministicSupervisionPolicy } from "../src/supervision.mjs";
|
|
5
|
+
import { routeRequest } from "../src/route.mjs";
|
|
6
|
+
|
|
7
|
+
const args = parseArgs(process.argv.slice(2));
|
|
8
|
+
const inputPath = replayCasePath(args.input);
|
|
9
|
+
const provider = args.provider ?? process.env.JEV_REPLAY_PROVIDER ?? "demo";
|
|
10
|
+
const allCases = await readRoutingCases(inputPath);
|
|
11
|
+
const cases = args.limit ? allCases.slice(0, args.limit) : allCases;
|
|
12
|
+
const results = [];
|
|
13
|
+
|
|
14
|
+
for (const routingCase of cases) {
|
|
15
|
+
const decision = await routeRequest(routingCase.request, { provider });
|
|
16
|
+
if (decision.execution?.enabled) throw new Error(`replay attempted host execution for ${routingCase.case_id}`);
|
|
17
|
+
results.push({
|
|
18
|
+
source_case_id: routingCase.case_id,
|
|
19
|
+
source_correlation_id: routingCase.correlation_id,
|
|
20
|
+
replay_correlation_id: decision.correlation_id,
|
|
21
|
+
surface: routingCase.request.context?.browser ? "browser" : "generic",
|
|
22
|
+
harness: routingCase.harness,
|
|
23
|
+
selected: decision.selected,
|
|
24
|
+
confidence: decision.confidence,
|
|
25
|
+
status: decision.status,
|
|
26
|
+
fallback: decision.fallback?.type ?? null,
|
|
27
|
+
latency_ms: decision.receipt?.latency_ms ?? null,
|
|
28
|
+
cost_usd: decision.receipt?.cost_usd ?? decision.raw_jev?.usage?.cost ?? null,
|
|
29
|
+
execution: decision.execution,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const supervisionCases = args.limit
|
|
34
|
+
? (await readSupervisionCases(inputPath)).slice(0, args.limit)
|
|
35
|
+
: await readSupervisionCases(inputPath);
|
|
36
|
+
const supervisionResults = supervisionCases.map((record) => ({
|
|
37
|
+
source_case_id: record.case_id,
|
|
38
|
+
source_correlation_id: record.correlation_id,
|
|
39
|
+
harness: record.harness,
|
|
40
|
+
assessment: record.supervision.assessment,
|
|
41
|
+
recorded_action: record.supervision.action,
|
|
42
|
+
replayed_policy: deterministicSupervisionPolicy({
|
|
43
|
+
assessment: record.supervision.assessment,
|
|
44
|
+
evidence: record.request.context?.evidence ?? {},
|
|
45
|
+
}),
|
|
46
|
+
host_execution: false,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
const summary = summarize(results);
|
|
51
|
+
summary.supervision_case_count = supervisionResults.length;
|
|
52
|
+
summary.supervision_policy_match_rate = rate(
|
|
53
|
+
supervisionResults.filter((result) => result.replayed_policy.action === result.recorded_action).length,
|
|
54
|
+
supervisionResults.length,
|
|
55
|
+
);
|
|
56
|
+
const report = {
|
|
57
|
+
ok: true,
|
|
58
|
+
source: inputPath,
|
|
59
|
+
provider,
|
|
60
|
+
replayed_cases: results.length,
|
|
61
|
+
replayed_supervision_cases: supervisionResults.length,
|
|
62
|
+
host_execution_calls: 0,
|
|
63
|
+
summary,
|
|
64
|
+
cases: results,
|
|
65
|
+
supervision_cases: supervisionResults,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (args.output) {
|
|
69
|
+
await mkdir(dirname(args.output), { recursive: true });
|
|
70
|
+
await writeFile(args.output, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
71
|
+
}
|
|
72
|
+
console.log(JSON.stringify(report, null, 2));
|
|
73
|
+
function summarize(results) {
|
|
74
|
+
const total = results.length;
|
|
75
|
+
const fallbackCount = results.filter((result) => result.status === "fallback").length;
|
|
76
|
+
const selectedCount = results.filter((result) => result.status === "selected" || result.status === "needs_confirmation").length;
|
|
77
|
+
const noDecisionCount = results.filter((result) => result.status === "no_decision").length;
|
|
78
|
+
const browserResults = results.filter((result) => result.surface === "browser");
|
|
79
|
+
const confidences = results.map((result) => result.confidence).filter((value) => typeof value === "number");
|
|
80
|
+
const latencies = results.map((result) => result.latency_ms).filter((value) => typeof value === "number");
|
|
81
|
+
const costs = results.map((result) => result.cost_usd).filter((value) => typeof value === "number");
|
|
82
|
+
return {
|
|
83
|
+
selected_rate: rate(selectedCount, total),
|
|
84
|
+
fallback_rate: rate(fallbackCount, total),
|
|
85
|
+
no_decision_rate: rate(noDecisionCount, total),
|
|
86
|
+
browser_case_count: browserResults.length,
|
|
87
|
+
browser_selected_rate: rate(browserResults.filter((result) => result.status === "selected" || result.status === "needs_confirmation").length, browserResults.length),
|
|
88
|
+
average_confidence: average(confidences),
|
|
89
|
+
average_latency_ms: average(latencies),
|
|
90
|
+
p50_latency_ms: percentile(latencies, 0.5),
|
|
91
|
+
p95_latency_ms: percentile(latencies, 0.95),
|
|
92
|
+
total_cost_usd: Number(costs.reduce((sum, value) => sum + value, 0).toFixed(9)),
|
|
93
|
+
status_counts: Object.fromEntries([...new Set(results.map((result) => result.status))].map((status) => [status, results.filter((result) => result.status === status).length])),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseArgs(argv) {
|
|
98
|
+
const result = {};
|
|
99
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
100
|
+
const value = argv[index];
|
|
101
|
+
if (value === "--input") result.input = argv[++index];
|
|
102
|
+
else if (value === "--provider") result.provider = argv[++index];
|
|
103
|
+
else if (value === "--limit") result.limit = Number(argv[++index]);
|
|
104
|
+
else if (value === "--output") result.output = argv[++index];
|
|
105
|
+
}
|
|
106
|
+
if (result.limit !== undefined && (!Number.isInteger(result.limit) || result.limit < 0)) throw new Error("--limit must be a non-negative integer");
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function average(values) {
|
|
111
|
+
return values.length ? Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(3)) : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function percentile(values, fraction) {
|
|
115
|
+
if (!values.length) return null;
|
|
116
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
117
|
+
const rank = Math.max(0, Math.ceil(sorted.length * fraction) - 1);
|
|
118
|
+
return Number(sorted[Math.min(sorted.length - 1, rank)].toFixed(3));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
function rate(part, total) {
|
|
123
|
+
return total ? Number((part / total).toFixed(4)) : 0;
|
|
124
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { routeRequest } from "../src/route.mjs";
|
|
4
|
+
|
|
5
|
+
const request = JSON.parse(await readFile(new URL("../examples/route-request.json", import.meta.url), "utf8"));
|
|
6
|
+
const selected = await routeRequest(request, { provider: "demo" });
|
|
7
|
+
assert.equal(selected.status, "selected");
|
|
8
|
+
assert.equal(selected.selected, "repo_search");
|
|
9
|
+
assert.equal(selected.execution.enabled, false);
|
|
10
|
+
assert.equal(selected.execution.status, "not_started");
|
|
11
|
+
assert.ok(selected.receipt.context_bytes > 0);
|
|
12
|
+
assert.equal(JSON.stringify(selected.raw_jev).includes("must-not-reach-provider"), false);
|
|
13
|
+
|
|
14
|
+
const unavailable = await routeRequest({
|
|
15
|
+
...request,
|
|
16
|
+
intent: "Modify a file",
|
|
17
|
+
capabilities: request.capabilities.map((capability) => ({ ...capability, availability: capability.id === "repo_search" ? { available: false, reason: "offline" } : undefined })),
|
|
18
|
+
}, { provider: "demo" });
|
|
19
|
+
assert.equal(unavailable.status, "no_decision");
|
|
20
|
+
|
|
21
|
+
const providerFailure = await routeRequest(request, {
|
|
22
|
+
provider: { name: "failing-test-provider", async decide() { throw new Error("synthetic provider failure"); } },
|
|
23
|
+
});
|
|
24
|
+
assert.equal(providerFailure.status, "fallback");
|
|
25
|
+
assert.equal(providerFailure.fallback.type, "provider_error");
|
|
26
|
+
assert.equal(providerFailure.execution.enabled, false);
|
|
27
|
+
|
|
28
|
+
console.log(JSON.stringify({
|
|
29
|
+
ok: true,
|
|
30
|
+
selected: selected.selected,
|
|
31
|
+
unavailable_status: unavailable.status,
|
|
32
|
+
provider_failure: providerFailure.fallback.type,
|
|
33
|
+
execution_started: selected.execution.enabled,
|
|
34
|
+
}));
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
import { deterministicSupervisionPolicy } from "../src/supervision.mjs";
|
|
9
|
+
|
|
10
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const casesPath = join(await mkdtemp(join(tmpdir(), "jev-supervision-e2e-")), "cases.jsonl");
|
|
12
|
+
const child = spawn(process.env.JEV_NODE ?? "node", [resolve(root, "src/mcp-server.mjs")], {
|
|
13
|
+
cwd: root,
|
|
14
|
+
env: { ...process.env, JEV_REPLAY_CASES: casesPath },
|
|
15
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16
|
+
});
|
|
17
|
+
const lines = createInterface({ input: child.stdout });
|
|
18
|
+
const pending = new Map();
|
|
19
|
+
lines.on("line", (line) => {
|
|
20
|
+
if (!line.trim()) return;
|
|
21
|
+
const message = JSON.parse(line);
|
|
22
|
+
const waiter = pending.get(message.id);
|
|
23
|
+
if (waiter) {
|
|
24
|
+
pending.delete(message.id);
|
|
25
|
+
waiter(message);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const initialized = await request(1, "initialize", {});
|
|
31
|
+
assert.equal(initialized.result.serverInfo.name, "jev-layer");
|
|
32
|
+
await notification("notifications/initialized", {});
|
|
33
|
+
const listed = await request(2, "tools/list", {});
|
|
34
|
+
assert.ok(listed.result.tools.some((tool) => tool.name === "jev_supervise"));
|
|
35
|
+
|
|
36
|
+
const disabled = await request(3, "tools/call", {
|
|
37
|
+
name: "jev_supervise",
|
|
38
|
+
arguments: { enabled: false, provider: "demo", job: {}, observation: {} },
|
|
39
|
+
});
|
|
40
|
+
assert.equal(disabled.result.structuredContent.fallback.type, "disabled");
|
|
41
|
+
|
|
42
|
+
const judged = await request(4, "tools/call", {
|
|
43
|
+
name: "jev_supervise",
|
|
44
|
+
arguments: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
provider: process.env.JEV_SUPERVISION_PROVIDER ?? "demo",
|
|
47
|
+
harness: "supervision-e2e",
|
|
48
|
+
job: { requirements: ["run tests", "inspect receipts"] },
|
|
49
|
+
observation: { status: "ready", progress: "complete" },
|
|
50
|
+
evidence: {
|
|
51
|
+
tests_passed: true,
|
|
52
|
+
judgments: {
|
|
53
|
+
requirements_addressed: 0.9,
|
|
54
|
+
verification_needed: 0.1,
|
|
55
|
+
meaningful_progress: 0.9,
|
|
56
|
+
worker_stuck: 0.05,
|
|
57
|
+
work_off_track: 0.05,
|
|
58
|
+
completion: 0.9,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const result = judged.result.structuredContent;
|
|
64
|
+
assert.equal(result.status, "judged");
|
|
65
|
+
assert.equal(result.action, "finish");
|
|
66
|
+
assert.equal(result.metrics.jev_calls, 1);
|
|
67
|
+
assert.ok(result.receipt.correlation_id);
|
|
68
|
+
|
|
69
|
+
const records = (await readFile(casesPath, "utf8")).trim().split(/\r?\n/).map(JSON.parse);
|
|
70
|
+
assert.deepEqual(records.map((record) => record.record_type), ["supervision_case"]);
|
|
71
|
+
assert.equal(records[0].supervision.action, "finish");
|
|
72
|
+
const replayed = deterministicSupervisionPolicy({
|
|
73
|
+
assessment: records[0].supervision.assessment,
|
|
74
|
+
evidence: records[0].request.context.evidence,
|
|
75
|
+
});
|
|
76
|
+
assert.equal(replayed.action, records[0].supervision.action);
|
|
77
|
+
console.log(JSON.stringify({
|
|
78
|
+
ok: true,
|
|
79
|
+
cases_path: casesPath,
|
|
80
|
+
action: result.action,
|
|
81
|
+
status: result.status,
|
|
82
|
+
dimensions: Object.keys(result.assessment).length,
|
|
83
|
+
latency_ms: result.receipt.latency_ms,
|
|
84
|
+
cost_usd: result.receipt.cost_usd,
|
|
85
|
+
receipts: records.length,
|
|
86
|
+
}, null, 2));
|
|
87
|
+
} finally {
|
|
88
|
+
child.kill();
|
|
89
|
+
await new Promise((resolveExit) => child.once("close", resolveExit));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function request(id, method, params) {
|
|
93
|
+
return new Promise((resolveResponse, reject) => {
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
pending.delete(id);
|
|
96
|
+
reject(new Error(`MCP request timed out: ${method}`));
|
|
97
|
+
}, 15_000);
|
|
98
|
+
pending.set(id, (message) => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
resolveResponse(message);
|
|
101
|
+
});
|
|
102
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function notification(method, params) {
|
|
107
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
108
|
+
return new Promise((resolveNotification) => setImmediate(resolveNotification));
|
|
109
|
+
}
|