@nexus-cortex/core 4.124.44 → 4.124.46

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 (41) hide show
  1. package/.env.defaults +144 -0
  2. package/dist/adapters/GatewayTranslationLayer.d.ts.map +1 -1
  3. package/dist/adapters/GatewayTranslationLayer.js +27 -1
  4. package/dist/adapters/GatewayTranslationLayer.js.map +1 -1
  5. package/dist/adapters/ServerSideToolDetection.d.ts.map +1 -1
  6. package/dist/adapters/ServerSideToolDetection.js +5 -1
  7. package/dist/adapters/ServerSideToolDetection.js.map +1 -1
  8. package/dist/config/effectiveConfig.d.ts.map +1 -1
  9. package/dist/config/effectiveConfig.js +16 -0
  10. package/dist/config/effectiveConfig.js.map +1 -1
  11. package/dist/orchestrator/APIClient.d.ts.map +1 -1
  12. package/dist/orchestrator/APIClient.js +120 -25
  13. package/dist/orchestrator/APIClient.js.map +1 -1
  14. package/dist/orchestrator/CortexOrchestrator.d.ts +22 -0
  15. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  16. package/dist/orchestrator/CortexOrchestrator.js +210 -83
  17. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  18. package/dist/orchestrator/appendOnlyHistory.d.ts +39 -0
  19. package/dist/orchestrator/appendOnlyHistory.d.ts.map +1 -0
  20. package/dist/orchestrator/appendOnlyHistory.js +79 -0
  21. package/dist/orchestrator/appendOnlyHistory.js.map +1 -0
  22. package/dist/orchestrator/transportCacheFixes.d.ts +22 -0
  23. package/dist/orchestrator/transportCacheFixes.d.ts.map +1 -0
  24. package/dist/orchestrator/transportCacheFixes.js +114 -0
  25. package/dist/orchestrator/transportCacheFixes.js.map +1 -0
  26. package/dist/orchestrator/transportEffortFixes.d.ts +62 -0
  27. package/dist/orchestrator/transportEffortFixes.d.ts.map +1 -0
  28. package/dist/orchestrator/transportEffortFixes.js +114 -0
  29. package/dist/orchestrator/transportEffortFixes.js.map +1 -0
  30. package/dist/orchestrator/wallGuard.d.ts +3 -1
  31. package/dist/orchestrator/wallGuard.d.ts.map +1 -1
  32. package/dist/orchestrator/wallGuard.js +15 -2
  33. package/dist/orchestrator/wallGuard.js.map +1 -1
  34. package/dist/tools/ClientSideToolFilter.d.ts +9 -2
  35. package/dist/tools/ClientSideToolFilter.d.ts.map +1 -1
  36. package/dist/tools/ClientSideToolFilter.js +26 -3
  37. package/dist/tools/ClientSideToolFilter.js.map +1 -1
  38. package/dist/training/DecisionStore.d.ts +1 -1
  39. package/dist/training/DecisionStore.d.ts.map +1 -1
  40. package/dist/training/DecisionStore.js.map +1 -1
  41. package/package.json +3 -3
@@ -91,6 +91,8 @@ import { InitCortexContext, MemoryWrite, MemoryRecall } from '../tools/context-m
91
91
  import { pruneAgedToolResults } from '../conversation/ToolResultPruner.js';
92
92
  import { detectTailRepetition, tailLoopGuardEnabled } from './tailRepetitionDetector.js';
93
93
  import { resolveEffortRamp, rampEffortFor } from './effortRamp.js';
94
+ import { firstRequestActionEffort } from './transportEffortFixes.js'; // CORTEX_ACTION_EFFORT_FIRST (dark)
95
+ import { resolveWallCacheFix, resolvePersistInjected, resolveAppendOnlyTools, resolveKeepMentorMessages, resolveResponsesSliceAll, chainedSliceStart, tailUnitAfterLastAssistant, isPreviousResponseUnavailable } from './appendOnlyHistory.js'; // R221-R228 append-only history (dark)
94
96
  import { resolveWallDrop, resolveOutputCap, dropWalledTurn, resolveWallSummary, extractWalledReasoning, clipReasoning, formatWallSummary } from './wallGuard.js';
95
97
  import { resolvePid1Guard, detectPid1Note } from './pid1Guard.js'; // R209
96
98
  import { PlateauStopTracker, plateauBootInstruction, plateauStatusSegment } from './plateauStop.js'; // R210
@@ -382,6 +384,7 @@ export class CortexOrchestrator {
382
384
  type: 'text',
383
385
  text: `<system-reminder>\nFull session context follows (deferred until your first action; applies from here on).\n</system-reminder>\n${corpus}`
384
386
  });
387
+ this.invalidateTailUnitConversions(); // R221 safety net (CORTEX_WALL_CACHE_FIX; off = no-op)
385
388
  if (this.config.debug) {
386
389
  console.log(`[Anchor] deferred static corpus delivered at lift (${corpus.length} chars)`);
387
390
  }
@@ -424,6 +427,7 @@ export class CortexOrchestrator {
424
427
  if (!r)
425
428
  return;
426
429
  lastMsg.message.content.push({ type: 'text', text: `<system-reminder>\n${r.note}\n</system-reminder>` });
430
+ this.invalidateTailUnitConversions(); // R221 safety net
427
431
  const store = this.getDecisionStore();
428
432
  if (store)
429
433
  void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'pid1_guard', detail: { noted: true, pid1Cmd: r.info.pid1Cmd, children: r.info.children, chars: r.note.length } }).catch(() => { });
@@ -447,6 +451,7 @@ export class CortexOrchestrator {
447
451
  return; // not the boundary yet — leave the one-shot open
448
452
  this.plateauInstructionDelivered = true;
449
453
  lastMsg.message.content.push({ type: 'text', text: `<system-reminder>\n${text}\n</system-reminder>` });
454
+ this.invalidateTailUnitConversions(); // R221 safety net
450
455
  if (this.config.debug)
451
456
  console.log('[PlateauStop] boot instruction delivered');
452
457
  }
@@ -521,6 +526,7 @@ export class CortexOrchestrator {
521
526
  text: `<system-reminder>\n${parts.join(' ')}\n</system-reminder>`
522
527
  });
523
528
  this.liftNudgeDelivered = true; // one-shot: consumed only on ACTUAL delivery
529
+ this.invalidateTailUnitConversions(); // R221 safety net
524
530
  if (this.config.debug) {
525
531
  console.log(`[Anchor] lift nudge delivered (${parts.join(' ').length} chars, ${hidden.length} hidden tools)`);
526
532
  }
@@ -657,6 +663,7 @@ export class CortexOrchestrator {
657
663
  `It is anchored to the REAL success criteria (what the task's own grader checks), NOT your own ` +
658
664
  `tests. Your own tests are a means, never the finish line.\n\n${plan}\n</system-reminder>`,
659
665
  });
666
+ this.invalidateTailUnitConversions(); // R221 safety net
660
667
  if (store)
661
668
  void store.recordEvent({
662
669
  sessionId,
@@ -702,6 +709,7 @@ export class CortexOrchestrator {
702
709
  return; // nothing authored (yet) — leave the one-shot open
703
710
  this.reqLedgerDelivered = true;
704
711
  lastMsg?.message?.content?.push?.({ type: 'text', text: `<system-reminder>\n${text}\n</system-reminder>` });
712
+ this.invalidateTailUnitConversions(); // R221 safety net
705
713
  const store = this.getDecisionStore();
706
714
  if (store)
707
715
  void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'requirement_ledger_delivered', toolName: 'EndTurn', detail: { lines: (entries ?? []).length, chars: text.length } }).catch(() => { });
@@ -2105,6 +2113,7 @@ export class CortexOrchestrator {
2105
2113
  }
2106
2114
  };
2107
2115
  messageHistoryForApi[messageHistoryForApi.length - 1] = userMessageForApi;
2116
+ this.rememberInjectedContent(userMessage.uuid, injectedContent); // R223 (CORTEX_PERSIST_INJECTED; off = no-op): later requests send the same bytes
2108
2117
  // canonicalHistory will be re-computed after effectiveModel is determined
2109
2118
  // (see input-slicing for cross-turn chain below).
2110
2119
  // Debug logging for tools (already gathered above)
@@ -2172,7 +2181,7 @@ export class CortexOrchestrator {
2172
2181
  const isPTCEnabled = this.config.enablePTC && effectiveModel.supportsPTC && effectiveModel.provider === 'anthropic';
2173
2182
  // Deferred loading: filter tools for non-PTC paths (essential + recently-used only)
2174
2183
  if (!isPTCEnabled && this.effectiveDeferredLoading && toolsToUse && toolsToUse.length > 0) {
2175
- toolsToUse = this.toolFilter.getFilteredTools(toolsToUse);
2184
+ toolsToUse = this.toolFilter.getFilteredTools(toolsToUse, { appendOnly: resolveAppendOnlyTools() });
2176
2185
  }
2177
2186
  // First-turn anchoring narrows further while armed (no-op once lifted).
2178
2187
  // Card-level home door: capture the active card's anchorProfile (env
@@ -2211,16 +2220,27 @@ export class CortexOrchestrator {
2211
2220
  this.mentorshipMiddleware?.resetSequentialCalls(this.currentSessionId);
2212
2221
  // §13-B2: force AskForAdvice this turn on high-confidence thrash (mentor-force armed).
2213
2222
  const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse, 0); // initial request: 0 tool calls yet
2214
- const preparedRequest = this.gatewayTranslation.prepareRequest(canonicalHistory, toolsToUse, effectiveModel, {
2223
+ const initialPrepOpts = {
2215
2224
  temperature: options.parameters?.temperature,
2216
2225
  maxTokens: options.parameters?.maxTokens,
2217
2226
  topP: options.parameters?.topP,
2218
- reasoningEffort: this.nextRampEffort(true) ?? options.parameters?.reasoningEffort, // HB-EFFORT-RAMP (dark) > request param
2227
+ reasoningEffort: this.nextRampEffort(true) ?? options.parameters?.reasoningEffort ?? firstRequestActionEffort(), // HB-EFFORT-RAMP (dark) > request param > CORTEX_ACTION_EFFORT on request 0 (CORTEX_ACTION_EFFORT_FIRST, dark)
2219
2228
  stream: options.streaming,
2220
2229
  staticSystemPrompt: this.currentStaticSystemPrompt, // R28
2221
2230
  conversationId: this.currentConversationId, // R28b
2222
2231
  toolChoice: forcedMentorChoice // §13-B2 forced mentor tool_choice
2223
- });
2232
+ };
2233
+ const preparedRequest = this.gatewayTranslation.prepareRequest(canonicalHistory, toolsToUse, effectiveModel, initialPrepOpts);
2234
+ // R228 fallback (dark): the unchained full-history twin of preparedRequest, built only if the held response is gone.
2235
+ const rebuildInitialFull = () => {
2236
+ const r = this.gatewayTranslation.prepareRequest(this.convertToCanonicalMessages(messageHistoryForApi), toolsToUse, effectiveModel, initialPrepOpts);
2237
+ r.conversationId = this.currentSessionId;
2238
+ if (isPTCEnabled && toolsToUse && toolsToUse.length > 0) {
2239
+ r.tools = this.gatewayTranslation.prepareToolsWithPTC(toolsToUse, effectiveModel);
2240
+ r.parameters.enablePTC = true;
2241
+ }
2242
+ return r;
2243
+ };
2224
2244
  // Stateful Responses API: chain from prior response when available
2225
2245
  // (works across user turns — lastResponseId is preserved between messages).
2226
2246
  // Only affects 'responses' api.pattern; other providers ignore this field.
@@ -2257,8 +2277,11 @@ export class CortexOrchestrator {
2257
2277
  // Use effectiveModel (which may have modified endpoint/pattern for server-side tools)
2258
2278
  // Wrap API call with retry middleware for automatic retry on transient errors
2259
2279
  if (this.retryMiddleware) {
2260
- const retryResult = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(preparedRequest, effectiveModel), 'primary_api_call');
2261
- apiResponse = retryResult.result;
2280
+ let retryResult;
2281
+ apiResponse = await this.sendWithChainFallback(preparedRequest, async (r) => {
2282
+ retryResult = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(r, effectiveModel), 'primary_api_call');
2283
+ return retryResult.result;
2284
+ }, rebuildInitialFull, 'primary_api_call'); // R228 fallback (dark; off = one send)
2262
2285
  // Log retry information if retries occurred
2263
2286
  if (this.config.debug && retryResult.attemptCount > 1) {
2264
2287
  console.log(`[Orchestrator] API call succeeded after ${retryResult.attemptCount} attempts`);
@@ -2267,7 +2290,7 @@ export class CortexOrchestrator {
2267
2290
  }
2268
2291
  else {
2269
2292
  // Fallback: direct call if middleware not available
2270
- apiResponse = await this.apiClient.sendRequest(preparedRequest, effectiveModel);
2293
+ apiResponse = await this.sendWithChainFallback(preparedRequest, (r) => this.apiClient.sendRequest(r, effectiveModel), rebuildInitialFull, 'primary_api_call'); // R228 fallback (dark)
2271
2294
  }
2272
2295
  }
2273
2296
  catch (error) {
@@ -2699,6 +2722,7 @@ export class CortexOrchestrator {
2699
2722
  if (isEmpty) {
2700
2723
  m.message = m.message || { role: 'assistant' };
2701
2724
  m.message.content = [{ type: 'text', text: '(no output)' }];
2725
+ this.invalidateConversions([m]); // R221 safety net
2702
2726
  if (this.config.debug) {
2703
2727
  console.log(`[Orchestrator] R26: repaired empty assistant message at history[${i}] before retry`);
2704
2728
  }
@@ -2740,7 +2764,7 @@ export class CortexOrchestrator {
2740
2764
  await this.ensureHistoryFitsModel(effectiveModel);
2741
2765
  const retryEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
2742
2766
  const buildRetryRequest = () => {
2743
- const retryCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
2767
+ const retryCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
2744
2768
  const req = this.gatewayTranslation.prepareRequest(retryCanonicalHistory, toolsToUse, effectiveModel, {
2745
2769
  temperature: options.parameters?.temperature,
2746
2770
  maxTokens: options.parameters?.maxTokens,
@@ -2789,6 +2813,7 @@ export class CortexOrchestrator {
2789
2813
  ...(retryConverted.usage && { usage: retryConverted.usage }),
2790
2814
  };
2791
2815
  this.messageHistory.push(retryAssistantMessage);
2816
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
2792
2817
  await this.historyStore.appendMessage(this.currentSessionId, retryAssistantMessage);
2793
2818
  // Update current pointer so the loop re-evaluates with new content.
2794
2819
  currentAssistantMessage = retryAssistantMessage;
@@ -2860,28 +2885,28 @@ export class CortexOrchestrator {
2860
2885
  this.messageHistory.push(inactionUserMessage);
2861
2886
  await this.historyStore.appendMessage(this.currentSessionId, inactionUserMessage);
2862
2887
  await this.ensureHistoryFitsModel(effectiveModel);
2863
- const inactionCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
2864
- const inactionRequest = this.gatewayTranslation.prepareRequest(inactionCanonicalHistory, toolsToUse, effectiveModel, {
2865
- temperature: options.parameters?.temperature,
2866
- maxTokens: options.parameters?.maxTokens,
2867
- topP: options.parameters?.topP,
2868
- reasoningEffort: this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort, // R153 backoff > request param
2869
- stream: options.streaming,
2870
- staticSystemPrompt: this.currentStaticSystemPrompt, // R28
2871
- conversationId: this.currentConversationId, // R28b
2872
- });
2873
- if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
2874
- inactionRequest.previousResponseId = this.lastResponseId;
2875
- }
2876
- inactionRequest.conversationId = this.currentSessionId;
2877
- let inactionApiResponse;
2878
- if (this.retryMiddleware) {
2879
- const r = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(inactionRequest, effectiveModel), 'inaction_nudge_retry');
2880
- inactionApiResponse = r.result;
2881
- }
2882
- else {
2883
- inactionApiResponse = await this.apiClient.sendRequest(inactionRequest, effectiveModel);
2884
- }
2888
+ const inactionEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param (consumed once)
2889
+ const buildInactionRequest = () => {
2890
+ const inactionCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
2891
+ const inactionRequest = this.gatewayTranslation.prepareRequest(inactionCanonicalHistory, toolsToUse, effectiveModel, {
2892
+ temperature: options.parameters?.temperature,
2893
+ maxTokens: options.parameters?.maxTokens,
2894
+ topP: options.parameters?.topP,
2895
+ reasoningEffort: inactionEffort,
2896
+ stream: options.streaming,
2897
+ staticSystemPrompt: this.currentStaticSystemPrompt, // R28
2898
+ conversationId: this.currentConversationId, // R28b
2899
+ });
2900
+ if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
2901
+ inactionRequest.previousResponseId = this.lastResponseId;
2902
+ }
2903
+ inactionRequest.conversationId = this.currentSessionId;
2904
+ return inactionRequest;
2905
+ };
2906
+ const sendInactionRequest = async (r) => this.retryMiddleware
2907
+ ? (await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(r, effectiveModel), 'inaction_nudge_retry')).result
2908
+ : this.apiClient.sendRequest(r, effectiveModel);
2909
+ const inactionApiResponse = await this.sendWithChainFallback(buildInactionRequest(), sendInactionRequest, buildInactionRequest, 'inaction_nudge_retry'); // R228 fallback (dark; off = one send)
2885
2910
  const inactionConverted = this.gatewayTranslation.convertResponse(inactionApiResponse.data, effectiveModel, {
2886
2911
  sessionId: this.currentSessionId,
2887
2912
  conversationId: this.currentConversationId,
@@ -2914,6 +2939,7 @@ export class CortexOrchestrator {
2914
2939
  ...(inactionConverted.usage && { usage: inactionConverted.usage }),
2915
2940
  };
2916
2941
  this.messageHistory.push(inactionAssistantMessage);
2942
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
2917
2943
  await this.historyStore.appendMessage(this.currentSessionId, inactionAssistantMessage);
2918
2944
  currentAssistantMessage = inactionAssistantMessage;
2919
2945
  currentAssistantCanonicalMessage = inactionAssistantCanonical;
@@ -3029,6 +3055,7 @@ export class CortexOrchestrator {
3029
3055
  if (isEmpty) {
3030
3056
  m.message = m.message || { role: 'assistant' };
3031
3057
  m.message.content = [{ type: 'text', text: '(no output)' }];
3058
+ this.invalidateConversions([m]); // R221 safety net
3032
3059
  }
3033
3060
  break;
3034
3061
  }
@@ -3075,28 +3102,28 @@ export class CortexOrchestrator {
3075
3102
  this.messageHistory.push(endTurnReminder);
3076
3103
  await this.historyStore.appendMessage(this.currentSessionId, endTurnReminder);
3077
3104
  await this.ensureHistoryFitsModel(effectiveModel);
3078
- const gateCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
3079
- const gateRequest = this.gatewayTranslation.prepareRequest(gateCanonicalHistory, toolsToUse, effectiveModel, {
3080
- temperature: options.parameters?.temperature,
3081
- maxTokens: options.parameters?.maxTokens,
3082
- topP: options.parameters?.topP,
3083
- reasoningEffort: this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort, // R153 backoff > request param
3084
- stream: options.streaming,
3085
- staticSystemPrompt: this.currentStaticSystemPrompt,
3086
- conversationId: this.currentConversationId,
3087
- });
3088
- if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
3089
- gateRequest.previousResponseId = this.lastResponseId;
3090
- }
3091
- gateRequest.conversationId = this.currentSessionId;
3092
- let gateApiResponse;
3093
- if (this.retryMiddleware) {
3094
- const gateResult = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(gateRequest, effectiveModel), 'end_turn_gate');
3095
- gateApiResponse = gateResult.result;
3096
- }
3097
- else {
3098
- gateApiResponse = await this.apiClient.sendRequest(gateRequest, effectiveModel);
3099
- }
3105
+ const gateEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param (consumed once)
3106
+ const buildGateRequest = () => {
3107
+ const gateCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
3108
+ const gateRequest = this.gatewayTranslation.prepareRequest(gateCanonicalHistory, toolsToUse, effectiveModel, {
3109
+ temperature: options.parameters?.temperature,
3110
+ maxTokens: options.parameters?.maxTokens,
3111
+ topP: options.parameters?.topP,
3112
+ reasoningEffort: gateEffort,
3113
+ stream: options.streaming,
3114
+ staticSystemPrompt: this.currentStaticSystemPrompt,
3115
+ conversationId: this.currentConversationId,
3116
+ });
3117
+ if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
3118
+ gateRequest.previousResponseId = this.lastResponseId;
3119
+ }
3120
+ gateRequest.conversationId = this.currentSessionId;
3121
+ return gateRequest;
3122
+ };
3123
+ const sendGate = async (r) => this.retryMiddleware
3124
+ ? (await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(r, effectiveModel), 'end_turn_gate')).result
3125
+ : this.apiClient.sendRequest(r, effectiveModel);
3126
+ const gateApiResponse = await this.sendWithChainFallback(buildGateRequest(), sendGate, buildGateRequest, 'end_turn_gate'); // R228 fallback (dark; off = one send)
3100
3127
  const gateConverted = this.gatewayTranslation.convertResponse(gateApiResponse.data, effectiveModel, {
3101
3128
  sessionId: this.currentSessionId,
3102
3129
  conversationId: this.currentConversationId,
@@ -3129,6 +3156,7 @@ export class CortexOrchestrator {
3129
3156
  ...(gateConverted.usage && { usage: gateConverted.usage }),
3130
3157
  };
3131
3158
  this.messageHistory.push(gateAssistantMessage);
3159
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
3132
3160
  await this.historyStore.appendMessage(this.currentSessionId, gateAssistantMessage);
3133
3161
  currentAssistantMessage = gateAssistantMessage;
3134
3162
  currentAssistantCanonicalMessage = gateAssistantCanonical;
@@ -3589,6 +3617,7 @@ export class CortexOrchestrator {
3589
3617
  const block = lastMsg.message.content[0];
3590
3618
  const existing = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
3591
3619
  block.content = existing + '\n\n' + signals;
3620
+ this.invalidateTailUnitConversions(); // R221 safety net
3592
3621
  // Observability (backlog item 3, micro-suite defect #4): this
3593
3622
  // mutation happens AFTER the tool_result was persisted — the
3594
3623
  // durable session lacks the steering the model saw. Bank a
@@ -3723,7 +3752,7 @@ export class CortexOrchestrator {
3723
3752
  // are included in the continuation request's tools array
3724
3753
  if (this.effectiveDeferredLoading && !isPTCEnabled) {
3725
3754
  const beforeCount = toolsToUse.length;
3726
- toolsToUse = this.toolFilter.getFilteredTools(allTools);
3755
+ toolsToUse = this.toolFilter.getFilteredTools(allTools, { appendOnly: resolveAppendOnlyTools() });
3727
3756
  // StructuredOutput: the re-filter rebuilds from allTools (which never
3728
3757
  // contained the request-scoped tool) — re-append so it stays present
3729
3758
  // on every request of the turn.
@@ -4028,7 +4057,7 @@ export class CortexOrchestrator {
4028
4057
  await this.ensureHistoryFitsModel(effectiveModel);
4029
4058
  const synthEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
4030
4059
  const buildSynthRequest = () => {
4031
- const synthCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
4060
+ const synthCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
4032
4061
  const req = this.gatewayTranslation.prepareRequest(synthCanonicalHistory,
4033
4062
  // Tools suppressed — the model MUST produce text, not call more tools.
4034
4063
  // NOTE (structured-output constraint, 2026-08-02): this is R29a's
@@ -4085,6 +4114,7 @@ export class CortexOrchestrator {
4085
4114
  ...(synthConverted.usage && { usage: synthConverted.usage }),
4086
4115
  };
4087
4116
  this.messageHistory.push(synthAssistantMessage);
4117
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
4088
4118
  await this.historyStore.appendMessage(this.currentSessionId, synthAssistantMessage);
4089
4119
  this.sessionTimeline.recordMessage(synthAssistantMessage.uuid, 'assistant');
4090
4120
  if (synthConverted.usage) {
@@ -4487,6 +4517,7 @@ export class CortexOrchestrator {
4487
4517
  }
4488
4518
  };
4489
4519
  messageHistoryForApi[messageHistoryForApi.length - 1] = userMessageForApi;
4520
+ this.rememberInjectedContent(userMessage.uuid, injectedContent); // R223 (CORTEX_PERSIST_INJECTED; off = no-op): later requests send the same bytes
4490
4521
  // canonicalHistory computed after effectiveModel is determined (input-slicing below).
4491
4522
  // Tools already gathered above for middleware injection
4492
4523
  // Server-side tool detection
@@ -4514,7 +4545,7 @@ export class CortexOrchestrator {
4514
4545
  const isPTCEnabled = this.config.enablePTC && effectiveModel.supportsPTC && effectiveModel.provider === 'anthropic';
4515
4546
  // Deferred loading: filter tools for non-PTC paths (essential + recently-used only)
4516
4547
  if (!isPTCEnabled && this.effectiveDeferredLoading && toolsToUse && toolsToUse.length > 0) {
4517
- toolsToUse = this.toolFilter.getFilteredTools(toolsToUse);
4548
+ toolsToUse = this.toolFilter.getFilteredTools(toolsToUse, { appendOnly: resolveAppendOnlyTools() });
4518
4549
  }
4519
4550
  // First-turn anchoring narrows further while armed (no-op once lifted).
4520
4551
  // Card-level home door: capture the active card's anchorProfile (env
@@ -4568,7 +4599,7 @@ export class CortexOrchestrator {
4568
4599
  temperature: options.parameters?.temperature,
4569
4600
  maxTokens: options.parameters?.maxTokens,
4570
4601
  topP: options.parameters?.topP,
4571
- reasoningEffort: this.nextRampEffort(true) ?? options.parameters?.reasoningEffort, // HB-EFFORT-RAMP (dark) > request param
4602
+ reasoningEffort: this.nextRampEffort(true) ?? options.parameters?.reasoningEffort ?? firstRequestActionEffort(), // HB-EFFORT-RAMP (dark) > request param > CORTEX_ACTION_EFFORT on request 0 (CORTEX_ACTION_EFFORT_FIRST, dark)
4572
4603
  stream: true, // Enable streaming!
4573
4604
  staticSystemPrompt: this.currentStaticSystemPrompt, // R28
4574
4605
  conversationId: this.currentConversationId, // R28b
@@ -4909,6 +4940,7 @@ export class CortexOrchestrator {
4909
4940
  if (isEmpty) {
4910
4941
  m.message = m.message || { role: 'assistant' };
4911
4942
  m.message.content = [{ type: 'text', text: '(no output)' }];
4943
+ this.invalidateConversions([m]); // R221 safety net
4912
4944
  }
4913
4945
  break;
4914
4946
  }
@@ -4942,7 +4974,7 @@ export class CortexOrchestrator {
4942
4974
  await this.ensureHistoryFitsModel(effectiveModel);
4943
4975
  const retryEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
4944
4976
  const buildRetryRequest = () => {
4945
- const retryCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
4977
+ const retryCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
4946
4978
  const req = this.gatewayTranslation.prepareRequest(retryCanonicalHistory, toolsToUse, effectiveModel, {
4947
4979
  temperature: options.parameters?.temperature,
4948
4980
  maxTokens: options.parameters?.maxTokens,
@@ -4991,6 +5023,7 @@ export class CortexOrchestrator {
4991
5023
  ...(retryConverted.usage && { usage: retryConverted.usage }),
4992
5024
  };
4993
5025
  this.messageHistory.push(retryAssistantMessage);
5026
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
4994
5027
  await this.historyStore.appendMessage(this.currentSessionId, retryAssistantMessage);
4995
5028
  currentAssistantCanonicalMessage = retryAssistantCanonical;
4996
5029
  lastStopReason = retryConverted.stopReason; // R153
@@ -5068,28 +5101,28 @@ export class CortexOrchestrator {
5068
5101
  await this.historyStore.appendMessage(this.currentSessionId, inactionUserMessage);
5069
5102
  try {
5070
5103
  await this.ensureHistoryFitsModel(effectiveModel);
5071
- const retryCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
5072
- const retryRequest = this.gatewayTranslation.prepareRequest(retryCanonicalHistory, toolsToUse, effectiveModel, {
5073
- temperature: options.parameters?.temperature,
5074
- maxTokens: options.parameters?.maxTokens,
5075
- topP: options.parameters?.topP,
5076
- reasoningEffort: this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort, // R153 backoff > request param
5077
- stream: false,
5078
- staticSystemPrompt: this.currentStaticSystemPrompt,
5079
- conversationId: this.currentConversationId,
5080
- });
5081
- if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
5082
- retryRequest.previousResponseId = this.lastResponseId;
5083
- }
5084
- retryRequest.conversationId = this.currentSessionId;
5085
- let retryApiResponse;
5086
- if (this.retryMiddleware) {
5087
- const retryResult = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(retryRequest, effectiveModel), 'inaction_nudge_retry');
5088
- retryApiResponse = retryResult.result;
5089
- }
5090
- else {
5091
- retryApiResponse = await this.apiClient.sendRequest(retryRequest, effectiveModel);
5092
- }
5104
+ const retryEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param (consumed once)
5105
+ const buildRetryRequest = () => {
5106
+ const retryCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
5107
+ const retryRequest = this.gatewayTranslation.prepareRequest(retryCanonicalHistory, toolsToUse, effectiveModel, {
5108
+ temperature: options.parameters?.temperature,
5109
+ maxTokens: options.parameters?.maxTokens,
5110
+ topP: options.parameters?.topP,
5111
+ reasoningEffort: retryEffort,
5112
+ stream: false,
5113
+ staticSystemPrompt: this.currentStaticSystemPrompt,
5114
+ conversationId: this.currentConversationId,
5115
+ });
5116
+ if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
5117
+ retryRequest.previousResponseId = this.lastResponseId;
5118
+ }
5119
+ retryRequest.conversationId = this.currentSessionId;
5120
+ return retryRequest;
5121
+ };
5122
+ const sendRetryRequest = async (r) => this.retryMiddleware
5123
+ ? (await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(r, effectiveModel), 'inaction_nudge_retry')).result
5124
+ : this.apiClient.sendRequest(r, effectiveModel);
5125
+ const retryApiResponse = await this.sendWithChainFallback(buildRetryRequest(), sendRetryRequest, buildRetryRequest, 'inaction_nudge_retry'); // R228 fallback (dark; off = one send)
5093
5126
  const retryConverted = this.gatewayTranslation.convertResponse(retryApiResponse.data, effectiveModel, {
5094
5127
  sessionId: this.currentSessionId,
5095
5128
  conversationId: this.currentConversationId,
@@ -5122,6 +5155,7 @@ export class CortexOrchestrator {
5122
5155
  ...(retryConverted.usage && { usage: retryConverted.usage }),
5123
5156
  };
5124
5157
  this.messageHistory.push(retryAssistantMessage);
5158
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
5125
5159
  await this.historyStore.appendMessage(this.currentSessionId, retryAssistantMessage);
5126
5160
  currentAssistantCanonicalMessage = retryAssistantCanonical;
5127
5161
  lastStopReason = retryConverted.stopReason; // R153
@@ -5428,6 +5462,7 @@ export class CortexOrchestrator {
5428
5462
  const block = lastMsg.message.content[0];
5429
5463
  const existing = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
5430
5464
  block.content = existing + '\n\n' + signals;
5465
+ this.invalidateTailUnitConversions(); // R221 safety net
5431
5466
  // Observability (backlog item 3): post-persist mutation — bank a
5432
5467
  // steering_injected event (streaming parity).
5433
5468
  const store = this.getDecisionStore();
@@ -5544,7 +5579,7 @@ export class CortexOrchestrator {
5544
5579
  }
5545
5580
  // Re-filter tools after SearchTools discovery (same as sendMessage path)
5546
5581
  if (this.effectiveDeferredLoading && !isPTCEnabled) {
5547
- toolsToUse = this.toolFilter.getFilteredTools(allTools);
5582
+ toolsToUse = this.toolFilter.getFilteredTools(allTools, { appendOnly: resolveAppendOnlyTools() });
5548
5583
  // StructuredOutput: re-filter rebuilds from allTools — re-append the
5549
5584
  // request-scoped tool (same as the sendMessage continuation path).
5550
5585
  if (structuredOutputState) {
@@ -5935,7 +5970,7 @@ export class CortexOrchestrator {
5935
5970
  await this.ensureHistoryFitsModel(effectiveModel);
5936
5971
  const synthEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
5937
5972
  const buildSynthRequest = () => {
5938
- const synthCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
5973
+ const synthCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
5939
5974
  const req = this.gatewayTranslation.prepareRequest(synthCanonicalHistory, [], // tools suppressed — must produce text
5940
5975
  effectiveModel, {
5941
5976
  temperature: options.parameters?.temperature,
@@ -5989,6 +6024,7 @@ export class CortexOrchestrator {
5989
6024
  ...(synthConverted.usage && { usage: synthConverted.usage }),
5990
6025
  };
5991
6026
  this.messageHistory.push(synthAssistantMessage);
6027
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
5992
6028
  await this.historyStore.appendMessage(this.currentSessionId, synthAssistantMessage);
5993
6029
  this.sessionTimeline.recordMessage(synthAssistantMessage.uuid, 'assistant');
5994
6030
  currentAssistantCanonicalMessage = synthCanonical;
@@ -6338,6 +6374,7 @@ export class CortexOrchestrator {
6338
6374
  // cleared on session reset so stale entries (including synthetic-repair
6339
6375
  // uuids from the previous session) can't leak into the new session.
6340
6376
  this.canonicalConversionCache.clear();
6377
+ this.injectedApiContent.clear(); // R223
6341
6378
  // Reset XAI Responses API chain for new session (was leaking across `--new`).
6342
6379
  // Without this, a new session would reuse lastResponseId from the previous
6343
6380
  // session, sending previous_response_id that the server doesn't recognize
@@ -6467,6 +6504,7 @@ export class CortexOrchestrator {
6467
6504
  // Round 6: clear canonical-conversion cache on session resume too —
6468
6505
  // entries from a previously running orchestrator instance are stale.
6469
6506
  this.canonicalConversionCache.clear();
6507
+ this.injectedApiContent.clear(); // R223
6470
6508
  // Reuse the persistedMetadata loaded above — avoid a second disk read.
6471
6509
  const savedMetadata = persistedMetadata;
6472
6510
  if (savedMetadata?.cacheMetrics) {
@@ -6937,6 +6975,7 @@ export class CortexOrchestrator {
6937
6975
  plateau: plateauStatusSegment(this.plateauTracker), // R210: null when CORTEX_PLATEAU_STOP is off or no metric seen → line unchanged
6938
6976
  });
6939
6977
  if (appendStatusToNewestToolResult(this.messageHistory, line)) {
6978
+ this.invalidateTailUnitConversions(); // R221 safety net
6940
6979
  this.turnStatusLines++;
6941
6980
  if (this.config.debug)
6942
6981
  console.log(`[TurnStatus] iteration ${iteration}: ${line}`);
@@ -9388,7 +9427,17 @@ export class CortexOrchestrator {
9388
9427
  try {
9389
9428
  if (!isReasoningExhaustion(cls) || !resolveWallDrop())
9390
9429
  return { dropped: false };
9430
+ // R228 (CORTEX_RESPONSES_SLICE_ALL): on a chained Responses session the server already HOLDS the walled turn (previous_response_id
9431
+ // = its id) — a local drop cannot remove it, and the nudge on an already-held tool_result would never be sent (sliced away). Keep
9432
+ // the turn locally so the caller pushes the nudge as a new input item.
9433
+ const tail = this.messageHistory[this.messageHistory.length - 1];
9434
+ if (resolveResponsesSliceAll() && this.lastResponseId && tail?.model?.apiPattern === 'responses')
9435
+ return { dropped: false };
9391
9436
  const r = dropWalledTurn(this.messageHistory, nudge);
9437
+ // R221 (CORTEX_WALL_CACHE_FIX): the carrier tool_result was converted (and cached by uuid) while the walled turn was the tail —
9438
+ // drop that stale copy so every later request carries the nudge (+ wall summary), not just the retry.
9439
+ if (r.dropped && r.carrierMessage)
9440
+ this.invalidateConversions([r.carrierMessage]);
9392
9441
  const store = this.getDecisionStore();
9393
9442
  if (r.dropped && store)
9394
9443
  void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'wall_drop', detail: { iteration, droppedChars: r.droppedChars, carrier: r.carrier, streaming } }).catch(() => { });
@@ -9413,6 +9462,7 @@ export class CortexOrchestrator {
9413
9462
  return;
9414
9463
  if (!appendStatusToNewestToolResult(this.messageHistory, buildComputeNudgeLine(reasoningTokens)))
9415
9464
  return;
9465
+ this.invalidateTailUnitConversions(); // R221 safety net
9416
9466
  this.computeNudgeLastRound = round;
9417
9467
  this.computeNudgeFired++;
9418
9468
  const store = this.getDecisionStore();
@@ -9454,7 +9504,7 @@ export class CortexOrchestrator {
9454
9504
  ? (await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(req, model), label)).result
9455
9505
  : this.apiClient.sendRequest(req, model);
9456
9506
  try {
9457
- return await send(build());
9507
+ return await this.sendWithChainFallback(build(), send, build, label); // R228 fallback (dark; off = send(build()))
9458
9508
  }
9459
9509
  catch (err) {
9460
9510
  if (!isImageRejectionError(err))
@@ -9463,6 +9513,7 @@ export class CortexOrchestrator {
9463
9513
  const stripped = stripRejectedImages(this.messageHistory, reason);
9464
9514
  if (stripped === 0)
9465
9515
  throw err;
9516
+ this.invalidateConversions(this.messageHistory); // R221 safety net: stubbed image blocks live in already-converted messages
9466
9517
  console.warn(`[Orchestrator] R154: provider rejected an image block on ${label} — stubbed ${stripped} image block(s) in history and retrying once.`);
9467
9518
  const store = this.getDecisionStore();
9468
9519
  if (store)
@@ -9970,6 +10021,11 @@ export class CortexOrchestrator {
9970
10021
  * so this cleanup just removes them from the in-memory array.
9971
10022
  */
9972
10023
  cleanupEphemeralMessages() {
10024
+ // R227 (CORTEX_KEEP_MENTOR_MESSAGES=on, dark): keep them — removing a mid-history message rewrites every later request's prefix and
10025
+ // shifts the Responses slice checkpoint (messageCountAtLastResponse counts them). Kept, the history stays append-only and the
10026
+ // checkpoint stays exact. Display already strips their system-reminder (sanitizeHistoryForDisplay); they were never persisted.
10027
+ if (resolveKeepMentorMessages())
10028
+ return;
9973
10029
  const beforeCount = this.messageHistory.length;
9974
10030
  this.messageHistory = this.messageHistory.filter((msg) => !msg.metadata?.ephemeral);
9975
10031
  const removedCount = beforeCount - this.messageHistory.length;
@@ -10362,7 +10418,78 @@ export class CortexOrchestrator {
10362
10418
  * Also validates and repairs the message history to handle crash recovery scenarios
10363
10419
  * where tool_use blocks might be orphaned without matching tool_results.
10364
10420
  */
10365
- convertToCanonicalMessages(messages) {
10421
+ /** R223 (CORTEX_PERSIST_INJECTED, dark): uuid → the injected user content the first request of that turn sent. The message in
10422
+ * messageHistory keeps the RAW content (storage, display, title, judges and every text reader see the raw text); only the API
10423
+ * conversion swaps the injected content in, so every request of the session carries the same bytes for that message. */
10424
+ injectedApiContent = new Map();
10425
+ rememberInjectedContent(uuid, injected) {
10426
+ if (resolvePersistInjected())
10427
+ this.injectedApiContent.set(uuid, injected);
10428
+ }
10429
+ /** The record convertSingleMessage sees for the API (R223 swap; identity when off / not injected). */
10430
+ apiViewOf(msg) {
10431
+ if (this.injectedApiContent.size === 0 || !msg?.uuid)
10432
+ return msg;
10433
+ const injected = this.injectedApiContent.get(msg.uuid);
10434
+ if (injected === undefined)
10435
+ return msg;
10436
+ return { ...msg, message: { ...msg.message, content: injected } };
10437
+ }
10438
+ /** R221 general safety net (CORTEX_WALL_CACHE_FIX=on, dark; off = no-op): drop the cached canonical conversion of
10439
+ * messages mutated in place, so the next request converts them fresh instead of re-sending a stale copy. */
10440
+ invalidateConversions(msgs) {
10441
+ if (!resolveWallCacheFix())
10442
+ return;
10443
+ for (const m of msgs)
10444
+ if (m?.uuid)
10445
+ this.canonicalConversionCache.delete(m.uuid);
10446
+ }
10447
+ /** R221 safety net for the steering carriers (all of them write into the messages after the newest assistant turn). */
10448
+ invalidateTailUnitConversions() {
10449
+ if (!resolveWallCacheFix())
10450
+ return;
10451
+ this.invalidateConversions(tailUnitAfterLastAssistant(this.messageHistory));
10452
+ }
10453
+ /** R228 (CORTEX_RESPONSES_SLICE_ALL, dark): history for a request that chains previous_response_id — only the items added since the
10454
+ * held response. Off / not Responses / no chain / no checkpoint → the full history (the pre-existing behaviour). */
10455
+ chainedHistoryForApi(model) {
10456
+ const h = [...this.messageHistory];
10457
+ if (!resolveResponsesSliceAll() || !this.lastResponseId || model.api.pattern !== 'responses')
10458
+ return h;
10459
+ const from = chainedSliceStart(this.messageCountAtLastResponse, h.length);
10460
+ return from > 0 ? h.slice(from) : h;
10461
+ }
10462
+ /** R228 (CORTEX_RESPONSES_SLICE_ALL) fallback: when the held response is gone (store off / expired / error naming it), drop the chain
10463
+ * and resend ONCE with the FULL history and NO previous_response_id — never both. `rebuildFull` is called AFTER the chain reset, so
10464
+ * every builder that reads this.lastResponseId / chainedHistoryForApi produces the unchained full-history request. Off, unchained, or
10465
+ * any other error → rethrown untouched (byte-identical). */
10466
+ async sendWithChainFallback(req, send, rebuildFull, label) {
10467
+ try {
10468
+ return await send(req);
10469
+ }
10470
+ catch (err) {
10471
+ if (!req.previousResponseId || !resolveResponsesSliceAll() || !isPreviousResponseUnavailable(err))
10472
+ throw err;
10473
+ const lost = req.previousResponseId;
10474
+ this.lastResponseId = null;
10475
+ this.lastResponseIdProvider = null;
10476
+ this.messageCountAtLastResponse = 0;
10477
+ const full = rebuildFull();
10478
+ delete full.previousResponseId; // never both: full history XOR a chain id
10479
+ console.warn(`[Orchestrator] R228: previous response ${lost} unavailable on ${label} — chain dropped, resending the full history once.`);
10480
+ const store = this.getDecisionStore();
10481
+ if (store)
10482
+ void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'responses_chain_fallback', detail: { label, reason: String(err?.message ?? err).slice(0, 160) } }).catch(() => { });
10483
+ return await send(full);
10484
+ }
10485
+ }
10486
+ /** R228: a chained non-continuation response (gate / retries / synthesis) was pushed — the server now holds everything up to here. */
10487
+ markChainedResponseCheckpoint(model) {
10488
+ if (resolveResponsesSliceAll() && model.api.pattern === 'responses')
10489
+ this.messageCountAtLastResponse = this.messageHistory.length;
10490
+ }
10491
+ convertToCanonicalMessages(rawMessages) {
10492
+ const messages = this.injectedApiContent.size > 0 ? rawMessages.map((m) => this.apiViewOf(m)) : rawMessages;
10366
10493
  const tailIdx = messages.length - 1;
10367
10494
  const converted = messages.map((msg, i) => {
10368
10495
  // Tail is recomputed every call (see cache doc-comment above).