@konneal/engine 0.2.150 → 0.2.152
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/dist/{ask-KVGDQTF7.js → ask-LP6M4QNS.js} +1 -1
- package/dist/{chunk-CPPIKASJ.js → chunk-VSK2RGUF.js} +15 -8
- package/dist/mcp-proto.d.ts +3 -0
- package/dist/worker_public/src/index.js +31 -3
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +25 -7
- package/workers/worker_public/src/mcp-proto.ts +39 -1
|
@@ -1642,6 +1642,13 @@ function toolNarration(t) {
|
|
|
1642
1642
|
return /^(?:the\s+)?[a-z][a-z_.]*\.[a-z_]+ tool returned\b/i.test(t.slice(0, 200)) || /tool returned,? for the query\b/i.test(t.slice(0, 300));
|
|
1643
1643
|
}
|
|
1644
1644
|
var TOOL_NARRATION_NOTICE = "Correction notice: your draft narrated an internal tool call ('the \u2026 tool returned \u2026') instead of answering. Rewrite: answer the question directly from the retrieved passages, cite the clauses, and never mention tools, queries, wire formats, or JSON.";
|
|
1645
|
+
function intentPreamble(t) {
|
|
1646
|
+
const s = (t ?? "").trim();
|
|
1647
|
+
if (s.length > 240) return false;
|
|
1648
|
+
if (!/^(?:i'll|i will|let me|let's|i'm going to|i am going to)\b/i.test(s)) return false;
|
|
1649
|
+
return !/\[\[u:|\[\^?\d+\]|\b\d+\.\d+\b/.test(s);
|
|
1650
|
+
}
|
|
1651
|
+
var INTENT_PREAMBLE_NOTICE = "Correction notice: your draft announced a plan ('I'll look up \u2026') instead of answering. Do not narrate intent \u2014 answer the question NOW from the retrieved passages: give the values, clause references and citations the question asks for.";
|
|
1645
1652
|
function userImageDataUrl(body) {
|
|
1646
1653
|
const img = body?.image;
|
|
1647
1654
|
if (img == null) return null;
|
|
@@ -2623,12 +2630,12 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2623
2630
|
}
|
|
2624
2631
|
if (full.length < 80) continue;
|
|
2625
2632
|
headSettled = true;
|
|
2626
|
-
if (toolNarration(full)) {
|
|
2627
|
-
console.log("tool-narration(stream): regenerating once");
|
|
2633
|
+
if (toolNarration(full) || intentPreamble(full)) {
|
|
2634
|
+
console.log(toolNarration(full) ? "tool-narration(stream): regenerating once" : "intent-preamble(stream): regenerating once");
|
|
2628
2635
|
send({ type: "retry" });
|
|
2629
2636
|
const regen = await generateStream(env, model, [
|
|
2630
2637
|
...messages,
|
|
2631
|
-
{ role: "system", content: TOOL_NARRATION_NOTICE }
|
|
2638
|
+
{ role: "system", content: toolNarration(full) ? TOOL_NARRATION_NOTICE : INTENT_PREAMBLE_NOTICE }
|
|
2632
2639
|
], routeEffort(routed.route));
|
|
2633
2640
|
if (regen) {
|
|
2634
2641
|
full = "";
|
|
@@ -2645,7 +2652,7 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2645
2652
|
}
|
|
2646
2653
|
if (!headSettled) {
|
|
2647
2654
|
headSettled = true;
|
|
2648
|
-
if (!toolNarration(full)) send({ type: "token", v: full });
|
|
2655
|
+
if (!toolNarration(full) && !intentPreamble(full)) send({ type: "token", v: full });
|
|
2649
2656
|
}
|
|
2650
2657
|
} catch {
|
|
2651
2658
|
}
|
|
@@ -2776,17 +2783,17 @@ Answer account questions from these records ONLY: name the record when you use i
|
|
|
2776
2783
|
);
|
|
2777
2784
|
return nums.filter((n) => tableNums.has(n)).length >= 2;
|
|
2778
2785
|
})();
|
|
2779
|
-
if (anchors.violations.length > 0 || retyped || unreferenced || toolNarration(answer)) {
|
|
2780
|
-
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "; toolNarration:", toolNarration(answer), "\u2014 regenerating");
|
|
2786
|
+
if (anchors.violations.length > 0 || retyped || unreferenced || toolNarration(answer) || intentPreamble(answer)) {
|
|
2787
|
+
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "; toolNarration:", toolNarration(answer), "; intentPreamble:", intentPreamble(answer), "\u2014 regenerating");
|
|
2781
2788
|
const tableUnitId = unreferenced ? used.find((h) => h.metadata.unit_id && h.metadata.block === "table")?.metadata.unit_id : void 0;
|
|
2782
|
-
const note = toolNarration(answer) ? TOOL_NARRATION_NOTICE : retyped || unreferenced ? `Correction notice: your draft reproduced a table as markdown or presented a served table's data without its reference. Rewrite the answer: describe the table in prose, cite the clause, and write the reference token [[u:${tableUnitId ?? "<unit id>"}]] exactly where the table belongs. Do not render any table as markdown.` : ANCHOR_CORRECTION_NOTE;
|
|
2789
|
+
const note = toolNarration(answer) ? TOOL_NARRATION_NOTICE : intentPreamble(answer) ? INTENT_PREAMBLE_NOTICE : retyped || unreferenced ? `Correction notice: your draft reproduced a table as markdown or presented a served table's data without its reference. Rewrite the answer: describe the table in prose, cite the clause, and write the reference token [[u:${tableUnitId ?? "<unit id>"}]] exactly where the table belongs. Do not render any table as markdown.` : ANCHOR_CORRECTION_NOTE;
|
|
2783
2790
|
generateRetries += 1;
|
|
2784
2791
|
const corrected = await generateOnce(env, model, [...messages, { role: "system", content: note }], routeEffort(routed.route));
|
|
2785
2792
|
if (corrected) {
|
|
2786
2793
|
const correctedAnswer = canonicalRefusal(corrected, refusalScope(answerCites(corrected)));
|
|
2787
2794
|
const retryAnchors = checkQuoteAnchors(correctedAnswer, used.map((h) => h.text));
|
|
2788
2795
|
const retryRetyped = tableRetyped(correctedAnswer, hasTableUnit);
|
|
2789
|
-
if (retryAnchors.violations.length < anchors.violations.length || !retryRetyped && retyped || unreferenced && correctedAnswer.includes("[[u:") || !toolNarration(correctedAnswer)) {
|
|
2796
|
+
if (retryAnchors.violations.length < anchors.violations.length || !retryRetyped && retyped || unreferenced && correctedAnswer.includes("[[u:") || !toolNarration(correctedAnswer) || !intentPreamble(correctedAnswer)) {
|
|
2790
2797
|
answer = correctedAnswer;
|
|
2791
2798
|
}
|
|
2792
2799
|
}
|
package/dist/mcp-proto.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface McpTool {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
export declare function buildTools(): McpTool[];
|
|
18
|
+
export declare const TOOL_RESULT_CHAR_CAP = 24000;
|
|
19
|
+
export declare const TRUNCATION_MARK = "--- TRUNCATED ---";
|
|
20
|
+
export declare function capToolResult(payload: unknown, cap?: number): unknown;
|
|
18
21
|
export type McpResult = {
|
|
19
22
|
ok: true;
|
|
20
23
|
result: unknown;
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
handleMemories,
|
|
5
5
|
scoreFaithfulness,
|
|
6
6
|
scoreJudge
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-VSK2RGUF.js";
|
|
8
8
|
import "../../chunk-ECOYO7EX.js";
|
|
9
9
|
import {
|
|
10
10
|
TOOLS_REGISTRY,
|
|
@@ -1021,6 +1021,34 @@ function buildTools() {
|
|
|
1021
1021
|
}))
|
|
1022
1022
|
];
|
|
1023
1023
|
}
|
|
1024
|
+
var TOOL_RESULT_CHAR_CAP = 24e3;
|
|
1025
|
+
var TRUNCATION_MARK = "--- TRUNCATED ---";
|
|
1026
|
+
var jsonLen = (x) => JSON.stringify(x)?.length ?? 0;
|
|
1027
|
+
function clipStrings(value, budget) {
|
|
1028
|
+
if (typeof value === "string") return value.length <= budget ? value : `${value.slice(0, budget)}
|
|
1029
|
+
${TRUNCATION_MARK}`;
|
|
1030
|
+
if (Array.isArray(value)) return value.map((v) => clipStrings(v, budget));
|
|
1031
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, clipStrings(v, budget)]));
|
|
1032
|
+
return value;
|
|
1033
|
+
}
|
|
1034
|
+
function capToolResult(payload, cap = TOOL_RESULT_CHAR_CAP) {
|
|
1035
|
+
if (jsonLen(payload) <= cap) return payload;
|
|
1036
|
+
let lo = 200;
|
|
1037
|
+
let hi = cap;
|
|
1038
|
+
let best = clipStrings(payload, lo);
|
|
1039
|
+
while (lo <= hi) {
|
|
1040
|
+
const mid = lo + hi >> 1;
|
|
1041
|
+
const candidate = clipStrings(payload, mid);
|
|
1042
|
+
if (jsonLen(candidate) <= cap) {
|
|
1043
|
+
best = candidate;
|
|
1044
|
+
lo = mid + 1;
|
|
1045
|
+
} else {
|
|
1046
|
+
hi = mid - 1;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (jsonLen(best) <= cap) return best;
|
|
1050
|
+
return { truncation: `the tool result exceeded the ${cap}-character budget and was withheld` };
|
|
1051
|
+
}
|
|
1024
1052
|
function dispatch(method, params, callTool) {
|
|
1025
1053
|
switch (method) {
|
|
1026
1054
|
case "initialize":
|
|
@@ -1036,7 +1064,7 @@ function dispatch(method, params, callTool) {
|
|
|
1036
1064
|
return Promise.resolve({ ok: false, code: -32602, message: `unknown tool: ${name}` });
|
|
1037
1065
|
}
|
|
1038
1066
|
return callTool(registryName(name), params?.arguments ?? {}).then(
|
|
1039
|
-
(payload) => ({ ok: true, result: { content: [{ type: "text", text: JSON.stringify(payload) }] } })
|
|
1067
|
+
(payload) => ({ ok: true, result: { content: [{ type: "text", text: JSON.stringify(capToolResult(payload)) }] } })
|
|
1040
1068
|
);
|
|
1041
1069
|
}
|
|
1042
1070
|
default:
|
|
@@ -1079,7 +1107,7 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
|
|
|
1079
1107
|
const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
|
|
1080
1108
|
payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
|
|
1081
1109
|
} else {
|
|
1082
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
1110
|
+
const res = name === "ask" ? await (await import("../../ask-LP6M4QNS.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-GAU5SEZI.js")).handleSearch(env, ctx, inner, tier, key);
|
|
1083
1111
|
payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
1084
1112
|
}
|
|
1085
1113
|
telemetry(env, ctx, tier, `mcp:${name}`, null, !payload?.error, JSON.stringify(payload).length, await sha256Hex(`${name}:${JSON.stringify(args ?? {})}`), void 0, void 0, { durationMs: Date.now() - t0 });
|
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"playwright": "^1.62.1",
|
|
28
28
|
"yaml": "^2.9.1"
|
|
29
29
|
},
|
|
30
|
-
"version": "0.2.
|
|
30
|
+
"version": "0.2.152",
|
|
31
31
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
32
32
|
"license": "BSD-3-Clause",
|
|
33
33
|
"type": "module",
|
|
@@ -65,6 +65,22 @@ function toolNarration(t: string): boolean {
|
|
|
65
65
|
const TOOL_NARRATION_NOTICE =
|
|
66
66
|
"Correction notice: your draft narrated an internal tool call ('the … tool returned …') instead of answering. Rewrite: answer the question directly from the retrieved passages, cite the clauses, and never mention tools, queries, wire formats, or JSON.";
|
|
67
67
|
|
|
68
|
+
// The intent preamble (2026-10-19, the ~200-word technical-file
|
|
69
|
+
// question): the draft is a PLAN, not an answer — "I'll look up the R
|
|
70
|
+
// 76-1 MPE clause and classification details in the corpus." — shipped
|
|
71
|
+
// while ten citations rode the same response. Narrow by construction:
|
|
72
|
+
// it must OPEN with a first-person intent verb AND carry no substance —
|
|
73
|
+
// short, no clause citation, no normative numbers (a real answer's
|
|
74
|
+
// opening names values or clauses).
|
|
75
|
+
function intentPreamble(t: string): boolean {
|
|
76
|
+
const s = (t ?? "").trim();
|
|
77
|
+
if (s.length > 240) return false;
|
|
78
|
+
if (!/^(?:i'll|i will|let me|let's|i'm going to|i am going to)\b/i.test(s)) return false;
|
|
79
|
+
return !/\[\[u:|\[\^?\d+\]|\b\d+\.\d+\b/.test(s);
|
|
80
|
+
}
|
|
81
|
+
const INTENT_PREAMBLE_NOTICE =
|
|
82
|
+
"Correction notice: your draft announced a plan ('I'll look up …') instead of answering. Do not narrate intent — answer the question NOW from the retrieved passages: give the values, clause references and citations the question asks for.";
|
|
83
|
+
|
|
68
84
|
/** User-uploaded image for multimodal questions: a data URL
|
|
69
85
|
* (data:image/(png|jpeg|webp|gif);base64,…) up to 6 MB of payload. The
|
|
70
86
|
* question text still drives retrieval; the image is CONTEXT for the
|
|
@@ -1570,12 +1586,12 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1570
1586
|
if (headSettled) { send({ type: "token", v: tok }); continue; }
|
|
1571
1587
|
if (full.length < 80) continue;
|
|
1572
1588
|
headSettled = true;
|
|
1573
|
-
if (toolNarration(full)) {
|
|
1574
|
-
console.log("tool-narration(stream): regenerating once");
|
|
1589
|
+
if (toolNarration(full) || intentPreamble(full)) {
|
|
1590
|
+
console.log(toolNarration(full) ? "tool-narration(stream): regenerating once" : "intent-preamble(stream): regenerating once");
|
|
1575
1591
|
send({ type: "retry" });
|
|
1576
1592
|
const regen = await generateStream(env, model, [
|
|
1577
1593
|
...messages,
|
|
1578
|
-
{ role: "system", content: TOOL_NARRATION_NOTICE },
|
|
1594
|
+
{ role: "system", content: toolNarration(full) ? TOOL_NARRATION_NOTICE : INTENT_PREAMBLE_NOTICE },
|
|
1579
1595
|
], routeEffort(routed.route));
|
|
1580
1596
|
if (regen) {
|
|
1581
1597
|
full = "";
|
|
@@ -1594,7 +1610,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1594
1610
|
}
|
|
1595
1611
|
if (!headSettled) {
|
|
1596
1612
|
headSettled = true;
|
|
1597
|
-
if (!toolNarration(full)) send({ type: "token", v: full });
|
|
1613
|
+
if (!toolNarration(full) && !intentPreamble(full)) send({ type: "token", v: full });
|
|
1598
1614
|
}
|
|
1599
1615
|
} catch {
|
|
1600
1616
|
// stream ended prematurely — deliver what we have
|
|
@@ -1776,8 +1792,8 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1776
1792
|
);
|
|
1777
1793
|
return nums.filter((n) => tableNums.has(n)).length >= 2;
|
|
1778
1794
|
})();
|
|
1779
|
-
if (anchors.violations.length > 0 || retyped || unreferenced || toolNarration(answer)) {
|
|
1780
|
-
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "; toolNarration:", toolNarration(answer), "— regenerating");
|
|
1795
|
+
if (anchors.violations.length > 0 || retyped || unreferenced || toolNarration(answer) || intentPreamble(answer)) {
|
|
1796
|
+
console.log("contract check:", anchors.violations.length, "anchor violations; tableRetyped:", retyped, "; tableDataUnreferenced:", unreferenced, "; toolNarration:", toolNarration(answer), "; intentPreamble:", intentPreamble(answer), "— regenerating");
|
|
1781
1797
|
// name the EXACT unit the token must reference — a generic note
|
|
1782
1798
|
// leaves the model guessing which id to write
|
|
1783
1799
|
const tableUnitId = unreferenced
|
|
@@ -1785,6 +1801,8 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1785
1801
|
: undefined;
|
|
1786
1802
|
const note = toolNarration(answer)
|
|
1787
1803
|
? TOOL_NARRATION_NOTICE
|
|
1804
|
+
: intentPreamble(answer)
|
|
1805
|
+
? INTENT_PREAMBLE_NOTICE
|
|
1788
1806
|
: retyped || unreferenced
|
|
1789
1807
|
? `Correction notice: your draft reproduced a table as markdown or presented a served table's data without its reference. Rewrite the answer: describe the table in prose, cite the clause, and write the reference token [[u:${tableUnitId ?? "<unit id>"}]] exactly where the table belongs. Do not render any table as markdown.`
|
|
1790
1808
|
: ANCHOR_CORRECTION_NOTE;
|
|
@@ -1794,7 +1812,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
|
|
|
1794
1812
|
const correctedAnswer = canonicalRefusal(corrected, refusalScope(answerCites(corrected)));
|
|
1795
1813
|
const retryAnchors = checkQuoteAnchors(correctedAnswer, used.map((h: Hit) => h.text));
|
|
1796
1814
|
const retryRetyped = tableRetyped(correctedAnswer, hasTableUnit);
|
|
1797
|
-
if (retryAnchors.violations.length < anchors.violations.length || (!retryRetyped && retyped) || (unreferenced && correctedAnswer.includes("[[u:")) || !toolNarration(correctedAnswer)) {
|
|
1815
|
+
if (retryAnchors.violations.length < anchors.violations.length || (!retryRetyped && retyped) || (unreferenced && correctedAnswer.includes("[[u:")) || !toolNarration(correctedAnswer) || !intentPreamble(correctedAnswer)) {
|
|
1798
1816
|
answer = correctedAnswer;
|
|
1799
1817
|
}
|
|
1800
1818
|
}
|
|
@@ -94,6 +94,44 @@ export function buildTools(): McpTool[] {
|
|
|
94
94
|
];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// The tool-result budget (the cloudflare/mcp pattern, 2026-10-19): an
|
|
98
|
+
// MCP client spends its own context reading tool results, so an
|
|
99
|
+
// unbounded payload (a broad db.find over the register, a large
|
|
100
|
+
// retrieve) can eat an agent's window. An oversized result comes back
|
|
101
|
+
// size-bounded and still valid JSON: short fields (names, numbers,
|
|
102
|
+
// metadata) stay whole, long strings clip, every cut is marked.
|
|
103
|
+
export const TOOL_RESULT_CHAR_CAP = 24000; // ≈6k tokens
|
|
104
|
+
export const TRUNCATION_MARK = "--- TRUNCATED ---";
|
|
105
|
+
|
|
106
|
+
const jsonLen = (x: unknown): number => JSON.stringify(x)?.length ?? 0;
|
|
107
|
+
|
|
108
|
+
function clipStrings(value: unknown, budget: number): unknown {
|
|
109
|
+
if (typeof value === "string") return value.length <= budget ? value : `${value.slice(0, budget)}\n${TRUNCATION_MARK}`;
|
|
110
|
+
if (Array.isArray(value)) return value.map((v) => clipStrings(v, budget));
|
|
111
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, clipStrings(v, budget)]));
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function capToolResult(payload: unknown, cap: number = TOOL_RESULT_CHAR_CAP): unknown {
|
|
116
|
+
if (jsonLen(payload) <= cap) return payload;
|
|
117
|
+
// the per-string budget shrinks until the whole payload fits
|
|
118
|
+
let lo = 200;
|
|
119
|
+
let hi = cap;
|
|
120
|
+
let best = clipStrings(payload, lo);
|
|
121
|
+
while (lo <= hi) {
|
|
122
|
+
const mid = (lo + hi) >> 1;
|
|
123
|
+
const candidate = clipStrings(payload, mid);
|
|
124
|
+
if (jsonLen(candidate) <= cap) {
|
|
125
|
+
best = candidate;
|
|
126
|
+
lo = mid + 1;
|
|
127
|
+
} else {
|
|
128
|
+
hi = mid - 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (jsonLen(best) <= cap) return best;
|
|
132
|
+
return { truncation: `the tool result exceeded the ${cap}-character budget and was withheld` };
|
|
133
|
+
}
|
|
134
|
+
|
|
97
135
|
export type McpResult =
|
|
98
136
|
| { ok: true; result: unknown }
|
|
99
137
|
| { ok: true; accepted: true }
|
|
@@ -120,7 +158,7 @@ export function dispatch(
|
|
|
120
158
|
return Promise.resolve({ ok: false, code: -32602, message: `unknown tool: ${name}` });
|
|
121
159
|
}
|
|
122
160
|
return callTool(registryName(name), params?.arguments ?? {}).then(
|
|
123
|
-
(payload) => ({ ok: true, result: { content: [{ type: "text", text: JSON.stringify(payload) }] } }) as McpResult,
|
|
161
|
+
(payload) => ({ ok: true, result: { content: [{ type: "text", text: JSON.stringify(capToolResult(payload)) }] } }) as McpResult,
|
|
124
162
|
);
|
|
125
163
|
}
|
|
126
164
|
default:
|