@nexus-cortex/core 4.92.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 +148 -7
  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
@@ -394,12 +396,14 @@ export class CortexOrchestrator {
394
396
  // timeout/non-zero is still useful; empty on total failure → plan from observations alone).
395
397
  const envReport = this.gatherEnvReport();
396
398
  const timeoutMs = parseInt(process.env.CORTEX_LIFT_PLAN_TIMEOUT_MS ?? '90000', 10);
399
+ const t0 = Date.now();
397
400
  const plan = await withTimeout(this.helperMiddleware.generateTaskPlan({
398
401
  task: this.lastRealUserText(),
399
402
  observations,
400
403
  envReport,
401
404
  helperModelId: this.config.reactiveMentorship?.helperModelId,
402
405
  }), timeoutMs);
406
+ const latencyMs = Date.now() - t0;
403
407
  if (!plan || !plan.trim()) {
404
408
  if (store)
405
409
  void store.recordEvent({
@@ -419,7 +423,9 @@ export class CortexOrchestrator {
419
423
  void store.recordEvent({
420
424
  sessionId,
421
425
  kind: 'lift_plan',
422
- detail: { fired: true, planChars: plan.length, retire, criteriaStated },
426
+ // OBSERVABILITY (resolver-AB follow-up): bank the plan TEXT + latency, not just counts —
427
+ // so a k=5 run can score plan QUALITY (and read the reasoning behind a RETIRE).
428
+ detail: { fired: true, planChars: plan.length, retire, criteriaStated, latencyMs, planText: plan.slice(0, 4000) },
423
429
  }).catch(() => { });
424
430
  if (this.config.debug) {
425
431
  console.log(`[LiftPlan] plan delivered at lift (${plan.length} chars, retire=${retire}, criteria=${criteriaStated})`);
@@ -505,6 +511,7 @@ export class CortexOrchestrator {
505
511
  const workProduct = `${this.lastAssistantText()}\n\n--- checks / tool outputs this task ---\n${outputs}`.slice(0, 8000);
506
512
  const attestation = JSON.stringify(etInput).slice(0, 2000);
507
513
  const timeoutMs = parseInt(process.env.CORTEX_ENDTURN_RESOLVER_TIMEOUT_MS ?? '90000', 10);
514
+ const t0 = Date.now();
508
515
  const text = await withTimeout(this.helperMiddleware.evaluateEndTurn({
509
516
  task,
510
517
  envReport: this.gatherEnvReport(),
@@ -512,15 +519,34 @@ export class CortexOrchestrator {
512
519
  attestation,
513
520
  helperModelId: this.config.reactiveMentorship?.helperModelId,
514
521
  }), timeoutMs);
522
+ const latencyMs = Date.now() - t0;
515
523
  const verdict = parseResolverVerdict(text ?? ''); // withTimeout → null on timeout; fail-open to MEETS
524
+ // OBSERVABILITY (resolver-AB follow-up, 2026-09-05): bank the TEXT the resolver produced +
525
+ // what it judged, not just counts — so a k=5 run can score the resolver's JUDGMENT QUALITY
526
+ // (was the GAP warranted? was the fix plan good? did it judge the right artifact?) and not
527
+ // merely that it fired. Bounded caps keep the decisions row sane. See HARNESS_IMPROVEMENT_BACKLOG.
516
528
  if (store)
517
529
  void store.recordEvent({
518
530
  sessionId,
519
531
  kind: 'endturn_resolver',
520
532
  toolName: 'EndTurn',
521
- detail: { meets: verdict.meets, planChars: verdict.plan.length, rejects: this.endTurnResolverRejects, parsed: verdict.parsed },
533
+ detail: {
534
+ meets: verdict.meets, retire: verdict.retire, abstained: verdict.retire && cfg.abstain,
535
+ planChars: verdict.plan.length, rejects: this.endTurnResolverRejects, parsed: verdict.parsed,
536
+ latencyMs, rawLen: (text ?? '').length,
537
+ planText: verdict.plan.slice(0, 4000),
538
+ workProductSample: workProduct.slice(0, 1500),
539
+ attestation: attestation.slice(0, 800),
540
+ },
522
541
  }).catch(() => { });
523
- 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) {
524
550
  this.endTurnResolverRejects += 1;
525
551
  ev.endTurnCalled = false; // VETO the finish
526
552
  et.is_error = true;
@@ -540,6 +566,78 @@ export class CortexOrchestrator {
540
566
  // fail-open: leave the finish accepted
541
567
  }
542
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
+ }
543
641
  /** Narrow `tools` to the anchor profile while the anchor is armed; no-op
544
642
  * once lifted or when CORTEX_TOOL_ANCHOR is unset. Applied AFTER the
545
643
  * deferred filter at both request-assembly sites. */
@@ -1370,6 +1468,16 @@ export class CortexOrchestrator {
1370
1468
  let toolCallIteration = 0;
1371
1469
  // Loop control settings — single source of truth via getLoopControlConfig()
1372
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
+ }
1373
1481
  const MAX_TOOL_ITERATIONS = loopDefaults.maxToolIterations;
1374
1482
  const TOOL_TIMEOUT_MS = loopDefaults.toolTimeoutMs;
1375
1483
  const MAX_CONSECUTIVE_ERRORS = loopDefaults.maxConsecutiveErrors;
@@ -2343,10 +2451,20 @@ export class CortexOrchestrator {
2343
2451
  const budgetSignal = computeToolBudgetSignal(effectiveToolBudgetCount, TOOL_BUDGET_SOFT, progressStalled);
2344
2452
  const diversityWarning = this.getDiversityWarning(toolCallCounts);
2345
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.
2346
2457
  let timeSignal = null;
2347
2458
  if (!timeWarnFired && timeBudgetState(Date.now() - loopStartMs, TURN_DEADLINE_MS) === 'warn') {
2348
- timeSignal = timeBudgetWarnNudge(Date.now() - loopStartMs, TURN_DEADLINE_MS);
2349
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);
2350
2468
  }
2351
2469
  if (budgetSignal || diversityWarning || ladderSignal || timeSignal) {
2352
2470
  const signals = [budgetSignal, diversityWarning, ladderSignal, timeSignal].filter(Boolean).join('\n');
@@ -3513,6 +3631,16 @@ export class CortexOrchestrator {
3513
3631
  let hasToolUse = initialToolUseBlocks.length > 0;
3514
3632
  // Loop control settings — single source of truth via getLoopControlConfig()
3515
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
+ }
3516
3644
  const MAX_TOOL_ITERATIONS = loopDefaults.maxToolIterations;
3517
3645
  const MAX_CONSECUTIVE_ERRORS = loopDefaults.maxConsecutiveErrors;
3518
3646
  const MAX_LOOP_REPETITIONS = loopDefaults.maxLoopRepetitions;
@@ -4048,11 +4176,19 @@ export class CortexOrchestrator {
4048
4176
  const progressStalled = isToolProgressStalled(allToolCalls);
4049
4177
  const budgetSignal = computeToolBudgetSignal(effectiveToolBudgetCount, TOOL_BUDGET_SOFT, progressStalled);
4050
4178
  const diversityWarning = this.getDiversityWarning(toolCallCounts);
4051
- // #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.
4052
4181
  let timeSignal = null;
4053
4182
  if (!timeWarnFired && timeBudgetState(Date.now() - loopStartMs, TURN_DEADLINE_MS) === 'warn') {
4054
- timeSignal = timeBudgetWarnNudge(Date.now() - loopStartMs, TURN_DEADLINE_MS);
4055
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);
4056
4192
  }
4057
4193
  if (budgetSignal || diversityWarning || ladderSignal || timeSignal) {
4058
4194
  const signals = [budgetSignal, diversityWarning, ladderSignal, timeSignal].filter(Boolean).join('\n');
@@ -4938,7 +5074,12 @@ export class CortexOrchestrator {
4938
5074
  });
4939
5075
  });
4940
5076
  }
4941
- 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]);
4942
5083
  }
4943
5084
  /**
4944
5085
  * Resume an existing session by loading its full history