@konneal/engine 0.2.79 → 0.2.81

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-DRJCAVNF.js";
3
+ } from "./chunk-YWMM5XTC.js";
4
4
  import "./chunk-7HWJOFTQ.js";
5
5
  import "./chunk-SPKPH54J.js";
6
6
  import "./chunk-A3QHHUN5.js";
@@ -2877,12 +2877,17 @@ Answer account questions from these records ONLY: name the record when you use i
2877
2877
  const pendingTools = [];
2878
2878
  if (/certificate|certified|certification/i.test(q.query) && env.DB) {
2879
2879
  try {
2880
- const probe = await env.AI.run(model, {
2881
- messages: [...messages.slice(0, -1), { role: "user", content: `${TOOL_DECLARATION}
2880
+ const imageTurn = typeof withImage === "string";
2881
+ const probeMessages = [...messages.slice(0, -1), { role: "user", content: `${TOOL_DECLARATION}
2882
+
2883
+ ${imageTurn ? "A photograph of the instrument is attached after this message. Transcribe the maker, model and any printed certificate number you can read, then emit the TOOL line with those tokens as the query \u2014 never invented." : ""}
2882
2884
 
2883
- ${q.query}` }],
2884
- max_tokens: 300,
2885
- reasoning_effort: "low"
2885
+ ${q.query}`.replace("\n\n\n\n", "\n\n") }];
2886
+ if (imageTurn) probeMessages.push({ role: "user", content: [{ type: "image_url", image_url: { url: withImage } }] });
2887
+ const probe = await env.AI.run(model, {
2888
+ messages: probeMessages,
2889
+ max_tokens: 600,
2890
+ reasoning_effort: imageTurn ? "high" : "low"
2886
2891
  });
2887
2892
  const probeText = typeof probe?.response === "string" ? probe.response : probe?.choices?.[0]?.message?.content ?? "";
2888
2893
  const call = parseToolCall(probeText);
@@ -3081,6 +3086,18 @@ ${q.query}` }],
3081
3086
  if (completionBlocks.length) console.log("contract completion:", completionBlocks.length, "table block(s) attached server-side");
3082
3087
  }
3083
3088
  completionBlocks.push(...await completeFigures(env.DB, answer, [...c2ns.blocks, ...completionBlocks], used));
3089
+ if (withImage && register?.rows?.length && answer.includes(refusalAnswer())) {
3090
+ console.log("blind-answer: the register note carried rows but the answer refused \u2014 regenerating once");
3091
+ const regen = await generateOnce(env, model, [
3092
+ ...messages.slice(0, -1),
3093
+ { role: "user", content: "The photograph was attached and the register note lists actual matching certificate rows. Present those rows \u2014 number, holder, model, status, and every document link \u2014 as the answer. Do not claim that nothing was provided." },
3094
+ messages[messages.length - 1]
3095
+ ], effort);
3096
+ if (regen && !regen.includes(refusalAnswer())) {
3097
+ answer = canonicalRefusal(regen);
3098
+ console.log("blind-answer: regeneration grounded");
3099
+ }
3100
+ }
3084
3101
  const jsonQuality = answerQuality(finalCites.map((c) => c.quality));
3085
3102
  let entailment = null;
3086
3103
  let entailmentNote = "";
@@ -6,7 +6,7 @@ import {
6
6
  modelGroundingBlock,
7
7
  scoreFaithfulness,
8
8
  scoreJudge
9
- } from "../../chunk-DRJCAVNF.js";
9
+ } from "../../chunk-YWMM5XTC.js";
10
10
  import {
11
11
  handleSearch
12
12
  } from "../../chunk-KO6DRI7C.js";
@@ -959,7 +959,7 @@ async function handleMcp(env, ctx, req, tier, key) {
959
959
  // stream:false forces the JSON lane (anon defaults to SSE)
960
960
  body: JSON.stringify({ ...args, stream: false })
961
961
  });
962
- const res = name === "ask" ? await (await import("../../ask-RRLFREOH.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
962
+ const res = name === "ask" ? await (await import("../../ask-M2JPJFEM.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
963
963
  return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
964
964
  });
965
965
  if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
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.79",
30
+ "version": "0.2.81",
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",
@@ -1222,12 +1222,20 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1222
1222
  // invocations record into pendingTools; the stream emits them as its
1223
1223
  // first events (the UI shows what the agent consulted).
1224
1224
  const pendingTools: { name: string; query: string; output: string }[] = [];
1225
+ // On an IMAGE turn the probe CARRIES THE IMAGE (2026-10-03, the owner's
1226
+ // architecture): the model that reads the plate is the model that issues
1227
+ // the tool call — one reader, one tool, no parallel extraction racing a
1228
+ // second vision pass that drifts hour to hour. The old probe dropped the
1229
+ // image-bearing message, which is why the private bridge had to exist.
1225
1230
  if (/certificate|certified|certification/i.test(q.query) && env.DB) {
1226
1231
  try {
1232
+ const imageTurn = typeof withImage === "string";
1233
+ const probeMessages: any[] = [...messages.slice(0, -1), { role: "user", content: `${TOOL_DECLARATION}\n\n${imageTurn ? "A photograph of the instrument is attached after this message. Transcribe the maker, model and any printed certificate number you can read, then emit the TOOL line with those tokens as the query — never invented." : ""}\n\n${q.query}`.replace("\n\n\n\n", "\n\n") }];
1234
+ if (imageTurn) probeMessages.push({ role: "user", content: [{ type: "image_url", image_url: { url: withImage } }] });
1227
1235
  const probe = await (env as any).AI.run(model, {
1228
- messages: [...messages.slice(0, -1), { role: "user", content: `${TOOL_DECLARATION}\n\n${q.query}` }],
1229
- max_tokens: 300,
1230
- reasoning_effort: "low",
1236
+ messages: probeMessages,
1237
+ max_tokens: 600,
1238
+ reasoning_effort: imageTurn ? "high" : "low",
1231
1239
  });
1232
1240
  const probeText = typeof probe?.response === "string" ? probe.response : probe?.choices?.[0]?.message?.content ?? "";
1233
1241
  const call = parseToolCall(probeText);
@@ -1255,6 +1263,7 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1255
1263
  const note = wantsMarkings
1256
1264
  ? "\n\n(The user attached a photo with this question. First transcribe every inscription you can actually read in the image — names, model references, accuracy classes, numeric values with their units, certificate or approval numbers — quoting them verbatim. Then answer the question from the numbered passages, citing the requirement each interpretation rests on. If an inscription is unreadable, say so; never invent a marking.)"
1257
1265
  : "\n\n(The user attached an image with this question; interpret it directly when answering.)";
1266
+
1258
1267
  if (Array.isArray(last.content)) {
1259
1268
  const textPart = last.content.find((p: any) => p.type === "text");
1260
1269
  if (textPart) textPart.text += note;
@@ -1503,6 +1512,24 @@ let convEntities: Array<{ entity: string; kind: string }> = [];
1503
1512
  // figure completion (#172) — see ./completion for the rationale
1504
1513
  completionBlocks.push(...(await completeFigures(env.DB, answer, [...c2ns.blocks, ...completionBlocks], used)));
1505
1514
 
1515
+ // the blind-answer regeneration (2026-10-03, live on the photo case):
1516
+ // the multimodal call can answer AROUND an attached image — a 200 whose
1517
+ // text claims nothing was provided — while the bridge succeeded and the
1518
+ // register note carries actual rows. One bounded regeneration, decided on
1519
+ // that exact signature; the retry carries a sterner ground instruction.
1520
+ if (withImage && register?.rows?.length && answer.includes(refusalAnswer())) {
1521
+ console.log("blind-answer: the register note carried rows but the answer refused — regenerating once");
1522
+ const regen = await generateOnce(env, model, [
1523
+ ...messages.slice(0, -1),
1524
+ { role: "user", content: "The photograph was attached and the register note lists actual matching certificate rows. Present those rows — number, holder, model, status, and every document link — as the answer. Do not claim that nothing was provided." },
1525
+ messages[messages.length - 1],
1526
+ ], effort);
1527
+ if (regen && !regen.includes(refusalAnswer())) {
1528
+ answer = canonicalRefusal(regen);
1529
+ console.log("blind-answer: regeneration grounded");
1530
+ }
1531
+ }
1532
+
1506
1533
  const jsonQuality = answerQuality(finalCites.map((c: any) => c.quality));
1507
1534
  // the entailment gate (TODO.new-era/10): the eval battery's faithfulness
1508
1535
  // scorer, promoted onto the serving path — the confidence line states a