@konneal/engine 0.2.153 → 0.2.155

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.
@@ -0,0 +1,4 @@
1
+ export declare function toolNarration(t: string): boolean;
2
+ export declare const 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.";
3
+ export declare function intentPreamble(t: string): boolean;
4
+ export declare const 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.";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-I6HU3EM6.js";
3
+ } from "./chunk-TXF5VMNM.js";
4
4
  import "./chunk-ECOYO7EX.js";
5
5
  import "./chunk-VYEMFSU5.js";
6
6
  import "./chunk-G2RE4XRV.js";
@@ -1637,7 +1637,7 @@ async function extractNameplate(ai, model, image) {
1637
1637
  return null;
1638
1638
  }
1639
1639
 
1640
- // workers/worker_public/src/ask.ts
1640
+ // workers/worker_public/src/answerGuards.ts
1641
1641
  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
  }
@@ -1649,6 +1649,8 @@ function intentPreamble(t) {
1649
1649
  return !/\[\[u:|\[\^?\d+\]|\b\d+\.\d+\b/.test(s);
1650
1650
  }
1651
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.";
1652
+
1653
+ // workers/worker_public/src/ask.ts
1652
1654
  function userImageDataUrl(body) {
1653
1655
  const img = body?.image;
1654
1656
  if (img == null) return null;
@@ -4,7 +4,7 @@ import {
4
4
  handleMemories,
5
5
  scoreFaithfulness,
6
6
  scoreJudge
7
- } from "../../chunk-I6HU3EM6.js";
7
+ } from "../../chunk-TXF5VMNM.js";
8
8
  import "../../chunk-ECOYO7EX.js";
9
9
  import {
10
10
  TOOLS_REGISTRY,
@@ -411,7 +411,11 @@ ${c.text.slice(0, 1500)}` }
411
411
  context = typeof raw === "string" ? raw.trim().replace(/^["\']|[\"']$/g, "").slice(0, 400) : "";
412
412
  if (!context) {
413
413
  console.log("enrich raw keys:", Object.keys(res ?? {}), "sample:", JSON.stringify(res).slice(0, 300));
414
- return { id: c.id, ok: false, error: "empty enrichment" };
414
+ if (contextOnly || abMode) return { id: c.id, ok: false, error: "empty enrichment" };
415
+ const rawText = typeof c.metadata.chunk_text === "string" && c.metadata.chunk_text ? c.metadata.chunk_text : c.text;
416
+ const rawVector = await embed(portModelRunner(env), MODELS.embed, rawText.slice(0, 6e3));
417
+ await env.VECTORIZE.upsert([{ id: c.id, values: rawVector, metadata: { ...c.metadata, chunk_text: rawText } }]);
418
+ return { id: c.id, ok: true, raw: true, cached: false, context: "" };
415
419
  }
416
420
  if (res?.usage) {
417
421
  usage.prompt_tokens += Number(res.usage.prompt_tokens ?? 0);
@@ -1107,7 +1111,7 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
1107
1111
  const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
1108
1112
  payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
1109
1113
  } else {
1110
- const res = name === "ask" ? await (await import("../../ask-UIIE5LD2.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-GAU5SEZI.js")).handleSearch(env, ctx, inner, tier, key);
1114
+ const res = name === "ask" ? await (await import("../../ask-MRVXR7CY.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-GAU5SEZI.js")).handleSearch(env, ctx, inner, tier, key);
1111
1115
  payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
1112
1116
  }
1113
1117
  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.153",
30
+ "version": "0.2.155",
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",
@@ -78,7 +78,20 @@ export async function handleEnrich(env: Env, ctx: ExecutionContext, req: Request
78
78
  context = typeof raw === "string" ? raw.trim().replace(/^["\']|[\"']$/g, "").slice(0, 400) : "";
79
79
  if (!context) {
80
80
  console.log("enrich raw keys:", Object.keys(res ?? {}), "sample:", JSON.stringify(res).slice(0, 300));
81
- return { id: c.id, ok: false, error: "empty enrichment" };
81
+ // The deterministic-empty class (observed 2026-10-20 on B 18's
82
+ // thin terms sections and two R 134-1 clauses — empty under
83
+ // BOTH the default and the alternate enrichment model): the
84
+ // chunk's own text is real corpus content, so the production
85
+ // flow serves it RAW rather than leaving a hole in the index
86
+ // — embed the original text without the context band, upsert
87
+ // without the ctx marker; a later forced re-enrich overwrites
88
+ // the vector in place. mode:"context" and "ab" keep failing:
89
+ // their builders want the signal, not a side-effecting write.
90
+ if (contextOnly || abMode) return { id: c.id, ok: false, error: "empty enrichment" };
91
+ const rawText = typeof c.metadata.chunk_text === "string" && c.metadata.chunk_text ? c.metadata.chunk_text : c.text;
92
+ const rawVector = await embed(portModelRunner(env), MODELS.embed, rawText.slice(0, 6000));
93
+ await env.VECTORIZE.upsert([{ id: c.id, values: rawVector, metadata: { ...c.metadata, chunk_text: rawText } }]);
94
+ return { id: c.id, ok: true, raw: true, cached: false, context: "" };
82
95
  }
83
96
  if (res?.usage) {
84
97
  usage.prompt_tokens += Number(res.usage.prompt_tokens ?? 0);
@@ -0,0 +1,36 @@
1
+ // The answer-contract's text guards, dependency-free so plain node can
2
+ // load them for unit tests (ask.ts imports .md prompts that only the
3
+ // bundler resolves — the guards were module-local there and rode e2e
4
+ // alone). Both lanes share them: the JSON lane's contract retry and the
5
+ // streamed lane's 80-char head ruling.
6
+
7
+ // The tool-narration leak (2026-10-14, the R 134-1 probe): the answer
8
+ // model sometimes NARRATES the agent loop's call in prose — 'The
9
+ // db.find tool returned, for the query "…": {…json…}' — instead of
10
+ // answering. The TOOL-line scrub cannot catch prose. The JSON lane
11
+ // regenerates through the contract retry; the streamed lane buffers
12
+ // the head and sends the retry sentinel (the blind-answer guard's
13
+ // client contract) before streaming the fresh answer. One bounded
14
+ // restart per lane.
15
+ export function toolNarration(t: string): boolean {
16
+ return /^(?:the\s+)?[a-z][a-z_.]*\.[a-z_]+ tool returned\b/i.test(t.slice(0, 200)) ||
17
+ /tool returned,? for the query\b/i.test(t.slice(0, 300));
18
+ }
19
+ export const TOOL_NARRATION_NOTICE =
20
+ "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.";
21
+
22
+ // The intent preamble (2026-10-19, the ~200-word technical-file
23
+ // question): the draft is a PLAN, not an answer — "I'll look up the R
24
+ // 76-1 MPE clause and classification details in the corpus." — shipped
25
+ // while ten citations rode the same response. Narrow by construction:
26
+ // it must OPEN with a first-person intent verb AND carry no substance —
27
+ // short, no clause citation, no normative numbers (a real answer's
28
+ // opening names values or clauses).
29
+ export function intentPreamble(t: string): boolean {
30
+ const s = (t ?? "").trim();
31
+ if (s.length > 240) return false;
32
+ if (!/^(?:i'll|i will|let me|let's|i'm going to|i am going to)\b/i.test(s)) return false;
33
+ return !/\[\[u:|\[\^?\d+\]|\b\d+\.\d+\b/.test(s);
34
+ }
35
+ export const INTENT_PREAMBLE_NOTICE =
36
+ "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.";
@@ -49,37 +49,8 @@ import { TOOL_DECLARATION, parseToolCall, runTool, toolNote } from "./tools";
49
49
  import { operationsLane } from "./operations.ts";
50
50
  import { extractNameplate, nameplateRegisterQuery } from "./nameplate.ts";
51
51
  import { P } from "./profile.ts";
52
+ import { toolNarration, intentPreamble, TOOL_NARRATION_NOTICE, INTENT_PREAMBLE_NOTICE } from "./answerGuards.ts";
52
53
 
53
- // the tool-narration leak (2026-10-14, the R 134-1 probe): the answer
54
- // model sometimes NARRATES the agent loop's call in prose — 'The
55
- // db.find tool returned, for the query "…": {…json…}' — instead of
56
- // answering. The TOOL-line scrub cannot catch prose. The JSON lane
57
- // regenerates through the contract retry; the streamed lane buffers
58
- // the head and sends the retry sentinel (the blind-answer guard's
59
- // client contract) before streaming the fresh answer. One bounded
60
- // restart per lane.
61
- function toolNarration(t: string): boolean {
62
- return /^(?:the\s+)?[a-z][a-z_.]*\.[a-z_]+ tool returned\b/i.test(t.slice(0, 200)) ||
63
- /tool returned,? for the query\b/i.test(t.slice(0, 300));
64
- }
65
- const TOOL_NARRATION_NOTICE =
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
-
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
54
 
84
55
  /** User-uploaded image for multimodal questions: a data URL
85
56
  * (data:image/(png|jpeg|webp|gif);base64,…) up to 6 MB of payload. The