@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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-CPPIKASJ.js";
3
+ } from "./chunk-VSK2RGUF.js";
4
4
  import "./chunk-ECOYO7EX.js";
5
5
  import "./chunk-VYEMFSU5.js";
6
6
  import "./chunk-G2RE4XRV.js";
@@ -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
  }
@@ -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-CPPIKASJ.js";
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-KVGDQTF7.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-GAU5SEZI.js")).handleSearch(env, ctx, inner, tier, key);
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.150",
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: