@nexus-cortex/core 4.124.43 → 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 (72) hide show
  1. package/.env.defaults +184 -0
  2. package/dist/adapters/GatewayTranslationLayer.d.ts.map +1 -1
  3. package/dist/adapters/GatewayTranslationLayer.js +14 -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/SettingsLoader.d.ts +3 -1
  9. package/dist/config/SettingsLoader.d.ts.map +1 -1
  10. package/dist/config/SettingsLoader.js +18 -4
  11. package/dist/config/SettingsLoader.js.map +1 -1
  12. package/dist/config/SettingsSchema.d.ts +1 -0
  13. package/dist/config/SettingsSchema.d.ts.map +1 -1
  14. package/dist/config/SettingsSchema.js +9 -0
  15. package/dist/config/SettingsSchema.js.map +1 -1
  16. package/dist/config/effectiveConfig.d.ts +1 -1
  17. package/dist/config/effectiveConfig.d.ts.map +1 -1
  18. package/dist/config/effectiveConfig.js +21 -1
  19. package/dist/config/effectiveConfig.js.map +1 -1
  20. package/dist/config/interactiveProfile.d.ts +86 -0
  21. package/dist/config/interactiveProfile.d.ts.map +1 -0
  22. package/dist/config/interactiveProfile.js +114 -0
  23. package/dist/config/interactiveProfile.js.map +1 -0
  24. package/dist/middleware/HelperModelMiddleware.d.ts +10 -0
  25. package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
  26. package/dist/middleware/HelperModelMiddleware.js +48 -5
  27. package/dist/middleware/HelperModelMiddleware.js.map +1 -1
  28. package/dist/middleware/SystemMessageMiddleware.d.ts.map +1 -1
  29. package/dist/middleware/SystemMessageMiddleware.js +4 -2
  30. package/dist/middleware/SystemMessageMiddleware.js.map +1 -1
  31. package/dist/middleware/helpers/helperProviderMatch.d.ts +40 -0
  32. package/dist/middleware/helpers/helperProviderMatch.d.ts.map +1 -0
  33. package/dist/middleware/helpers/helperProviderMatch.js +50 -0
  34. package/dist/middleware/helpers/helperProviderMatch.js.map +1 -0
  35. package/dist/orchestrator/APIClient.d.ts.map +1 -1
  36. package/dist/orchestrator/APIClient.js +92 -13
  37. package/dist/orchestrator/APIClient.js.map +1 -1
  38. package/dist/orchestrator/CortexOrchestrator.d.ts +22 -0
  39. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  40. package/dist/orchestrator/CortexOrchestrator.js +211 -82
  41. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  42. package/dist/orchestrator/OrchestratorFactory.d.ts.map +1 -1
  43. package/dist/orchestrator/OrchestratorFactory.js +10 -0
  44. package/dist/orchestrator/OrchestratorFactory.js.map +1 -1
  45. package/dist/orchestrator/appendOnlyHistory.d.ts +39 -0
  46. package/dist/orchestrator/appendOnlyHistory.d.ts.map +1 -0
  47. package/dist/orchestrator/appendOnlyHistory.js +75 -0
  48. package/dist/orchestrator/appendOnlyHistory.js.map +1 -0
  49. package/dist/orchestrator/transportCacheFixes.d.ts +22 -0
  50. package/dist/orchestrator/transportCacheFixes.d.ts.map +1 -0
  51. package/dist/orchestrator/transportCacheFixes.js +114 -0
  52. package/dist/orchestrator/transportCacheFixes.js.map +1 -0
  53. package/dist/orchestrator/transportFixes.d.ts +55 -0
  54. package/dist/orchestrator/transportFixes.d.ts.map +1 -0
  55. package/dist/orchestrator/transportFixes.js +182 -0
  56. package/dist/orchestrator/transportFixes.js.map +1 -0
  57. package/dist/orchestrator/wallGuard.d.ts +3 -1
  58. package/dist/orchestrator/wallGuard.d.ts.map +1 -1
  59. package/dist/orchestrator/wallGuard.js +15 -2
  60. package/dist/orchestrator/wallGuard.js.map +1 -1
  61. package/dist/system-messages/promptPresets.d.ts +29 -1
  62. package/dist/system-messages/promptPresets.d.ts.map +1 -1
  63. package/dist/system-messages/promptPresets.js +44 -2
  64. package/dist/system-messages/promptPresets.js.map +1 -1
  65. package/dist/tools/ClientSideToolFilter.d.ts +9 -2
  66. package/dist/tools/ClientSideToolFilter.d.ts.map +1 -1
  67. package/dist/tools/ClientSideToolFilter.js +26 -3
  68. package/dist/tools/ClientSideToolFilter.js.map +1 -1
  69. package/dist/training/DecisionStore.d.ts +1 -1
  70. package/dist/training/DecisionStore.d.ts.map +1 -1
  71. package/dist/training/DecisionStore.js.map +1 -1
  72. package/package.json +3 -3
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * Based on: docs/v4-core_library/Orchestrator_ARCHITECTURE.md
9
9
  */
10
+ import { liftPlanAllowedInSession } from '../config/interactiveProfile.js';
10
11
  import { v4 as uuidv4 } from 'uuid';
11
12
  import { ToolNamingHandler } from '../adapters/ToolNamingHandler.js';
12
13
  // Phase 2.4: Server-Side Tools Support
@@ -90,6 +91,7 @@ import { InitCortexContext, MemoryWrite, MemoryRecall } from '../tools/context-m
90
91
  import { pruneAgedToolResults } from '../conversation/ToolResultPruner.js';
91
92
  import { detectTailRepetition, tailLoopGuardEnabled } from './tailRepetitionDetector.js';
92
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)
93
95
  import { resolveWallDrop, resolveOutputCap, dropWalledTurn, resolveWallSummary, extractWalledReasoning, clipReasoning, formatWallSummary } from './wallGuard.js';
94
96
  import { resolvePid1Guard, detectPid1Note } from './pid1Guard.js'; // R209
95
97
  import { PlateauStopTracker, plateauBootInstruction, plateauStatusSegment } from './plateauStop.js'; // R210
@@ -381,6 +383,7 @@ export class CortexOrchestrator {
381
383
  type: 'text',
382
384
  text: `<system-reminder>\nFull session context follows (deferred until your first action; applies from here on).\n</system-reminder>\n${corpus}`
383
385
  });
386
+ this.invalidateTailUnitConversions(); // R221 safety net (CORTEX_WALL_CACHE_FIX; off = no-op)
384
387
  if (this.config.debug) {
385
388
  console.log(`[Anchor] deferred static corpus delivered at lift (${corpus.length} chars)`);
386
389
  }
@@ -423,6 +426,7 @@ export class CortexOrchestrator {
423
426
  if (!r)
424
427
  return;
425
428
  lastMsg.message.content.push({ type: 'text', text: `<system-reminder>\n${r.note}\n</system-reminder>` });
429
+ this.invalidateTailUnitConversions(); // R221 safety net
426
430
  const store = this.getDecisionStore();
427
431
  if (store)
428
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(() => { });
@@ -446,6 +450,7 @@ export class CortexOrchestrator {
446
450
  return; // not the boundary yet — leave the one-shot open
447
451
  this.plateauInstructionDelivered = true;
448
452
  lastMsg.message.content.push({ type: 'text', text: `<system-reminder>\n${text}\n</system-reminder>` });
453
+ this.invalidateTailUnitConversions(); // R221 safety net
449
454
  if (this.config.debug)
450
455
  console.log('[PlateauStop] boot instruction delivered');
451
456
  }
@@ -520,6 +525,7 @@ export class CortexOrchestrator {
520
525
  text: `<system-reminder>\n${parts.join(' ')}\n</system-reminder>`
521
526
  });
522
527
  this.liftNudgeDelivered = true; // one-shot: consumed only on ACTUAL delivery
528
+ this.invalidateTailUnitConversions(); // R221 safety net
523
529
  if (this.config.debug) {
524
530
  console.log(`[Anchor] lift nudge delivered (${parts.join(' ').length} chars, ${hidden.length} hidden tools)`);
525
531
  }
@@ -550,7 +556,9 @@ export class CortexOrchestrator {
550
556
  // there is no human watching — fine. In an interactive TUI a silent pause reads as a frozen UI,
551
557
  // so the planner is SUPPRESSED there UNLESS a thinking-indicator is wired and opted in via
552
558
  // CORTEX_LIFT_PLAN_INTERACTIVE=true. (autoApproveActions = !(stdin.isTTY && stdout.isTTY).)
553
- if (!this.approvalMode.autoApproveActions && process.env.CORTEX_LIFT_PLAN_INTERACTIVE !== 'true') {
559
+ // R219: the gate lives in config/interactiveProfile.ts (same env the CORTEX_INTERACTIVE_PROFILE resolver fills;
560
+ // bench-lite leaves it closed — NEEDS-ADAPTATION: silent pause, first-request-only, grader persona).
561
+ if (!liftPlanAllowedInSession(this.approvalMode.autoApproveActions)) {
554
562
  return; // interactive session, no thinking-indicator opt-in → do not fire (do not consume one-shot)
555
563
  }
556
564
  this.liftPlanDelivered = true; // one-shot even if the call fails (never re-fire mid-task)
@@ -654,6 +662,7 @@ export class CortexOrchestrator {
654
662
  `It is anchored to the REAL success criteria (what the task's own grader checks), NOT your own ` +
655
663
  `tests. Your own tests are a means, never the finish line.\n\n${plan}\n</system-reminder>`,
656
664
  });
665
+ this.invalidateTailUnitConversions(); // R221 safety net
657
666
  if (store)
658
667
  void store.recordEvent({
659
668
  sessionId,
@@ -699,6 +708,7 @@ export class CortexOrchestrator {
699
708
  return; // nothing authored (yet) — leave the one-shot open
700
709
  this.reqLedgerDelivered = true;
701
710
  lastMsg?.message?.content?.push?.({ type: 'text', text: `<system-reminder>\n${text}\n</system-reminder>` });
711
+ this.invalidateTailUnitConversions(); // R221 safety net
702
712
  const store = this.getDecisionStore();
703
713
  if (store)
704
714
  void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'requirement_ledger_delivered', toolName: 'EndTurn', detail: { lines: (entries ?? []).length, chars: text.length } }).catch(() => { });
@@ -2102,6 +2112,7 @@ export class CortexOrchestrator {
2102
2112
  }
2103
2113
  };
2104
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
2105
2116
  // canonicalHistory will be re-computed after effectiveModel is determined
2106
2117
  // (see input-slicing for cross-turn chain below).
2107
2118
  // Debug logging for tools (already gathered above)
@@ -2169,7 +2180,7 @@ export class CortexOrchestrator {
2169
2180
  const isPTCEnabled = this.config.enablePTC && effectiveModel.supportsPTC && effectiveModel.provider === 'anthropic';
2170
2181
  // Deferred loading: filter tools for non-PTC paths (essential + recently-used only)
2171
2182
  if (!isPTCEnabled && this.effectiveDeferredLoading && toolsToUse && toolsToUse.length > 0) {
2172
- toolsToUse = this.toolFilter.getFilteredTools(toolsToUse);
2183
+ toolsToUse = this.toolFilter.getFilteredTools(toolsToUse, { appendOnly: resolveAppendOnlyTools() });
2173
2184
  }
2174
2185
  // First-turn anchoring narrows further while armed (no-op once lifted).
2175
2186
  // Card-level home door: capture the active card's anchorProfile (env
@@ -2208,7 +2219,7 @@ export class CortexOrchestrator {
2208
2219
  this.mentorshipMiddleware?.resetSequentialCalls(this.currentSessionId);
2209
2220
  // §13-B2: force AskForAdvice this turn on high-confidence thrash (mentor-force armed).
2210
2221
  const forcedMentorChoice = await this.resolveForcedMentorChoice(toolsToUse, 0); // initial request: 0 tool calls yet
2211
- const preparedRequest = this.gatewayTranslation.prepareRequest(canonicalHistory, toolsToUse, effectiveModel, {
2222
+ const initialPrepOpts = {
2212
2223
  temperature: options.parameters?.temperature,
2213
2224
  maxTokens: options.parameters?.maxTokens,
2214
2225
  topP: options.parameters?.topP,
@@ -2217,7 +2228,18 @@ export class CortexOrchestrator {
2217
2228
  staticSystemPrompt: this.currentStaticSystemPrompt, // R28
2218
2229
  conversationId: this.currentConversationId, // R28b
2219
2230
  toolChoice: forcedMentorChoice // §13-B2 forced mentor tool_choice
2220
- });
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
+ };
2221
2243
  // Stateful Responses API: chain from prior response when available
2222
2244
  // (works across user turns — lastResponseId is preserved between messages).
2223
2245
  // Only affects 'responses' api.pattern; other providers ignore this field.
@@ -2254,8 +2276,11 @@ export class CortexOrchestrator {
2254
2276
  // Use effectiveModel (which may have modified endpoint/pattern for server-side tools)
2255
2277
  // Wrap API call with retry middleware for automatic retry on transient errors
2256
2278
  if (this.retryMiddleware) {
2257
- const retryResult = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(preparedRequest, effectiveModel), 'primary_api_call');
2258
- 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)
2259
2284
  // Log retry information if retries occurred
2260
2285
  if (this.config.debug && retryResult.attemptCount > 1) {
2261
2286
  console.log(`[Orchestrator] API call succeeded after ${retryResult.attemptCount} attempts`);
@@ -2264,7 +2289,7 @@ export class CortexOrchestrator {
2264
2289
  }
2265
2290
  else {
2266
2291
  // Fallback: direct call if middleware not available
2267
- 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)
2268
2293
  }
2269
2294
  }
2270
2295
  catch (error) {
@@ -2696,6 +2721,7 @@ export class CortexOrchestrator {
2696
2721
  if (isEmpty) {
2697
2722
  m.message = m.message || { role: 'assistant' };
2698
2723
  m.message.content = [{ type: 'text', text: '(no output)' }];
2724
+ this.invalidateConversions([m]); // R221 safety net
2699
2725
  if (this.config.debug) {
2700
2726
  console.log(`[Orchestrator] R26: repaired empty assistant message at history[${i}] before retry`);
2701
2727
  }
@@ -2737,7 +2763,7 @@ export class CortexOrchestrator {
2737
2763
  await this.ensureHistoryFitsModel(effectiveModel);
2738
2764
  const retryEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
2739
2765
  const buildRetryRequest = () => {
2740
- const retryCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
2766
+ const retryCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
2741
2767
  const req = this.gatewayTranslation.prepareRequest(retryCanonicalHistory, toolsToUse, effectiveModel, {
2742
2768
  temperature: options.parameters?.temperature,
2743
2769
  maxTokens: options.parameters?.maxTokens,
@@ -2786,6 +2812,7 @@ export class CortexOrchestrator {
2786
2812
  ...(retryConverted.usage && { usage: retryConverted.usage }),
2787
2813
  };
2788
2814
  this.messageHistory.push(retryAssistantMessage);
2815
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
2789
2816
  await this.historyStore.appendMessage(this.currentSessionId, retryAssistantMessage);
2790
2817
  // Update current pointer so the loop re-evaluates with new content.
2791
2818
  currentAssistantMessage = retryAssistantMessage;
@@ -2857,28 +2884,28 @@ export class CortexOrchestrator {
2857
2884
  this.messageHistory.push(inactionUserMessage);
2858
2885
  await this.historyStore.appendMessage(this.currentSessionId, inactionUserMessage);
2859
2886
  await this.ensureHistoryFitsModel(effectiveModel);
2860
- const inactionCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
2861
- const inactionRequest = this.gatewayTranslation.prepareRequest(inactionCanonicalHistory, toolsToUse, effectiveModel, {
2862
- temperature: options.parameters?.temperature,
2863
- maxTokens: options.parameters?.maxTokens,
2864
- topP: options.parameters?.topP,
2865
- reasoningEffort: this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort, // R153 backoff > request param
2866
- stream: options.streaming,
2867
- staticSystemPrompt: this.currentStaticSystemPrompt, // R28
2868
- conversationId: this.currentConversationId, // R28b
2869
- });
2870
- if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
2871
- inactionRequest.previousResponseId = this.lastResponseId;
2872
- }
2873
- inactionRequest.conversationId = this.currentSessionId;
2874
- let inactionApiResponse;
2875
- if (this.retryMiddleware) {
2876
- const r = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(inactionRequest, effectiveModel), 'inaction_nudge_retry');
2877
- inactionApiResponse = r.result;
2878
- }
2879
- else {
2880
- inactionApiResponse = await this.apiClient.sendRequest(inactionRequest, effectiveModel);
2881
- }
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)
2882
2909
  const inactionConverted = this.gatewayTranslation.convertResponse(inactionApiResponse.data, effectiveModel, {
2883
2910
  sessionId: this.currentSessionId,
2884
2911
  conversationId: this.currentConversationId,
@@ -2911,6 +2938,7 @@ export class CortexOrchestrator {
2911
2938
  ...(inactionConverted.usage && { usage: inactionConverted.usage }),
2912
2939
  };
2913
2940
  this.messageHistory.push(inactionAssistantMessage);
2941
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
2914
2942
  await this.historyStore.appendMessage(this.currentSessionId, inactionAssistantMessage);
2915
2943
  currentAssistantMessage = inactionAssistantMessage;
2916
2944
  currentAssistantCanonicalMessage = inactionAssistantCanonical;
@@ -3026,6 +3054,7 @@ export class CortexOrchestrator {
3026
3054
  if (isEmpty) {
3027
3055
  m.message = m.message || { role: 'assistant' };
3028
3056
  m.message.content = [{ type: 'text', text: '(no output)' }];
3057
+ this.invalidateConversions([m]); // R221 safety net
3029
3058
  }
3030
3059
  break;
3031
3060
  }
@@ -3072,28 +3101,28 @@ export class CortexOrchestrator {
3072
3101
  this.messageHistory.push(endTurnReminder);
3073
3102
  await this.historyStore.appendMessage(this.currentSessionId, endTurnReminder);
3074
3103
  await this.ensureHistoryFitsModel(effectiveModel);
3075
- const gateCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
3076
- const gateRequest = this.gatewayTranslation.prepareRequest(gateCanonicalHistory, toolsToUse, effectiveModel, {
3077
- temperature: options.parameters?.temperature,
3078
- maxTokens: options.parameters?.maxTokens,
3079
- topP: options.parameters?.topP,
3080
- reasoningEffort: this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort, // R153 backoff > request param
3081
- stream: options.streaming,
3082
- staticSystemPrompt: this.currentStaticSystemPrompt,
3083
- conversationId: this.currentConversationId,
3084
- });
3085
- if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
3086
- gateRequest.previousResponseId = this.lastResponseId;
3087
- }
3088
- gateRequest.conversationId = this.currentSessionId;
3089
- let gateApiResponse;
3090
- if (this.retryMiddleware) {
3091
- const gateResult = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(gateRequest, effectiveModel), 'end_turn_gate');
3092
- gateApiResponse = gateResult.result;
3093
- }
3094
- else {
3095
- gateApiResponse = await this.apiClient.sendRequest(gateRequest, effectiveModel);
3096
- }
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)
3097
3126
  const gateConverted = this.gatewayTranslation.convertResponse(gateApiResponse.data, effectiveModel, {
3098
3127
  sessionId: this.currentSessionId,
3099
3128
  conversationId: this.currentConversationId,
@@ -3126,6 +3155,7 @@ export class CortexOrchestrator {
3126
3155
  ...(gateConverted.usage && { usage: gateConverted.usage }),
3127
3156
  };
3128
3157
  this.messageHistory.push(gateAssistantMessage);
3158
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
3129
3159
  await this.historyStore.appendMessage(this.currentSessionId, gateAssistantMessage);
3130
3160
  currentAssistantMessage = gateAssistantMessage;
3131
3161
  currentAssistantCanonicalMessage = gateAssistantCanonical;
@@ -3586,6 +3616,7 @@ export class CortexOrchestrator {
3586
3616
  const block = lastMsg.message.content[0];
3587
3617
  const existing = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
3588
3618
  block.content = existing + '\n\n' + signals;
3619
+ this.invalidateTailUnitConversions(); // R221 safety net
3589
3620
  // Observability (backlog item 3, micro-suite defect #4): this
3590
3621
  // mutation happens AFTER the tool_result was persisted — the
3591
3622
  // durable session lacks the steering the model saw. Bank a
@@ -3720,7 +3751,7 @@ export class CortexOrchestrator {
3720
3751
  // are included in the continuation request's tools array
3721
3752
  if (this.effectiveDeferredLoading && !isPTCEnabled) {
3722
3753
  const beforeCount = toolsToUse.length;
3723
- toolsToUse = this.toolFilter.getFilteredTools(allTools);
3754
+ toolsToUse = this.toolFilter.getFilteredTools(allTools, { appendOnly: resolveAppendOnlyTools() });
3724
3755
  // StructuredOutput: the re-filter rebuilds from allTools (which never
3725
3756
  // contained the request-scoped tool) — re-append so it stays present
3726
3757
  // on every request of the turn.
@@ -4025,7 +4056,7 @@ export class CortexOrchestrator {
4025
4056
  await this.ensureHistoryFitsModel(effectiveModel);
4026
4057
  const synthEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
4027
4058
  const buildSynthRequest = () => {
4028
- const synthCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
4059
+ const synthCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
4029
4060
  const req = this.gatewayTranslation.prepareRequest(synthCanonicalHistory,
4030
4061
  // Tools suppressed — the model MUST produce text, not call more tools.
4031
4062
  // NOTE (structured-output constraint, 2026-08-02): this is R29a's
@@ -4082,6 +4113,7 @@ export class CortexOrchestrator {
4082
4113
  ...(synthConverted.usage && { usage: synthConverted.usage }),
4083
4114
  };
4084
4115
  this.messageHistory.push(synthAssistantMessage);
4116
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
4085
4117
  await this.historyStore.appendMessage(this.currentSessionId, synthAssistantMessage);
4086
4118
  this.sessionTimeline.recordMessage(synthAssistantMessage.uuid, 'assistant');
4087
4119
  if (synthConverted.usage) {
@@ -4484,6 +4516,7 @@ export class CortexOrchestrator {
4484
4516
  }
4485
4517
  };
4486
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
4487
4520
  // canonicalHistory computed after effectiveModel is determined (input-slicing below).
4488
4521
  // Tools already gathered above for middleware injection
4489
4522
  // Server-side tool detection
@@ -4511,7 +4544,7 @@ export class CortexOrchestrator {
4511
4544
  const isPTCEnabled = this.config.enablePTC && effectiveModel.supportsPTC && effectiveModel.provider === 'anthropic';
4512
4545
  // Deferred loading: filter tools for non-PTC paths (essential + recently-used only)
4513
4546
  if (!isPTCEnabled && this.effectiveDeferredLoading && toolsToUse && toolsToUse.length > 0) {
4514
- toolsToUse = this.toolFilter.getFilteredTools(toolsToUse);
4547
+ toolsToUse = this.toolFilter.getFilteredTools(toolsToUse, { appendOnly: resolveAppendOnlyTools() });
4515
4548
  }
4516
4549
  // First-turn anchoring narrows further while armed (no-op once lifted).
4517
4550
  // Card-level home door: capture the active card's anchorProfile (env
@@ -4906,6 +4939,7 @@ export class CortexOrchestrator {
4906
4939
  if (isEmpty) {
4907
4940
  m.message = m.message || { role: 'assistant' };
4908
4941
  m.message.content = [{ type: 'text', text: '(no output)' }];
4942
+ this.invalidateConversions([m]); // R221 safety net
4909
4943
  }
4910
4944
  break;
4911
4945
  }
@@ -4939,7 +4973,7 @@ export class CortexOrchestrator {
4939
4973
  await this.ensureHistoryFitsModel(effectiveModel);
4940
4974
  const retryEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
4941
4975
  const buildRetryRequest = () => {
4942
- const retryCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
4976
+ const retryCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
4943
4977
  const req = this.gatewayTranslation.prepareRequest(retryCanonicalHistory, toolsToUse, effectiveModel, {
4944
4978
  temperature: options.parameters?.temperature,
4945
4979
  maxTokens: options.parameters?.maxTokens,
@@ -4988,6 +5022,7 @@ export class CortexOrchestrator {
4988
5022
  ...(retryConverted.usage && { usage: retryConverted.usage }),
4989
5023
  };
4990
5024
  this.messageHistory.push(retryAssistantMessage);
5025
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
4991
5026
  await this.historyStore.appendMessage(this.currentSessionId, retryAssistantMessage);
4992
5027
  currentAssistantCanonicalMessage = retryAssistantCanonical;
4993
5028
  lastStopReason = retryConverted.stopReason; // R153
@@ -5065,28 +5100,28 @@ export class CortexOrchestrator {
5065
5100
  await this.historyStore.appendMessage(this.currentSessionId, inactionUserMessage);
5066
5101
  try {
5067
5102
  await this.ensureHistoryFitsModel(effectiveModel);
5068
- const retryCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
5069
- const retryRequest = this.gatewayTranslation.prepareRequest(retryCanonicalHistory, toolsToUse, effectiveModel, {
5070
- temperature: options.parameters?.temperature,
5071
- maxTokens: options.parameters?.maxTokens,
5072
- topP: options.parameters?.topP,
5073
- reasoningEffort: this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort, // R153 backoff > request param
5074
- stream: false,
5075
- staticSystemPrompt: this.currentStaticSystemPrompt,
5076
- conversationId: this.currentConversationId,
5077
- });
5078
- if (this.lastResponseId && effectiveModel.api.pattern === 'responses') {
5079
- retryRequest.previousResponseId = this.lastResponseId;
5080
- }
5081
- retryRequest.conversationId = this.currentSessionId;
5082
- let retryApiResponse;
5083
- if (this.retryMiddleware) {
5084
- const retryResult = await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(retryRequest, effectiveModel), 'inaction_nudge_retry');
5085
- retryApiResponse = retryResult.result;
5086
- }
5087
- else {
5088
- retryApiResponse = await this.apiClient.sendRequest(retryRequest, effectiveModel);
5089
- }
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)
5090
5125
  const retryConverted = this.gatewayTranslation.convertResponse(retryApiResponse.data, effectiveModel, {
5091
5126
  sessionId: this.currentSessionId,
5092
5127
  conversationId: this.currentConversationId,
@@ -5119,6 +5154,7 @@ export class CortexOrchestrator {
5119
5154
  ...(retryConverted.usage && { usage: retryConverted.usage }),
5120
5155
  };
5121
5156
  this.messageHistory.push(retryAssistantMessage);
5157
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
5122
5158
  await this.historyStore.appendMessage(this.currentSessionId, retryAssistantMessage);
5123
5159
  currentAssistantCanonicalMessage = retryAssistantCanonical;
5124
5160
  lastStopReason = retryConverted.stopReason; // R153
@@ -5425,6 +5461,7 @@ export class CortexOrchestrator {
5425
5461
  const block = lastMsg.message.content[0];
5426
5462
  const existing = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
5427
5463
  block.content = existing + '\n\n' + signals;
5464
+ this.invalidateTailUnitConversions(); // R221 safety net
5428
5465
  // Observability (backlog item 3): post-persist mutation — bank a
5429
5466
  // steering_injected event (streaming parity).
5430
5467
  const store = this.getDecisionStore();
@@ -5541,7 +5578,7 @@ export class CortexOrchestrator {
5541
5578
  }
5542
5579
  // Re-filter tools after SearchTools discovery (same as sendMessage path)
5543
5580
  if (this.effectiveDeferredLoading && !isPTCEnabled) {
5544
- toolsToUse = this.toolFilter.getFilteredTools(allTools);
5581
+ toolsToUse = this.toolFilter.getFilteredTools(allTools, { appendOnly: resolveAppendOnlyTools() });
5545
5582
  // StructuredOutput: re-filter rebuilds from allTools — re-append the
5546
5583
  // request-scoped tool (same as the sendMessage continuation path).
5547
5584
  if (structuredOutputState) {
@@ -5932,7 +5969,7 @@ export class CortexOrchestrator {
5932
5969
  await this.ensureHistoryFitsModel(effectiveModel);
5933
5970
  const synthEffort = this.consumeReasoningBackoff() ?? options.parameters?.reasoningEffort; // R153 backoff > request param
5934
5971
  const buildSynthRequest = () => {
5935
- const synthCanonicalHistory = this.convertToCanonicalMessages([...this.messageHistory]);
5972
+ const synthCanonicalHistory = this.convertToCanonicalMessages(this.chainedHistoryForApi(effectiveModel)); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = full history)
5936
5973
  const req = this.gatewayTranslation.prepareRequest(synthCanonicalHistory, [], // tools suppressed — must produce text
5937
5974
  effectiveModel, {
5938
5975
  temperature: options.parameters?.temperature,
@@ -5986,6 +6023,7 @@ export class CortexOrchestrator {
5986
6023
  ...(synthConverted.usage && { usage: synthConverted.usage }),
5987
6024
  };
5988
6025
  this.messageHistory.push(synthAssistantMessage);
6026
+ this.markChainedResponseCheckpoint(effectiveModel); // R228 (CORTEX_RESPONSES_SLICE_ALL; off = no-op)
5989
6027
  await this.historyStore.appendMessage(this.currentSessionId, synthAssistantMessage);
5990
6028
  this.sessionTimeline.recordMessage(synthAssistantMessage.uuid, 'assistant');
5991
6029
  currentAssistantCanonicalMessage = synthCanonical;
@@ -6335,6 +6373,7 @@ export class CortexOrchestrator {
6335
6373
  // cleared on session reset so stale entries (including synthetic-repair
6336
6374
  // uuids from the previous session) can't leak into the new session.
6337
6375
  this.canonicalConversionCache.clear();
6376
+ this.injectedApiContent.clear(); // R223
6338
6377
  // Reset XAI Responses API chain for new session (was leaking across `--new`).
6339
6378
  // Without this, a new session would reuse lastResponseId from the previous
6340
6379
  // session, sending previous_response_id that the server doesn't recognize
@@ -6464,6 +6503,7 @@ export class CortexOrchestrator {
6464
6503
  // Round 6: clear canonical-conversion cache on session resume too —
6465
6504
  // entries from a previously running orchestrator instance are stale.
6466
6505
  this.canonicalConversionCache.clear();
6506
+ this.injectedApiContent.clear(); // R223
6467
6507
  // Reuse the persistedMetadata loaded above — avoid a second disk read.
6468
6508
  const savedMetadata = persistedMetadata;
6469
6509
  if (savedMetadata?.cacheMetrics) {
@@ -6934,6 +6974,7 @@ export class CortexOrchestrator {
6934
6974
  plateau: plateauStatusSegment(this.plateauTracker), // R210: null when CORTEX_PLATEAU_STOP is off or no metric seen → line unchanged
6935
6975
  });
6936
6976
  if (appendStatusToNewestToolResult(this.messageHistory, line)) {
6977
+ this.invalidateTailUnitConversions(); // R221 safety net
6937
6978
  this.turnStatusLines++;
6938
6979
  if (this.config.debug)
6939
6980
  console.log(`[TurnStatus] iteration ${iteration}: ${line}`);
@@ -9385,7 +9426,17 @@ export class CortexOrchestrator {
9385
9426
  try {
9386
9427
  if (!isReasoningExhaustion(cls) || !resolveWallDrop())
9387
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 };
9388
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]);
9389
9440
  const store = this.getDecisionStore();
9390
9441
  if (r.dropped && store)
9391
9442
  void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'wall_drop', detail: { iteration, droppedChars: r.droppedChars, carrier: r.carrier, streaming } }).catch(() => { });
@@ -9410,6 +9461,7 @@ export class CortexOrchestrator {
9410
9461
  return;
9411
9462
  if (!appendStatusToNewestToolResult(this.messageHistory, buildComputeNudgeLine(reasoningTokens)))
9412
9463
  return;
9464
+ this.invalidateTailUnitConversions(); // R221 safety net
9413
9465
  this.computeNudgeLastRound = round;
9414
9466
  this.computeNudgeFired++;
9415
9467
  const store = this.getDecisionStore();
@@ -9451,7 +9503,7 @@ export class CortexOrchestrator {
9451
9503
  ? (await this.retryMiddleware.executeWithRetry(() => this.apiClient.sendRequest(req, model), label)).result
9452
9504
  : this.apiClient.sendRequest(req, model);
9453
9505
  try {
9454
- return await send(build());
9506
+ return await this.sendWithChainFallback(build(), send, build, label); // R228 fallback (dark; off = send(build()))
9455
9507
  }
9456
9508
  catch (err) {
9457
9509
  if (!isImageRejectionError(err))
@@ -9460,6 +9512,7 @@ export class CortexOrchestrator {
9460
9512
  const stripped = stripRejectedImages(this.messageHistory, reason);
9461
9513
  if (stripped === 0)
9462
9514
  throw err;
9515
+ this.invalidateConversions(this.messageHistory); // R221 safety net: stubbed image blocks live in already-converted messages
9463
9516
  console.warn(`[Orchestrator] R154: provider rejected an image block on ${label} — stubbed ${stripped} image block(s) in history and retrying once.`);
9464
9517
  const store = this.getDecisionStore();
9465
9518
  if (store)
@@ -9967,6 +10020,11 @@ export class CortexOrchestrator {
9967
10020
  * so this cleanup just removes them from the in-memory array.
9968
10021
  */
9969
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;
9970
10028
  const beforeCount = this.messageHistory.length;
9971
10029
  this.messageHistory = this.messageHistory.filter((msg) => !msg.metadata?.ephemeral);
9972
10030
  const removedCount = beforeCount - this.messageHistory.length;
@@ -10359,7 +10417,78 @@ export class CortexOrchestrator {
10359
10417
  * Also validates and repairs the message history to handle crash recovery scenarios
10360
10418
  * where tool_use blocks might be orphaned without matching tool_results.
10361
10419
  */
10362
- 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;
10363
10492
  const tailIdx = messages.length - 1;
10364
10493
  const converted = messages.map((msg, i) => {
10365
10494
  // Tail is recomputed every call (see cache doc-comment above).