@nexus-cortex/core 4.108.2 → 4.108.4

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 (31) hide show
  1. package/.env.defaults +22 -0
  2. package/dist/config/RuntimeConfigRegistry.d.ts.map +1 -1
  3. package/dist/config/RuntimeConfigRegistry.js +1 -0
  4. package/dist/config/RuntimeConfigRegistry.js.map +1 -1
  5. package/dist/config/SettingsLoader.d.ts.map +1 -1
  6. package/dist/config/SettingsLoader.js +1 -0
  7. package/dist/config/SettingsLoader.js.map +1 -1
  8. package/dist/config/SettingsSchema.d.ts +1 -0
  9. package/dist/config/SettingsSchema.d.ts.map +1 -1
  10. package/dist/config/SettingsSchema.js +9 -0
  11. package/dist/config/SettingsSchema.js.map +1 -1
  12. package/dist/config/effectiveConfig.d.ts.map +1 -1
  13. package/dist/config/effectiveConfig.js +1 -0
  14. package/dist/config/effectiveConfig.js.map +1 -1
  15. package/dist/orchestrator/CortexOrchestrator.d.ts +6 -0
  16. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  17. package/dist/orchestrator/CortexOrchestrator.js +82 -16
  18. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  19. package/dist/orchestrator/compactionResume.d.ts +27 -1
  20. package/dist/orchestrator/compactionResume.d.ts.map +1 -1
  21. package/dist/orchestrator/compactionResume.js +74 -2
  22. package/dist/orchestrator/compactionResume.js.map +1 -1
  23. package/dist/orchestrator/compactionResumeTemplate.d.ts +9 -1
  24. package/dist/orchestrator/compactionResumeTemplate.d.ts.map +1 -1
  25. package/dist/orchestrator/compactionResumeTemplate.js +20 -2
  26. package/dist/orchestrator/compactionResumeTemplate.js.map +1 -1
  27. package/dist/system-messages/SystemReminderInjector.d.ts +3 -1
  28. package/dist/system-messages/SystemReminderInjector.d.ts.map +1 -1
  29. package/dist/system-messages/SystemReminderInjector.js +5 -2
  30. package/dist/system-messages/SystemReminderInjector.js.map +1 -1
  31. package/package.json +3 -3
@@ -16,7 +16,7 @@ import { sanitizeHistoryForDisplay } from './displayHistorySanitizer.js';
16
16
  import { verifyCoordinates, deterministicCoordinateScore } from './coordinateVerification.js';
17
17
  import { buildRouterSample, appendJsonlRotating } from './cortexTrainingRecord.js';
18
18
  import { join as pathJoin } from 'path';
19
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
19
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
20
20
  import { execSync } from 'node:child_process';
21
21
  import { ENV_RECON_COMMAND, resolveLiftPlanConfig } from '../training/liftPlanner.js';
22
22
  import { resolveEndTurnResolverConfig, parseResolverVerdict } from '../training/endTurnResolver.js';
@@ -38,8 +38,8 @@ import { toolFactory } from '../tools/ToolFactory.js';
38
38
  import { resolveToolProfile, resolveToolAnchor, resolveFrameProfile, resolveLiftNudge, resolveLiftPlan, resolveEndTurnResolver, resolveHeadlessDropAskUser, resolveDeferredLoading, isNarrowProfile, isToolAllowedByProfile, applyToolProfile, webToolBlocked, resolveVisionHelperModel, resolveVisionHandoffMax, resolveSliceNudge, resolveSliceBlock, resolveSliceBlockAt, resolveSliceBlockMax } from '../tools/ToolProfile.js';
39
39
  import { sliceReadFile, decideSliceBlock } from './sliceBlock.js';
40
40
  import { TurnEvidence, evaluateEndTurnGates, buildMissingEndTurnReminder } from './endTurnGates.js';
41
- import { resolveCompactionResume, extractFirstUserText, pickDropped, buildCompactionResumeReminder, isCompactionResumeMessage } from './compactionResume.js';
42
- import { renderResumeMemoryPrompt, buildRebuildInstructions, resolveCheckpointBand } from './compactionResumeTemplate.js';
41
+ import { resolveCompactionResume, pickDropped, buildCompactionResumeReminder, isCompactionResumeMessage, resolveTaskText, resumeMemoryTargetTokens, coversAll, buildRollingSeedMessage, memoryIndexLine, upsertMemoryIndex } from './compactionResume.js';
42
+ import { renderResumeMemoryPrompt, buildRebuildInstructions, resolveCheckpointBand, resolveRearmBand } from './compactionResumeTemplate.js';
43
43
  import { collectWorkspaceDelta, detectCheckCommand, runCheck, resolveJudgeGroundingConfig } from '../training/judgeEvidence.js';
44
44
  import { resolveMentorRoleConfig, describeMentorWire, describeMentorDelivery, mentorSurfaceTimeoutMs } from '../training/mentorRole.js';
45
45
  import { resolveOuterToolDeadlineMs } from './outerToolTimeout.js';
@@ -115,6 +115,9 @@ export class CortexOrchestrator {
115
115
  /** HB-COMPACTION-RESUME v2: pressure-rung checkpoint state (once per band; reused at compaction). */
116
116
  lastCheckpointBand = 0;
117
117
  lastResumeMemory = null;
118
+ pinnedTaskText = '';
119
+ /** 4.108.4: session start — the mtime floor for the non-git workspace delta at compaction. */
120
+ sessionStartMs = Date.now();
118
121
  /** Pending TURN_SUMMARY_PREDICTION awaiting the user's ACTUAL next message —
119
122
  * scored + recorded at the next sendMessage (graduation-signal capture,
120
123
  * Decision Capture Layer §5). In-memory only; lost on restart by design. */
@@ -5689,12 +5692,40 @@ export class CortexOrchestrator {
5689
5692
  /** HB-COMPACTION-RESUME: best-effort write of the resume memory to .cortex/memory/resume-<session>.md (synchronous fs). */
5690
5693
  writeResumeMemoryFile(text, why) {
5691
5694
  try {
5692
- const dir = pathJoin(this.config.projectPath || process.cwd(), '.cortex', 'memory');
5695
+ const cortexDir = pathJoin(this.config.projectPath || process.cwd(), '.cortex');
5696
+ const dir = pathJoin(cortexDir, 'memory');
5693
5697
  mkdirSync(dir, { recursive: true });
5694
- writeFileSync(pathJoin(dir, `resume-${this.currentSessionId}.md`), `---\nname: resume-${this.currentSessionId}\ndescription: resume memory — ${why}\ntype: project\n---\n${text}\n`);
5698
+ const name = `resume-${this.currentSessionId}`;
5699
+ writeFileSync(pathJoin(dir, `${name}.md`), `---\nname: ${name}\ndescription: resume memory — ${why}\ntype: project\n---\n${text}\n`);
5700
+ // 4.108.4: index it in .cortex/MEMORY.md (the harness's two-tier memory) so MemoryRecall and a resumed session can find it.
5701
+ const indexPath = pathJoin(cortexDir, 'MEMORY.md');
5702
+ let current = '';
5703
+ try {
5704
+ current = readFileSync(indexPath, 'utf8');
5705
+ }
5706
+ catch {
5707
+ current = '';
5708
+ }
5709
+ writeFileSync(indexPath, upsertMemoryIndex(current, name, memoryIndexLine(name, `resume memory (${why}) — load after a context compaction or session resume`)));
5695
5710
  }
5696
5711
  catch { /* best-effort */ }
5697
5712
  }
5713
+ /** 4.108.4: the working tree as it is RIGHT NOW — git state when the workspace is a repo (forced past the per-turn lever), else
5714
+ * the mtime-based file delta since session start. Read from disk at compaction; capped so it cannot crowd the kept history. */
5715
+ buildCompactionWorkspaceState() {
5716
+ try {
5717
+ const root = process.env.PROJECT_ROOT || this.config.projectPath || this.config.workingDirectory || process.cwd();
5718
+ let state = this.systemReminderInjector.buildGitContextSection(root, { force: true }) ?? '';
5719
+ if (!state.trim()) {
5720
+ state = collectWorkspaceDelta(root, this.sessionStartMs, { ...resolveJudgeGroundingConfig(), delta: true, deltaMaxFiles: 0 });
5721
+ }
5722
+ const MAX = 2500;
5723
+ return state.length > MAX ? state.slice(0, MAX) + '\n[… workspace state truncated]' : state;
5724
+ }
5725
+ catch {
5726
+ return '';
5727
+ }
5728
+ }
5698
5729
  async ensureHistoryFitsModel(model) {
5699
5730
  // Capture the active model's context window for request-build-time
5700
5731
  // pruning (pruneAgedForRequest) — set before any early return so the
@@ -5720,8 +5751,13 @@ export class CortexOrchestrator {
5720
5751
  actualToolTokens: this.currentToolTokens,
5721
5752
  actualSystemTokens: 0
5722
5753
  };
5723
- // Calculate compaction threshold using model's configuration (with accurate overrides)
5724
- const threshold = this.contextBudgetManager.getCompactionThreshold(model, budgetOverrides);
5754
+ // Calculate compaction threshold using model's configuration (with accurate overrides).
5755
+ // CORTEX_COMPACTION_THRESHOLD_TOKENS (4.108.3, test/ops override): a positive integer replaces the card-derived threshold so the
5756
+ // checkpoint rung + compaction can be exercised in a short live run (efficacy proof before any long-horizon bench).
5757
+ const thresholdOverride = Number(String(process.env.CORTEX_COMPACTION_THRESHOLD_TOKENS ?? '').trim() || 0);
5758
+ const threshold = thresholdOverride > 0
5759
+ ? thresholdOverride
5760
+ : this.contextBudgetManager.getCompactionThreshold(model, budgetOverrides);
5725
5761
  if (this.config.debug) {
5726
5762
  console.log(`[Orchestrator Context] Current tokens: ${currentTokens}, Threshold: ${threshold} (tool reserve: ${this.currentToolTokens})`);
5727
5763
  }
@@ -5734,10 +5770,12 @@ export class CortexOrchestrator {
5734
5770
  if (band > this.lastCheckpointBand) {
5735
5771
  this.lastCheckpointBand = band;
5736
5772
  try {
5737
- const r = await this.helperMiddleware.summarizeForResume(this.convertToCanonicalMessages([...this.messageHistory]), model, 6000, renderResumeMemoryPrompt('{{CONVERSATION}}', 6000));
5773
+ const tgt = resumeMemoryTargetTokens(threshold);
5774
+ const r = await this.helperMiddleware.summarizeForResume(this.convertToCanonicalMessages([...this.messageHistory]), model, tgt, renderResumeMemoryPrompt('{{CONVERSATION}}', tgt));
5738
5775
  const text = String(r?.summary ?? '');
5739
5776
  if (text.trim()) {
5740
- this.lastResumeMemory = { text, tokensAt: currentTokens, turn: this.turnNumber, helperModelId: r?.helperModelId, cost: Number(r?.cost ?? 0) };
5777
+ this.lastResumeMemory = { text, tokensAt: currentTokens, turn: this.turnNumber, helperModelId: r?.helperModelId, cost: Number(r?.cost ?? 0),
5778
+ covered: new WeakSet(this.messageHistory) };
5741
5779
  this.writeResumeMemoryFile(text, `checkpoint band ${band} (${currentTokens}/${threshold} tokens, turn ${this.turnNumber})`);
5742
5780
  const store = this.getDecisionStore();
5743
5781
  if (store)
@@ -5762,6 +5800,8 @@ export class CortexOrchestrator {
5762
5800
  try {
5763
5801
  // Calculate available budget for history (with accurate overrides)
5764
5802
  const budget = this.contextBudgetManager.calculateBudget(model, budgetOverrides);
5803
+ if (thresholdOverride > 0)
5804
+ budget.availableForHistory = Math.floor(thresholdOverride * 0.5); // override: keep ~half the forced window
5765
5805
  // Determine selection strategy based on model configuration
5766
5806
  const strategy = model.compaction?.behavior?.compactOlder ? 'preserve-critical' : 'sliding-window';
5767
5807
  // Select messages that fit within budget (standard path — no thinking stripping)
@@ -5780,13 +5820,16 @@ export class CortexOrchestrator {
5780
5820
  // session JSONL (so a bench can count compactions), (5) .cortex/memory/resume-<session>.md is written.
5781
5821
  const compactionResume = resolveCompactionResume();
5782
5822
  const dropped = pickDropped(this.messageHistory, selectedMessages);
5783
- const taskText = extractFirstUserText(this.messageHistory);
5823
+ const taskText = resolveTaskText(this.messageHistory, this.pinnedTaskText);
5824
+ if (taskText)
5825
+ this.pinnedTaskText = taskText;
5784
5826
  let resumeText = '';
5785
5827
  let resumeHelper;
5786
5828
  let resumeCost = 0;
5787
5829
  let memorySource = 'none';
5788
- // v2: a checkpoint written within the last ~15% of the threshold is fresh enough to reuse (no second helper call)
5789
- if (compactionResume && this.lastResumeMemory && (currentTokens - this.lastResumeMemory.tokensAt) <= threshold * 0.15) {
5830
+ // v3 (4.108.3): reuse the checkpoint only if it already covered every message being dropped. The v2 token-distance test went
5831
+ // negative after the first compaction and replayed one stale checkpoint forever (the hardened live proof: 27 compactions, 0 fresh memories).
5832
+ if (compactionResume && this.lastResumeMemory && coversAll(this.lastResumeMemory.covered, dropped)) {
5790
5833
  resumeText = this.lastResumeMemory.text;
5791
5834
  resumeHelper = this.lastResumeMemory.helperModelId;
5792
5835
  resumeCost = 0;
@@ -5794,11 +5837,22 @@ export class CortexOrchestrator {
5794
5837
  }
5795
5838
  else if (compactionResume && dropped.length > 0 && this.config.useHelperModels && typeof this.helperMiddleware?.summarizeForResume === 'function') {
5796
5839
  try {
5797
- const r = await this.helperMiddleware.summarizeForResume(this.convertToCanonicalMessages(dropped), model, 6000, renderResumeMemoryPrompt('{{CONVERSATION}}', 6000));
5840
+ const tgt = resumeMemoryTargetTokens(threshold);
5841
+ // v3: roll the prior memory forward — the helper summarizes [prior memory, ...newly dropped] into ONE memory.
5842
+ const prior = this.lastResumeMemory;
5843
+ const seed = prior && prior.text.trim() ? [buildRollingSeedMessage(prior.text)] : [];
5844
+ const r = await this.helperMiddleware.summarizeForResume(this.convertToCanonicalMessages([...seed, ...dropped]), model, tgt, renderResumeMemoryPrompt('{{CONVERSATION}}', tgt));
5798
5845
  resumeText = String(r?.summary ?? '');
5799
5846
  resumeHelper = r?.helperModelId;
5800
5847
  resumeCost = Number(r?.cost ?? 0);
5801
- memorySource = resumeText.trim() ? 'dropped' : 'none';
5848
+ memorySource = resumeText.trim() ? (seed.length ? 'rolled' : 'dropped') : 'none';
5849
+ if (resumeText.trim()) {
5850
+ const covered = prior?.covered ?? new WeakSet();
5851
+ for (const m of dropped)
5852
+ if (m && typeof m === 'object')
5853
+ covered.add(m);
5854
+ this.lastResumeMemory = { text: resumeText, tokensAt: currentTokens, turn: this.turnNumber, helperModelId: resumeHelper, cost: resumeCost, covered };
5855
+ }
5802
5856
  }
5803
5857
  catch (e) {
5804
5858
  console.error('[Orchestrator Context] resume memory summary failed (degrading to task-only reminder):', e?.message ?? e);
@@ -5807,10 +5861,21 @@ export class CortexOrchestrator {
5807
5861
  // Update message history with selected messages (dropping any OLDER compaction reminder — the new one supersedes it)
5808
5862
  this.messageHistory = compactionResume ? selectedMessages.filter((m) => !isCompactionResumeMessage(m)) : selectedMessages;
5809
5863
  if (compactionResume && dropped.length > 0) {
5864
+ // v3: re-arm the checkpoint rung relative to the post-compaction level so a fresh whole-conversation memory is written
5865
+ // again as the context climbs (v2 left lastCheckpointBand at its pre-compaction high-water mark → the rung never re-fired).
5866
+ this.lastCheckpointBand = resolveRearmBand(newTokenCount, threshold);
5867
+ const workspaceState = this.buildCompactionWorkspaceState();
5868
+ let sessionPath;
5869
+ try {
5870
+ sessionPath = this.currentSessionId ? this.historyStore.getSessionPath(this.currentSessionId) : undefined;
5871
+ }
5872
+ catch {
5873
+ sessionPath = undefined;
5874
+ }
5810
5875
  const text = buildCompactionResumeReminder({
5811
5876
  turn: this.turnNumber, droppedCount: dropped.length, keptCount: this.messageHistory.length,
5812
5877
  tokensBefore: currentTokens, tokensAfter: newTokenCount, resumeText, taskText, helperModelId: resumeHelper,
5813
- rebuild: buildRebuildInstructions(), memorySource,
5878
+ rebuild: buildRebuildInstructions({ sessionPath }), memorySource, workspaceState,
5814
5879
  });
5815
5880
  const reminder = {
5816
5881
  uuid: uuidv4(),
@@ -5833,7 +5898,8 @@ export class CortexOrchestrator {
5833
5898
  kind: 'compaction',
5834
5899
  detail: { mode: 'proactive', turn: this.turnNumber, tokensBefore: currentTokens, tokensAfter: newTokenCount,
5835
5900
  dropped: dropped.length, kept: selectedMessages.length, resumeChars: resumeText.length,
5836
- helperModelId: resumeHelper ?? null, cost: resumeCost, taskPinned: taskText.length > 0, memorySource },
5901
+ helperModelId: resumeHelper ?? null, cost: resumeCost, taskPinned: taskText.length > 0, memorySource,
5902
+ workspaceStateChars: workspaceState.length, sessionPath: sessionPath ?? null },
5837
5903
  }).catch(() => { });
5838
5904
  if (this.config.debug)
5839
5905
  console.log(`[Orchestrator Context] resume reminder injected (${resumeText.length} chars memory, task pinned=${taskText.length > 0})`);