@nexus-cortex/core 4.89.0 → 4.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.env.example +73 -3
  2. package/dist/config/RuntimeConfigRegistry.d.ts.map +1 -1
  3. package/dist/config/RuntimeConfigRegistry.js +2 -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 +2 -0
  7. package/dist/config/SettingsLoader.js.map +1 -1
  8. package/dist/config/SettingsSchema.d.ts +4 -0
  9. package/dist/config/SettingsSchema.d.ts.map +1 -1
  10. package/dist/config/SettingsSchema.js +19 -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 +3 -1
  14. package/dist/config/effectiveConfig.js.map +1 -1
  15. package/dist/middleware/HelperModelMiddleware.d.ts +13 -0
  16. package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
  17. package/dist/middleware/HelperModelMiddleware.js +32 -0
  18. package/dist/middleware/HelperModelMiddleware.js.map +1 -1
  19. package/dist/middleware/helpers/HelperMiddlewareAdapter.interface.d.ts +6 -1
  20. package/dist/middleware/helpers/HelperMiddlewareAdapter.interface.d.ts.map +1 -1
  21. package/dist/middleware/helpers/HelperMiddlewareAdapter.interface.js.map +1 -1
  22. package/dist/middleware/helpers/adapters/ChatCompletionsAPIHelperAdapter.d.ts +9 -1
  23. package/dist/middleware/helpers/adapters/ChatCompletionsAPIHelperAdapter.d.ts.map +1 -1
  24. package/dist/middleware/helpers/adapters/ChatCompletionsAPIHelperAdapter.js +18 -4
  25. package/dist/middleware/helpers/adapters/ChatCompletionsAPIHelperAdapter.js.map +1 -1
  26. package/dist/orchestrator/CortexOrchestrator.d.ts +5 -0
  27. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  28. package/dist/orchestrator/CortexOrchestrator.js +64 -6
  29. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  30. package/dist/orchestrator/requirementsVerification.d.ts +11 -0
  31. package/dist/orchestrator/requirementsVerification.d.ts.map +1 -1
  32. package/dist/orchestrator/requirementsVerification.js +58 -1
  33. package/dist/orchestrator/requirementsVerification.js.map +1 -1
  34. package/dist/tools/ToolProfile.d.ts +7 -0
  35. package/dist/tools/ToolProfile.d.ts.map +1 -1
  36. package/dist/tools/ToolProfile.js +13 -0
  37. package/dist/tools/ToolProfile.js.map +1 -1
  38. package/dist/tools/registries/BaseToolRegistry.d.ts.map +1 -1
  39. package/dist/tools/registries/BaseToolRegistry.js +6 -2
  40. package/dist/tools/registries/BaseToolRegistry.js.map +1 -1
  41. package/package.json +3 -3
@@ -30,7 +30,7 @@ import { CacheMetricsAccumulator } from '../session/CacheMetricsAccumulator.js';
30
30
  import { SubAgentProcessManager, createSubAgentProcessManager } from './SubAgentProcessManager.js';
31
31
  // Phase 1: Tool Architecture Refactor - Unified Tool Registry
32
32
  import { toolFactory } from '../tools/ToolFactory.js';
33
- import { resolveToolProfile, resolveToolAnchor, resolveFrameProfile, resolveLiftNudge, resolveHeadlessDropAskUser, resolveDeferredLoading, isNarrowProfile, isToolAllowedByProfile, applyToolProfile, webToolBlocked } from '../tools/ToolProfile.js';
33
+ import { resolveToolProfile, resolveToolAnchor, resolveFrameProfile, resolveLiftNudge, resolveHeadlessDropAskUser, resolveDeferredLoading, isNarrowProfile, isToolAllowedByProfile, applyToolProfile, webToolBlocked, resolveVisionHelperModel } from '../tools/ToolProfile.js';
34
34
  import { readStagedDoctrine, applyCuratedDoctrine, runOrientForStaging, withTimeout } from './doctrineCuration.js';
35
35
  import { ExactRepeatTracker } from '../training/loopLadder.js';
36
36
  import { slashCommandRegistry } from '../commands/SlashCommandRegistry.js';
@@ -44,7 +44,7 @@ import { classifyToolOutcome } from '../training/toolOutcome.js';
44
44
  import { LoopLadder, formatLadderSignal } from '../training/loopLadder.js';
45
45
  import { shouldNudgeInaction, formatInactionNudge } from './inactionGuard.js';
46
46
  import { applyImageTtlForRequest } from './imageTtl.js';
47
- import { verifyRequirements, resolveEndTurnRequirementsMode } from './requirementsVerification.js';
47
+ import { verifyRequirements, resolveEndTurnRequirementsMode, resolveEndTurnRequirementsStrict } from './requirementsVerification.js';
48
48
  import { verifyIntegrity, resolveEndTurnIntegrityMode } from './integrityVerification.js';
49
49
  import { detectSurrenderText, resolveSurrenderNudgeMode, SURRENDER_REMINDER } from './turnEndGuards.js';
50
50
  import { ModelRouterMatrix } from '../training/ModelRouterMatrix.js';
@@ -478,7 +478,7 @@ export class CortexOrchestrator {
478
478
  const catalogVision = this.getModel(this.currentModelId ?? '')?.vision === true;
479
479
  return [...toolFactory.getAllTools(), ...extras]
480
480
  .filter((t) => endTurnGateOn || t.name !== 'EndTurn')
481
- .filter((t) => catalogVision || t.name !== 'ReadImage')
481
+ .filter((t) => catalogVision || !!resolveVisionHelperModel(process.env) || t.name !== 'ReadImage')
482
482
  .filter((t) => (seen.has(t.name) ? false : (seen.add(t.name), true)))
483
483
  .map((t) => ({
484
484
  name: namingHandler.convertName(t.name, convention),
@@ -634,7 +634,7 @@ export class CortexOrchestrator {
634
634
  const visionOn = model?.vision === true;
635
635
  const factoryTools = this.applyBaseToolAllowlist(toolFactory.getAllTools()
636
636
  .filter(t => endTurnGateOn || t.name !== 'EndTurn')
637
- .filter(t => visionOn || t.name !== 'ReadImage'));
637
+ .filter(t => visionOn || !!resolveVisionHelperModel(process.env) || t.name !== 'ReadImage'));
638
638
  // Tool-profile: MCP/management/context tools bypass ToolFactory, so the
639
639
  // bash-only arm must suppress them here or the surface leaks (lean keeps
640
640
  // them — they are part of the "guided" surface under test).
@@ -898,7 +898,7 @@ export class CortexOrchestrator {
898
898
  // Thread the CARD's anchor to the executors (per-orchestrator config, by
899
899
  // reference) so framing-aware executor behavior (ShellTool steering
900
900
  // default) is model-card/registry-scoped, not process-global env.
901
- this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions });
901
+ this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions, headless: this.approvalMode.autoApproveActions });
902
902
  if (toolsToUse && toolsToUse.length > 0) {
903
903
  toolsToUse = this.applyAnchorIfArmed(toolsToUse);
904
904
  }
@@ -2095,6 +2095,8 @@ export class CortexOrchestrator {
2095
2095
  verification: etUse?.input?.verification,
2096
2096
  userTaskText: this.lastRealUserText(),
2097
2097
  turnUsedMutatingTool,
2098
+ strict: resolveEndTurnRequirementsStrict(process.env),
2099
+ toolOutputs: thisTurnToolOutputs.join('\n'),
2098
2100
  });
2099
2101
  if (s4.ok) {
2100
2102
  endTurnCalled = true;
@@ -2156,6 +2158,7 @@ export class CortexOrchestrator {
2156
2158
  // Item 7: scrub ReadImage payloads from metadata BEFORE persist;
2157
2159
  // the images inject as a user message after the results land.
2158
2160
  const pendingImages = this.collectPendingImages(toolResults, effectiveModel.vision === true);
2161
+ await this.applyVisionHandoffs();
2159
2162
  // Create tool_result messages and add to history
2160
2163
  for (const toolResult of toolResults) {
2161
2164
  const toolResultMessageId = uuidv4();
@@ -3196,7 +3199,7 @@ export class CortexOrchestrator {
3196
3199
  // Thread the CARD's anchor to the executors (per-orchestrator config, by
3197
3200
  // reference) so framing-aware executor behavior (ShellTool steering
3198
3201
  // default) is model-card/registry-scoped, not process-global env.
3199
- this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions });
3202
+ this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions, headless: this.approvalMode.autoApproveActions });
3200
3203
  if (toolsToUse && toolsToUse.length > 0) {
3201
3204
  toolsToUse = this.applyAnchorIfArmed(toolsToUse);
3202
3205
  }
@@ -3808,6 +3811,7 @@ export class CortexOrchestrator {
3808
3811
  }
3809
3812
  // Item 7: scrub ReadImage payloads before persist (streaming parity).
3810
3813
  const pendingImages = this.collectPendingImages(toolResults, effectiveModel.vision === true);
3814
+ await this.applyVisionHandoffs();
3811
3815
  // Create tool_result messages and save to history
3812
3816
  for (const toolResult of toolResults) {
3813
3817
  // Emit tool result as streaming chunk for CLI display
@@ -6801,6 +6805,43 @@ export class CortexOrchestrator {
6801
6805
  * hallucinated/PTC call that slipped the surface gate), the result is
6802
6806
  * rewritten into an actionable error — a text-only model must never have
6803
6807
  * an image dangled at it. */
6808
+ /** Vision hand-off queue (filled by collectPendingImages for text-only primaries). */
6809
+ pendingVisionHandoffs = [];
6810
+ /** Resolve queued ReadImage hand-offs through the helper middleware: the vision helper's TEXT
6811
+ * replaces the tool_result content. Failures degrade to the pre-hand-off guidance (bash OCR). */
6812
+ async applyVisionHandoffs() {
6813
+ const queue = this.pendingVisionHandoffs;
6814
+ this.pendingVisionHandoffs = [];
6815
+ for (const h of queue) {
6816
+ const modelId = resolveVisionHelperModel(process.env) || 'deepseek-v4-flash-vision-exp';
6817
+ try {
6818
+ const t0 = Date.now();
6819
+ const text = await this.helperMiddleware.describeImage({
6820
+ mediaType: h.payload.mediaType, base64: h.payload.base64, prompt: h.prompt, filePath: h.filePath, helperModelId: modelId,
6821
+ });
6822
+ h.tr.is_error = false;
6823
+ h.tr.content =
6824
+ `[ReadImage → vision helper ${modelId}${h.filePath ? ` on ${h.filePath}` : ''}]\n` +
6825
+ (text || '(the vision helper returned no description)') +
6826
+ `\n\n(Read by the vision helper on your behalf — the active model is text-only. Ask again with a more specific \`prompt\` if you need a different reading.)`;
6827
+ if (this.config.debug)
6828
+ console.log(`[VisionHandoff] ${modelId} described ${h.filePath || 'image'} in ${Date.now() - t0}ms (${(text || '').length} chars)`);
6829
+ const store = this.getDecisionStore();
6830
+ if (store) {
6831
+ void store.recordEvent({
6832
+ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', toolName: 'ReadImage',
6833
+ detail: { kind: 'vision_handoff', helper: modelId, chars: (text || '').length, filePath: h.filePath },
6834
+ }).catch(() => { });
6835
+ }
6836
+ }
6837
+ catch (err) {
6838
+ h.tr.is_error = true;
6839
+ h.tr.content =
6840
+ `ReadImage vision hand-off failed (${modelId}: ${err?.message || err}). ` +
6841
+ 'Extract what you need through bash instead (e.g. tesseract OCR, ffprobe, PIL).';
6842
+ }
6843
+ }
6844
+ }
6804
6845
  collectPendingImages(toolResults, modelVision) {
6805
6846
  const out = [];
6806
6847
  for (const tr of toolResults) {
@@ -6811,6 +6852,16 @@ export class CortexOrchestrator {
6811
6852
  if (tr.is_error)
6812
6853
  continue;
6813
6854
  if (!modelVision) {
6855
+ // VISION HAND-OFF (2026-09-02): a text-only primary called ReadImage. If a vision helper
6856
+ // is configured, queue the payload for the helper middleware (applyVisionHandoffs runs
6857
+ // right after this at both call sites and REPLACES the tool_result with the helper's
6858
+ // text). The primary never receives an image payload — item 7's safety intent holds.
6859
+ if (resolveVisionHelperModel(process.env)) {
6860
+ this.pendingVisionHandoffs.push({ tr, payload, prompt: tr.metadata?.imagePrompt, filePath: tr.metadata?.imageFilePath });
6861
+ delete tr.metadata.imagePrompt;
6862
+ delete tr.metadata.imageFilePath;
6863
+ continue;
6864
+ }
6814
6865
  tr.is_error = true;
6815
6866
  tr.content =
6816
6867
  'ReadImage requires a vision-capable model — the active model cannot see images. ' +
@@ -7055,6 +7106,13 @@ export class CortexOrchestrator {
7055
7106
  const store = this.getDecisionStore();
7056
7107
  if (!store)
7057
7108
  return result;
7109
+ // TOOL_TIMEOUT_MODE engagement evidence: a promoted-to-background tool call.
7110
+ if (result.metadata?.promotedToBackground) {
7111
+ void store.recordEvent({
7112
+ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', toolName: toolUse.name,
7113
+ detail: { kind: 'tool_promoted', bashId: result.metadata.promotedToBackground },
7114
+ }).catch(() => { });
7115
+ }
7058
7116
  // Unified outcome layer (docs/UNIFIED_OUTCOME_LADDER.md): ONE failure
7059
7117
  // semantics for recording + reminders. Critically, a bash command that
7060
7118
  // ran and exited nonzero is a FAILURE here even though its tool result