@nexus-cortex/core 4.116.2 → 4.117.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/.env.defaults +43 -0
- package/dist/config/RuntimeConfigRegistry.d.ts.map +1 -1
- package/dist/config/RuntimeConfigRegistry.js +6 -0
- package/dist/config/RuntimeConfigRegistry.js.map +1 -1
- package/dist/config/SettingsLoader.d.ts.map +1 -1
- package/dist/config/SettingsLoader.js +6 -0
- package/dist/config/SettingsLoader.js.map +1 -1
- package/dist/config/SettingsSchema.d.ts +6 -0
- package/dist/config/SettingsSchema.d.ts.map +1 -1
- package/dist/config/SettingsSchema.js +54 -0
- package/dist/config/SettingsSchema.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +6 -0
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/middleware/HelperModelMiddleware.d.ts +3 -0
- package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
- package/dist/middleware/HelperModelMiddleware.js +7 -7
- package/dist/middleware/HelperModelMiddleware.js.map +1 -1
- package/dist/orchestrator/CortexOrchestrator.d.ts +6 -0
- package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
- package/dist/orchestrator/CortexOrchestrator.js +141 -9
- package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
- package/dist/orchestrator/turnContractValidator.d.ts +25 -0
- package/dist/orchestrator/turnContractValidator.d.ts.map +1 -0
- package/dist/orchestrator/turnContractValidator.js +41 -0
- package/dist/orchestrator/turnContractValidator.js.map +1 -0
- package/dist/training/DecisionStore.d.ts +1 -1
- package/dist/training/DecisionStore.d.ts.map +1 -1
- package/dist/training/DecisionStore.js.map +1 -1
- package/dist/training/endTurnResolver.d.ts +53 -5
- package/dist/training/endTurnResolver.d.ts.map +1 -1
- package/dist/training/endTurnResolver.js +99 -23
- package/dist/training/endTurnResolver.js.map +1 -1
- package/dist/training/judgeEvidence.d.ts +6 -0
- package/dist/training/judgeEvidence.d.ts.map +1 -1
- package/dist/training/judgeEvidence.js +67 -2
- package/dist/training/judgeEvidence.js.map +1 -1
- package/package.json +3 -3
|
@@ -19,7 +19,7 @@ import { join as pathJoin } from 'path';
|
|
|
19
19
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
20
20
|
import { execSync } from 'node:child_process';
|
|
21
21
|
import { ENV_RECON_COMMAND, resolveLiftPlanConfig } from '../training/liftPlanner.js';
|
|
22
|
-
import { resolveEndTurnResolverConfig, parseResolverVerdict, effectiveMaxRejects, budgetedVetoEscalation, decideVetoAction, shouldFinishConfirm, buildFinishConfirmMessage } from '../training/endTurnResolver.js';
|
|
22
|
+
import { resolveEndTurnResolverConfig, parseResolverVerdict, effectiveMaxRejects, budgetedVetoEscalation, decideVetoAction, shouldFinishConfirm, buildFinishConfirmMessage, buildMeetsConfirmMessage } from '../training/endTurnResolver.js';
|
|
23
23
|
import { resolveDeadlineExitConfig, deadlineExitCallBudget, parseDeadlineExitVerdict } from '../training/deadlineExitMentor.js';
|
|
24
24
|
import { isTaskShaped } from './requirementsVerification.js';
|
|
25
25
|
import { extractServerSideMetadata, XAIServerSideTools, OpenAIServerSideTools, toCanonicalTool } from '../tools/ServerSideTools.js';
|
|
@@ -41,7 +41,7 @@ import { TurnEvidence, evaluateEndTurnGates, buildMissingEndTurnReminder } from
|
|
|
41
41
|
import { resolveCompactionResume, pickDropped, buildCompactionResumeReminder, isCompactionResumeMessage, resolveTaskText, resumeMemoryTargetTokens, coversAll, buildRollingSeedMessage, memoryIndexLine, upsertMemoryIndex, scaleEstimateToRequestView, approxCharsOf, anchoredRequestEstimate, resolveCompactionHandoffQA, resolveHandoffQAMaxQuestions, runHandoffQA } from './compactionResume.js';
|
|
42
42
|
import { renderResumeMemoryPrompt, buildRebuildInstructions, resolveCheckpointBand, resolveRearmBand } from './compactionResumeTemplate.js';
|
|
43
43
|
import { resolveCortexStateDir, cortexStatePath } from '../utils/stateDir.js';
|
|
44
|
-
import { collectWorkspaceDelta, detectCheckCommand, runCheck, resolveJudgeGroundingConfig, classifyCheckRun } from '../training/judgeEvidence.js';
|
|
44
|
+
import { collectWorkspaceDelta, detectCheckCommand, runCheck, resolveJudgeGroundingConfig, classifyCheckRun, isInvestigateCommandAllowed, readFileSlice, formatEvidenceRound } from '../training/judgeEvidence.js';
|
|
45
45
|
import { resolveMentorRoleConfig, describeMentorWire, describeMentorDelivery, mentorSurfaceTimeoutMs } from '../training/mentorRole.js';
|
|
46
46
|
import { resolveOuterToolDeadlineMs, resolveOuterToolTimeoutFloorMs } from './outerToolTimeout.js';
|
|
47
47
|
import { resolveSubAgentTimeoutMs } from './subAgentTimeout.js';
|
|
@@ -72,6 +72,7 @@ import { closestToolMatches } from './toolNameMatcher.js';
|
|
|
72
72
|
import { classifyApiError } from './apiErrorClassifier.js';
|
|
73
73
|
import { pinStaticSystemPrompt } from './staticSystemPromptPin.js';
|
|
74
74
|
import { hasVisibleAssistantText, shouldForceSynthesis } from './assistantTextPresence.js';
|
|
75
|
+
import { resolveTurnContractEnforce, checkTurnFormat, buildFormatRejectMessage } from './turnContractValidator.js'; // HB-TURN-CONTRACT-ENFORCE
|
|
75
76
|
import { computeToolBudgetSignal, isToolProgressStalled } from './toolBudgetSignal.js';
|
|
76
77
|
import { createStructuredOutputTurnState, ensureStructuredOutputTool, evaluateStructuredOutputCall, finalizeStructuredOutput, isStructuredOutputToolName, } from './structuredOutput.js';
|
|
77
78
|
// Phase 2.6: MCP Model Management Tools
|
|
@@ -231,6 +232,9 @@ export class CortexOrchestrator {
|
|
|
231
232
|
judgePriorNamedPassed = 0; // R166a: named checks passing at the last veto
|
|
232
233
|
finishConfirms = 0; // R167: informed finish confirmations issued this turn
|
|
233
234
|
judgeAcceptedWithGap = false; // R167: the judge accepted this finish WITH a gap → regex nudges may still fire
|
|
235
|
+
judgeMeetsUnevidenced = false; // R168: the judge said MEETS but no proving check passed → regex nudges may still fire
|
|
236
|
+
turnFormatRejects = 0; // HB-TURN-CONTRACT-ENFORCE: format rejections issued this turn
|
|
237
|
+
pendingFormatReject = null; // set by the loop, consumed by handleToolCalls (all three execution paths)
|
|
234
238
|
judgeNamedChecks = [];
|
|
235
239
|
judgePriorPlan = '';
|
|
236
240
|
judgeEscalated = false;
|
|
@@ -594,6 +598,38 @@ export class CortexOrchestrator {
|
|
|
594
598
|
* Bounded by CORTEX_ENDTURN_RESOLVER_MAX_REJECTS then fallback-accepts (liveness). No-op when the resolver
|
|
595
599
|
* is unarmed → byte-identical. Fail-open on any error → leaves the finish accepted.
|
|
596
600
|
*/
|
|
601
|
+
/** HB-TURN-CONTRACT-ENFORCE: decide whether this tool-calling response is rejected on format (ANALYSIS + PLAN missing).
|
|
602
|
+
* Sets `pendingFormatReject` for handleToolCalls; bounded per turn; finish batches (EndTurn) exempt; events banked. */
|
|
603
|
+
armTurnFormatReject(content, toolUseBlocks, iteration, streaming) {
|
|
604
|
+
this.pendingFormatReject = null;
|
|
605
|
+
if (toolUseBlocks.length === 0)
|
|
606
|
+
return;
|
|
607
|
+
const tc = resolveTurnContractEnforce(process.env);
|
|
608
|
+
if (!tc.enforce)
|
|
609
|
+
return;
|
|
610
|
+
if (toolUseBlocks.some((t) => t.name === 'EndTurn'))
|
|
611
|
+
return; // the finish path has its own gates + judge
|
|
612
|
+
const text = (Array.isArray(content) ? content : []).filter((b) => b?.type === 'text').map((b) => String(b.text ?? '')).join('\n');
|
|
613
|
+
const fmt = checkTurnFormat(text);
|
|
614
|
+
if (fmt.ok)
|
|
615
|
+
return;
|
|
616
|
+
const store = this.getDecisionStore();
|
|
617
|
+
const sessionId = this.currentSessionId ?? 'unknown';
|
|
618
|
+
if (this.turnFormatRejects >= tc.max) {
|
|
619
|
+
if (this.turnFormatRejects === tc.max) { // bank the fallback once per turn
|
|
620
|
+
this.turnFormatRejects += 1;
|
|
621
|
+
console.warn(`[TurnContract] format still missing ${fmt.missing.join('+')} after ${tc.max} rejection(s) — executing the batch as-is (fallback)`);
|
|
622
|
+
if (store)
|
|
623
|
+
void store.recordEvent({ sessionId, kind: 'turn_contract_fallback', detail: { iteration, rejects: tc.max, missing: fmt.missing, tools: toolUseBlocks.map((t) => t.name).slice(0, 8), streaming } }).catch(() => { });
|
|
624
|
+
}
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
this.turnFormatRejects += 1;
|
|
628
|
+
this.pendingFormatReject = { missing: fmt.missing, index: this.turnFormatRejects, max: tc.max };
|
|
629
|
+
console.warn(`[TurnContract] REJECTED response format (missing ${fmt.missing.join('+')}; ${toolUseBlocks.length} tool call(s) not executed; rejection ${this.turnFormatRejects}/${tc.max})`);
|
|
630
|
+
if (store)
|
|
631
|
+
void store.recordEvent({ sessionId, kind: 'turn_contract_reject', detail: { iteration, rejects: this.turnFormatRejects, max: tc.max, missing: fmt.missing, tools: toolUseBlocks.map((t) => t.name).slice(0, 8), textChars: text.length, streaming } }).catch(() => { });
|
|
632
|
+
}
|
|
597
633
|
async adjudicateEndTurn(ev, toolResults, toolUses, model) {
|
|
598
634
|
if (!resolveEndTurnResolver(process.env, model.endTurnResolver))
|
|
599
635
|
return;
|
|
@@ -609,6 +645,7 @@ export class CortexOrchestrator {
|
|
|
609
645
|
return;
|
|
610
646
|
this.judgeAdjudicatedThisFinish = false;
|
|
611
647
|
this.judgeAcceptedWithGap = false; // R167
|
|
648
|
+
this.judgeMeetsUnevidenced = false; // R168
|
|
612
649
|
// R160 HB-RESOLVER-BUDGET-CAP: the cap is budget-aware — while >= CORTEX_BUDGET_CONTINUE_MIN_REMAINING of the wall
|
|
613
650
|
// budget remains the judge may veto up to maxRejectsBudgeted times; below that (or with no deadline) the liveness cap.
|
|
614
651
|
const resolverRemainingMs = this.turnDeadlineMsActive > 0 && this.turnLoopStartMs > 0 ? Math.max(0, this.turnDeadlineMsActive - (Date.now() - this.turnLoopStartMs)) : null;
|
|
@@ -672,7 +709,8 @@ export class CortexOrchestrator {
|
|
|
672
709
|
: undefined;
|
|
673
710
|
const timeoutMs = mentorSurfaceTimeoutMs('endturn-resolver', parseInt(process.env.CORTEX_ENDTURN_RESOLVER_TIMEOUT_MS ?? '90000', 10)); // thinking-aware
|
|
674
711
|
const t0 = Date.now();
|
|
675
|
-
const
|
|
712
|
+
const evidenceRounds = []; // R170
|
|
713
|
+
const judgeOnce = (reasoning, investigate) => withTimeout(this.helperMiddleware.evaluateEndTurn({
|
|
676
714
|
task,
|
|
677
715
|
liftPlan: this.liftPlanText || undefined,
|
|
678
716
|
envReport: this.gatherEnvReport({ fresh: true }),
|
|
@@ -683,15 +721,71 @@ export class CortexOrchestrator {
|
|
|
683
721
|
priorVetoItems,
|
|
684
722
|
progressSummary,
|
|
685
723
|
reasoning,
|
|
724
|
+
evidenceRounds: evidenceRounds.length ? evidenceRounds.slice() : undefined, // R170
|
|
725
|
+
investigate,
|
|
686
726
|
helperModelId: this.config.reactiveMentorship?.helperModelId,
|
|
687
727
|
}), reasoning === 'on' ? mentorSurfaceTimeoutMs('endturn-resolver', Math.max(timeoutMs, 180000)) : timeoutMs);
|
|
688
|
-
|
|
689
|
-
|
|
728
|
+
// R170 HB-JUDGE-TOOL-LOOP: up to cfg.toolRounds calls; while rounds and the aggregate budget remain the judge may
|
|
729
|
+
// INVESTIGATE (checks run via the existing runCheck surface behind a read-only denylist; file slices read in-workspace),
|
|
730
|
+
// each round's EVIDENCE block rides on the next prompt; the last round withdraws the option. toolRounds=1 = one call.
|
|
731
|
+
let text = null;
|
|
732
|
+
let verdict = parseResolverVerdict('');
|
|
733
|
+
let roundsUsed = 0;
|
|
734
|
+
let investigateChecks = 0;
|
|
735
|
+
let investigateReads = 0;
|
|
736
|
+
let investigateRefused = 0;
|
|
737
|
+
const roundLatencyMs = [];
|
|
738
|
+
for (let round = 1; round <= cfg.toolRounds; round++) {
|
|
739
|
+
const loopElapsed = Date.now() - t0;
|
|
740
|
+
const canInvestigate = cfg.toolRounds > 1 && round < cfg.toolRounds && loopElapsed < cfg.toolRoundBudgetMs &&
|
|
741
|
+
(resolverRemainingMs === null || resolverRemainingMs - loopElapsed > 2 * cfg.toolRoundBudgetMs);
|
|
742
|
+
const mode = cfg.toolRounds > 1 ? (canInvestigate ? 'offer' : 'withdraw') : undefined;
|
|
743
|
+
const r0 = Date.now();
|
|
744
|
+
text = await judgeOnce(undefined, mode);
|
|
745
|
+
roundLatencyMs.push(Date.now() - r0);
|
|
746
|
+
roundsUsed = round;
|
|
747
|
+
verdict = parseResolverVerdict(text ?? ''); // withTimeout → null on timeout; blank → ABSTAIN (below)
|
|
748
|
+
if (!verdict.investigate)
|
|
749
|
+
break;
|
|
750
|
+
if (!canInvestigate) {
|
|
751
|
+
verdict = { ...verdict, blank: true, parsed: false };
|
|
752
|
+
break;
|
|
753
|
+
} // asked after withdrawal → abstain, never re-loop
|
|
754
|
+
const checkResults = [];
|
|
755
|
+
const readResults = [];
|
|
756
|
+
for (const c of verdict.checks.slice(0, 3)) {
|
|
757
|
+
if (!isInvestigateCommandAllowed(c)) {
|
|
758
|
+
investigateRefused += 1;
|
|
759
|
+
checkResults.push(`CHECK RUN: \`${c}\` → REFUSED (investigation checks must be read-only)`);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const r = runCheck(judgeCwd, c, jcfg);
|
|
763
|
+
investigateChecks += 1;
|
|
764
|
+
namedRan += 1;
|
|
765
|
+
{
|
|
766
|
+
const k = classifyCheckRun(r);
|
|
767
|
+
if (k === 'passed')
|
|
768
|
+
namedPassed += 1;
|
|
769
|
+
else if (k === 'failed')
|
|
770
|
+
namedFailed += 1;
|
|
771
|
+
else
|
|
772
|
+
namedInconclusive += 1;
|
|
773
|
+
}
|
|
774
|
+
checkResults.push(r);
|
|
775
|
+
}
|
|
776
|
+
for (const p of verdict.reads.slice(0, 3)) {
|
|
777
|
+
readResults.push(readFileSlice(judgeCwd, p));
|
|
778
|
+
investigateReads += 1;
|
|
779
|
+
}
|
|
780
|
+
evidenceRounds.push(formatEvidenceRound(round, checkResults, readResults));
|
|
781
|
+
console.warn(`[EndTurnResolver] R170 INVESTIGATE round ${round}/${cfg.toolRounds} — ${checkResults.length} check(s), ${readResults.length} read(s)`);
|
|
782
|
+
}
|
|
690
783
|
// R166 HB-JUDGE-EVIDENCE-VETO: in evidence mode the checks THIS verdict names run now — a GAP without a failing
|
|
691
784
|
// command is an opinion, and opinion no longer holds a finish (18 of 19 grader-accepted solutions were vetoed first).
|
|
692
785
|
let namedRanNow = 0;
|
|
693
786
|
let namedResultsNow = '';
|
|
694
|
-
|
|
787
|
+
// R168: with meetsConfirm the checks a MEETS names run too — their passing is what lets the MEETS stand.
|
|
788
|
+
if (cfg.semantic && cfg.vetoMode === 'evidence' && (!verdict.meets || cfg.meetsConfirm) && !verdict.blank && !verdict.investigate && verdict.checks.length > 0) {
|
|
695
789
|
for (const c of verdict.checks.slice(0, 3)) {
|
|
696
790
|
const r = runCheck(judgeCwd, c, jcfg);
|
|
697
791
|
namedRanNow += 1;
|
|
@@ -714,7 +808,7 @@ export class CortexOrchestrator {
|
|
|
714
808
|
if (action === 'escalate') {
|
|
715
809
|
// R165: the junior re-attested without working the plan — one thinking-on adjudication before we accept with the gap recorded.
|
|
716
810
|
this.judgeEscalated = true;
|
|
717
|
-
if (cfg.escalateReasoning) {
|
|
811
|
+
if (cfg.escalateReasoning && roundsUsed <= 1) { // R170: a multi-round adjudication already spent the extra reasoning
|
|
718
812
|
console.warn(`[EndTurnResolver] R165: re-attest without progress (${callsSince} calls since the veto) — escalating the judge to reasoning-on once.`);
|
|
719
813
|
const text2 = await judgeOnce('on');
|
|
720
814
|
const v2 = parseResolverVerdict(text2 ?? '');
|
|
@@ -745,6 +839,7 @@ export class CortexOrchestrator {
|
|
|
745
839
|
cap: resolverCap, capLiveness: cfg.maxRejects, capBudgeted: cfg.maxRejectsBudgeted, remainingFrac: resolverRemainingFrac === null ? null : Number(resolverRemainingFrac.toFixed(3)), // R160
|
|
746
840
|
semantic: cfg.semantic, action, confidence: verdict.confidence, namedChecks: verdict.checks.length, namedRan, namedPassed, namedFailed, callsSince, progressed, escalated: this.judgeEscalated, // R165
|
|
747
841
|
vetoMode: cfg.vetoMode, evidenceCap: cfg.evidenceCap, namedRanNow, namedInconclusive, // R166 / R166b
|
|
842
|
+
toolRounds: cfg.toolRounds, roundsUsed, investigateChecks, investigateReads, investigateRefused, roundLatencyMs, evidenceChars: evidenceRounds.join('').length, // R170
|
|
748
843
|
latencyMs, rawLen: (text ?? '').length,
|
|
749
844
|
deltaChars: workspaceDelta.length, checkRan: !!checkResult, checkPassed: checkResult ? /→ PASSED/.test(checkResult) : null,
|
|
750
845
|
liftPlanChars: this.liftPlanText.length,
|
|
@@ -759,7 +854,7 @@ export class CortexOrchestrator {
|
|
|
759
854
|
// content / no verdict line / timeout). That is a judge FAILURE, not a judgment — the finish stands, no
|
|
760
855
|
// veto, no reject increment, and the event says so (abstained + blank + failOpen) instead of counting as MEETS.
|
|
761
856
|
if (this.config.debug)
|
|
762
|
-
console.warn(`[EndTurnResolver] BLANK — abstained (no verdict from the judge, rawLen=${(text ?? '').length})`);
|
|
857
|
+
console.warn(`[EndTurnResolver] BLANK — abstained (no verdict from the judge, rawLen=${(text ?? '').length}${verdict.investigate ? ', INVESTIGATE after withdrawal' : ''})`);
|
|
763
858
|
}
|
|
764
859
|
else if (verdict.retire && cfg.abstain) {
|
|
765
860
|
// ABSTENTION — the finish is structurally unclosable; accept it and STOP the reject
|
|
@@ -809,6 +904,28 @@ export class CortexOrchestrator {
|
|
|
809
904
|
if (store)
|
|
810
905
|
void store.recordEvent({ sessionId, kind: 'endturn_gap_accepted', toolName: 'EndTurn', detail: { action, rejects: this.endTurnResolverRejects, cap: resolverCap, confidence: verdict.confidence, plan: verdict.plan.slice(0, 2000), checks: verdict.checks, callsSince, escalated: this.judgeEscalated } }).catch(() => { });
|
|
811
906
|
}
|
|
907
|
+
else if (action === 'accept' && verdict.meets) {
|
|
908
|
+
// R168 HB-MEETS-CONFIRM: a MEETS stands only when a proving check passed. Otherwise, with budget left, hold ONCE
|
|
909
|
+
// (shared counter with R167) showing what was (not) verified; the next EndTurn stands.
|
|
910
|
+
const evidencePassed = namedPassed > 0 || (!!checkResult && /→ PASSED/.test(checkResult));
|
|
911
|
+
if (cfg.meetsConfirm && !evidencePassed)
|
|
912
|
+
this.judgeMeetsUnevidenced = true; // let the budget-continue / surrender nudges see this finish
|
|
913
|
+
if (shouldFinishConfirm({ action, remainingFrac: resolverRemainingFrac, confirmsUsed: this.finishConfirms, cfg, meets: true, evidencePassed })) {
|
|
914
|
+
this.finishConfirms += 1;
|
|
915
|
+
ev.endTurnCalled = false;
|
|
916
|
+
et.is_error = true;
|
|
917
|
+
et.content = buildMeetsConfirmMessage({
|
|
918
|
+
remainingFrac: resolverRemainingFrac ?? 0, remainingMs: resolverRemainingMs ?? 0,
|
|
919
|
+
checksNamed: verdict.checks.length, checkResults: namedResultsNow, openItems: etInput.open_items,
|
|
920
|
+
});
|
|
921
|
+
console.warn(`[EndTurnResolver] R168 MEETS-CONFIRM — unverified MEETS held once (checks named ${verdict.checks.length}, passed ${namedPassed}, budget remaining ${Math.round((resolverRemainingFrac ?? 0) * 100)}%)`);
|
|
922
|
+
if (store)
|
|
923
|
+
void store.recordEvent({ sessionId, kind: 'finish_confirm', toolName: 'EndTurn', detail: { action, meets: true, evidencePassed, checksNamed: verdict.checks.length, namedRanNow, namedPassed, namedFailed, namedInconclusive, remainingFrac: resolverRemainingFrac, confirms: this.finishConfirms, planChars: verdict.plan.length, openItems: Array.isArray(etInput.open_items) ? etInput.open_items.length : 0 } }).catch(() => { });
|
|
924
|
+
}
|
|
925
|
+
else if (this.config.debug) {
|
|
926
|
+
console.log(`[EndTurnResolver] MEETS — finish confirmed (parsed=${verdict.parsed}, evidencePassed=${evidencePassed})`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
812
929
|
else if (this.config.debug) {
|
|
813
930
|
console.log(`[EndTurnResolver] MEETS — finish confirmed (parsed=${verdict.parsed})`);
|
|
814
931
|
}
|
|
@@ -1730,6 +1847,9 @@ export class CortexOrchestrator {
|
|
|
1730
1847
|
this.judgePriorNamedPassed = 0;
|
|
1731
1848
|
this.finishConfirms = 0;
|
|
1732
1849
|
this.judgeAcceptedWithGap = false;
|
|
1850
|
+
this.judgeMeetsUnevidenced = false;
|
|
1851
|
+
this.turnFormatRejects = 0;
|
|
1852
|
+
this.pendingFormatReject = null;
|
|
1733
1853
|
this.judgeNamedChecks = [];
|
|
1734
1854
|
this.judgePriorPlan = '';
|
|
1735
1855
|
this.judgeEscalated = false;
|
|
@@ -1879,6 +1999,7 @@ export class CortexOrchestrator {
|
|
|
1879
1999
|
if (this.config.debug && toolUseBlocks.length > 0) {
|
|
1880
2000
|
console.log(`[Orchestrator] Extracted tools: ${toolUseBlocks.map((t) => t.name).join(', ')}`);
|
|
1881
2001
|
}
|
|
2002
|
+
this.armTurnFormatReject(currentAssistantCanonicalMessage.content, toolUseBlocks, toolCallIteration, false); // HB-TURN-CONTRACT-ENFORCE
|
|
1882
2003
|
// If no tool calls, we're done — UNLESS the response is empty.
|
|
1883
2004
|
if (toolUseBlocks.length === 0) {
|
|
1884
2005
|
// Round 18b (multi-model bench finding): detect empty responses
|
|
@@ -2203,7 +2324,7 @@ export class CortexOrchestrator {
|
|
|
2203
2324
|
.join('\n')
|
|
2204
2325
|
: String(currentAssistantCanonicalMessage.content ?? '');
|
|
2205
2326
|
// R165: when the semantic judge adjudicated this finish, the regex surrender/open-items nudges defer to it.
|
|
2206
|
-
const judgeOwnsFinish = this.judgeAdjudicatedThisFinish && resolveEndTurnResolverConfig().semantic && !this.judgeAcceptedWithGap; // R167: an accept-with-gap leaves the regex nudges live
|
|
2327
|
+
const judgeOwnsFinish = this.judgeAdjudicatedThisFinish && resolveEndTurnResolverConfig().semantic && !this.judgeAcceptedWithGap && !this.judgeMeetsUnevidenced; // R167: an accept-with-gap leaves the regex nudges live; R168: so does an unverified MEETS
|
|
2207
2328
|
const surrenderUnsatisfied = resolveSurrenderNudgeMode() && ev.usedTools && !surrenderNudgeUsed && !judgeOwnsFinish &&
|
|
2208
2329
|
detectSurrenderText(surrenderDraftText);
|
|
2209
2330
|
// R151 HB-BUDGET-VISIBILITY: an open-items finish with >= MIN_REMAINING of the wall budget left gets ONE
|
|
@@ -3926,6 +4047,9 @@ export class CortexOrchestrator {
|
|
|
3926
4047
|
this.judgePriorNamedPassed = 0;
|
|
3927
4048
|
this.finishConfirms = 0;
|
|
3928
4049
|
this.judgeAcceptedWithGap = false;
|
|
4050
|
+
this.judgeMeetsUnevidenced = false;
|
|
4051
|
+
this.turnFormatRejects = 0;
|
|
4052
|
+
this.pendingFormatReject = null;
|
|
3929
4053
|
this.judgeNamedChecks = [];
|
|
3930
4054
|
this.judgePriorPlan = '';
|
|
3931
4055
|
this.judgeEscalated = false;
|
|
@@ -4068,6 +4192,7 @@ export class CortexOrchestrator {
|
|
|
4068
4192
|
input: block.toolUse.input
|
|
4069
4193
|
}));
|
|
4070
4194
|
ev.noteToolUses(toolUseBlocks);
|
|
4195
|
+
this.armTurnFormatReject(currentAssistantCanonicalMessage.content, toolUseBlocks, toolCallIteration, true); // HB-TURN-CONTRACT-ENFORCE
|
|
4071
4196
|
if (toolUseBlocks.length === 0 && !gateReRequest) {
|
|
4072
4197
|
// R32 (streaming R18b parity): detect empty/thinking-only responses.
|
|
4073
4198
|
// sendMessage retries ONCE inside the loop with tools preserved.
|
|
@@ -6602,6 +6727,13 @@ export class CortexOrchestrator {
|
|
|
6602
6727
|
}).catch(() => { });
|
|
6603
6728
|
}
|
|
6604
6729
|
async handleToolCalls(toolUseBlocks, signal, structuredOutputState) {
|
|
6730
|
+
// HB-TURN-CONTRACT-ENFORCE: a batch the loop rejected on format carries the re-prompt back as error results; nothing runs.
|
|
6731
|
+
if (this.pendingFormatReject) {
|
|
6732
|
+
const rej = this.pendingFormatReject;
|
|
6733
|
+
this.pendingFormatReject = null;
|
|
6734
|
+
const msg = buildFormatRejectMessage(rej.missing, rej.index, rej.max, toolUseBlocks.map((t) => t.name));
|
|
6735
|
+
return toolUseBlocks.map((t) => ({ tool_use_id: t.id, tool_name: t.name, content: msg, is_error: true, metadata: { formatRejected: true } }));
|
|
6736
|
+
}
|
|
6605
6737
|
// StructuredOutput (grok-build port): intercept BEFORE any dispatch — the
|
|
6606
6738
|
// synthetic tool is request-scoped (never in a registry) and must NEVER
|
|
6607
6739
|
// reach a real executor. Each call gets a synthesized tool_result:
|