@nexus-cortex/core 4.81.0 → 4.83.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/dist/adapters/GatewayTranslationLayer.d.ts +19 -0
  2. package/dist/adapters/GatewayTranslationLayer.d.ts.map +1 -1
  3. package/dist/adapters/GatewayTranslationLayer.js +15 -1
  4. package/dist/adapters/GatewayTranslationLayer.js.map +1 -1
  5. package/dist/config/SettingsLoader.d.ts +5 -2
  6. package/dist/config/SettingsLoader.d.ts.map +1 -1
  7. package/dist/config/SettingsLoader.js +32 -2
  8. package/dist/config/SettingsLoader.js.map +1 -1
  9. package/dist/config/SettingsSchema.js +12 -12
  10. package/dist/config/SettingsSchema.js.map +1 -1
  11. package/dist/interfaces/APITransport.d.ts +4 -0
  12. package/dist/interfaces/APITransport.d.ts.map +1 -1
  13. package/dist/middleware/HelperModelMiddleware.d.ts +17 -0
  14. package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
  15. package/dist/middleware/HelperModelMiddleware.js +24 -0
  16. package/dist/middleware/HelperModelMiddleware.js.map +1 -1
  17. package/dist/orchestrator/APIClient.d.ts +9 -0
  18. package/dist/orchestrator/APIClient.d.ts.map +1 -1
  19. package/dist/orchestrator/APIClient.js +31 -0
  20. package/dist/orchestrator/APIClient.js.map +1 -1
  21. package/dist/orchestrator/CortexOrchestrator.d.ts +27 -0
  22. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  23. package/dist/orchestrator/CortexOrchestrator.js +145 -4
  24. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  25. package/dist/orchestrator/toolChoiceTranslation.d.ts +32 -0
  26. package/dist/orchestrator/toolChoiceTranslation.d.ts.map +1 -0
  27. package/dist/orchestrator/toolChoiceTranslation.js +74 -0
  28. package/dist/orchestrator/toolChoiceTranslation.js.map +1 -0
  29. package/dist/tools/registries/BaseToolRegistry.d.ts.map +1 -1
  30. package/dist/tools/registries/BaseToolRegistry.js +30 -0
  31. package/dist/tools/registries/BaseToolRegistry.js.map +1 -1
  32. package/dist/training/DecisionStore.d.ts +14 -1
  33. package/dist/training/DecisionStore.d.ts.map +1 -1
  34. package/dist/training/DecisionStore.js +67 -0
  35. package/dist/training/DecisionStore.js.map +1 -1
  36. package/dist/training/mentorConsult.d.ts +51 -0
  37. package/dist/training/mentorConsult.d.ts.map +1 -0
  38. package/dist/training/mentorConsult.js +73 -0
  39. package/dist/training/mentorConsult.js.map +1 -0
  40. package/dist/training/thrashDetector.d.ts +42 -0
  41. package/dist/training/thrashDetector.d.ts.map +1 -0
  42. package/dist/training/thrashDetector.js +45 -0
  43. package/dist/training/thrashDetector.js.map +1 -0
  44. package/package.json +3 -3
@@ -37,6 +37,8 @@ import { slashCommandRegistry } from '../commands/SlashCommandRegistry.js';
37
37
  import { prefixMcpToolName, parseMcpToolName } from '../mcp/mcpToolNamespacing.js';
38
38
  import { DecisionStore } from '../training/DecisionStore.js';
39
39
  import { formatPriorReminder, formatFamilyReminder, formatApproachReminder } from '../training/DecisionPriorInjector.js';
40
+ import { resolveConsultRung, resolveMentorConfig, bounceMessage, rateLimitedMessage, } from '../training/mentorConsult.js';
41
+ import { resolveThrashState, resolveThrashConfig } from '../training/thrashDetector.js';
40
42
  import { classifyErrorFamily } from '../training/errorFamily.js';
41
43
  import { classifyToolOutcome } from '../training/toolOutcome.js';
42
44
  import { LoopLadder, formatLadderSignal } from '../training/loopLadder.js';
@@ -811,8 +813,15 @@ export class CortexOrchestrator {
811
813
  if (structuredOutputState) {
812
814
  toolsToUse = ensureStructuredOutputTool(toolsToUse, structuredOutputState);
813
815
  }
816
+ // AskForAdvice (MENTORSHIP_ASK_FOR_ADVICE_SPEC §12): append the mentor tool AFTER
817
+ // the deferred filter + anchor (standard-tier tools are otherwise stripped), so a
818
+ // thrashing model can call it without a SearchTools round-trip. Session-stable (gated
819
+ // on mentorship-active, off by default), so the tool-prefix cache is not toggled mid-run.
820
+ toolsToUse = this.ensureAskForAdviceTool(toolsToUse);
814
821
  // Reset sequential call counter at start of each user turn
815
822
  this.mentorshipMiddleware?.resetSequentialCalls(this.currentSessionId);
823
+ // §13-B2: force AskForAdvice this turn on high-confidence thrash (mentor-force armed).
824
+ const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse);
816
825
  const preparedRequest = this.gatewayTranslation.prepareRequest(canonicalHistory, toolsToUse, effectiveModel, {
817
826
  temperature: options.parameters?.temperature,
818
827
  maxTokens: options.parameters?.maxTokens,
@@ -820,7 +829,8 @@ export class CortexOrchestrator {
820
829
  reasoningEffort: options.parameters?.reasoningEffort, // GPT-5.1 reasoning level
821
830
  stream: options.streaming,
822
831
  staticSystemPrompt: this.currentStaticSystemPrompt, // R28
823
- conversationId: this.currentConversationId // R28b
832
+ conversationId: this.currentConversationId, // R28b
833
+ toolChoice: forcedMentorChoice // §13-B2 forced mentor tool_choice
824
834
  });
825
835
  // Stateful Responses API: chain from prior response when available
826
836
  // (works across user turns — lastResponseId is preserved between messages).
@@ -3045,6 +3055,11 @@ export class CortexOrchestrator {
3045
3055
  if (structuredOutputState) {
3046
3056
  toolsToUse = ensureStructuredOutputTool(toolsToUse, structuredOutputState);
3047
3057
  }
3058
+ // AskForAdvice (MENTORSHIP_ASK_FOR_ADVICE_SPEC §12): append the mentor tool AFTER
3059
+ // the deferred filter + anchor (standard-tier tools are otherwise stripped), so a
3060
+ // thrashing model can call it without a SearchTools round-trip. Session-stable (gated
3061
+ // on mentorship-active, off by default), so the tool-prefix cache is not toggled mid-run.
3062
+ toolsToUse = this.ensureAskForAdviceTool(toolsToUse);
3048
3063
  // Reset sequential call counter at start of each user turn
3049
3064
  this.mentorshipMiddleware?.resetSequentialCalls(this.currentSessionId);
3050
3065
  // Input-slicing at initial (streaming) request: send only items since last checkpoint
@@ -3060,6 +3075,8 @@ export class CortexOrchestrator {
3060
3075
  if (initialCanSliceInputStreaming && this.config.debug) {
3061
3076
  console.log(`[Orchestrator Streaming] Input-sliced initial for cross-turn chain: sent ${initialHistoryForApiStreaming.length}/${messageHistoryForApi.length} messages`);
3062
3077
  }
3078
+ // §13-B2: force AskForAdvice this turn on high-confidence thrash (mentor-force armed).
3079
+ const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse);
3063
3080
  // Prepare request
3064
3081
  const preparedRequest = this.gatewayTranslation.prepareRequest(canonicalHistory, toolsToUse, effectiveModel, {
3065
3082
  temperature: options.parameters?.temperature,
@@ -3068,7 +3085,8 @@ export class CortexOrchestrator {
3068
3085
  reasoningEffort: options.parameters?.reasoningEffort, // GPT-5.1 reasoning level
3069
3086
  stream: true, // Enable streaming!
3070
3087
  staticSystemPrompt: this.currentStaticSystemPrompt, // R28
3071
- conversationId: this.currentConversationId // R28b
3088
+ conversationId: this.currentConversationId, // R28b
3089
+ toolChoice: forcedMentorChoice // §13-B2 forced mentor tool_choice
3072
3090
  });
3073
3091
  // Stateful Responses API: chain from prior response when available (cross-turn).
3074
3092
  if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
@@ -5468,6 +5486,8 @@ export class CortexOrchestrator {
5468
5486
  'MemoryRecall'
5469
5487
  ];
5470
5488
  const isContextManagementTool = contextManagementToolNames.includes(toolUse.name);
5489
+ // AskForAdvice (MENTORSHIP_ASK_FOR_ADVICE_SPEC): orchestrator-dispatched mentor tool.
5490
+ const isMentorTool = toolUse.name === 'AskForAdvice';
5471
5491
  // Phase 2.6: Check if this is an MCP management tool
5472
5492
  const mcpManagementToolNames = [
5473
5493
  'ListAvailableMcpServers',
@@ -5482,8 +5502,8 @@ export class CortexOrchestrator {
5482
5502
  // Phase 2.5 Day 4: Check if this is an MCP tool
5483
5503
  const mcpServerName = this.getMcpServerForTool(toolUse.name);
5484
5504
  const isMcpTool = mcpServerName !== undefined;
5485
- // Check if tool exists (context management, MCP management, MCP, or executor)
5486
- if (!isContextManagementTool && !isMcpManagementTool && !isMcpTool && !this.executorRegistry.hasExecutor(toolUse.name)) {
5505
+ // Check if tool exists (context management, mentor, MCP management, MCP, or executor)
5506
+ if (!isContextManagementTool && !isMentorTool && !isMcpManagementTool && !isMcpTool && !this.executorRegistry.hasExecutor(toolUse.name)) {
5487
5507
  const availableExecutors = this.executorRegistry.getExecutorNames();
5488
5508
  const availableMcpTools = this.mcpManager ? this.mcpManager.getAllTools().map(t => t.name) : [];
5489
5509
  const allAvailable = [...availableExecutors, ...availableMcpTools, ...contextManagementToolNames, ...mcpManagementToolNames];
@@ -5567,6 +5587,10 @@ export class CortexOrchestrator {
5567
5587
  }
5568
5588
  };
5569
5589
  }
5590
+ // AskForAdvice — consult the stronger mentor for a hint (spec §4-§5)
5591
+ else if (isMentorTool) {
5592
+ result = await this.executeAskForAdvice(toolUse.input);
5593
+ }
5570
5594
  // Phase 2.6: Execute MCP management tool
5571
5595
  else if (isMcpManagementTool) {
5572
5596
  if (this.config.debug) {
@@ -6699,6 +6723,123 @@ export class CortexOrchestrator {
6699
6723
  * Returns the (possibly-augmented) result. Failures in the store path
6700
6724
  * are swallowed so training never breaks tool execution.
6701
6725
  */
6726
+ /** Include AskForAdvice in the turn's tool set when mentorship is active (spec §12).
6727
+ * Appended post-filter so the deferred filter can't strip this standard-tier tool;
6728
+ * gated on reactiveMentorship.enabled (off by default) and idempotent. */
6729
+ ensureAskForAdviceTool(tools) {
6730
+ if (!this.config.reactiveMentorship?.enabled || !tools)
6731
+ return tools;
6732
+ if (tools.some((t) => t.name === 'AskForAdvice'))
6733
+ return tools;
6734
+ const def = toolFactory.getTool('AskForAdvice');
6735
+ return def ? [...tools, def] : tools;
6736
+ }
6737
+ /**
6738
+ * AskForAdvice v2 (§13-B2): decide whether to FORCE `AskForAdvice` for the NEXT
6739
+ * request (delivers the mentor hint through the heed-friendly tool-result channel,
6740
+ * routing around flash's weak voluntary heed — v1 measured 0/6). Gated on ALL of:
6741
+ * mentorship active, `CORTEX_MENTOR_FORCE=true` (default off), the tool is present
6742
+ * in this request's surface, the session is not yet rate-limited, and HIGH-confidence
6743
+ * thrash (the full `resolveThrashState` window — past the turn floor, a full window
6744
+ * of mostly-failures, currently failing). Returns the CANONICAL forced choice; the
6745
+ * gateway converts its name to the wire form. `tool_choice` is body-level → cache-safe.
6746
+ */
6747
+ async resolveForcedMentorChoice(toolsToUse) {
6748
+ if (!this.config.reactiveMentorship?.enabled)
6749
+ return undefined;
6750
+ if (process.env.CORTEX_MENTOR_FORCE !== 'true')
6751
+ return undefined;
6752
+ if (!toolsToUse?.some((t) => t.name === 'AskForAdvice'))
6753
+ return undefined;
6754
+ const sessionId = this.currentSessionId ?? 'unknown';
6755
+ if ((this.mentorConsultCounts.get(sessionId) ?? 0) >= resolveMentorConfig().maxConsults)
6756
+ return undefined;
6757
+ const store = this.getDecisionStore();
6758
+ if (!store)
6759
+ return undefined;
6760
+ try {
6761
+ const outcomes = await store.recentOutcomes(resolveThrashConfig().window);
6762
+ return resolveThrashState(outcomes, this.turnNumber).thrashing
6763
+ ? { type: 'tool', name: 'AskForAdvice' }
6764
+ : undefined;
6765
+ }
6766
+ catch {
6767
+ return undefined; // fail-open: never let the thrash read break a turn
6768
+ }
6769
+ }
6770
+ /** Honored AskForAdvice consults per session (rate-limit + rung state; persists across turns). */
6771
+ mentorConsultCounts = new Map();
6772
+ /**
6773
+ * AskForAdvice executor (MENTORSHIP_ASK_FOR_ADVICE_SPEC v1). Orchestrator-dispatched
6774
+ * because it needs helperMiddleware + the decision store + session state, which the
6775
+ * packages/executors executors structurally lack. Graduated ladder: premature bounce →
6776
+ * mentor DIRECTED REFRAME → structured interview → rate-limited. The stronger mentor
6777
+ * (reactiveMentorship.helperModelId, e.g. deepseek-v4-pro) returns a HINT, never the
6778
+ * solution. Banks a `mentor_consult` episode for the apprentice data lake. Fail-open —
6779
+ * a mentor error never breaks the turn.
6780
+ */
6781
+ async executeAskForAdvice(input) {
6782
+ const sessionId = this.currentSessionId ?? 'unknown';
6783
+ const store = this.getDecisionStore();
6784
+ const failedRows = store ? await store.recentFailures(6) : [];
6785
+ const failed = failedRows.map((d) => ({
6786
+ call: d.inputSummary || d.toolName,
6787
+ error: d.errorSnippet || '',
6788
+ }));
6789
+ const honored = this.mentorConsultCounts.get(sessionId) ?? 0;
6790
+ // v1 gate: the model self-selected by CALLING the tool, so a light "real struggle
6791
+ // exists" check (>=2 recent failures) suffices. The full thrashDetector drives the
6792
+ // proactive invite/forced-choice path (v2), not this executor.
6793
+ const thrashing = failed.length >= 2;
6794
+ const rung = resolveConsultRung(honored, thrashing);
6795
+ if (rung === 'bounce') {
6796
+ return { success: true, llmContent: bounceMessage(failed.length), metadata: { source: 'mentor-consult', rung } };
6797
+ }
6798
+ if (rung === 'ratelimited') {
6799
+ return { success: true, llmContent: rateLimitedMessage(resolveMentorConfig().maxConsults), metadata: { source: 'mentor-consult', rung } };
6800
+ }
6801
+ let hint;
6802
+ try {
6803
+ hint = await this.helperMiddleware.generateMentorHint({
6804
+ rung,
6805
+ task: this.lastRealUserText(),
6806
+ failed,
6807
+ question: input?.question,
6808
+ helperModelId: this.config.reactiveMentorship?.helperModelId,
6809
+ });
6810
+ }
6811
+ catch (err) {
6812
+ return {
6813
+ success: true,
6814
+ llmContent: 'Advice is unavailable right now — keep working the problem: re-read the task and try a distinct approach.',
6815
+ metadata: { source: 'mentor-consult', rung, error: String(err).slice(0, 120) },
6816
+ };
6817
+ }
6818
+ this.mentorConsultCounts.set(sessionId, honored + 1);
6819
+ if (store) {
6820
+ // §13-B3: the FULL episode for the apprentice data pump (§10). Tagged
6821
+ // `mentor_episode` so the distiller/canon can filter; `turn` lets it JOIN to
6822
+ // this task's graded outcome (the reward label lives on the task row, known
6823
+ // only at grade time). Full hint (not truncated) + the question + the failed
6824
+ // trace = the training-ready thrash→ask→hint record. Best-effort, never throws.
6825
+ store.recordEvent({
6826
+ sessionId,
6827
+ kind: 'mentor_consult',
6828
+ toolName: 'AskForAdvice',
6829
+ detail: {
6830
+ tag: 'mentor_episode',
6831
+ rung,
6832
+ turn: this.turnNumber,
6833
+ helperModel: this.config.reactiveMentorship?.helperModelId ?? 'default',
6834
+ question: input?.question ?? null,
6835
+ hint,
6836
+ failedCount: failed.length,
6837
+ failedTrace: failed.slice(0, 6),
6838
+ },
6839
+ }).catch(() => { });
6840
+ }
6841
+ return { success: true, llmContent: hint, metadata: { source: 'mentor-consult', rung, helperModel: this.config.reactiveMentorship?.helperModelId } };
6842
+ }
6702
6843
  async processToolTraining(toolUse, result) {
6703
6844
  const store = this.getDecisionStore();
6704
6845
  if (!store)