@nexus-cortex/core 4.89.0 → 4.90.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.
Files changed (41) hide show
  1. package/.env.example +76 -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 +77 -11
  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 +71 -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';
@@ -64,6 +64,8 @@ import { detectTailRepetition, tailLoopGuardEnabled } from './tailRepetitionDete
64
64
  import { classifyEmptyResponse, emptyResponseNudge } from './emptyResponseClassifier.js';
65
65
  // PTC: Progressive tool loading for non-PTC providers
66
66
  import { ClientSideToolFilter } from '../tools/ClientSideToolFilter.js';
67
+ /** Grace the orchestrator's outer tool-abort waits past the tool's own deadline (ShellTool promote/kill fires first; 4.90.1). */
68
+ const OUTER_TIMEOUT_GRACE_MS = 30_000;
67
69
  /**
68
70
  * Cortex Orchestrator - Phase 2.1
69
71
  *
@@ -478,7 +480,7 @@ export class CortexOrchestrator {
478
480
  const catalogVision = this.getModel(this.currentModelId ?? '')?.vision === true;
479
481
  return [...toolFactory.getAllTools(), ...extras]
480
482
  .filter((t) => endTurnGateOn || t.name !== 'EndTurn')
481
- .filter((t) => catalogVision || t.name !== 'ReadImage')
483
+ .filter((t) => catalogVision || !!resolveVisionHelperModel(process.env) || t.name !== 'ReadImage')
482
484
  .filter((t) => (seen.has(t.name) ? false : (seen.add(t.name), true)))
483
485
  .map((t) => ({
484
486
  name: namingHandler.convertName(t.name, convention),
@@ -634,7 +636,7 @@ export class CortexOrchestrator {
634
636
  const visionOn = model?.vision === true;
635
637
  const factoryTools = this.applyBaseToolAllowlist(toolFactory.getAllTools()
636
638
  .filter(t => endTurnGateOn || t.name !== 'EndTurn')
637
- .filter(t => visionOn || t.name !== 'ReadImage'));
639
+ .filter(t => visionOn || !!resolveVisionHelperModel(process.env) || t.name !== 'ReadImage'));
638
640
  // Tool-profile: MCP/management/context tools bypass ToolFactory, so the
639
641
  // bash-only arm must suppress them here or the surface leaks (lean keeps
640
642
  // them — they are part of the "guided" surface under test).
@@ -898,7 +900,7 @@ export class CortexOrchestrator {
898
900
  // Thread the CARD's anchor to the executors (per-orchestrator config, by
899
901
  // reference) so framing-aware executor behavior (ShellTool steering
900
902
  // default) is model-card/registry-scoped, not process-global env.
901
- this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions });
903
+ this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions, headless: this.approvalMode.autoApproveActions });
902
904
  if (toolsToUse && toolsToUse.length > 0) {
903
905
  toolsToUse = this.applyAnchorIfArmed(toolsToUse);
904
906
  }
@@ -1909,11 +1911,16 @@ export class CortexOrchestrator {
1909
1911
  break;
1910
1912
  }
1911
1913
  // Phase 2.5 Day 3: Execute tools with timeout support
1914
+ // 🔴 4.90.1: this OUTER abort must not race ShellTool's own deadline — ShellTool promotes a
1915
+ // still-running Bash to the background AT TOOL_TIMEOUT_MS (TOOL_TIMEOUT_MODE), and an outer
1916
+ // abort at the same instant made the promote inert (v4 pro/train-fasttext: 22 "cancelled",
1917
+ // 0 promotions). The outer timer is the last-resort cap for hung tools: it fires a grace
1918
+ // period AFTER the tool's own deadline.
1912
1919
  const abortController = new AbortController();
1913
1920
  const timeoutId = setTimeout(() => {
1914
- console.warn(`[Orchestrator Phase 2.5] Tool execution timeout after ${TOOL_TIMEOUT_MS}ms`);
1921
+ console.warn(`[Orchestrator Phase 2.5] Tool execution timeout after ${TOOL_TIMEOUT_MS + OUTER_TIMEOUT_GRACE_MS}ms`);
1915
1922
  abortController.abort();
1916
- }, TOOL_TIMEOUT_MS);
1923
+ }, TOOL_TIMEOUT_MS + OUTER_TIMEOUT_GRACE_MS);
1917
1924
  // Track which tool_use_ids have been processed to avoid duplicates in error handler
1918
1925
  const processedToolUseIds = new Set();
1919
1926
  try {
@@ -2095,6 +2102,8 @@ export class CortexOrchestrator {
2095
2102
  verification: etUse?.input?.verification,
2096
2103
  userTaskText: this.lastRealUserText(),
2097
2104
  turnUsedMutatingTool,
2105
+ strict: resolveEndTurnRequirementsStrict(process.env),
2106
+ toolOutputs: thisTurnToolOutputs.join('\n'),
2098
2107
  });
2099
2108
  if (s4.ok) {
2100
2109
  endTurnCalled = true;
@@ -2156,6 +2165,7 @@ export class CortexOrchestrator {
2156
2165
  // Item 7: scrub ReadImage payloads from metadata BEFORE persist;
2157
2166
  // the images inject as a user message after the results land.
2158
2167
  const pendingImages = this.collectPendingImages(toolResults, effectiveModel.vision === true);
2168
+ await this.applyVisionHandoffs();
2159
2169
  // Create tool_result messages and add to history
2160
2170
  for (const toolResult of toolResults) {
2161
2171
  const toolResultMessageId = uuidv4();
@@ -3196,7 +3206,7 @@ export class CortexOrchestrator {
3196
3206
  // Thread the CARD's anchor to the executors (per-orchestrator config, by
3197
3207
  // reference) so framing-aware executor behavior (ShellTool steering
3198
3208
  // default) is model-card/registry-scoped, not process-global env.
3199
- this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions });
3209
+ this.executorRegistry.updateConfig({ activeAnchorProfile: this.cardAnchorProfile, allowCommandSubstitution: this.approvalMode.autoApproveActions, headless: this.approvalMode.autoApproveActions });
3200
3210
  if (toolsToUse && toolsToUse.length > 0) {
3201
3211
  toolsToUse = this.applyAnchorIfArmed(toolsToUse);
3202
3212
  }
@@ -3735,14 +3745,15 @@ export class CortexOrchestrator {
3735
3745
  if (toolCallIteration >= MAX_TOOL_ITERATIONS) {
3736
3746
  break;
3737
3747
  }
3738
- // Execute tools with timeout
3748
+ // Execute tools with timeout (outer cap fires a grace period AFTER the tool's own deadline —
3749
+ // see the non-stream site: ShellTool's promote-at-deadline must win the race; 4.90.1)
3739
3750
  const abortController = new AbortController();
3740
3751
  const timeoutId = setTimeout(() => {
3741
3752
  if (this.config.debug) {
3742
- console.warn(`[Orchestrator Streaming] Tool execution timeout after ${TOOL_TIMEOUT_MS}ms`);
3753
+ console.warn(`[Orchestrator Streaming] Tool execution timeout after ${TOOL_TIMEOUT_MS + OUTER_TIMEOUT_GRACE_MS}ms`);
3743
3754
  }
3744
3755
  abortController.abort();
3745
- }, TOOL_TIMEOUT_MS);
3756
+ }, TOOL_TIMEOUT_MS + OUTER_TIMEOUT_GRACE_MS);
3746
3757
  try {
3747
3758
  // Execute tools (reuse existing method)
3748
3759
  const toolResults = await this.handleToolCalls(toolUseBlocks, abortController.signal, structuredOutputState);
@@ -3808,6 +3819,7 @@ export class CortexOrchestrator {
3808
3819
  }
3809
3820
  // Item 7: scrub ReadImage payloads before persist (streaming parity).
3810
3821
  const pendingImages = this.collectPendingImages(toolResults, effectiveModel.vision === true);
3822
+ await this.applyVisionHandoffs();
3811
3823
  // Create tool_result messages and save to history
3812
3824
  for (const toolResult of toolResults) {
3813
3825
  // Emit tool result as streaming chunk for CLI display
@@ -6801,6 +6813,43 @@ export class CortexOrchestrator {
6801
6813
  * hallucinated/PTC call that slipped the surface gate), the result is
6802
6814
  * rewritten into an actionable error — a text-only model must never have
6803
6815
  * an image dangled at it. */
6816
+ /** Vision hand-off queue (filled by collectPendingImages for text-only primaries). */
6817
+ pendingVisionHandoffs = [];
6818
+ /** Resolve queued ReadImage hand-offs through the helper middleware: the vision helper's TEXT
6819
+ * replaces the tool_result content. Failures degrade to the pre-hand-off guidance (bash OCR). */
6820
+ async applyVisionHandoffs() {
6821
+ const queue = this.pendingVisionHandoffs;
6822
+ this.pendingVisionHandoffs = [];
6823
+ for (const h of queue) {
6824
+ const modelId = resolveVisionHelperModel(process.env) || 'deepseek-v4-flash-vision-exp';
6825
+ try {
6826
+ const t0 = Date.now();
6827
+ const text = await this.helperMiddleware.describeImage({
6828
+ mediaType: h.payload.mediaType, base64: h.payload.base64, prompt: h.prompt, filePath: h.filePath, helperModelId: modelId,
6829
+ });
6830
+ h.tr.is_error = false;
6831
+ h.tr.content =
6832
+ `[ReadImage → vision helper ${modelId}${h.filePath ? ` on ${h.filePath}` : ''}]\n` +
6833
+ (text || '(the vision helper returned no description)') +
6834
+ `\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.)`;
6835
+ if (this.config.debug)
6836
+ console.log(`[VisionHandoff] ${modelId} described ${h.filePath || 'image'} in ${Date.now() - t0}ms (${(text || '').length} chars)`);
6837
+ const store = this.getDecisionStore();
6838
+ if (store) {
6839
+ void store.recordEvent({
6840
+ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', toolName: 'ReadImage',
6841
+ detail: { kind: 'vision_handoff', helper: modelId, chars: (text || '').length, filePath: h.filePath },
6842
+ }).catch(() => { });
6843
+ }
6844
+ }
6845
+ catch (err) {
6846
+ h.tr.is_error = true;
6847
+ h.tr.content =
6848
+ `ReadImage vision hand-off failed (${modelId}: ${err?.message || err}). ` +
6849
+ 'Extract what you need through bash instead (e.g. tesseract OCR, ffprobe, PIL).';
6850
+ }
6851
+ }
6852
+ }
6804
6853
  collectPendingImages(toolResults, modelVision) {
6805
6854
  const out = [];
6806
6855
  for (const tr of toolResults) {
@@ -6811,6 +6860,16 @@ export class CortexOrchestrator {
6811
6860
  if (tr.is_error)
6812
6861
  continue;
6813
6862
  if (!modelVision) {
6863
+ // VISION HAND-OFF (2026-09-02): a text-only primary called ReadImage. If a vision helper
6864
+ // is configured, queue the payload for the helper middleware (applyVisionHandoffs runs
6865
+ // right after this at both call sites and REPLACES the tool_result with the helper's
6866
+ // text). The primary never receives an image payload — item 7's safety intent holds.
6867
+ if (resolveVisionHelperModel(process.env)) {
6868
+ this.pendingVisionHandoffs.push({ tr, payload, prompt: tr.metadata?.imagePrompt, filePath: tr.metadata?.imageFilePath });
6869
+ delete tr.metadata.imagePrompt;
6870
+ delete tr.metadata.imageFilePath;
6871
+ continue;
6872
+ }
6814
6873
  tr.is_error = true;
6815
6874
  tr.content =
6816
6875
  'ReadImage requires a vision-capable model — the active model cannot see images. ' +
@@ -7055,6 +7114,13 @@ export class CortexOrchestrator {
7055
7114
  const store = this.getDecisionStore();
7056
7115
  if (!store)
7057
7116
  return result;
7117
+ // TOOL_TIMEOUT_MODE engagement evidence: a promoted-to-background tool call.
7118
+ if (result.metadata?.promotedToBackground) {
7119
+ void store.recordEvent({
7120
+ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', toolName: toolUse.name,
7121
+ detail: { kind: 'tool_promoted', bashId: result.metadata.promotedToBackground },
7122
+ }).catch(() => { });
7123
+ }
7058
7124
  // Unified outcome layer (docs/UNIFIED_OUTCOME_LADDER.md): ONE failure
7059
7125
  // semantics for recording + reminders. Critically, a bash command that
7060
7126
  // ran and exited nonzero is a FAILURE here even though its tool result