@nexus-cortex/core 4.124.44 → 4.124.45

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