@nexus-cortex/core 4.63.9 → 4.65.1

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.
@@ -143,6 +143,52 @@ export class CortexOrchestrator {
143
143
  /** The active model card's anchorProfile (captured at request assembly) —
144
144
  * env CORTEX_TOOL_ANCHOR still overrides inside resolveToolAnchor. */
145
145
  cardAnchorProfile = null;
146
+ /** P6 deferral (CORTEX_PROMPT_MASS=defer): the static corpus is delivered
147
+ * exactly once, at the anchor-lift boundary. One-shot per orchestrator. */
148
+ deferredCorpusDelivered = false;
149
+ /**
150
+ * P6 deferral: at the anchor-lift boundary, append the deferred static
151
+ * corpus (every static doc except the core system_prompt) as an extra
152
+ * TEXT BLOCK on the just-recorded first tool_result message — the corpus
153
+ * arrives as appended conversation content exactly where the full tool
154
+ * catalog appears (act -> observe -> lift). Rides the budget-signal
155
+ * precedent (mutate the last history message's content array); adapters
156
+ * render mixed [tool_result, text] user messages correctly per dialect.
157
+ * No-op unless CORTEX_PROMPT_MASS=defer; fires at most once.
158
+ */
159
+ async deliverDeferredCorpusAtLift(model) {
160
+ if (this.deferredCorpusDelivered)
161
+ return;
162
+ if ((process.env.CORTEX_PROMPT_MASS ?? '').trim().toLowerCase() !== 'defer')
163
+ return;
164
+ this.deferredCorpusDelivered = true; // one-shot even if assembly fails
165
+ if (!this.systemMessageMiddleware)
166
+ return;
167
+ try {
168
+ const corpus = await this.systemMessageMiddleware.buildDeferredStaticCorpus(model, true, {
169
+ sessionId: this.currentSessionId,
170
+ conversationId: this.currentConversationId,
171
+ turnNumber: this.turnNumber,
172
+ modelId: model.id,
173
+ config: this.config
174
+ });
175
+ if (!corpus)
176
+ return;
177
+ const lastMsg = this.messageHistory[this.messageHistory.length - 1];
178
+ if (lastMsg?.message?.content?.[0]?.type === 'tool_result') {
179
+ lastMsg.message.content.push({
180
+ type: 'text',
181
+ text: `<system-reminder>\nFull session context follows (deferred until your first action; applies from here on).\n</system-reminder>\n${corpus}`
182
+ });
183
+ if (this.config.debug) {
184
+ console.log(`[Anchor] deferred static corpus delivered at lift (${corpus.length} chars)`);
185
+ }
186
+ }
187
+ }
188
+ catch (e) {
189
+ console.warn(`[Anchor] deferred corpus delivery failed (continuing without): ${e?.message ?? e}`);
190
+ }
191
+ }
146
192
  /** Narrow `tools` to the anchor profile while the anchor is armed; no-op
147
193
  * once lifted or when CORTEX_TOOL_ANCHOR is unset. Applied AFTER the
148
194
  * deferred filter at both request-assembly sites. */
@@ -920,7 +966,10 @@ export class CortexOrchestrator {
920
966
  const MAX_CONSECUTIVE_ERRORS = loopDefaults.maxConsecutiveErrors;
921
967
  const MAX_LOOP_REPETITIONS = loopDefaults.maxLoopRepetitions;
922
968
  const TOOL_BUDGET_SOFT = loopDefaults.toolBudgetSoft; // R29b brake
923
- const TOOL_BUDGET_HARD = TOOL_BUDGET_SOFT * 2; // force-synthesis cap
969
+ // R64: TOOL_BUDGET_SOFT <= 0 disables the budget-pressure system entirely
970
+ // (no reminders, no force-synthesis cap). Runaway protection then rests on
971
+ // loop detection, the consecutive-error breaker, and MAX_TOOL_ITERATIONS.
972
+ const TOOL_BUDGET_HARD = TOOL_BUDGET_SOFT > 0 ? TOOL_BUDGET_SOFT * 2 : Infinity; // force-synthesis cap
924
973
  let totalToolErrors = 0;
925
974
  // Round 18b: track whether we've already retried on empty response so
926
975
  // we don't loop forever. See empty-detection block below.
@@ -1737,6 +1786,9 @@ export class CortexOrchestrator {
1737
1786
  toolsToUse = ensureStructuredOutputTool(toolsToUse, structuredOutputState);
1738
1787
  }
1739
1788
  }
1789
+ // P6 deferral: the static corpus arrives at the same boundary as the
1790
+ // full tool catalog (no-op unless CORTEX_PROMPT_MASS=defer).
1791
+ await this.deliverDeferredCorpusAtLift(effectiveModel);
1740
1792
  if (this.config.debug)
1741
1793
  console.log('[Anchor] lifted at first tool_result boundary — session profile applies');
1742
1794
  }
@@ -2738,7 +2790,10 @@ export class CortexOrchestrator {
2738
2790
  const MAX_LOOP_REPETITIONS = loopDefaults.maxLoopRepetitions;
2739
2791
  const TOOL_TIMEOUT_MS = loopDefaults.toolTimeoutMs;
2740
2792
  const TOOL_BUDGET_SOFT = loopDefaults.toolBudgetSoft; // R29b brake
2741
- const TOOL_BUDGET_HARD = TOOL_BUDGET_SOFT * 2; // force-synthesis cap
2793
+ // R64: TOOL_BUDGET_SOFT <= 0 disables the budget-pressure system entirely
2794
+ // (no reminders, no force-synthesis cap). Runaway protection then rests on
2795
+ // loop detection, the consecutive-error breaker, and MAX_TOOL_ITERATIONS.
2796
+ const TOOL_BUDGET_HARD = TOOL_BUDGET_SOFT > 0 ? TOOL_BUDGET_SOFT * 2 : Infinity; // force-synthesis cap
2742
2797
  while (hasToolUse && toolCallIteration < MAX_TOOL_ITERATIONS) {
2743
2798
  toolCallIteration++;
2744
2799
  // Extract tool use blocks from current message
@@ -3115,6 +3170,8 @@ export class CortexOrchestrator {
3115
3170
  toolsToUse = ensureStructuredOutputTool(toolsToUse, structuredOutputState);
3116
3171
  }
3117
3172
  }
3173
+ // P6 deferral: same one-shot corpus delivery as the sendMessage path.
3174
+ await this.deliverDeferredCorpusAtLift(effectiveModel);
3118
3175
  if (this.config.debug)
3119
3176
  console.log('[Anchor] lifted at first tool_result boundary — session profile applies');
3120
3177
  }
@@ -3714,9 +3771,15 @@ export class CortexOrchestrator {
3714
3771
  getLoopControlConfig() {
3715
3772
  const softRaw = this.config.loopControl?.toolBudgetSoft;
3716
3773
  return {
3717
- maxToolIterations: this.config.loopControl?.maxToolIterations ?? 50,
3774
+ // R64: failsafe ceiling, not a work limit — keep in lockstep with
3775
+ // SettingsSchema DEFAULT_SETTINGS (this fallback covers orchestrators
3776
+ // constructed without a SettingsLoader-derived config).
3777
+ maxToolIterations: this.config.loopControl?.maxToolIterations ?? 1000,
3718
3778
  maxConsecutiveErrors: this.config.loopControl?.maxConsecutiveErrors ?? 3,
3719
- toolBudgetSoft: Number.isFinite(softRaw) && softRaw > 0 ? softRaw : 15,
3779
+ // R64: 0 is a VALID value (disables budget pressure entirely) — only
3780
+ // undefined/negative/NaN fall back to the default. The old `> 0 : 15`
3781
+ // coercion silently replaced an explicit 0 with 15.
3782
+ toolBudgetSoft: Number.isFinite(softRaw) && softRaw >= 0 ? softRaw : 400,
3720
3783
  toolTimeoutMs: this.config.loopControl?.toolTimeoutMs ?? 120000,
3721
3784
  maxLoopRepetitions: this.config.loopControl?.maxLoopRepetitions ?? 5,
3722
3785
  };