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.
- package/CHANGELOG.md +45 -34
- package/CONTRIBUTING.md +146 -146
- package/LICENSE +21 -21
- package/README.md +123 -126
- package/README.ru.md +138 -117
- package/README.zh-CN.md +137 -117
- package/RELEASE.md +53 -53
- package/SECURITY.md +56 -56
- package/bin/jev.mjs +213 -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 +22 -22
- package/config/omp.mcp.json +14 -14
- package/config/providers.env.example +13 -13
- package/docs/AGENT-IMPLEMENTATION.md +250 -0
- package/docs/COMPATIBILITY.md +15 -15
- package/docs/HERMES-LOCAL-AGENT-HANDOFF.ru.md +177 -0
- package/docs/PROVIDERS.md +67 -0
- package/docs/SCHEMA-VERSIONING.md +96 -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 +150 -51
- package/integrations/hermes/plugin.yaml +10 -5
- package/integrations/hermes/schemas.py +89 -12
- package/integrations/omp/extension.js +105 -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 +162 -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 +113 -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 +571 -569
- package/src/cli.mjs +33 -33
- package/src/config.mjs +85 -75
- package/src/context-filter.mjs +56 -56
- package/src/contract.mjs +72 -72
- package/src/discovery.mjs +65 -65
- package/src/hermes-adapter.mjs +123 -0
- package/src/mcp-server.mjs +267 -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 +126 -126
- package/src/receipts.mjs +227 -226
- package/src/registry.mjs +109 -109
- package/src/relevance-filter.mjs +99 -99
- package/src/route.mjs +219 -221
- package/src/shadow-compaction.mjs +170 -0
- package/src/supervision.mjs +268 -244
- package/test/browser.test.mjs +199 -199
- package/test/capability.test.mjs +54 -54
- package/test/context-filter.test.mjs +65 -65
- package/test/hermes-adapter.test.mjs +62 -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 +126 -0
- package/test/receipts.test.mjs +71 -71
- package/test/route.test.mjs +74 -74
- package/test/shadow-compaction.test.mjs +67 -0
- package/test/supervision.test.mjs +117 -99
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-shot transport used by the Hermes plugin.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately an adapter, not an executor: it accepts a closed host
|
|
6
|
+
* request and returns bounded Jev decisions/receipts. Native Hermes retains
|
|
7
|
+
* registry lookup, approvals, execution, retries, recovery, and final output.
|
|
8
|
+
*/
|
|
9
|
+
import { createInterface } from "node:readline";
|
|
10
|
+
import { decideBrowserStep } from "./browser.mjs";
|
|
11
|
+
import { configuredProvider, configuredReplayPath, loadConfig } from "./config.mjs";
|
|
12
|
+
import { appendExecutionReceipt, appendRoutingCase, buildExecutionReceipt, replayCasePath } from "./receipts.mjs";
|
|
13
|
+
import { routeRequest } from "./route.mjs";
|
|
14
|
+
import { superviseWork } from "./supervision.mjs";
|
|
15
|
+
import { buildShadowCompactionReport } from "./shadow-compaction.mjs";
|
|
16
|
+
import { recommendModelRoute } from "./model-routing.mjs";
|
|
17
|
+
|
|
18
|
+
const { config } = await loadConfig();
|
|
19
|
+
const casesPath = replayCasePath(configuredReplayPath(config));
|
|
20
|
+
const input = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
21
|
+
|
|
22
|
+
for await (const line of input) {
|
|
23
|
+
if (!line.trim()) continue;
|
|
24
|
+
try {
|
|
25
|
+
const envelope = JSON.parse(line);
|
|
26
|
+
const result = await handle(envelope);
|
|
27
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
process.stdout.write(`${JSON.stringify(fallback(error))}\n`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function handle({ operation, args = {}, decision = null } = {}) {
|
|
34
|
+
const provider = configuredProvider(config, args.provider);
|
|
35
|
+
if (operation === "route") {
|
|
36
|
+
const { provider: _provider, engine = "native", ...request } = args;
|
|
37
|
+
const result = await routeRequest(request, {
|
|
38
|
+
provider,
|
|
39
|
+
config,
|
|
40
|
+
engine,
|
|
41
|
+
contextFilterMode: request.policy?.context_filter_mode ?? config.features?.context_filter,
|
|
42
|
+
});
|
|
43
|
+
await persistRoutingCase(request, result);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
if (operation === "browser_step") {
|
|
47
|
+
const { provider: _provider, enabled, ...input } = args;
|
|
48
|
+
const routed = await decideBrowserStep(input, {
|
|
49
|
+
enabled: enabled ?? config.features?.browser_fast_path,
|
|
50
|
+
provider,
|
|
51
|
+
config,
|
|
52
|
+
});
|
|
53
|
+
routed.decision.browser_action = routed.action
|
|
54
|
+
? { id: routed.action.id, operation: routed.action.operation, target_id: routed.action.target_id ?? null,
|
|
55
|
+
option_id: routed.action.option_id ?? null, consequential: routed.action.consequential }
|
|
56
|
+
: null;
|
|
57
|
+
await persistRoutingCase(routed.request, routed.decision);
|
|
58
|
+
return routed.decision;
|
|
59
|
+
}
|
|
60
|
+
if (operation === "supervise") {
|
|
61
|
+
const { provider: _provider, enabled, ...input } = args;
|
|
62
|
+
return superviseWork({
|
|
63
|
+
...input,
|
|
64
|
+
config,
|
|
65
|
+
enabled: enabled ?? config.features?.supervision,
|
|
66
|
+
provider,
|
|
67
|
+
receiptPath: casesPath,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (operation === "model_route") {
|
|
71
|
+
const { provider: _provider, ...input } = args;
|
|
72
|
+
const result = await recommendModelRoute({ ...input, provider, config });
|
|
73
|
+
await persistRoutingCase({
|
|
74
|
+
schema_version: 1,
|
|
75
|
+
harness: input.harness ?? "hermes",
|
|
76
|
+
intent: input.intent,
|
|
77
|
+
context: { ...(input.context ?? {}), model_route: { mode: "shadow" } },
|
|
78
|
+
capabilities: (input.models ?? []).map((profile) => ({
|
|
79
|
+
id: profile.id,
|
|
80
|
+
kind: "model",
|
|
81
|
+
name: profile.model ?? profile.id,
|
|
82
|
+
description: profile.description ?? "",
|
|
83
|
+
source: profile.provider ?? null,
|
|
84
|
+
risk: "low",
|
|
85
|
+
})),
|
|
86
|
+
policy: input.policy ?? {},
|
|
87
|
+
}, result);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
if (operation === "shadow_compaction") {
|
|
91
|
+
const { provider: _provider, ...input } = args;
|
|
92
|
+
return buildShadowCompactionReport({ ...input, provider, config });
|
|
93
|
+
}
|
|
94
|
+
if (operation === "record_execution") {
|
|
95
|
+
if (!decision || typeof decision !== "object") throw new TypeError("decision is required for record_execution");
|
|
96
|
+
if (args.correlation_id !== decision.correlation_id) throw new TypeError("correlation_id does not match the routed decision");
|
|
97
|
+
try {
|
|
98
|
+
const { record } = await appendExecutionReceipt({ path: casesPath, request: decision._jev_request ?? {}, decision, host: args });
|
|
99
|
+
return record;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const record = buildExecutionReceipt({ request: decision._jev_request ?? {}, decision, host: args });
|
|
102
|
+
return { ...record, persistence: { persisted: false, error: error instanceof Error ? error.message : String(error) } };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw new TypeError(`unsupported Hermes adapter operation: ${operation}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function persistRoutingCase(request, decision) {
|
|
109
|
+
decision._jev_request = request;
|
|
110
|
+
try {
|
|
111
|
+
await appendRoutingCase({ path: casesPath, request, decision });
|
|
112
|
+
decision.receipt = { ...decision.receipt, replay_case_id: decision.correlation_id, replay_persisted: true };
|
|
113
|
+
} catch (error) {
|
|
114
|
+
decision.receipt = { ...decision.receipt, replay_persisted: false,
|
|
115
|
+
replay_error: error instanceof Error ? error.message : String(error) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function fallback(error) {
|
|
120
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
121
|
+
return { schema_version: 1, status: "fallback", selected: null, reason,
|
|
122
|
+
fallback: { type: "adapter_error", reason }, execution: { enabled: false, status: "not_started" } };
|
|
123
|
+
}
|
package/src/mcp-server.mjs
CHANGED
|
@@ -1,210 +1,267 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createInterface } from "node:readline";
|
|
3
|
-
import { appendExecutionReceipt, appendRoutingCase, buildExecutionReceipt, replayCasePath } from "./receipts.mjs";
|
|
4
|
-
import { configuredProvider, configuredReplayPath, loadConfig } from "./config.mjs";
|
|
5
|
-
import { decideBrowserStep } from "./browser.mjs";
|
|
6
|
-
import { superviseWork } from "./supervision.mjs";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { appendExecutionReceipt, appendRoutingCase, buildExecutionReceipt, replayCasePath } from "./receipts.mjs";
|
|
4
|
+
import { configuredProvider, configuredReplayPath, loadConfig } from "./config.mjs";
|
|
5
|
+
import { decideBrowserStep } from "./browser.mjs";
|
|
6
|
+
import { superviseWork } from "./supervision.mjs";
|
|
7
|
+
import { buildShadowCompactionReport } from "./shadow-compaction.mjs";
|
|
8
|
+
import { recommendModelRoute } from "./model-routing.mjs";
|
|
9
|
+
import { routeRequest } from "./route.mjs";
|
|
10
|
+
|
|
11
|
+
const { config } = await loadConfig();
|
|
12
|
+
const serverInfo = { name: "jev-layer", version: "0.1.0" };
|
|
13
|
+
const CASES_PATH = replayCasePath(configuredReplayPath(config));
|
|
14
|
+
const pendingDecisions = new Map();
|
|
15
|
+
const tools = [
|
|
16
|
+
{
|
|
17
|
+
name: "jev_route",
|
|
18
|
+
description: "Return one bounded Jev capability decision. The host executes the selected capability; this tool never executes it.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
required: ["intent", "capabilities"],
|
|
22
|
+
properties: {
|
|
23
|
+
intent: { type: "string" },
|
|
24
|
+
harness: { type: "string" },
|
|
25
|
+
context: { type: "object" },
|
|
26
|
+
actor_permissions: { type: "array", items: { type: "string" } },
|
|
27
|
+
capabilities: { type: "array", items: { type: "object" } },
|
|
28
|
+
policy: { type: "object" },
|
|
29
|
+
provider: { type: "string", enum: ["demo", "typesafe", "openrouter"] },
|
|
30
|
+
engine: { type: "string", enum: ["native", "jevrouter"] },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "jev_browser_step",
|
|
36
|
+
description: "Choose one bounded browser action from the host's visible observation. The host executes it or hands control back; this tool never drives a browser.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
required: ["goal", "observation"],
|
|
40
|
+
properties: {
|
|
41
|
+
goal: { type: "string" },
|
|
42
|
+
harness: { type: "string" },
|
|
43
|
+
start_url: { type: ["string", "null"] },
|
|
44
|
+
observation: { type: "object" },
|
|
45
|
+
policy: { type: "object" },
|
|
46
|
+
provider: { type: "string", enum: ["demo", "typesafe", "openrouter"] },
|
|
47
|
+
enabled: { type: "boolean" },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "jev_supervise",
|
|
53
|
+
description: "Judge bounded work-state dimensions; deterministic host policy returns continue, verify, retry, finish, or escalate.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
required: ["job", "observation"],
|
|
57
|
+
properties: {
|
|
58
|
+
job: { type: "object" },
|
|
59
|
+
observation: { type: "object" },
|
|
60
|
+
evidence: { type: "object" },
|
|
61
|
+
harness: { type: "string" },
|
|
62
|
+
actor_permissions: { type: "array", items: { type: "string" } },
|
|
63
|
+
policy: { type: "object" },
|
|
64
|
+
attempts: { type: "integer", minimum: 0 },
|
|
65
|
+
provider: { type: "string", enum: ["demo", "typesafe", "openrouter"] },
|
|
66
|
+
enabled: { type: "boolean" },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "jev_model_route",
|
|
72
|
+
description: "Recommend one host-declared model profile for the next call. This is shadow-only: it never changes provider, model, reasoning, or execution.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
required: ["intent", "models"],
|
|
76
|
+
properties: {
|
|
77
|
+
intent: { type: "string" },
|
|
78
|
+
context: { type: "object" },
|
|
79
|
+
models: { type: "array", minItems: 1, items: { type: "object" } },
|
|
80
|
+
harness: { type: "string" },
|
|
81
|
+
policy: { type: "object" },
|
|
82
|
+
provider: { type: "string", enum: ["demo", "typesafe", "openrouter"] },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "jev_shadow_compaction",
|
|
88
|
+
description: "Report conservative Jev keep/drop candidates for host-supplied context. This tool never changes, summarizes, or deletes context.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
required: ["intent", "context"],
|
|
92
|
+
properties: {
|
|
93
|
+
intent: { type: "string" },
|
|
94
|
+
context: { type: "object" },
|
|
95
|
+
provider: { type: "string", enum: ["demo", "typesafe", "openrouter"] },
|
|
96
|
+
batch_size: { type: "integer", minimum: 1, maximum: 8 },
|
|
97
|
+
keep_threshold: { type: "number", minimum: 0, maximum: 1 },
|
|
98
|
+
min_confidence: { type: "number", minimum: 0, maximum: 1 },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "jev_record_execution",
|
|
104
|
+
description: "Attach a host execution result to a Jev decision and persist the unified execution receipt.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: "object",
|
|
107
|
+
required: ["correlation_id", "status"],
|
|
108
|
+
properties: {
|
|
109
|
+
correlation_id: { type: "string" },
|
|
110
|
+
harness: { type: "string" },
|
|
111
|
+
capability_id: { type: "string" },
|
|
112
|
+
status: { type: "string", enum: ["completed", "failed", "not_started"] },
|
|
113
|
+
result: {},
|
|
114
|
+
error: {},
|
|
115
|
+
browser: { type: "object" },
|
|
116
|
+
exit_status: { type: ["integer", "null"] },
|
|
117
|
+
duration_ms: { type: ["number", "null"] },
|
|
118
|
+
started_at: { type: ["string", "null"] },
|
|
119
|
+
completed_at: { type: ["string", "null"] },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
const input = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
126
|
+
for await (const line of input) {
|
|
127
|
+
if (!line.trim()) continue;
|
|
128
|
+
let message;
|
|
129
|
+
try {
|
|
130
|
+
message = JSON.parse(line);
|
|
131
|
+
const response = await handle(message);
|
|
132
|
+
if (response) process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const id = message?.id ?? null;
|
|
135
|
+
process.stdout.write(`${JSON.stringify(jsonRpcError(id, -32603, error instanceof Error ? error.message : String(error)))}\n`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function handle(message) {
|
|
140
|
+
const { id, method, params = {} } = message;
|
|
141
|
+
if (method === "initialize") {
|
|
142
|
+
return { jsonrpc: "2.0", id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo } };
|
|
143
|
+
}
|
|
144
|
+
if (method === "notifications/initialized") return null;
|
|
145
|
+
if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools } };
|
|
146
|
+
if (method !== "tools/call") return jsonRpcError(id, -32601, `method not found: ${method}`);
|
|
147
|
+
|
|
148
|
+
const args = params.arguments && typeof params.arguments === "object" ? params.arguments : {};
|
|
149
|
+
if (params.name === "jev_route") return route(args, id);
|
|
150
|
+
if (params.name === "jev_browser_step") return browserStep(args, id);
|
|
151
|
+
if (params.name === "jev_supervise") return supervise(args, id);
|
|
152
|
+
if (params.name === "jev_model_route") return modelRoute(args, id);
|
|
153
|
+
if (params.name === "jev_shadow_compaction") return shadowCompaction(args, id);
|
|
154
|
+
if (params.name === "jev_record_execution") return recordExecution(args, id);
|
|
155
|
+
return jsonRpcError(id, -32602, `unknown tool: ${params.name}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function route(args, id) {
|
|
159
|
+
const { provider: requestedProvider, engine = "native", ...request } = args;
|
|
160
|
+
const provider = configuredProvider(config, requestedProvider);
|
|
161
|
+
const decision = await routeRequest(request, {
|
|
162
|
+
provider,
|
|
163
|
+
engine,
|
|
164
|
+
config,
|
|
165
|
+
contextFilterMode: request.policy?.context_filter_mode ?? config.features?.context_filter,
|
|
166
|
+
});
|
|
167
|
+
return persistDecision(request, decision, id);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function browserStep(args, id) {
|
|
171
|
+
const { provider: requestedProvider, enabled, ...input } = args;
|
|
172
|
+
const routed = await decideBrowserStep(input, {
|
|
173
|
+
config,
|
|
174
|
+
enabled: enabled ?? config.features?.browser_fast_path,
|
|
175
|
+
provider: configuredProvider(config, requestedProvider),
|
|
176
|
+
});
|
|
177
|
+
routed.decision.browser_action = routed.action
|
|
178
|
+
? {
|
|
179
|
+
id: routed.action.id,
|
|
180
|
+
operation: routed.action.operation,
|
|
181
|
+
target_id: routed.action.target_id ?? null,
|
|
182
|
+
option_id: routed.action.option_id ?? null,
|
|
183
|
+
consequential: routed.action.consequential,
|
|
184
|
+
}
|
|
185
|
+
: null;
|
|
186
|
+
return persistDecision(routed.request, routed.decision, id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function supervise(args, id) {
|
|
190
|
+
const { provider: requestedProvider, enabled, ...input } = args;
|
|
191
|
+
const result = await superviseWork({
|
|
192
|
+
...input,
|
|
193
|
+
config,
|
|
194
|
+
enabled: enabled ?? config.features?.supervision,
|
|
195
|
+
provider: configuredProvider(config, requestedProvider),
|
|
196
|
+
receiptPath: CASES_PATH,
|
|
197
|
+
});
|
|
198
|
+
return toolResult(id, result, false);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function modelRoute(args, id) {
|
|
202
|
+
const { provider: requestedProvider, ...input } = args;
|
|
203
|
+
const result = await recommendModelRoute({
|
|
204
|
+
...input,
|
|
205
|
+
provider: configuredProvider(config, requestedProvider),
|
|
206
|
+
});
|
|
207
|
+
return toolResult(id, result, false);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function shadowCompaction(args, id) {
|
|
211
|
+
const { provider: requestedProvider, ...input } = args;
|
|
212
|
+
const result = await buildShadowCompactionReport({
|
|
213
|
+
...input,
|
|
214
|
+
provider: configuredProvider(config, requestedProvider),
|
|
215
|
+
});
|
|
216
|
+
return toolResult(id, result, false);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function persistDecision(request, decision, id) {
|
|
220
|
+
pendingDecisions.set(decision.correlation_id, { request, decision });
|
|
221
|
+
try {
|
|
222
|
+
await appendRoutingCase({ path: CASES_PATH, request, decision });
|
|
223
|
+
decision.receipt = { ...decision.receipt, replay_case_id: decision.correlation_id, replay_persisted: true };
|
|
224
|
+
} catch (error) {
|
|
225
|
+
decision.receipt = {
|
|
226
|
+
...decision.receipt,
|
|
227
|
+
replay_persisted: false,
|
|
228
|
+
replay_error: error instanceof Error ? error.message : String(error),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return toolResult(id, decision, decision.status === "error");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function recordExecution(args, id) {
|
|
235
|
+
const correlationId = typeof args.correlation_id === "string" ? args.correlation_id : null;
|
|
236
|
+
const pending = correlationId ? pendingDecisions.get(correlationId) : null;
|
|
237
|
+
if (!pending) return jsonRpcError(id, -32004, `unknown Jev correlation_id: ${correlationId ?? "missing"}`);
|
|
238
|
+
const { request, decision } = pending;
|
|
239
|
+
let record;
|
|
240
|
+
try {
|
|
241
|
+
({ record } = await appendExecutionReceipt({ path: CASES_PATH, request, decision, host: args }));
|
|
242
|
+
} catch (error) {
|
|
243
|
+
record = buildExecutionReceipt({ request, decision, host: args });
|
|
244
|
+
record.persistence = {
|
|
245
|
+
persisted: false,
|
|
246
|
+
error: error instanceof Error ? error.message : String(error),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
pendingDecisions.delete(correlationId);
|
|
250
|
+
return toolResult(id, record, false);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function toolResult(id, value, isError) {
|
|
254
|
+
return {
|
|
255
|
+
jsonrpc: "2.0",
|
|
256
|
+
id,
|
|
257
|
+
result: {
|
|
258
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
259
|
+
structuredContent: value,
|
|
260
|
+
isError,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function jsonRpcError(id, code, message) {
|
|
266
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
267
|
+
}
|