dsh-llm-verifier 0.2.1 → 0.3.1
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/README.md +15 -11
- package/lib/client.js +23 -3
- package/lib/client.js.map +1 -1
- package/lib/index.js +389 -115
- package/lib/index.js.map +1 -1
- package/lib/types/auto.d.ts +32 -12
- package/lib/types/auto.d.ts.map +1 -1
- package/lib/types/auto.js +50 -38
- package/lib/types/auto.js.map +1 -1
- package/lib/types/client-i18n.d.ts +21 -1
- package/lib/types/client-i18n.d.ts.map +1 -1
- package/lib/types/client-i18n.js +22 -4
- package/lib/types/client-i18n.js.map +1 -1
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/client.js +1 -1
- package/lib/types/client.js.map +1 -1
- package/lib/types/engine.d.ts +19 -0
- package/lib/types/engine.d.ts.map +1 -1
- package/lib/types/engine.js +54 -4
- package/lib/types/engine.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +32 -43
- package/lib/types/index.js.map +1 -1
- package/lib/types/router.d.ts +31 -0
- package/lib/types/router.d.ts.map +1 -1
- package/lib/types/router.js +192 -17
- package/lib/types/router.js.map +1 -1
- package/lib/types/statistics.d.ts +28 -0
- package/lib/types/statistics.d.ts.map +1 -1
- package/lib/types/statistics.js +101 -0
- package/lib/types/statistics.js.map +1 -1
- package/package.json +1 -1
- package/src/auto.test.ts +42 -23
- package/src/auto.ts +55 -46
- package/src/client-i18n.ts +32 -4
- package/src/client.test.ts +48 -3
- package/src/client.tsx +2 -0
- package/src/config.test.ts +6 -3
- package/src/engine.test.ts +40 -1
- package/src/engine.ts +56 -4
- package/src/index.ts +34 -44
- package/src/router.test.ts +218 -3
- package/src/router.ts +182 -14
- package/src/statistics.test.ts +116 -0
- package/src/statistics.ts +112 -0
package/lib/index.js
CHANGED
|
@@ -384,6 +384,51 @@ function blankStats() {
|
|
|
384
384
|
function unorderedPair(a, b) {
|
|
385
385
|
return a < b ? a + "," + b : b + "," + a;
|
|
386
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Deterministic A/B slot for one pivot-round pair, balanced by construction.
|
|
389
|
+
*
|
|
390
|
+
* The round used to emit `[candidate, pivot]` for every edge, so the ring leaders sat
|
|
391
|
+
* in trajectory B in all of their extra matches: with a judge that merely prefers slot A
|
|
392
|
+
* the pivots averaged 0.36 against 0.60 for everyone else and sank in the final ranking.
|
|
393
|
+
* Alternating on the sum of the two RANKS — not the raw indices: the pivot set is
|
|
394
|
+
* selected by score, so its indices can share a parity and a parity rule would then
|
|
395
|
+
* handicap half the field — gives every pivot and every candidate both slots within one
|
|
396
|
+
* edge of each other, without a second model call.
|
|
397
|
+
*
|
|
398
|
+
* Deliberately NOT in core.ts: `pivotRoundPairs` is compared against the upstream Python
|
|
399
|
+
* reference in `parity.test.ts`, so the orientation stays an orchestration-side decision.
|
|
400
|
+
* @param pair - one unordered pair from the pivot round.
|
|
401
|
+
* @param pivotRanks - pivot index → its rank among the pivots.
|
|
402
|
+
* @param nonPivotRanks - candidate index → its rank among the non-pivots.
|
|
403
|
+
* @returns The pair in the order it should be presented.
|
|
404
|
+
*/
|
|
405
|
+
function orientRoundPairs(pairs) {
|
|
406
|
+
const balance = /* @__PURE__ */ new Map();
|
|
407
|
+
return pairs.map(([a, b]) => {
|
|
408
|
+
const first = balance.get(a) ?? 0;
|
|
409
|
+
const [x, y] = (balance.get(b) ?? 0) < first ? [b, a] : [a, b];
|
|
410
|
+
balance.set(x, (balance.get(x) ?? 0) + 1);
|
|
411
|
+
balance.set(y, (balance.get(y) ?? 0) - 1);
|
|
412
|
+
return [x, y];
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Arbitrary but fixed A/B slot for the single pair of a two-candidate select.
|
|
417
|
+
*
|
|
418
|
+
* There is only one match, so nothing can cancel a preference; the mix at least stops
|
|
419
|
+
* candidate 0 from always sitting in slot A. Real comparisons go through `compare`,
|
|
420
|
+
* where `routedRepeats` runs an even number of rounds instead.
|
|
421
|
+
* @param a - first candidate index of the pair.
|
|
422
|
+
* @param b - second candidate index of the pair.
|
|
423
|
+
* @returns The pair in the order it should be presented.
|
|
424
|
+
*/
|
|
425
|
+
function orientPair(a, b) {
|
|
426
|
+
const lo = Math.min(a, b);
|
|
427
|
+
const hi = Math.max(a, b);
|
|
428
|
+
let mixed = Math.imul(lo + 1, 2654435761) ^ Math.imul(hi + 1, 2246822519);
|
|
429
|
+
mixed = Math.imul(mixed ^ mixed >>> 15, 625341585);
|
|
430
|
+
return ((mixed ^ mixed >>> 13) & 1) === 0 ? [a, b] : [b, a];
|
|
431
|
+
}
|
|
387
432
|
function judgeLabel(client) {
|
|
388
433
|
return client.label?.trim() || client.provider + "/" + client.model;
|
|
389
434
|
}
|
|
@@ -739,10 +784,11 @@ var VerifierEngine = class {
|
|
|
739
784
|
};
|
|
740
785
|
}
|
|
741
786
|
if (options.candidates.length === 2) {
|
|
742
|
-
const
|
|
787
|
+
const single = orientPair(0, 1);
|
|
788
|
+
const { rewards, judgeRewards, judgeOk, judgeErrors, judgeCalls, stats } = await this.scorePairs(options, [single], signal);
|
|
743
789
|
const wins = [0, 0];
|
|
744
790
|
const counts = [0, 0];
|
|
745
|
-
accumulatePairs([
|
|
791
|
+
accumulatePairs([single], rewards, wins, counts);
|
|
746
792
|
const ranked = rankScores(wins, counts);
|
|
747
793
|
const index = ranked[0].index;
|
|
748
794
|
const judges = this.clients.map((client, k) => {
|
|
@@ -787,7 +833,7 @@ var VerifierEngine = class {
|
|
|
787
833
|
accumulatePairs(ring, ringScores.rewards, firstWins, firstCounts);
|
|
788
834
|
const pivots = topPivots(firstWins, firstCounts, options.pivots ?? 2);
|
|
789
835
|
const ringPairs = new Set(ring.map((pair) => unorderedPair(pair[0], pair[1])));
|
|
790
|
-
const rounds = pivotRoundPairs(options.candidates.length, pivots).filter((pair) => !ringPairs.has(unorderedPair(pair[0], pair[1])));
|
|
836
|
+
const rounds = orientRoundPairs(pivotRoundPairs(options.candidates.length, pivots).filter((pair) => !ringPairs.has(unorderedPair(pair[0], pair[1]))));
|
|
791
837
|
const roundScores = await this.scorePairs(options, rounds, signal);
|
|
792
838
|
const allRewards = new Map([...ringScores.rewards, ...roundScores.rewards]);
|
|
793
839
|
const wins = new Array(options.candidates.length).fill(0);
|
|
@@ -1108,6 +1154,25 @@ function blockText$1(blocks) {
|
|
|
1108
1154
|
visit(blocks);
|
|
1109
1155
|
return parts.join("\n").trim();
|
|
1110
1156
|
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Text of one assistant turn, excluding reasoning blocks.
|
|
1159
|
+
*
|
|
1160
|
+
* Used only as the newest checkpoint's narration. Prose is not evidence, but for a
|
|
1161
|
+
* deliverable that lives in prose (a review, an analysis) it is the only thing that
|
|
1162
|
+
* describes the current state at all, so the judge receives it explicitly labelled
|
|
1163
|
+
* as a claim instead of being shown nothing about the deliverable.
|
|
1164
|
+
* @param blocks - content blocks of an `assistant/message` event.
|
|
1165
|
+
* @returns The joined text, trimmed.
|
|
1166
|
+
*/
|
|
1167
|
+
function narrativeText(blocks) {
|
|
1168
|
+
const parts = [];
|
|
1169
|
+
const visit = (items) => {
|
|
1170
|
+
for (const block of items) if (block.type === "text") parts.push(block.text);
|
|
1171
|
+
else if (block.type === "tool-result") visit(block.content);
|
|
1172
|
+
};
|
|
1173
|
+
visit(blocks);
|
|
1174
|
+
return parts.join("\n").trim();
|
|
1175
|
+
}
|
|
1111
1176
|
function successful(event) {
|
|
1112
1177
|
return event.data.error === void 0 && event.data.message.content.every((block) => block.isError !== true);
|
|
1113
1178
|
}
|
|
@@ -1130,6 +1195,8 @@ function buildEvidenceIndex(events) {
|
|
|
1130
1195
|
const teamTasks = /* @__PURE__ */ new Map();
|
|
1131
1196
|
const paired = /* @__PURE__ */ new Map();
|
|
1132
1197
|
const currentTeamTasks = /* @__PURE__ */ new Map();
|
|
1198
|
+
let narration;
|
|
1199
|
+
const dispatchedTools = /* @__PURE__ */ new Map();
|
|
1133
1200
|
for (const rawEvent of relevant) {
|
|
1134
1201
|
const event = rawEvent;
|
|
1135
1202
|
if (event.type === "tool/call") calls.set(String(event.data.callId), rawEvent);
|
|
@@ -1141,8 +1208,21 @@ function buildEvidenceIndex(events) {
|
|
|
1141
1208
|
currentTeamTasks.set(data.task.id, { ...data.task });
|
|
1142
1209
|
teamTasks.set(event.seq, [...currentTeamTasks.values()]);
|
|
1143
1210
|
}
|
|
1211
|
+
} else if (event.type === "assistant/message") {
|
|
1212
|
+
const blocks = event.data?.message?.content ?? event.data?.content;
|
|
1213
|
+
const text = narrativeText(Array.isArray(blocks) ? blocks : []);
|
|
1214
|
+
if (text) narration = {
|
|
1215
|
+
seq: event.seq,
|
|
1216
|
+
text
|
|
1217
|
+
};
|
|
1144
1218
|
} else if (event.type === "tool/ptc-dispatch" || event.type === "tool/code-dispatch") {
|
|
1145
1219
|
const data = event.data;
|
|
1220
|
+
const rootCallId = typeof data.rootCallId === "string" && data.rootCallId ? data.rootCallId : void 0;
|
|
1221
|
+
if (rootCallId) {
|
|
1222
|
+
const names = dispatchedTools.get(rootCallId) ?? /* @__PURE__ */ new Set();
|
|
1223
|
+
names.add(data.name);
|
|
1224
|
+
dispatchedTools.set(rootCallId, names);
|
|
1225
|
+
}
|
|
1146
1226
|
if (data.isError !== true && (!Array.isArray(data.content) || data.content.every((b) => b.isError !== true))) {
|
|
1147
1227
|
const subCallId = String(data.subCallId ?? "code:" + event.seq);
|
|
1148
1228
|
const content = Array.isArray(data.content) ? data.content : [];
|
|
@@ -1157,7 +1237,9 @@ function buildEvidenceIndex(events) {
|
|
|
1157
1237
|
}
|
|
1158
1238
|
for (const [callId, call] of calls) {
|
|
1159
1239
|
const result = results.get(callId);
|
|
1160
|
-
if (result)
|
|
1240
|
+
if (!result) continue;
|
|
1241
|
+
if (onlyBookkeepingDispatches(dispatchedTools.get(callId))) continue;
|
|
1242
|
+
paired.set(callId, {
|
|
1161
1243
|
name: call.data.name,
|
|
1162
1244
|
callSeq: call.seq,
|
|
1163
1245
|
resultSeq: result.seq,
|
|
@@ -1168,7 +1250,8 @@ function buildEvidenceIndex(events) {
|
|
|
1168
1250
|
problemSeq: taskStartSeq,
|
|
1169
1251
|
calls: paired,
|
|
1170
1252
|
todos,
|
|
1171
|
-
teamTasks
|
|
1253
|
+
teamTasks,
|
|
1254
|
+
narration
|
|
1172
1255
|
};
|
|
1173
1256
|
}
|
|
1174
1257
|
function parseTrustedWorkflow(value, callId, callSeq, resultSeq, maxCandidates, maxItemChars, maxInputChars) {
|
|
@@ -1225,23 +1308,68 @@ function canonicalTodoSnapshots(index) {
|
|
|
1225
1308
|
return values;
|
|
1226
1309
|
}
|
|
1227
1310
|
/**
|
|
1311
|
+
* Tools whose successful output only maintains the agent's own bookkeeping.
|
|
1312
|
+
*
|
|
1313
|
+
* A checkpoint already renders the todo/team snapshot these tools wrote, so their
|
|
1314
|
+
* own result repeats it while displacing the real work output that came just before
|
|
1315
|
+
* them.
|
|
1316
|
+
*
|
|
1317
|
+
* `present` belongs here for the same reason: declaring deliverables produces no
|
|
1318
|
+
* independent output, and it is always the LAST call of a turn. A live session ran
|
|
1319
|
+
* typecheck + the full suite, committed, then presented — and every stop boundary
|
|
1320
|
+
* showed the judge "Latest observed tool output at routing time (run_code):
|
|
1321
|
+
* presented: 8" instead of the verification run one call earlier. Following its own
|
|
1322
|
+
* rule ("a state without real verification should not exceed K"), the judge capped
|
|
1323
|
+
* the newest checkpoint at exactly K = 52.6% against a 0.8 threshold on four
|
|
1324
|
+
* consecutive routes, so each one steered "continue the unfinished work" for work
|
|
1325
|
+
* that was finished and verified. The `deliverables/presented` event still records
|
|
1326
|
+
* what was presented; the tool result adds nothing the judge can grade.
|
|
1327
|
+
*/
|
|
1328
|
+
const BOOKKEEPING_TOOLS = /* @__PURE__ */ new Set([
|
|
1329
|
+
"todo_write",
|
|
1330
|
+
"create_goal",
|
|
1331
|
+
"get_goal",
|
|
1332
|
+
"update_goal",
|
|
1333
|
+
"interrupt_agent",
|
|
1334
|
+
"list_agents",
|
|
1335
|
+
"exit_plan_mode",
|
|
1336
|
+
"skill",
|
|
1337
|
+
"present"
|
|
1338
|
+
]);
|
|
1339
|
+
/** Whether a wrapper's dispatches were all bookkeeping; undefined/empty means it dispatched nothing. */
|
|
1340
|
+
function onlyBookkeepingDispatches(names) {
|
|
1341
|
+
if (names === void 0 || names.size === 0) return false;
|
|
1342
|
+
for (const name of names) if (!BOOKKEEPING_TOOLS.has(name)) return false;
|
|
1343
|
+
return true;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1228
1346
|
* Observed tool evidence available at one checkpoint.
|
|
1229
1347
|
*
|
|
1230
1348
|
* A checkpoint rendered from todo/team text alone can never clear the progress
|
|
1231
1349
|
* threshold: the judge prompt explicitly refuses to credit a state that carries
|
|
1232
1350
|
* no observed output. The most recent successful tool result at or before the
|
|
1233
1351
|
* checkpoint is therefore attached as evidence.
|
|
1352
|
+
*
|
|
1353
|
+
* Bookkeeping tools are skipped: the checkpoint already renders the todo/team
|
|
1354
|
+
* snapshot they wrote, so their own result repeats it while displacing the real
|
|
1355
|
+
* output produced just before them (a live session attached `create_goal`,
|
|
1356
|
+
* `update_goal` and `interrupt_agent` output to checkpoints that had already run
|
|
1357
|
+
* the task's test suite).
|
|
1234
1358
|
* @param index - evidence index of the current task.
|
|
1235
|
-
* @param seq - checkpoint sequence number.
|
|
1359
|
+
* @param seq - checkpoint sequence number, or `Infinity` for the current state.
|
|
1236
1360
|
* @param budget - maximum characters the evidence may occupy.
|
|
1361
|
+
* @param current - render the newest output in the task instead of the newest one before `seq`.
|
|
1237
1362
|
* @returns Evidence block, or '' when the task produced none yet.
|
|
1238
1363
|
*/
|
|
1239
|
-
function checkpointEvidence(index, seq, budget) {
|
|
1364
|
+
function checkpointEvidence(index, seq, budget, current = false) {
|
|
1240
1365
|
if (budget < 64) return "";
|
|
1241
1366
|
let latest;
|
|
1242
|
-
for (const pair of index.calls.values())
|
|
1367
|
+
for (const pair of index.calls.values()) {
|
|
1368
|
+
if (BOOKKEEPING_TOOLS.has(pair.name) || !pair.text.trim()) continue;
|
|
1369
|
+
if (pair.resultSeq <= seq && (latest === void 0 || pair.resultSeq > latest.resultSeq)) latest = pair;
|
|
1370
|
+
}
|
|
1243
1371
|
if (latest === void 0) return "";
|
|
1244
|
-
const prefix = "\n\nLatest observed tool output before this checkpoint (" + latest.name + "):\n";
|
|
1372
|
+
const prefix = current ? "\n\nLatest observed tool output at routing time (" + latest.name + "):\n" : "\n\nLatest observed tool output before this checkpoint (" + latest.name + "):\n";
|
|
1245
1373
|
if (prefix.length >= budget) return "";
|
|
1246
1374
|
return prefix + sanitizeVerifierText(latest.text, budget - prefix.length);
|
|
1247
1375
|
}
|
|
@@ -1276,6 +1404,12 @@ const MAX_ROUTED_CHECKPOINTS = 6;
|
|
|
1276
1404
|
* The result fits both caps by construction: each step is at most min(maxItemChars,
|
|
1277
1405
|
* maxInputChars / kept.length) characters, so the combined length can never exceed
|
|
1278
1406
|
* maxInputChars and boundDecision() no longer drops the whole route.
|
|
1407
|
+
*
|
|
1408
|
+
* The newest checkpoint is also the state the route is judging, so it is rendered as
|
|
1409
|
+
* the CURRENT state: its evidence is the newest observed output in the task rather
|
|
1410
|
+
* than the newest one before the last todo snapshot (which is often several tool calls
|
|
1411
|
+
* stale), and it carries the agent's latest prose as an explicitly labelled claim (see
|
|
1412
|
+
* {@link currentNarration}), because prose deliverables never reach a tool.
|
|
1279
1413
|
* @param index - evidence index of the current task.
|
|
1280
1414
|
* @param sources - checkpoints in chronological order.
|
|
1281
1415
|
* @param maxItemChars - hard per-item cap enforced by boundDecision().
|
|
@@ -1295,13 +1429,35 @@ function renderCheckpointSteps(index, sources, maxItemChars, maxInputChars) {
|
|
|
1295
1429
|
return {
|
|
1296
1430
|
steps: kept.map((source, position) => {
|
|
1297
1431
|
const note = position === 0 && omitted > 0 ? "Earlier " + omitted + " checkpoint(s) omitted; showing the " + kept.length + " most recent.\n" : "";
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1432
|
+
const isCurrent = position === kept.length - 1;
|
|
1433
|
+
const observed = checkpointEvidence(index, isCurrent ? Number.POSITIVE_INFINITY : source.seq, isCurrent ? Math.floor(evidenceBudget / 2) : evidenceBudget, isCurrent);
|
|
1434
|
+
const narration = isCurrent ? currentNarration(index, evidenceBudget - observed.length) : "";
|
|
1435
|
+
return sanitizeVerifierText(note + source.label + source.body, Math.max(1, stepCap - observed.length - narration.length)) + observed + narration;
|
|
1300
1436
|
}),
|
|
1301
1437
|
evidenceSeqs: kept.map((source) => source.seq),
|
|
1302
1438
|
omitted
|
|
1303
1439
|
};
|
|
1304
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* The agent's newest unverified prose, attached to the current checkpoint.
|
|
1443
|
+
*
|
|
1444
|
+
* Progress checkpoints are graded against "would this state satisfy the task", and
|
|
1445
|
+
* for work whose deliverable is prose the todo snapshot plus one tool result say
|
|
1446
|
+
* nothing about it: every checkpoint then scores "certainly NO" even though the
|
|
1447
|
+
* deliverable exists (a live review task scored 0% on all four checkpoints while the
|
|
1448
|
+
* final session acceptance of the same work passed). The block is labelled as a claim
|
|
1449
|
+
* so the judge can weigh it without treating it as observed output.
|
|
1450
|
+
* @param index - evidence index of the current task.
|
|
1451
|
+
* @param budget - maximum characters the narration may occupy.
|
|
1452
|
+
* @returns Narration block, or '' when the task produced no prose or has no budget.
|
|
1453
|
+
*/
|
|
1454
|
+
function currentNarration(index, budget) {
|
|
1455
|
+
const narration = index.narration;
|
|
1456
|
+
if (narration === void 0 || budget < 64) return "";
|
|
1457
|
+
const prefix = "\n\nNewest agent narration in this task (the agent's own claim — NOT observed evidence):\n";
|
|
1458
|
+
if (87 >= budget) return "";
|
|
1459
|
+
return prefix + sanitizeVerifierText(narration.text, budget - 87);
|
|
1460
|
+
}
|
|
1305
1461
|
function canonicalTeamTaskSnapshots(index) {
|
|
1306
1462
|
const values = [];
|
|
1307
1463
|
let previous = "";
|
|
@@ -1368,7 +1524,8 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
|
|
|
1368
1524
|
reason: "changed durable todo snapshots",
|
|
1369
1525
|
fingerprint: stableHash({
|
|
1370
1526
|
kind: "track",
|
|
1371
|
-
snapshots
|
|
1527
|
+
snapshots,
|
|
1528
|
+
steps: rendered.steps
|
|
1372
1529
|
}),
|
|
1373
1530
|
steps: rendered.steps,
|
|
1374
1531
|
checkpoints: rendered.steps.map((_, i) => i + 1),
|
|
@@ -1389,7 +1546,8 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
|
|
|
1389
1546
|
reason: "changed durable team tasks",
|
|
1390
1547
|
fingerprint: stableHash({
|
|
1391
1548
|
kind: "track",
|
|
1392
|
-
teamSnapshots
|
|
1549
|
+
teamSnapshots,
|
|
1550
|
+
steps: rendered.steps
|
|
1393
1551
|
}),
|
|
1394
1552
|
steps: rendered.steps,
|
|
1395
1553
|
checkpoints: rendered.steps.map((_, i) => i + 1),
|
|
@@ -1398,12 +1556,35 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
|
|
|
1398
1556
|
}
|
|
1399
1557
|
}
|
|
1400
1558
|
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Artifacts that only the classifier can turn into candidates.
|
|
1561
|
+
*
|
|
1562
|
+
* Todo and team snapshots are deliberately absent. They are the structured track
|
|
1563
|
+
* route's own input, and that route runs first — so listing them here could only make
|
|
1564
|
+
* the hint true in shapes the structured pass already claimed (or in a task that
|
|
1565
|
+
* already ran an explicit `verifier_track`, where re-classifying the same snapshots is
|
|
1566
|
+
* not wanted). The one shape left out is a snapshot series whose lists are all shorter
|
|
1567
|
+
* than two items, which is not worth a classification call.
|
|
1568
|
+
*/
|
|
1569
|
+
const HINT_ARTIFACTS = /* @__PURE__ */ new Set([
|
|
1570
|
+
"subagent",
|
|
1571
|
+
"subagent_fork",
|
|
1572
|
+
"workflow",
|
|
1573
|
+
"exit_plan_mode"
|
|
1574
|
+
]);
|
|
1575
|
+
/**
|
|
1576
|
+
* Whether a smart-mode stop boundary is worth a semantic classification call.
|
|
1577
|
+
*
|
|
1578
|
+
* The semantic phase only runs when the structured pass produced nothing, so this
|
|
1579
|
+
* answers "is there material the structured pass never consumes?" — never "are there
|
|
1580
|
+
* todo/team snapshots?", which the structured pass would have used already.
|
|
1581
|
+
* @param events - Session event log.
|
|
1582
|
+
* @returns True when a subagent/workflow/plan artifact exists.
|
|
1583
|
+
*/
|
|
1401
1584
|
function semanticRouteHint(events) {
|
|
1402
1585
|
const index = buildEvidenceIndex(events);
|
|
1403
1586
|
if (!index) return false;
|
|
1404
|
-
|
|
1405
|
-
if (canonicalTodoSnapshots(index).length >= 2) return true;
|
|
1406
|
-
if (canonicalTeamTaskSnapshots(index).length >= 2) return true;
|
|
1587
|
+
for (const pair of index.calls.values()) if (HINT_ARTIFACTS.has(pair.name)) return true;
|
|
1407
1588
|
return false;
|
|
1408
1589
|
}
|
|
1409
1590
|
function buildSemanticRoutePrompt(problem, events, maxCandidates, maxItemChars = 2e4, maxInputChars = 6e4) {
|
|
@@ -1413,6 +1594,7 @@ function buildSemanticRoutePrompt(problem, events, maxCandidates, maxItemChars =
|
|
|
1413
1594
|
let used = 0;
|
|
1414
1595
|
let omitted = 0;
|
|
1415
1596
|
for (const [callId, pair] of [...index.calls.entries()].reverse()) {
|
|
1597
|
+
if (BOOKKEEPING_TOOLS.has(pair.name)) continue;
|
|
1416
1598
|
const text = sanitizeVerifierText(pair.text, maxItemChars);
|
|
1417
1599
|
if (artifacts.length > 0 && used + text.length > maxInputChars) {
|
|
1418
1600
|
omitted += 1;
|
|
@@ -1513,7 +1695,8 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
|
|
|
1513
1695
|
const perItem = itemBudget(output.candidateCallIds.length, maxItemChars, maxInputChars);
|
|
1514
1696
|
const candidates = output.candidateCallIds.map((callId, i) => {
|
|
1515
1697
|
const pair = index.calls.get(callId);
|
|
1516
|
-
|
|
1698
|
+
if (!pair || BOOKKEEPING_TOOLS.has(pair.name)) return void 0;
|
|
1699
|
+
return {
|
|
1517
1700
|
id: callId,
|
|
1518
1701
|
groupId: "semantic",
|
|
1519
1702
|
label: pair.name + " " + (i + 1),
|
|
@@ -1521,7 +1704,7 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
|
|
|
1521
1704
|
callId,
|
|
1522
1705
|
fromSeq: pair.callSeq,
|
|
1523
1706
|
toSeq: pair.resultSeq
|
|
1524
|
-
}
|
|
1707
|
+
};
|
|
1525
1708
|
}).filter((candidate) => candidate !== void 0);
|
|
1526
1709
|
if (candidates.length !== output.candidateCallIds.length) return void 0;
|
|
1527
1710
|
const fingerprint = stableHash({
|
|
@@ -1556,6 +1739,26 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
|
|
|
1556
1739
|
* @param criteriaCount - number of criteria evaluated per comparison.
|
|
1557
1740
|
* @returns The planned model-call count, never below 1.
|
|
1558
1741
|
*/
|
|
1742
|
+
/**
|
|
1743
|
+
* Scoring repeats a routed decision actually runs.
|
|
1744
|
+
*
|
|
1745
|
+
* `compare` judges ONE unordered pair, and `VerifierEngine.compare` only swaps the
|
|
1746
|
+
* candidates on odd repeat indices — with the shipped default of a single round the
|
|
1747
|
+
* first candidate therefore always sat in slot A, so the winner was partly decided by
|
|
1748
|
+
* listing order. Rounding its count up to an even number averages a swapped round and
|
|
1749
|
+
* cancels that.
|
|
1750
|
+
*
|
|
1751
|
+
* `select` does not need it: its ring is symmetric by construction and the pivot round
|
|
1752
|
+
* is oriented per pair by the engine, so one round is already unbiased. `track` scores
|
|
1753
|
+
* a single checkpoint list and has no slots at all.
|
|
1754
|
+
* @param decision - the routed decision about to run.
|
|
1755
|
+
* @param configured - the configured auto-route repeat count.
|
|
1756
|
+
* @returns The repeat count to pass to the engine.
|
|
1757
|
+
*/
|
|
1758
|
+
function routedRepeats(decision, configured) {
|
|
1759
|
+
if (decision.kind !== "compare") return configured;
|
|
1760
|
+
return configured % 2 === 0 ? configured : configured + 1;
|
|
1761
|
+
}
|
|
1559
1762
|
function estimateRoutedCalls(decision, repeats, criteriaCount) {
|
|
1560
1763
|
if (decision.kind === "compare") return Math.max(1, criteriaCount * repeats);
|
|
1561
1764
|
if (decision.kind === "track") return Math.max(1, repeats);
|
|
@@ -1754,7 +1957,23 @@ function parseSessionVerdict(value) {
|
|
|
1754
1957
|
if (start < 0 || end <= start) return void 0;
|
|
1755
1958
|
try {
|
|
1756
1959
|
const parsed = JSON.parse(text.slice(start, end + 1));
|
|
1757
|
-
|
|
1960
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1961
|
+
const row = parsed;
|
|
1962
|
+
const criteria = [];
|
|
1963
|
+
for (const entry of Array.isArray(row.criteria) ? row.criteria : []) {
|
|
1964
|
+
const item = typeof entry === "object" && entry !== null ? entry : {};
|
|
1965
|
+
if (typeof item.id !== "string" || typeof item.scoreA !== "number" || !Number.isFinite(item.scoreA)) continue;
|
|
1966
|
+
criteria.push({
|
|
1967
|
+
id: item.id,
|
|
1968
|
+
...typeof item.name === "string" ? { name: item.name } : {},
|
|
1969
|
+
score: item.scoreA
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
return {
|
|
1973
|
+
winner: row.winner,
|
|
1974
|
+
score: row.score,
|
|
1975
|
+
criteria
|
|
1976
|
+
};
|
|
1758
1977
|
} catch {
|
|
1759
1978
|
return;
|
|
1760
1979
|
}
|
|
@@ -1810,7 +2029,8 @@ function analyzeAutoTask(events, policy) {
|
|
|
1810
2029
|
manualVerdicts.push({
|
|
1811
2030
|
seq: result.seq,
|
|
1812
2031
|
winner: verdict?.winner,
|
|
1813
|
-
score: verdict?.score
|
|
2032
|
+
score: verdict?.score,
|
|
2033
|
+
criteria: verdict?.criteria
|
|
1814
2034
|
});
|
|
1815
2035
|
}
|
|
1816
2036
|
for (const entry of successfulDispatches) {
|
|
@@ -1819,11 +2039,16 @@ function analyzeAutoTask(events, policy) {
|
|
|
1819
2039
|
manualVerdicts.push({
|
|
1820
2040
|
seq: entry.seq,
|
|
1821
2041
|
winner: verdict?.winner,
|
|
1822
|
-
score: verdict?.score
|
|
2042
|
+
score: verdict?.score,
|
|
2043
|
+
criteria: verdict?.criteria
|
|
1823
2044
|
});
|
|
1824
2045
|
}
|
|
1825
2046
|
const hasManualSessionVerification = manualVerdicts.length > 0;
|
|
1826
|
-
const manualVerificationAccepted = manualVerdicts.some((verdict) => verdict.winner === "A" && typeof verdict.score === "number" &&
|
|
2047
|
+
const manualVerificationAccepted = manualVerdicts.some((verdict) => verdict.winner === "A" && typeof verdict.score === "number" && sessionAccepted({
|
|
2048
|
+
score: verdict.score,
|
|
2049
|
+
winner: "A",
|
|
2050
|
+
criteria: verdict.criteria
|
|
2051
|
+
}, policy.threshold) && !stale(verdict.seq));
|
|
1827
2052
|
if (policy.mode === "manual") return {
|
|
1828
2053
|
taskStartSeq,
|
|
1829
2054
|
toolCalls,
|
|
@@ -1886,49 +2111,40 @@ function analyzeAutoTask(events, policy) {
|
|
|
1886
2111
|
};
|
|
1887
2112
|
}
|
|
1888
2113
|
/**
|
|
1889
|
-
*
|
|
2114
|
+
* Criteria that do not clear the acceptance threshold.
|
|
1890
2115
|
*
|
|
1891
|
-
* The
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
2116
|
+
* The acceptance score is the MEAN over criteria, so a session with one requirement at
|
|
2117
|
+
* zero and the others perfect averaged ~0.67 and cleared the 0.65 default: a single
|
|
2118
|
+
* failed requirement was arithmetically invisible. The gate therefore also requires
|
|
2119
|
+
* every criterion to clear the threshold on its own.
|
|
2120
|
+
* @param criteria - per-criterion A-side scores, when the judge reported them.
|
|
2121
|
+
* @param threshold - acceptance threshold.
|
|
2122
|
+
* @returns The failing criteria, in report order.
|
|
1895
2123
|
*/
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
return false;
|
|
1916
|
-
}
|
|
1917
|
-
state.taskAttempts += 1;
|
|
1918
|
-
state.sessionAttempts += 1;
|
|
1919
|
-
state.lastEvaluatedSeq = lastSeq;
|
|
1920
|
-
this.states.set(id, state);
|
|
1921
|
-
return true;
|
|
1922
|
-
}
|
|
1923
|
-
release(agent) {
|
|
1924
|
-
this.states.delete(String(agent.id));
|
|
1925
|
-
}
|
|
1926
|
-
};
|
|
1927
|
-
function automaticFeedback(score, baselineScore, winner, threshold) {
|
|
2124
|
+
function failedAcceptanceCriteria(criteria, threshold) {
|
|
2125
|
+
return (criteria ?? []).filter((criterion) => !(criterion.score >= threshold));
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Whether a session acceptance clears the gate.
|
|
2129
|
+
*
|
|
2130
|
+
* The empty-work baseline is a fixed sentence that always scores 0, so the comparison
|
|
2131
|
+
* itself is decorative; what actually decides is the session's own score, the winner
|
|
2132
|
+
* against that baseline, and (since the mean could hide a failed requirement) every
|
|
2133
|
+
* criterion clearing the threshold.
|
|
2134
|
+
* @param evidence - session acceptance result (score, winner and per-criterion scores).
|
|
2135
|
+
* @param threshold - acceptance threshold.
|
|
2136
|
+
* @returns True only when the session may conclude.
|
|
2137
|
+
*/
|
|
2138
|
+
function sessionAccepted(evidence, threshold) {
|
|
2139
|
+
if (evidence.winner !== "A" || !(evidence.score >= threshold)) return false;
|
|
2140
|
+
return failedAcceptanceCriteria(evidence.criteria, threshold).length === 0;
|
|
2141
|
+
}
|
|
2142
|
+
function automaticFeedback(score, baselineScore, winner, threshold, failedCriteria = []) {
|
|
1928
2143
|
const percent = (value) => (value * 100).toFixed(1) + "%";
|
|
1929
2144
|
return [
|
|
1930
2145
|
"[Automatic verifier gate]",
|
|
1931
2146
|
`The independent verifier did not clear this task for completion: evidence score ${percent(score)}, baseline ${percent(baselineScore)}, verdict ${winner}, required ${percent(threshold)}.`,
|
|
2147
|
+
...failedCriteria.length > 0 ? ["Criteria below the threshold: " + failedCriteria.map((criterion) => (criterion.name ?? criterion.id) + " " + percent(criterion.score)).join("; ") + "."] : [],
|
|
1932
2148
|
"Re-open the task requirements, inspect the actual tool outputs for unresolved errors or missing proof, make any necessary corrections, and run a directly relevant verification command before concluding. Do not merely restate that the task is complete."
|
|
1933
2149
|
].join("\n");
|
|
1934
2150
|
}
|
|
@@ -2069,12 +2285,100 @@ const VERIFIER_TOOL_NAMES = [
|
|
|
2069
2285
|
"verifier_track",
|
|
2070
2286
|
"verifier_current_session"
|
|
2071
2287
|
];
|
|
2288
|
+
/** Upper bound on the stored checkpoint progression; one explicit call can legitimately carry 32 steps. */
|
|
2289
|
+
const MAX_VERDICT_SCORES = 64;
|
|
2290
|
+
/** Upper bound on the stored per-criterion breakdown; the default rubric has three criteria. */
|
|
2291
|
+
const MAX_VERDICT_CRITERIA = 16;
|
|
2292
|
+
/**
|
|
2293
|
+
* Compact summary of what the judges decided, stored beside the call counters so
|
|
2294
|
+
* the dashboard can answer "why did this fail?" instead of only "how much did it cost".
|
|
2295
|
+
*
|
|
2296
|
+
* Pure on purpose: this mapping used to live inside the plugin closure, where the two
|
|
2297
|
+
* bugs it carried (a track verdict reported as the historical minimum, a comparison
|
|
2298
|
+
* reported as the loser's score under a threshold it never used) could not be tested.
|
|
2299
|
+
* @param toolName - the verifier tool that produced the value.
|
|
2300
|
+
* @param value - its rendered result.
|
|
2301
|
+
* @param phase - which stage produced it (explicit | compare | select | track | final | ...).
|
|
2302
|
+
* @param thresholds - resolved acceptance thresholds.
|
|
2303
|
+
* @returns A verdict summary; score fields are omitted when the tool has none.
|
|
2304
|
+
*/
|
|
2305
|
+
function summarizeVerdict(toolName, value, phase, thresholds) {
|
|
2306
|
+
const row = typeof value === "object" && value !== null ? value : {};
|
|
2307
|
+
const numberAt = (key) => typeof row[key] === "number" && Number.isFinite(row[key]) ? row[key] : void 0;
|
|
2308
|
+
const scores = Array.isArray(row.scores) ? row.scores.filter((entry) => typeof entry === "number" && Number.isFinite(entry)) : [];
|
|
2309
|
+
const winner = row.winner === "A" || row.winner === "B" || row.winner === "tie" ? row.winner : void 0;
|
|
2310
|
+
if (toolName === "verifier_route_classify") return {
|
|
2311
|
+
phase,
|
|
2312
|
+
outcome: "classified"
|
|
2313
|
+
};
|
|
2314
|
+
if (toolName === "verifier_select") {
|
|
2315
|
+
const index = numberAt("index");
|
|
2316
|
+
const best = index === void 0 ? void 0 : scores[index];
|
|
2317
|
+
return {
|
|
2318
|
+
phase,
|
|
2319
|
+
outcome: "ranked",
|
|
2320
|
+
...best !== void 0 ? { score: best } : {}
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
if (toolName === "verifier_track") {
|
|
2324
|
+
const latest = scores.length > 0 ? scores[scores.length - 1] : void 0;
|
|
2325
|
+
const threshold = thresholds.autoTrackCompletionThreshold;
|
|
2326
|
+
return {
|
|
2327
|
+
phase,
|
|
2328
|
+
outcome: latest !== void 0 && latest >= threshold ? "passed" : "below-threshold",
|
|
2329
|
+
...latest !== void 0 ? { score: latest } : {},
|
|
2330
|
+
...scores.length > 1 ? { scores: [...scores] } : {},
|
|
2331
|
+
threshold
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
if (toolName === "verifier_compare") {
|
|
2335
|
+
const scoreA = numberAt("score") ?? numberAt("scoreA");
|
|
2336
|
+
const scoreB = numberAt("scoreB");
|
|
2337
|
+
const score = winner === "B" ? scoreB : scoreA;
|
|
2338
|
+
return {
|
|
2339
|
+
phase,
|
|
2340
|
+
outcome: winner === "tie" ? "tie" : "compared",
|
|
2341
|
+
...score !== void 0 ? { score } : {},
|
|
2342
|
+
...scoreB !== void 0 ? { scoreB } : {},
|
|
2343
|
+
...winner !== void 0 ? { winner } : {}
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
const score = numberAt("score") ?? numberAt("scoreA");
|
|
2347
|
+
const baselineScore = numberAt("baselineScore");
|
|
2348
|
+
const threshold = thresholds.autoVerifyThreshold;
|
|
2349
|
+
const criteria = Array.isArray(row.criteria) ? row.criteria.map((entry) => typeof entry === "object" && entry !== null ? entry : {}).filter((entry) => typeof entry.id === "string" && typeof entry.scoreA === "number" && Number.isFinite(entry.scoreA)).slice(0, MAX_VERDICT_CRITERIA).map((entry) => ({
|
|
2350
|
+
id: entry.id,
|
|
2351
|
+
score: entry.scoreA
|
|
2352
|
+
})) : [];
|
|
2353
|
+
const belowThreshold = criteria.filter((entry) => !(entry.score >= threshold));
|
|
2354
|
+
return {
|
|
2355
|
+
phase,
|
|
2356
|
+
outcome: winner === "tie" ? "tie" : winner === "A" && score !== void 0 && score >= threshold && belowThreshold.length === 0 ? "passed" : "below-threshold",
|
|
2357
|
+
...score !== void 0 ? { score } : {},
|
|
2358
|
+
...baselineScore !== void 0 ? { baselineScore } : {},
|
|
2359
|
+
...criteria.length > 0 ? { criteria } : {},
|
|
2360
|
+
...winner !== void 0 ? { winner } : {},
|
|
2361
|
+
threshold
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2072
2364
|
function cleanVerdict(input) {
|
|
2073
2365
|
if (typeof input !== "object" || input === null || Array.isArray(input)) return void 0;
|
|
2074
2366
|
const verdict = {};
|
|
2075
2367
|
if (typeof input.phase === "string") verdict.phase = input.phase;
|
|
2076
2368
|
if (typeof input.outcome === "string") verdict.outcome = input.outcome;
|
|
2077
2369
|
if (typeof input.score === "number" && Number.isFinite(input.score)) verdict.score = input.score;
|
|
2370
|
+
if (Array.isArray(input.scores)) {
|
|
2371
|
+
const scores = input.scores.filter((entry) => typeof entry === "number" && Number.isFinite(entry)).slice(0, MAX_VERDICT_SCORES);
|
|
2372
|
+
if (scores.length > 0) verdict.scores = scores;
|
|
2373
|
+
}
|
|
2374
|
+
if (typeof input.scoreB === "number" && Number.isFinite(input.scoreB)) verdict.scoreB = input.scoreB;
|
|
2375
|
+
if (Array.isArray(input.criteria)) {
|
|
2376
|
+
const criteria = input.criteria.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string" && typeof entry.score === "number" && Number.isFinite(entry.score)).slice(0, MAX_VERDICT_CRITERIA).map((entry) => ({
|
|
2377
|
+
id: entry.id,
|
|
2378
|
+
score: entry.score
|
|
2379
|
+
}));
|
|
2380
|
+
if (criteria.length > 0) verdict.criteria = criteria;
|
|
2381
|
+
}
|
|
2078
2382
|
if (typeof input.baselineScore === "number" && Number.isFinite(input.baselineScore)) verdict.baselineScore = input.baselineScore;
|
|
2079
2383
|
if (input.winner === "A" || input.winner === "B" || input.winner === "tie") verdict.winner = input.winner;
|
|
2080
2384
|
if (typeof input.threshold === "number" && Number.isFinite(input.threshold)) verdict.threshold = input.threshold;
|
|
@@ -2842,54 +3146,12 @@ function apply(ctx, config = {}) {
|
|
|
2842
3146
|
const requireEnabled = () => {
|
|
2843
3147
|
if (!current().enabled) throw new Error("llm-verifier: verifier tools are disabled — enable them in Settings → LLM Verifier");
|
|
2844
3148
|
};
|
|
2845
|
-
/**
|
|
2846
|
-
* Compact summary of what the judges decided, stored beside the call counters so
|
|
2847
|
-
* the dashboard can answer "why did this fail?" instead of only "how much did it cost".
|
|
2848
|
-
* @param toolName - the verifier tool that produced the value.
|
|
2849
|
-
* @param value - its rendered result.
|
|
2850
|
-
* @param phase - which stage produced it (explicit | compare | select | track | final | ...).
|
|
2851
|
-
* @returns A verdict summary; score fields are omitted when the tool has none.
|
|
2852
|
-
*/
|
|
2853
3149
|
const verdictFrom = (toolName, value, phase) => {
|
|
2854
3150
|
const selected = current();
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
if (toolName === "verifier_route_classify") return {
|
|
2860
|
-
phase,
|
|
2861
|
-
outcome: "classified"
|
|
2862
|
-
};
|
|
2863
|
-
if (toolName === "verifier_select") {
|
|
2864
|
-
const index = numberAt("index");
|
|
2865
|
-
const best = index === void 0 ? void 0 : scores[index];
|
|
2866
|
-
return {
|
|
2867
|
-
phase,
|
|
2868
|
-
outcome: "ranked",
|
|
2869
|
-
...best !== void 0 ? { score: best } : {}
|
|
2870
|
-
};
|
|
2871
|
-
}
|
|
2872
|
-
if (toolName === "verifier_track") {
|
|
2873
|
-
const worst = scores.length > 0 ? Math.min(...scores) : void 0;
|
|
2874
|
-
const threshold = selected.autoTrackCompletionThreshold;
|
|
2875
|
-
return {
|
|
2876
|
-
phase,
|
|
2877
|
-
outcome: worst !== void 0 && worst >= threshold ? "passed" : "below-threshold",
|
|
2878
|
-
...worst !== void 0 ? { score: worst } : {},
|
|
2879
|
-
threshold
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
|
-
const score = numberAt("score") ?? numberAt("scoreA");
|
|
2883
|
-
const baselineScore = numberAt("baselineScore");
|
|
2884
|
-
const threshold = selected.autoVerifyThreshold;
|
|
2885
|
-
return {
|
|
2886
|
-
phase,
|
|
2887
|
-
outcome: winner === "tie" ? "tie" : winner === "A" && score !== void 0 && score >= threshold ? "passed" : "below-threshold",
|
|
2888
|
-
...score !== void 0 ? { score } : {},
|
|
2889
|
-
...baselineScore !== void 0 ? { baselineScore } : {},
|
|
2890
|
-
...winner !== void 0 ? { winner } : {},
|
|
2891
|
-
threshold
|
|
2892
|
-
};
|
|
3151
|
+
return summarizeVerdict(toolName, value, phase, {
|
|
3152
|
+
autoVerifyThreshold: selected.autoVerifyThreshold,
|
|
3153
|
+
autoTrackCompletionThreshold: selected.autoTrackCompletionThreshold
|
|
3154
|
+
});
|
|
2893
3155
|
};
|
|
2894
3156
|
const record = async (toolName, agent, operation, phase = "explicit") => {
|
|
2895
3157
|
const startedAt = Date.now();
|
|
@@ -2996,6 +3258,11 @@ function apply(ctx, config = {}) {
|
|
|
2996
3258
|
score: compared.scoreA,
|
|
2997
3259
|
baselineScore: compared.scoreB,
|
|
2998
3260
|
winner: compared.winner,
|
|
3261
|
+
criteria: compared.criteria.map((row) => ({
|
|
3262
|
+
id: row.id,
|
|
3263
|
+
name: row.name,
|
|
3264
|
+
score: row.scoreA
|
|
3265
|
+
})),
|
|
2999
3266
|
fromSeq: extracted.fromSeq,
|
|
3000
3267
|
toSeq: extracted.toSeq,
|
|
3001
3268
|
omittedCharacters: extracted.omittedCharacters,
|
|
@@ -3235,8 +3502,9 @@ function apply(ctx, config = {}) {
|
|
|
3235
3502
|
}
|
|
3236
3503
|
const verdict = parseVerdictLetter(classified.text);
|
|
3237
3504
|
if (verdict === void 0) {
|
|
3238
|
-
|
|
3239
|
-
|
|
3505
|
+
const strict = selected.autoVerifyMode === "strict";
|
|
3506
|
+
autoRouter.fail(agent, taskReservation, strict);
|
|
3507
|
+
ctx.logger.warn("llm-verifier team task verification produced no verdict line; task " + task.id + (strict ? " stays blocked" : " was not gated"));
|
|
3240
3508
|
continue;
|
|
3241
3509
|
}
|
|
3242
3510
|
if (verdict.score >= selected.autoVerifyThreshold) {
|
|
@@ -3330,7 +3598,8 @@ function apply(ctx, config = {}) {
|
|
|
3330
3598
|
}
|
|
3331
3599
|
if (!stillCurrent()) return;
|
|
3332
3600
|
if (decision) {
|
|
3333
|
-
const
|
|
3601
|
+
const repeats = routedRepeats(decision, selected.autoVerifyRepeats);
|
|
3602
|
+
const expectedCalls = estimateRoutedCalls(decision, repeats, DEFAULT_CRITERIA.length) * selected.judges.length;
|
|
3334
3603
|
const reservation = autoRouter.reserve(agent, decision.kind, decision.fingerprint, expectedCalls, policy);
|
|
3335
3604
|
if (reservation === void 0) {
|
|
3336
3605
|
const exhausted = autoRouter.budgetExhausted(agent, expectedCalls, policy);
|
|
@@ -3354,7 +3623,7 @@ function apply(ctx, config = {}) {
|
|
|
3354
3623
|
if (reservation) try {
|
|
3355
3624
|
const extracted = await extractTask(agent, evidence.taskStartSeq, admittedLastSeq, selected.autoVerifyMaxChars, signal);
|
|
3356
3625
|
if (decision.kind === "compare") {
|
|
3357
|
-
const result = await compareCandidates(agent, extracted.problem, decision.candidates[0].content, decision.candidates[1].content,
|
|
3626
|
+
const result = await compareCandidates(agent, extracted.problem, decision.candidates[0].content, decision.candidates[1].content, repeats, signal, extracted.images, "compare");
|
|
3358
3627
|
if (!stillCurrent()) {
|
|
3359
3628
|
autoRouter.fail(agent, reservation, false);
|
|
3360
3629
|
return;
|
|
@@ -3365,7 +3634,7 @@ function apply(ctx, config = {}) {
|
|
|
3365
3634
|
return;
|
|
3366
3635
|
}
|
|
3367
3636
|
if (decision.kind === "select") {
|
|
3368
|
-
const result = await selectCandidates(agent, extracted.problem, decision.candidates.map((candidate) => candidate.content),
|
|
3637
|
+
const result = await selectCandidates(agent, extracted.problem, decision.candidates.map((candidate) => candidate.content), repeats, signal, extracted.images, "select");
|
|
3369
3638
|
if (!stillCurrent()) {
|
|
3370
3639
|
autoRouter.fail(agent, reservation, false);
|
|
3371
3640
|
return;
|
|
@@ -3375,14 +3644,14 @@ function apply(ctx, config = {}) {
|
|
|
3375
3644
|
agent.steer(routeFeedback(decision, "Ranking:\n" + ranking + "\nProceed with " + decision.candidates[result.index].label + "."));
|
|
3376
3645
|
return;
|
|
3377
3646
|
}
|
|
3378
|
-
const result = await trackProgress(agent, extracted.problem, decision.steps, decision.checkpoints,
|
|
3647
|
+
const result = await trackProgress(agent, extracted.problem, decision.steps, decision.checkpoints, repeats, signal, extracted.images, "track");
|
|
3379
3648
|
if (!stillCurrent()) {
|
|
3380
3649
|
autoRouter.fail(agent, reservation, false);
|
|
3381
3650
|
return;
|
|
3382
3651
|
}
|
|
3383
3652
|
if (!autoRouter.commit(agent, reservation, admittedLastSeq)) return;
|
|
3384
3653
|
const detail = result.scores.map((score, index) => "Checkpoint step " + decision.checkpoints[index] + ": " + (score * 100).toFixed(1) + "%").join("\n");
|
|
3385
|
-
const continuation = result.scores.
|
|
3654
|
+
const continuation = (result.scores.length > 0 ? result.scores[result.scores.length - 1] : 0) < selected.autoTrackCompletionThreshold ? "\nContinue the unfinished work." : "\nPrepare final delivery evidence; final session verification is mandatory.";
|
|
3386
3655
|
agent.steer(routeFeedback(decision, detail + continuation));
|
|
3387
3656
|
return;
|
|
3388
3657
|
} catch (error) {
|
|
@@ -3449,13 +3718,18 @@ function apply(ctx, config = {}) {
|
|
|
3449
3718
|
autoRouter.fail(agent, finalReservation, false);
|
|
3450
3719
|
return;
|
|
3451
3720
|
}
|
|
3452
|
-
|
|
3721
|
+
const failed = failedAcceptanceCriteria(result.criteria, selected.autoVerifyThreshold);
|
|
3722
|
+
if (sessionAccepted({
|
|
3723
|
+
score: result.score,
|
|
3724
|
+
winner: result.winner,
|
|
3725
|
+
criteria: result.criteria
|
|
3726
|
+
}, selected.autoVerifyThreshold)) autoRouter.commit(agent, finalReservation);
|
|
3453
3727
|
else {
|
|
3454
3728
|
autoRouter.fail(agent, finalReservation, selected.autoVerifyMode === "strict");
|
|
3455
3729
|
agent.steer(createUserMessage({
|
|
3456
3730
|
content: [{
|
|
3457
3731
|
type: "text",
|
|
3458
|
-
text: automaticFeedback(result.score, result.baselineScore, result.winner, selected.autoVerifyThreshold)
|
|
3732
|
+
text: automaticFeedback(result.score, result.baselineScore, result.winner, selected.autoVerifyThreshold, failed)
|
|
3459
3733
|
}],
|
|
3460
3734
|
source: {
|
|
3461
3735
|
kind: "plugin",
|
|
@@ -3843,6 +4117,6 @@ function apply(ctx, config = {}) {
|
|
|
3843
4117
|
}));
|
|
3844
4118
|
}
|
|
3845
4119
|
//#endregion
|
|
3846
|
-
export {
|
|
4120
|
+
export { AutoVerifierRouter, Config, DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, GRANULARITY, LETTERS, MAX_ROUTED_CHECKPOINTS, MAX_TEAM_TASK_METADATA_CHARS, RequestLimiter, SCALE_DESCRIPTION, ScoreCache, SingleFlight, StatisticsStore, UNTRUSTED_EVIDENCE_NOTE, VERIFIER_TOOL_NAMES, VerifierEngine, accumulatePairs, analyzeAutoTask, analyzeStructuredRoute, apply, automaticFeedback, boundDecision, bradleyTerry, buildEvidenceIndex, buildPairwisePrompt, buildPlanPreReviewPrompt, buildProgressPrompt, buildSemanticRoutePrompt, buildTeamTaskVerificationPrompt, callVerifier, emptyRunStats, errorDetails, estimateRoutedCalls, evidenceNonce, extractProgressScore, extractScore, failedAcceptanceCriteria, inject, inspectTeamTasks, isSubagentSession, latestDirectUserSeq, mergeStatisticsOverviews, name, normalizeCriteria, normalizeScoreLetter, orientRoundPairs, parseSemanticRoute, parseStatisticsQuery, parseVerdictLetter, pivotRoundPairs, planFromArguments, rankScores, renderDelimitedBlock, resolveCacheFile, resolveStatisticsFile, resolveTopicDataDir, ringCycle, routedRepeats, seededRandom, semanticDecision, semanticRouteHint, sessionAccepted, stableHash, summarizeVerdict, topPivots };
|
|
3847
4121
|
|
|
3848
4122
|
//# sourceMappingURL=index.js.map
|