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
package/src/registry.mjs
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
import { CAPABILITY_KINDS, RISK_ORDER } from "./contract.mjs";
|
|
2
|
-
|
|
3
|
-
const KIND_ALIASES = Object.freeze({ mcp_tool: "mcp" });
|
|
4
|
-
|
|
5
|
-
export const DEFAULT_POLICY = Object.freeze({
|
|
6
|
-
min_confidence: 0.62,
|
|
7
|
-
max_risk: "high",
|
|
8
|
-
confirmation_risk_levels: ["medium", "high"],
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
export function normalizeCapability(raw, index = 0) {
|
|
12
|
-
if (!raw || typeof raw !== "object") throw new TypeError(`capabilities[${index}] must be an object`);
|
|
13
|
-
if (typeof raw.id !== "string" || raw.id.trim() === "") throw new TypeError(`capabilities[${index}].id is required`);
|
|
14
|
-
if (typeof raw.name !== "string" || raw.name.trim() === "") throw new TypeError(`capabilities[${index}].name is required`);
|
|
15
|
-
if (typeof raw.description !== "string" || raw.description.trim() === "") throw new TypeError(`capabilities[${index}].description is required`);
|
|
16
|
-
|
|
17
|
-
const rawKind = raw.kind ?? raw.type ?? raw.execution?.mode ?? "tool";
|
|
18
|
-
const kind = KIND_ALIASES[rawKind] ?? rawKind;
|
|
19
|
-
if (!CAPABILITY_KINDS.has(kind)) throw new TypeError(`capabilities[${index}].kind is unsupported: ${rawKind}`);
|
|
20
|
-
|
|
21
|
-
const risk = raw.risk?.level ?? raw.risk ?? "medium";
|
|
22
|
-
if (!(risk in RISK_ORDER)) throw new TypeError(`capabilities[${index}].risk is unsupported: ${risk}`);
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
id: raw.id.trim(),
|
|
26
|
-
kind,
|
|
27
|
-
name: raw.name.trim(),
|
|
28
|
-
description: raw.description.trim(),
|
|
29
|
-
permissions: Array.isArray(raw.permissions) ? raw.permissions.filter((value) => typeof value === "string") : [],
|
|
30
|
-
risk,
|
|
31
|
-
available: raw.available !== false && raw.availability?.available !== false,
|
|
32
|
-
availability_reason: raw.availability?.reason ?? null,
|
|
33
|
-
source: typeof raw.source === "string" ? raw.source : null,
|
|
34
|
-
verified: raw.verified === true,
|
|
35
|
-
metadata: normalizeMetadata(raw.metadata ?? raw.discovery),
|
|
36
|
-
execution: {
|
|
37
|
-
mode: "host",
|
|
38
|
-
target: typeof raw.execution?.target === "string" ? raw.execution.target : undefined,
|
|
39
|
-
},
|
|
40
|
-
requires_confirmation: Boolean(raw.policy?.requires_confirmation) || risk !== "low",
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function normalizeCapabilities(rawCapabilities) {
|
|
45
|
-
const seen = new Set();
|
|
46
|
-
return rawCapabilities.map((raw, index) => {
|
|
47
|
-
const capability = normalizeCapability(raw, index);
|
|
48
|
-
if (seen.has(capability.id)) throw new TypeError(`duplicate capability id: ${capability.id}`);
|
|
49
|
-
seen.add(capability.id);
|
|
50
|
-
return capability;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function filterCapabilities(capabilities, request, policy = {}) {
|
|
55
|
-
const effective = { ...DEFAULT_POLICY, ...policy };
|
|
56
|
-
const maxRisk = effective.max_risk ?? "high";
|
|
57
|
-
const actorPermissions = Array.isArray(request.actor_permissions) ? new Set(request.actor_permissions) : null;
|
|
58
|
-
const assessments = capabilities.map((capability) => {
|
|
59
|
-
let reason = null;
|
|
60
|
-
if (!capability.available) reason = capability.availability_reason || "capability unavailable";
|
|
61
|
-
else if (RISK_ORDER[capability.risk] > RISK_ORDER[maxRisk]) reason = `risk ${capability.risk} exceeds policy maximum ${maxRisk}`;
|
|
62
|
-
else if (actorPermissions && capability.permissions.some((permission) => !actorPermissions.has(permission))) reason = "actor lacks required capability permissions";
|
|
63
|
-
return { capability, filtered: Boolean(reason), filter_reason: reason };
|
|
64
|
-
});
|
|
65
|
-
return {
|
|
66
|
-
policy: effective,
|
|
67
|
-
assessments,
|
|
68
|
-
eligible: assessments.filter((assessment) => !assessment.filtered).map((assessment) => assessment.capability),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
export function decisionCandidates(assessments, probabilities = {}, confidence = null) {
|
|
72
|
-
return assessments.map(({ capability, filtered, filter_reason }) => ({
|
|
73
|
-
id: capability.id,
|
|
74
|
-
kind: capability.kind,
|
|
75
|
-
name: capability.name,
|
|
76
|
-
risk: capability.risk,
|
|
77
|
-
available: capability.available,
|
|
78
|
-
filtered,
|
|
79
|
-
filter_reason,
|
|
80
|
-
probability: typeof probabilities[capability.id] === "number" ? probabilities[capability.id] : null,
|
|
81
|
-
confidence: confidence ?? null,
|
|
82
|
-
requires_confirmation: capability.requires_confirmation,
|
|
83
|
-
source: capability.source,
|
|
84
|
-
verified: capability.verified,
|
|
85
|
-
metadata: capability.metadata,
|
|
86
|
-
}));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function deterministicCandidate(capabilities, policy = {}) {
|
|
90
|
-
let eligible = capabilities;
|
|
91
|
-
const requestedId = typeof policy.deterministic_capability_id === "string" ? policy.deterministic_capability_id : null;
|
|
92
|
-
if (requestedId) eligible = eligible.filter((capability) => capability.id === requestedId);
|
|
93
|
-
if (typeof policy.prefer_kind === "string") eligible = eligible.filter((capability) => capability.kind === policy.prefer_kind);
|
|
94
|
-
if (typeof policy.prefer_source === "string") eligible = eligible.filter((capability) => capability.source === policy.prefer_source);
|
|
95
|
-
if (policy.prefer_verified === true) eligible = eligible.filter((capability) => capability.verified);
|
|
96
|
-
if (eligible.length !== 1) return null;
|
|
97
|
-
return {
|
|
98
|
-
capability: eligible[0],
|
|
99
|
-
reason: requestedId ? "explicit capability policy" : "one capability remains after deterministic policy",
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function normalizeMetadata(value) {
|
|
104
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
105
|
-
const allowed = ["skill", "mcp", "cli", "dsh", "subagent", "model", "manifest", "source_ref"];
|
|
106
|
-
return Object.fromEntries(allowed
|
|
107
|
-
.filter((key) => typeof value[key] === "string" || typeof value[key] === "boolean")
|
|
108
|
-
.map((key) => [key, value[key]]));
|
|
109
|
-
}
|
|
1
|
+
import { CAPABILITY_KINDS, RISK_ORDER } from "./contract.mjs";
|
|
2
|
+
|
|
3
|
+
const KIND_ALIASES = Object.freeze({ mcp_tool: "mcp" });
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_POLICY = Object.freeze({
|
|
6
|
+
min_confidence: 0.62,
|
|
7
|
+
max_risk: "high",
|
|
8
|
+
confirmation_risk_levels: ["medium", "high"],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export function normalizeCapability(raw, index = 0) {
|
|
12
|
+
if (!raw || typeof raw !== "object") throw new TypeError(`capabilities[${index}] must be an object`);
|
|
13
|
+
if (typeof raw.id !== "string" || raw.id.trim() === "") throw new TypeError(`capabilities[${index}].id is required`);
|
|
14
|
+
if (typeof raw.name !== "string" || raw.name.trim() === "") throw new TypeError(`capabilities[${index}].name is required`);
|
|
15
|
+
if (typeof raw.description !== "string" || raw.description.trim() === "") throw new TypeError(`capabilities[${index}].description is required`);
|
|
16
|
+
|
|
17
|
+
const rawKind = raw.kind ?? raw.type ?? raw.execution?.mode ?? "tool";
|
|
18
|
+
const kind = KIND_ALIASES[rawKind] ?? rawKind;
|
|
19
|
+
if (!CAPABILITY_KINDS.has(kind)) throw new TypeError(`capabilities[${index}].kind is unsupported: ${rawKind}`);
|
|
20
|
+
|
|
21
|
+
const risk = raw.risk?.level ?? raw.risk ?? "medium";
|
|
22
|
+
if (!(risk in RISK_ORDER)) throw new TypeError(`capabilities[${index}].risk is unsupported: ${risk}`);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
id: raw.id.trim(),
|
|
26
|
+
kind,
|
|
27
|
+
name: raw.name.trim(),
|
|
28
|
+
description: raw.description.trim(),
|
|
29
|
+
permissions: Array.isArray(raw.permissions) ? raw.permissions.filter((value) => typeof value === "string") : [],
|
|
30
|
+
risk,
|
|
31
|
+
available: raw.available !== false && raw.availability?.available !== false,
|
|
32
|
+
availability_reason: raw.availability?.reason ?? null,
|
|
33
|
+
source: typeof raw.source === "string" ? raw.source : null,
|
|
34
|
+
verified: raw.verified === true,
|
|
35
|
+
metadata: normalizeMetadata(raw.metadata ?? raw.discovery),
|
|
36
|
+
execution: {
|
|
37
|
+
mode: "host",
|
|
38
|
+
target: typeof raw.execution?.target === "string" ? raw.execution.target : undefined,
|
|
39
|
+
},
|
|
40
|
+
requires_confirmation: Boolean(raw.policy?.requires_confirmation) || risk !== "low",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizeCapabilities(rawCapabilities) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
return rawCapabilities.map((raw, index) => {
|
|
47
|
+
const capability = normalizeCapability(raw, index);
|
|
48
|
+
if (seen.has(capability.id)) throw new TypeError(`duplicate capability id: ${capability.id}`);
|
|
49
|
+
seen.add(capability.id);
|
|
50
|
+
return capability;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function filterCapabilities(capabilities, request, policy = {}) {
|
|
55
|
+
const effective = { ...DEFAULT_POLICY, ...policy };
|
|
56
|
+
const maxRisk = effective.max_risk ?? "high";
|
|
57
|
+
const actorPermissions = Array.isArray(request.actor_permissions) ? new Set(request.actor_permissions) : null;
|
|
58
|
+
const assessments = capabilities.map((capability) => {
|
|
59
|
+
let reason = null;
|
|
60
|
+
if (!capability.available) reason = capability.availability_reason || "capability unavailable";
|
|
61
|
+
else if (RISK_ORDER[capability.risk] > RISK_ORDER[maxRisk]) reason = `risk ${capability.risk} exceeds policy maximum ${maxRisk}`;
|
|
62
|
+
else if (actorPermissions && capability.permissions.some((permission) => !actorPermissions.has(permission))) reason = "actor lacks required capability permissions";
|
|
63
|
+
return { capability, filtered: Boolean(reason), filter_reason: reason };
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
policy: effective,
|
|
67
|
+
assessments,
|
|
68
|
+
eligible: assessments.filter((assessment) => !assessment.filtered).map((assessment) => assessment.capability),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function decisionCandidates(assessments, probabilities = {}, confidence = null) {
|
|
72
|
+
return assessments.map(({ capability, filtered, filter_reason }) => ({
|
|
73
|
+
id: capability.id,
|
|
74
|
+
kind: capability.kind,
|
|
75
|
+
name: capability.name,
|
|
76
|
+
risk: capability.risk,
|
|
77
|
+
available: capability.available,
|
|
78
|
+
filtered,
|
|
79
|
+
filter_reason,
|
|
80
|
+
probability: typeof probabilities[capability.id] === "number" ? probabilities[capability.id] : null,
|
|
81
|
+
confidence: confidence ?? null,
|
|
82
|
+
requires_confirmation: capability.requires_confirmation,
|
|
83
|
+
source: capability.source,
|
|
84
|
+
verified: capability.verified,
|
|
85
|
+
metadata: capability.metadata,
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function deterministicCandidate(capabilities, policy = {}) {
|
|
90
|
+
let eligible = capabilities;
|
|
91
|
+
const requestedId = typeof policy.deterministic_capability_id === "string" ? policy.deterministic_capability_id : null;
|
|
92
|
+
if (requestedId) eligible = eligible.filter((capability) => capability.id === requestedId);
|
|
93
|
+
if (typeof policy.prefer_kind === "string") eligible = eligible.filter((capability) => capability.kind === policy.prefer_kind);
|
|
94
|
+
if (typeof policy.prefer_source === "string") eligible = eligible.filter((capability) => capability.source === policy.prefer_source);
|
|
95
|
+
if (policy.prefer_verified === true) eligible = eligible.filter((capability) => capability.verified);
|
|
96
|
+
if (eligible.length !== 1) return null;
|
|
97
|
+
return {
|
|
98
|
+
capability: eligible[0],
|
|
99
|
+
reason: requestedId ? "explicit capability policy" : "one capability remains after deterministic policy",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeMetadata(value) {
|
|
104
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
105
|
+
const allowed = ["skill", "mcp", "cli", "dsh", "subagent", "model", "manifest", "source_ref"];
|
|
106
|
+
return Object.fromEntries(allowed
|
|
107
|
+
.filter((key) => typeof value[key] === "string" || typeof value[key] === "boolean")
|
|
108
|
+
.map((key) => [key, value[key]]));
|
|
109
|
+
}
|
package/src/relevance-filter.mjs
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
const MODES = new Set(["shadow", "conservative"]);
|
|
2
|
-
const SIGNALS = [
|
|
3
|
-
/(?:^|[\s"'`])(?:\/[\w.\-~]+|~\/[^\s"'`]+|[A-Za-z]:\\[^\s"'`]+)/u,
|
|
4
|
-
/\b(?:error|exception|traceback|failed|failure|exit\s+status|stderr)\b/i,
|
|
5
|
-
/`[^`\n]+`/u,
|
|
6
|
-
/(?:^|\n)\s*(?:[$#])\s*[A-Za-z0-9_.-]+/u,
|
|
7
|
-
/\b(?:must|required|requirement|acceptance|should|need\s+to)\b/i,
|
|
8
|
-
];
|
|
9
|
-
const LIST_KEYS = new Set(["messages", "events", "logs", "tool_results", "history", "transcript"]);
|
|
10
|
-
|
|
11
|
-
export function normalizeRelevanceMode(mode) {
|
|
12
|
-
if (mode === true) return "shadow";
|
|
13
|
-
if (typeof mode !== "string") return null;
|
|
14
|
-
const normalized = mode.trim().toLowerCase();
|
|
15
|
-
return MODES.has(normalized) ? normalized : null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function filterContext(context, { mode = "shadow", recent = 8 } = {}) {
|
|
19
|
-
const normalizedMode = normalizeRelevanceMode(mode);
|
|
20
|
-
if (!normalizedMode) return { context, report: disabledReport() };
|
|
21
|
-
const source = context && typeof context === "object" ? context : {};
|
|
22
|
-
const report = {
|
|
23
|
-
mode: normalizedMode,
|
|
24
|
-
changed: false,
|
|
25
|
-
considered: 0,
|
|
26
|
-
kept: 0,
|
|
27
|
-
dropped: 0,
|
|
28
|
-
pinned: 0,
|
|
29
|
-
pinned_reasons: [],
|
|
30
|
-
};
|
|
31
|
-
if (normalizedMode === "shadow") {
|
|
32
|
-
inspectValue(source, report);
|
|
33
|
-
return { context: source, report };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const output = Array.isArray(source) ? source.slice() : { ...source };
|
|
37
|
-
for (const [key, value] of Object.entries(source)) {
|
|
38
|
-
if (!LIST_KEYS.has(key) || !Array.isArray(value)) continue;
|
|
39
|
-
const filtered = filterList(value, normalizedMode, recent, report);
|
|
40
|
-
output[key] = filtered;
|
|
41
|
-
}
|
|
42
|
-
if (report.dropped > 0) {
|
|
43
|
-
report.changed = true;
|
|
44
|
-
output._jev_relevance = {
|
|
45
|
-
mode: normalizedMode,
|
|
46
|
-
dropped: report.dropped,
|
|
47
|
-
pinned: report.pinned,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return { context: output, report };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function isPinnedEvidence(value) {
|
|
54
|
-
const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
55
|
-
const reasons = [];
|
|
56
|
-
if (SIGNALS[0].test(text)) reasons.push("path");
|
|
57
|
-
if (SIGNALS[1].test(text)) reasons.push("error");
|
|
58
|
-
if (SIGNALS[2].test(text) || SIGNALS[3].test(text)) reasons.push("command");
|
|
59
|
-
if (SIGNALS[4].test(text)) reasons.push("requirement");
|
|
60
|
-
return { pinned: reasons.length > 0, reasons };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function filterList(items, mode, recent, report) {
|
|
64
|
-
const keepFrom = Math.max(0, items.length - Math.max(0, recent));
|
|
65
|
-
return items.filter((item, index) => {
|
|
66
|
-
report.considered += 1;
|
|
67
|
-
const evidence = isPinnedEvidence(item);
|
|
68
|
-
const keep = evidence.pinned || index >= keepFrom || mode === "conservative" && index === items.length - 1;
|
|
69
|
-
if (evidence.pinned) {
|
|
70
|
-
report.pinned += 1;
|
|
71
|
-
report.pinned_reasons.push(...evidence.reasons);
|
|
72
|
-
}
|
|
73
|
-
if (keep) report.kept += 1;
|
|
74
|
-
else report.dropped += 1;
|
|
75
|
-
return keep;
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function inspectValue(value, report) {
|
|
80
|
-
if (Array.isArray(value)) {
|
|
81
|
-
for (const item of value) {
|
|
82
|
-
report.considered += 1;
|
|
83
|
-
const evidence = isPinnedEvidence(item);
|
|
84
|
-
if (evidence.pinned) {
|
|
85
|
-
report.pinned += 1;
|
|
86
|
-
report.pinned_reasons.push(...evidence.reasons);
|
|
87
|
-
}
|
|
88
|
-
inspectValue(item, report);
|
|
89
|
-
}
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (value && typeof value === "object") {
|
|
93
|
-
for (const child of Object.values(value)) inspectValue(child, report);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function disabledReport() {
|
|
98
|
-
return { mode: null, changed: false, considered: 0, kept: 0, dropped: 0, pinned: 0, pinned_reasons: [] };
|
|
99
|
-
}
|
|
1
|
+
const MODES = new Set(["shadow", "conservative"]);
|
|
2
|
+
const SIGNALS = [
|
|
3
|
+
/(?:^|[\s"'`])(?:\/[\w.\-~]+|~\/[^\s"'`]+|[A-Za-z]:\\[^\s"'`]+)/u,
|
|
4
|
+
/\b(?:error|exception|traceback|failed|failure|exit\s+status|stderr)\b/i,
|
|
5
|
+
/`[^`\n]+`/u,
|
|
6
|
+
/(?:^|\n)\s*(?:[$#])\s*[A-Za-z0-9_.-]+/u,
|
|
7
|
+
/\b(?:must|required|requirement|acceptance|should|need\s+to)\b/i,
|
|
8
|
+
];
|
|
9
|
+
const LIST_KEYS = new Set(["messages", "events", "logs", "tool_results", "history", "transcript"]);
|
|
10
|
+
|
|
11
|
+
export function normalizeRelevanceMode(mode) {
|
|
12
|
+
if (mode === true) return "shadow";
|
|
13
|
+
if (typeof mode !== "string") return null;
|
|
14
|
+
const normalized = mode.trim().toLowerCase();
|
|
15
|
+
return MODES.has(normalized) ? normalized : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function filterContext(context, { mode = "shadow", recent = 8 } = {}) {
|
|
19
|
+
const normalizedMode = normalizeRelevanceMode(mode);
|
|
20
|
+
if (!normalizedMode) return { context, report: disabledReport() };
|
|
21
|
+
const source = context && typeof context === "object" ? context : {};
|
|
22
|
+
const report = {
|
|
23
|
+
mode: normalizedMode,
|
|
24
|
+
changed: false,
|
|
25
|
+
considered: 0,
|
|
26
|
+
kept: 0,
|
|
27
|
+
dropped: 0,
|
|
28
|
+
pinned: 0,
|
|
29
|
+
pinned_reasons: [],
|
|
30
|
+
};
|
|
31
|
+
if (normalizedMode === "shadow") {
|
|
32
|
+
inspectValue(source, report);
|
|
33
|
+
return { context: source, report };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const output = Array.isArray(source) ? source.slice() : { ...source };
|
|
37
|
+
for (const [key, value] of Object.entries(source)) {
|
|
38
|
+
if (!LIST_KEYS.has(key) || !Array.isArray(value)) continue;
|
|
39
|
+
const filtered = filterList(value, normalizedMode, recent, report);
|
|
40
|
+
output[key] = filtered;
|
|
41
|
+
}
|
|
42
|
+
if (report.dropped > 0) {
|
|
43
|
+
report.changed = true;
|
|
44
|
+
output._jev_relevance = {
|
|
45
|
+
mode: normalizedMode,
|
|
46
|
+
dropped: report.dropped,
|
|
47
|
+
pinned: report.pinned,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { context: output, report };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isPinnedEvidence(value) {
|
|
54
|
+
const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
55
|
+
const reasons = [];
|
|
56
|
+
if (SIGNALS[0].test(text)) reasons.push("path");
|
|
57
|
+
if (SIGNALS[1].test(text)) reasons.push("error");
|
|
58
|
+
if (SIGNALS[2].test(text) || SIGNALS[3].test(text)) reasons.push("command");
|
|
59
|
+
if (SIGNALS[4].test(text)) reasons.push("requirement");
|
|
60
|
+
return { pinned: reasons.length > 0, reasons };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function filterList(items, mode, recent, report) {
|
|
64
|
+
const keepFrom = Math.max(0, items.length - Math.max(0, recent));
|
|
65
|
+
return items.filter((item, index) => {
|
|
66
|
+
report.considered += 1;
|
|
67
|
+
const evidence = isPinnedEvidence(item);
|
|
68
|
+
const keep = evidence.pinned || index >= keepFrom || mode === "conservative" && index === items.length - 1;
|
|
69
|
+
if (evidence.pinned) {
|
|
70
|
+
report.pinned += 1;
|
|
71
|
+
report.pinned_reasons.push(...evidence.reasons);
|
|
72
|
+
}
|
|
73
|
+
if (keep) report.kept += 1;
|
|
74
|
+
else report.dropped += 1;
|
|
75
|
+
return keep;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function inspectValue(value, report) {
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
for (const item of value) {
|
|
82
|
+
report.considered += 1;
|
|
83
|
+
const evidence = isPinnedEvidence(item);
|
|
84
|
+
if (evidence.pinned) {
|
|
85
|
+
report.pinned += 1;
|
|
86
|
+
report.pinned_reasons.push(...evidence.reasons);
|
|
87
|
+
}
|
|
88
|
+
inspectValue(item, report);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (value && typeof value === "object") {
|
|
93
|
+
for (const child of Object.values(value)) inspectValue(child, report);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function disabledReport() {
|
|
98
|
+
return { mode: null, changed: false, considered: 0, kept: 0, dropped: 0, pinned: 0, pinned_reasons: [] };
|
|
99
|
+
}
|