@nexus-cortex/core 4.93.0 → 4.94.0

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.
Files changed (44) hide show
  1. package/{.env.example → .env.defaults} +93 -25
  2. package/dist/config/MentorshipConfigService.d.ts +0 -1
  3. package/dist/config/MentorshipConfigService.d.ts.map +1 -1
  4. package/dist/config/MentorshipConfigService.js +10 -5
  5. package/dist/config/MentorshipConfigService.js.map +1 -1
  6. package/dist/config/SettingsLoader.d.ts +92 -11
  7. package/dist/config/SettingsLoader.d.ts.map +1 -1
  8. package/dist/config/SettingsLoader.js +331 -131
  9. package/dist/config/SettingsLoader.js.map +1 -1
  10. package/dist/config/effectiveConfig.d.ts +1 -1
  11. package/dist/config/effectiveConfig.d.ts.map +1 -1
  12. package/dist/config/effectiveConfig.js +6 -1
  13. package/dist/config/effectiveConfig.js.map +1 -1
  14. package/dist/middleware/HelperModelMiddleware.d.ts +16 -0
  15. package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
  16. package/dist/middleware/HelperModelMiddleware.js +32 -5
  17. package/dist/middleware/HelperModelMiddleware.js.map +1 -1
  18. package/dist/models/cards/deepseek/deepseek-v4-flash.d.ts.map +1 -1
  19. package/dist/models/cards/deepseek/deepseek-v4-flash.js +4 -2
  20. package/dist/models/cards/deepseek/deepseek-v4-flash.js.map +1 -1
  21. package/dist/orchestrator/CortexOrchestrator.d.ts +14 -0
  22. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  23. package/dist/orchestrator/CortexOrchestrator.js +131 -6
  24. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  25. package/dist/orchestrator/OrchestratorFactory.js +1 -1
  26. package/dist/orchestrator/OrchestratorFactory.js.map +1 -1
  27. package/dist/orchestrator/displayHistorySanitizer.d.ts +29 -0
  28. package/dist/orchestrator/displayHistorySanitizer.d.ts.map +1 -0
  29. package/dist/orchestrator/displayHistorySanitizer.js +62 -0
  30. package/dist/orchestrator/displayHistorySanitizer.js.map +1 -0
  31. package/dist/tools/context-management/InitCortexContext.js +1 -1
  32. package/dist/tools/context-management/InitCortexContext.js.map +1 -1
  33. package/dist/training/DecisionStore.d.ts +1 -1
  34. package/dist/training/DecisionStore.d.ts.map +1 -1
  35. package/dist/training/DecisionStore.js.map +1 -1
  36. package/dist/training/deadlineExitMentor.d.ts +67 -0
  37. package/dist/training/deadlineExitMentor.d.ts.map +1 -0
  38. package/dist/training/deadlineExitMentor.js +99 -0
  39. package/dist/training/deadlineExitMentor.js.map +1 -0
  40. package/dist/training/endTurnResolver.d.ts +14 -3
  41. package/dist/training/endTurnResolver.d.ts.map +1 -1
  42. package/dist/training/endTurnResolver.js +27 -10
  43. package/dist/training/endTurnResolver.js.map +1 -1
  44. package/package.json +5 -5
@@ -12,6 +12,7 @@ import { ToolNamingHandler } from '../adapters/ToolNamingHandler.js';
12
12
  // Phase 2.4: Server-Side Tools Support
13
13
  import { shouldUseServerSideTools, isServerSideToolsEnabled, modelSupportsServerSideTools } from '../adapters/ServerSideToolDetection.js';
14
14
  import { shouldAutoInjectMcp } from './mcpAutoInjectPolicy.js';
15
+ import { sanitizeHistoryForDisplay } from './displayHistorySanitizer.js';
15
16
  import { verifyCoordinates, deterministicCoordinateScore } from './coordinateVerification.js';
16
17
  import { buildRouterSample, appendJsonlRotating } from './cortexTrainingRecord.js';
17
18
  import { join as pathJoin } from 'path';
@@ -19,6 +20,7 @@ import { existsSync } from 'fs';
19
20
  import { execSync } from 'node:child_process';
20
21
  import { ENV_RECON_COMMAND, resolveLiftPlanConfig } from '../training/liftPlanner.js';
21
22
  import { resolveEndTurnResolverConfig, parseResolverVerdict } from '../training/endTurnResolver.js';
23
+ import { resolveDeadlineExitConfig, deadlineExitCallBudget, parseDeadlineExitVerdict } from '../training/deadlineExitMentor.js';
22
24
  import { isTaskShaped } from './requirementsVerification.js';
23
25
  import { extractServerSideMetadata, XAIServerSideTools, OpenAIServerSideTools, toCanonicalTool } from '../tools/ServerSideTools.js';
24
26
  // Phase 2.2.1: Timeline and History Storage
@@ -529,14 +531,22 @@ export class CortexOrchestrator {
529
531
  kind: 'endturn_resolver',
530
532
  toolName: 'EndTurn',
531
533
  detail: {
532
- meets: verdict.meets, planChars: verdict.plan.length, rejects: this.endTurnResolverRejects, parsed: verdict.parsed,
534
+ meets: verdict.meets, retire: verdict.retire, abstained: verdict.retire && cfg.abstain,
535
+ planChars: verdict.plan.length, rejects: this.endTurnResolverRejects, parsed: verdict.parsed,
533
536
  latencyMs, rawLen: (text ?? '').length,
534
537
  planText: verdict.plan.slice(0, 4000),
535
538
  workProductSample: workProduct.slice(0, 1500),
536
539
  attestation: attestation.slice(0, 800),
537
540
  },
538
541
  }).catch(() => { });
539
- if (!verdict.meets && verdict.plan) {
542
+ if (verdict.retire && cfg.abstain) {
543
+ // ABSTENTION — the finish is structurally unclosable; accept it and STOP the reject
544
+ // loop (no veto, no reject increment) instead of burning more pro@max cycles on a task
545
+ // the junior can't fix. The finish stands (ev.endTurnCalled stays true).
546
+ if (this.config.debug)
547
+ console.warn(`[EndTurnResolver] RETIRE — abstained (unclosable): ${verdict.plan.slice(0, 160)}`);
548
+ }
549
+ else if (!verdict.meets && verdict.plan) {
540
550
  this.endTurnResolverRejects += 1;
541
551
  ev.endTurnCalled = false; // VETO the finish
542
552
  et.is_error = true;
@@ -556,6 +566,78 @@ export class CortexOrchestrator {
556
566
  // fail-open: leave the finish accepted
557
567
  }
558
568
  }
569
+ /**
570
+ * deadlineExitCheckpoint — the mentor-as-deadline-checkpoint at the WARN rung (~0.9 of the
571
+ * per-turn deadline, where residual still exists). Returns null when disabled/not-applicable
572
+ * (caller uses its normal warn nudge). Otherwise a bounded, residual-scaled mentor call decides:
573
+ * shouldBreak=true → FINISH or RETIRE: end the loop now (post-loop synthesis; resolver bypassed
574
+ * via abnormal-exit) — a clean finish or a clean retirement instead of
575
+ * grinding the residual.
576
+ * shouldBreak=false → CONTINUE (signal='' → normal nudge) or ACTION (signal=the one directed step).
577
+ * Dark by default (CORTEX_DEADLINE_EXIT_MENTOR); fail-safe to null on any error (never truncate
578
+ * a turn on a broken checkpoint call).
579
+ */
580
+ async deadlineExitCheckpoint(task, workProduct, remainingMs) {
581
+ const cfg = resolveDeadlineExitConfig();
582
+ if (!cfg.enabled)
583
+ return null;
584
+ if (!this.helperMiddleware?.evaluateDeadlineExit)
585
+ return null;
586
+ if (!isTaskShaped(task))
587
+ return null;
588
+ const { timeoutMs, outputBudgetTokens } = deadlineExitCallBudget(cfg, remainingMs);
589
+ const store = this.getDecisionStore();
590
+ const sessionId = this.currentSessionId ?? 'unknown';
591
+ try {
592
+ const t0 = Date.now();
593
+ const text = await withTimeout(this.helperMiddleware.evaluateDeadlineExit({
594
+ task,
595
+ envReport: this.gatherEnvReport(),
596
+ workProduct,
597
+ remainingBudget: `~${Math.max(0, Math.round(remainingMs / 1000))}s of budget left`,
598
+ outputBudgetTokens,
599
+ effort: cfg.effort,
600
+ helperModelId: this.config.reactiveMentorship?.helperModelId,
601
+ }), timeoutMs);
602
+ const v = parseDeadlineExitVerdict(text ?? '');
603
+ if (store)
604
+ void store.recordEvent({
605
+ sessionId,
606
+ kind: 'deadline_exit_mentor',
607
+ detail: {
608
+ decision: v.decision, parsed: v.parsed, remainingMs, latencyMs: Date.now() - t0,
609
+ detailText: v.detail.slice(0, 2000), workProductSample: workProduct.slice(0, 1200),
610
+ },
611
+ }).catch(() => { });
612
+ switch (v.decision) {
613
+ case 'finish':
614
+ if (this.config.debug)
615
+ console.warn(`[DeadlineExit] FINISH — mentor confirms criteria met; ending turn`);
616
+ return { shouldBreak: true, signal: '' };
617
+ case 'retire':
618
+ if (this.config.debug)
619
+ console.warn(`[DeadlineExit] RETIRE — unclosable in residual (${v.detail.slice(0, 120)}); ending turn`);
620
+ return { shouldBreak: true, signal: '' };
621
+ case 'action':
622
+ return { shouldBreak: false, signal: `Time is short — a reviewer says ONE action closes the gap:\n${v.detail}\nDo exactly that, verify it against the task's real criteria, then finish.` };
623
+ default:
624
+ return { shouldBreak: false, signal: '' }; // CONTINUE → caller uses the normal warn nudge
625
+ }
626
+ }
627
+ catch (e) {
628
+ if (store)
629
+ void store.recordEvent({ sessionId, kind: 'deadline_exit_mentor', detail: { error: String(e?.message ?? e).slice(0, 120) } }).catch(() => { });
630
+ return null; // fail-safe: fall back to the normal warn nudge
631
+ }
632
+ }
633
+ /** Build the bounded work-product snapshot for the deadline checkpoint (latest answer + recent tool outputs). */
634
+ buildDeadlineWorkProduct(toolResults) {
635
+ const outs = toolResults
636
+ .slice(-6)
637
+ .map((r) => `[${r.tool_name}${r.is_error ? ' ERR' : ''}] ${(typeof r.content === 'string' ? r.content : JSON.stringify(r.content ?? '')).slice(0, 500)}`)
638
+ .join('\n');
639
+ return `${this.lastAssistantText()}\n\n--- recent tool outputs ---\n${outs}`.slice(0, 8000);
640
+ }
559
641
  /** Narrow `tools` to the anchor profile while the anchor is armed; no-op
560
642
  * once lifted or when CORTEX_TOOL_ANCHOR is unset. Applied AFTER the
561
643
  * deferred filter at both request-assembly sites. */
@@ -1386,6 +1468,16 @@ export class CortexOrchestrator {
1386
1468
  let toolCallIteration = 0;
1387
1469
  // Loop control settings — single source of truth via getLoopControlConfig()
1388
1470
  const loopDefaults = this.getLoopControlConfig();
1471
+ // CORTEX_ACTION_EFFORT: static reasoning effort for the PRIMARY (action) model — fills the
1472
+ // request param when unset (`??=`), so it overrides the model card's default but NOT an
1473
+ // explicit request param or the dynamic effort-pulse (both still win). Mentor calls
1474
+ // (lift-plan / endturn-resolver / deadline-exit) use their own *_EFFORT and are unaffected.
1475
+ // Enables the pro-track "wide effort delta" A/B (pro@low action + pro@max mentors).
1476
+ const __actionEffort = (process.env.CORTEX_ACTION_EFFORT || '').trim();
1477
+ if (__actionEffort) {
1478
+ options.parameters = options.parameters || {};
1479
+ options.parameters.reasoningEffort ??= __actionEffort;
1480
+ }
1389
1481
  const MAX_TOOL_ITERATIONS = loopDefaults.maxToolIterations;
1390
1482
  const TOOL_TIMEOUT_MS = loopDefaults.toolTimeoutMs;
1391
1483
  const MAX_CONSECUTIVE_ERRORS = loopDefaults.maxConsecutiveErrors;
@@ -2359,10 +2451,20 @@ export class CortexOrchestrator {
2359
2451
  const budgetSignal = computeToolBudgetSignal(effectiveToolBudgetCount, TOOL_BUDGET_SOFT, progressStalled);
2360
2452
  const diversityWarning = this.getDiversityWarning(toolCallCounts);
2361
2453
  // #2: one-shot wall-clock warning at 90% of the deadline — tell the model to converge + EndTurn.
2454
+ // DARK deadline-exit-mentor (CORTEX_DEADLINE_EXIT_MENTOR): at the warn rung, a bounded mentor
2455
+ // may decide FINISH/RETIRE (end now, cleanly) or ACTION (one directed step) instead of the dumb
2456
+ // "wrap up" nudge; null/CONTINUE falls back to the normal nudge. Hard-floor break below stays.
2362
2457
  let timeSignal = null;
2363
2458
  if (!timeWarnFired && timeBudgetState(Date.now() - loopStartMs, TURN_DEADLINE_MS) === 'warn') {
2364
- timeSignal = timeBudgetWarnNudge(Date.now() - loopStartMs, TURN_DEADLINE_MS);
2365
2459
  timeWarnFired = true;
2460
+ const de = await this.deadlineExitCheckpoint(this.lastRealUserText(), this.buildDeadlineWorkProduct(toolResults), TURN_DEADLINE_MS - (Date.now() - loopStartMs));
2461
+ if (de?.shouldBreak) {
2462
+ const dstore = this.getDecisionStore();
2463
+ if (dstore)
2464
+ void dstore.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'deadline_exit_break', detail: { iteration: toolCallIteration } }).catch(() => { });
2465
+ break; // mentor-directed clean exit → post-loop synthesis (resolver bypassed via abnormal-exit)
2466
+ }
2467
+ timeSignal = de?.signal || timeBudgetWarnNudge(Date.now() - loopStartMs, TURN_DEADLINE_MS);
2366
2468
  }
2367
2469
  if (budgetSignal || diversityWarning || ladderSignal || timeSignal) {
2368
2470
  const signals = [budgetSignal, diversityWarning, ladderSignal, timeSignal].filter(Boolean).join('\n');
@@ -3529,6 +3631,16 @@ export class CortexOrchestrator {
3529
3631
  let hasToolUse = initialToolUseBlocks.length > 0;
3530
3632
  // Loop control settings — single source of truth via getLoopControlConfig()
3531
3633
  const loopDefaults = this.getLoopControlConfig();
3634
+ // CORTEX_ACTION_EFFORT: static reasoning effort for the PRIMARY (action) model — fills the
3635
+ // request param when unset (`??=`), so it overrides the model card's default but NOT an
3636
+ // explicit request param or the dynamic effort-pulse (both still win). Mentor calls
3637
+ // (lift-plan / endturn-resolver / deadline-exit) use their own *_EFFORT and are unaffected.
3638
+ // Enables the pro-track "wide effort delta" A/B (pro@low action + pro@max mentors).
3639
+ const __actionEffort = (process.env.CORTEX_ACTION_EFFORT || '').trim();
3640
+ if (__actionEffort) {
3641
+ options.parameters = options.parameters || {};
3642
+ options.parameters.reasoningEffort ??= __actionEffort;
3643
+ }
3532
3644
  const MAX_TOOL_ITERATIONS = loopDefaults.maxToolIterations;
3533
3645
  const MAX_CONSECUTIVE_ERRORS = loopDefaults.maxConsecutiveErrors;
3534
3646
  const MAX_LOOP_REPETITIONS = loopDefaults.maxLoopRepetitions;
@@ -4064,11 +4176,19 @@ export class CortexOrchestrator {
4064
4176
  const progressStalled = isToolProgressStalled(allToolCalls);
4065
4177
  const budgetSignal = computeToolBudgetSignal(effectiveToolBudgetCount, TOOL_BUDGET_SOFT, progressStalled);
4066
4178
  const diversityWarning = this.getDiversityWarning(toolCallCounts);
4067
- // #2: one-shot wall-clock warning at 90% of the deadline (streaming parity).
4179
+ // #2: one-shot wall-clock warning at 90% of the deadline (streaming parity). DARK
4180
+ // deadline-exit-mentor may FINISH/RETIRE (end cleanly) or ACTION (one step) here.
4068
4181
  let timeSignal = null;
4069
4182
  if (!timeWarnFired && timeBudgetState(Date.now() - loopStartMs, TURN_DEADLINE_MS) === 'warn') {
4070
- timeSignal = timeBudgetWarnNudge(Date.now() - loopStartMs, TURN_DEADLINE_MS);
4071
4183
  timeWarnFired = true;
4184
+ const de = await this.deadlineExitCheckpoint(this.lastRealUserText(), this.buildDeadlineWorkProduct(toolResults), TURN_DEADLINE_MS - (Date.now() - loopStartMs));
4185
+ if (de?.shouldBreak) {
4186
+ const dstore = this.getDecisionStore();
4187
+ if (dstore)
4188
+ void dstore.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'deadline_exit_break', detail: { iteration: toolCallIteration } }).catch(() => { });
4189
+ break; // mentor-directed clean exit → post-loop synthesis (resolver bypassed via abnormal-exit)
4190
+ }
4191
+ timeSignal = de?.signal || timeBudgetWarnNudge(Date.now() - loopStartMs, TURN_DEADLINE_MS);
4072
4192
  }
4073
4193
  if (budgetSignal || diversityWarning || ladderSignal || timeSignal) {
4074
4194
  const signals = [budgetSignal, diversityWarning, ladderSignal, timeSignal].filter(Boolean).join('\n');
@@ -4954,7 +5074,12 @@ export class CortexOrchestrator {
4954
5074
  });
4955
5075
  });
4956
5076
  }
4957
- return [...this.messageHistory];
5077
+ // Strip model-only <system-reminder> scaffolding (lift-nudge / deferred
5078
+ // corpus / lift-plan append these onto history messages) — this accessor
5079
+ // feeds the TUI history view + session export, never the model request, so
5080
+ // the reminders must not surface to a human. Returns sanitized copies; the
5081
+ // model-facing messageHistory objects are untouched. See displayHistorySanitizer.
5082
+ return sanitizeHistoryForDisplay([...this.messageHistory]);
4958
5083
  }
4959
5084
  /**
4960
5085
  * Resume an existing session by loading its full history