@nexus-cortex/core 4.130.0 → 4.131.2
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.
- package/.env.defaults +172 -2
- package/dist/adapters/ChatCompletionsAPIAdapter.d.ts.map +1 -1
- package/dist/adapters/ChatCompletionsAPIAdapter.js +5 -1
- package/dist/adapters/ChatCompletionsAPIAdapter.js.map +1 -1
- package/dist/commands/SlashCommandRegistry.d.ts.map +1 -1
- package/dist/commands/SlashCommandRegistry.js +7 -0
- package/dist/commands/SlashCommandRegistry.js.map +1 -1
- package/dist/config/AnthropicCredentialService.d.ts.map +1 -1
- package/dist/config/AnthropicCredentialService.js +2 -1
- package/dist/config/AnthropicCredentialService.js.map +1 -1
- package/dist/config/RuntimeConfigRegistry.d.ts.map +1 -1
- package/dist/config/RuntimeConfigRegistry.js +18 -0
- package/dist/config/RuntimeConfigRegistry.js.map +1 -1
- package/dist/config/SettingsLoader.d.ts +3 -0
- package/dist/config/SettingsLoader.d.ts.map +1 -1
- package/dist/config/SettingsLoader.js +42 -0
- package/dist/config/SettingsLoader.js.map +1 -1
- package/dist/config/SettingsSchema.d.ts +19 -0
- package/dist/config/SettingsSchema.d.ts.map +1 -1
- package/dist/config/SettingsSchema.js +172 -0
- package/dist/config/SettingsSchema.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +20 -2
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +4 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/localBrowser.d.ts +13 -0
- package/dist/config/localBrowser.d.ts.map +1 -0
- package/dist/config/localBrowser.js +53 -0
- package/dist/config/localBrowser.js.map +1 -0
- package/dist/config/logRedaction.d.ts +5 -0
- package/dist/config/logRedaction.d.ts.map +1 -0
- package/dist/config/logRedaction.js +68 -0
- package/dist/config/logRedaction.js.map +1 -0
- package/dist/config/secretProviders.d.ts +16 -0
- package/dist/config/secretProviders.d.ts.map +1 -0
- package/dist/config/secretProviders.js +26 -0
- package/dist/config/secretProviders.js.map +1 -0
- package/dist/config/secretsVault.d.ts +121 -0
- package/dist/config/secretsVault.d.ts.map +1 -0
- package/dist/config/secretsVault.js +320 -0
- package/dist/config/secretsVault.js.map +1 -0
- package/dist/config/spaSecretTypes.d.ts +329 -0
- package/dist/config/spaSecretTypes.d.ts.map +1 -0
- package/dist/config/spaSecretTypes.js +326 -0
- package/dist/config/spaSecretTypes.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/McpClient.d.ts +2 -0
- package/dist/mcp/McpClient.d.ts.map +1 -1
- package/dist/mcp/McpClient.js +9 -1
- package/dist/mcp/McpClient.js.map +1 -1
- package/dist/mcp/McpServerRegistry.d.ts +2 -13
- package/dist/mcp/McpServerRegistry.d.ts.map +1 -1
- package/dist/mcp/McpServerRegistry.js +17 -2
- package/dist/mcp/McpServerRegistry.js.map +1 -1
- package/dist/mcp/essentialMcpTools.d.ts +11 -0
- package/dist/mcp/essentialMcpTools.d.ts.map +1 -0
- package/dist/mcp/essentialMcpTools.js +22 -0
- package/dist/mcp/essentialMcpTools.js.map +1 -0
- package/dist/mcp/mcpSchemaCompat.d.ts +9 -0
- package/dist/mcp/mcpSchemaCompat.d.ts.map +1 -0
- package/dist/mcp/mcpSchemaCompat.js +32 -0
- package/dist/mcp/mcpSchemaCompat.js.map +1 -0
- package/dist/middleware/HelperModelMiddleware.d.ts +4 -0
- package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
- package/dist/middleware/HelperModelMiddleware.js +2 -2
- package/dist/middleware/HelperModelMiddleware.js.map +1 -1
- package/dist/middleware/SystemMessageMiddleware.d.ts.map +1 -1
- package/dist/middleware/SystemMessageMiddleware.js +6 -0
- package/dist/middleware/SystemMessageMiddleware.js.map +1 -1
- package/dist/middleware/permissions/DefaultPolicies.d.ts.map +1 -1
- package/dist/middleware/permissions/DefaultPolicies.js +2 -0
- package/dist/middleware/permissions/DefaultPolicies.js.map +1 -1
- package/dist/orchestrator/APIClient.d.ts.map +1 -1
- package/dist/orchestrator/APIClient.js +46 -1
- package/dist/orchestrator/APIClient.js.map +1 -1
- package/dist/orchestrator/CortexOrchestrator.d.ts +43 -8
- package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
- package/dist/orchestrator/CortexOrchestrator.js +341 -40
- package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
- package/dist/orchestrator/HerdrSubAgentRunner.d.ts.map +1 -1
- package/dist/orchestrator/HerdrSubAgentRunner.js +2 -1
- package/dist/orchestrator/HerdrSubAgentRunner.js.map +1 -1
- package/dist/orchestrator/SubAgentProcessManager.d.ts.map +1 -1
- package/dist/orchestrator/SubAgentProcessManager.js +2 -0
- package/dist/orchestrator/SubAgentProcessManager.js.map +1 -1
- package/dist/orchestrator/browserWallNudge.d.ts +93 -0
- package/dist/orchestrator/browserWallNudge.d.ts.map +1 -0
- package/dist/orchestrator/browserWallNudge.js +227 -0
- package/dist/orchestrator/browserWallNudge.js.map +1 -0
- package/dist/orchestrator/citationVerification.d.ts.map +1 -1
- package/dist/orchestrator/citationVerification.js +16 -0
- package/dist/orchestrator/citationVerification.js.map +1 -1
- package/dist/orchestrator/cortexProxyFetch.d.ts +8 -0
- package/dist/orchestrator/cortexProxyFetch.d.ts.map +1 -1
- package/dist/orchestrator/cortexProxyFetch.js +32 -0
- package/dist/orchestrator/cortexProxyFetch.js.map +1 -1
- package/dist/orchestrator/emptyResponseClassifier.d.ts +1 -1
- package/dist/orchestrator/emptyResponseClassifier.d.ts.map +1 -1
- package/dist/orchestrator/emptyResponseClassifier.js +8 -2
- package/dist/orchestrator/emptyResponseClassifier.js.map +1 -1
- package/dist/orchestrator/endTurnGates.d.ts +39 -0
- package/dist/orchestrator/endTurnGates.d.ts.map +1 -1
- package/dist/orchestrator/endTurnGates.js +159 -3
- package/dist/orchestrator/endTurnGates.js.map +1 -1
- package/dist/orchestrator/imageTtl.d.ts.map +1 -1
- package/dist/orchestrator/imageTtl.js +14 -5
- package/dist/orchestrator/imageTtl.js.map +1 -1
- package/dist/orchestrator/integrityVerification.d.ts +2 -0
- package/dist/orchestrator/integrityVerification.d.ts.map +1 -1
- package/dist/orchestrator/integrityVerification.js +2 -2
- package/dist/orchestrator/integrityVerification.js.map +1 -1
- package/dist/orchestrator/requestDebugLog.d.ts +4 -0
- package/dist/orchestrator/requestDebugLog.d.ts.map +1 -0
- package/dist/orchestrator/requestDebugLog.js +55 -0
- package/dist/orchestrator/requestDebugLog.js.map +1 -0
- package/dist/orchestrator/requirementsVerification.d.ts +4 -0
- package/dist/orchestrator/requirementsVerification.d.ts.map +1 -1
- package/dist/orchestrator/requirementsVerification.js +12 -6
- package/dist/orchestrator/requirementsVerification.js.map +1 -1
- package/dist/orchestrator/subAgentTimeout.d.ts +3 -0
- package/dist/orchestrator/subAgentTimeout.d.ts.map +1 -1
- package/dist/orchestrator/subAgentTimeout.js +6 -0
- package/dist/orchestrator/subAgentTimeout.js.map +1 -1
- package/dist/orchestrator/toolAllowlist.d.ts +29 -0
- package/dist/orchestrator/toolAllowlist.d.ts.map +1 -1
- package/dist/orchestrator/toolAllowlist.js +45 -0
- package/dist/orchestrator/toolAllowlist.js.map +1 -1
- package/dist/tools/ToolProfile.d.ts +5 -21
- package/dist/tools/ToolProfile.d.ts.map +1 -1
- package/dist/tools/ToolProfile.js +17 -2
- package/dist/tools/ToolProfile.js.map +1 -1
- package/dist/tools/registries/BaseToolRegistry.d.ts.map +1 -1
- package/dist/tools/registries/BaseToolRegistry.js +26 -6
- package/dist/tools/registries/BaseToolRegistry.js.map +1 -1
- package/dist/training/AutoResearchConfirm.d.ts +105 -0
- package/dist/training/AutoResearchConfirm.d.ts.map +1 -0
- package/dist/training/AutoResearchConfirm.js +111 -0
- package/dist/training/AutoResearchConfirm.js.map +1 -0
- package/dist/training/AutoResearchGate.d.ts +6 -0
- package/dist/training/AutoResearchGate.d.ts.map +1 -1
- package/dist/training/AutoResearchGate.js +8 -2
- package/dist/training/AutoResearchGate.js.map +1 -1
- package/dist/training/AutoResearchGuards.d.ts +86 -0
- package/dist/training/AutoResearchGuards.d.ts.map +1 -0
- package/dist/training/AutoResearchGuards.js +81 -0
- package/dist/training/AutoResearchGuards.js.map +1 -0
- package/dist/training/BenchRunner.d.ts +21 -5
- package/dist/training/BenchRunner.d.ts.map +1 -1
- package/dist/training/BenchRunner.js +126 -54
- package/dist/training/BenchRunner.js.map +1 -1
- package/dist/training/EvalGuard.d.ts +43 -0
- package/dist/training/EvalGuard.d.ts.map +1 -0
- package/dist/training/EvalGuard.js +131 -0
- package/dist/training/EvalGuard.js.map +1 -0
- package/dist/training/ExperimentRunner.d.ts +22 -0
- package/dist/training/ExperimentRunner.d.ts.map +1 -1
- package/dist/training/ExperimentRunner.js +45 -9
- package/dist/training/ExperimentRunner.js.map +1 -1
- package/dist/training/endTurnResolver.d.ts +23 -1
- package/dist/training/endTurnResolver.d.ts.map +1 -1
- package/dist/training/endTurnResolver.js +35 -3
- package/dist/training/endTurnResolver.js.map +1 -1
- package/dist/training/mentorRole.d.ts.map +1 -1
- package/dist/training/mentorRole.js +6 -1
- package/dist/training/mentorRole.js.map +1 -1
- package/dist/training/scopeCheck.d.ts +41 -0
- package/dist/training/scopeCheck.d.ts.map +1 -0
- package/dist/training/scopeCheck.js +119 -0
- package/dist/training/scopeCheck.js.map +1 -0
- package/dist/training/usageAccounting.d.ts +13 -0
- package/dist/training/usageAccounting.d.ts.map +1 -1
- package/dist/training/usageAccounting.js +11 -0
- package/dist/training/usageAccounting.js.map +1 -1
- package/dist/utils/repairToolJson.d.ts +19 -0
- package/dist/utils/repairToolJson.d.ts.map +1 -0
- package/dist/utils/repairToolJson.js +122 -0
- package/dist/utils/repairToolJson.js.map +1 -0
- package/package.json +5 -4
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { liftPlanAllowedInSession } from '../config/interactiveProfile.js';
|
|
11
11
|
import { featureAppliedKeys } from '../config/featurePayload.js';
|
|
12
|
+
import { maskToolResults, maskSecretText } from '../config/secretsVault.js';
|
|
13
|
+
import { resolveBrowserBackend } from '../config/localBrowser.js';
|
|
14
|
+
import { normalizeMcpSchema } from '../mcp/mcpSchemaCompat.js';
|
|
12
15
|
import { v4 as uuidv4 } from 'uuid';
|
|
13
16
|
import { ToolNamingHandler } from '../adapters/ToolNamingHandler.js';
|
|
14
17
|
// Phase 2.4: Server-Side Tools Support
|
|
@@ -22,7 +25,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
|
22
25
|
import { execSync } from 'node:child_process';
|
|
23
26
|
import { ENV_RECON_COMMAND, resolveLiftPlanConfig, parsePlannerResponse, bankPlanText } from '../training/liftPlanner.js';
|
|
24
27
|
import { clipIn, FULL_CAPS, HINTS, resolveInventory, resolveInventoryPath } from '../training/steerInputs.js';
|
|
25
|
-
import { resolveEndTurnResolverConfig, parseResolverVerdict, effectiveMaxRejects, budgetFloorActive, applyBudgetFloor, flooredEvidenceCap, budgetedVetoEscalation, decideVetoAction, shouldFinishConfirm, buildFinishConfirmMessage, buildMeetsConfirmMessage, gapHoldable, parseSpecChecks, groundSpecCheck, specFailFraction, applyVetoFloor, holdProgressed, planSimilarity, specCheckEvidence, specBlockForJudge, dropSpecEchoes, specBlockForWriter, vetoMessageLead, RESOLVER_SCALE_CLAUSE } from '../training/endTurnResolver.js';
|
|
28
|
+
import { resolveEndTurnResolverConfig, parseResolverVerdict, effectiveMaxRejects, budgetFloorActive, applyBudgetFloor, flooredEvidenceCap, budgetedVetoEscalation, decideVetoAction, shouldFinishConfirm, webTurnEvidenced, webFinishStands, resolverWebRuleEnabled, buildFinishConfirmMessage, buildMeetsConfirmMessage, gapHoldable, parseSpecChecks, groundSpecCheck, specFailFraction, applyVetoFloor, holdProgressed, planSimilarity, specCheckEvidence, specBlockForJudge, dropSpecEchoes, specBlockForWriter, vetoMessageLead, RESOLVER_SCALE_CLAUSE } from '../training/endTurnResolver.js';
|
|
26
29
|
import { jevAvailable, jevNoul, buildGapHoldState, GAP_HOLD_QUESTIONS } from '../training/jevGate.js'; // R173b
|
|
27
30
|
import { resolveAcceptGateConfig, buildAcceptGateState, decideAcceptGate, decisionNoul, formatAcceptGateHoldMessage, isAcceptClass, ACCEPT_GATE_QUESTIONS } from '../training/acceptGate.js'; // R260
|
|
28
31
|
import { resolveFrameConfig, FRAME_CONTRACT, FRAME_TOOL_NAME } from '../frames/frameConfig.js'; // R179
|
|
@@ -46,19 +49,22 @@ import { CacheMetricsAccumulator } from '../session/CacheMetricsAccumulator.js';
|
|
|
46
49
|
import { SubAgentProcessManager, createSubAgentProcessManager } from './SubAgentProcessManager.js';
|
|
47
50
|
// Phase 1: Tool Architecture Refactor - Unified Tool Registry
|
|
48
51
|
import { toolFactory } from '../tools/ToolFactory.js';
|
|
49
|
-
import { resolveToolProfile, resolveToolAnchor, resolveFrameProfile, resolveLiftNudge, resolveLiftPlan, resolveEndTurnResolver, resolveHeadlessDropAskUser, resolveDeferredLoading, isNarrowProfile, isToolAllowedByProfile,
|
|
52
|
+
import { applyToolProfile, resolveToolProfile, resolveToolAnchor, resolveFrameProfile, resolveLiftNudge, resolveLiftPlan, resolveEndTurnResolver, resolveHeadlessDropAskUser, resolveDeferredLoading, isNarrowProfile, isToolAllowedByProfile, webToolBlocked, resolveVisionHelperModel, resolveVisionHandoffMax, resolveSliceNudge, resolveSliceBlock, resolveSliceBlockAt, resolveSliceBlockMax, anchorAppliesToProcess } from '../tools/ToolProfile.js';
|
|
50
53
|
import { sliceReadFile, decideSliceBlock, sliceReadStep } from './sliceBlock.js';
|
|
51
|
-
import { TurnEvidence, evaluateEndTurnGates, buildMissingEndTurnReminder } from './endTurnGates.js';
|
|
54
|
+
import { normalizeEndTurnInput, TurnEvidence, evaluateEndTurnGates, buildMissingEndTurnReminder } from './endTurnGates.js';
|
|
52
55
|
import { resolveCompactionResume, pickDropped, buildCompactionResumeReminder, isCompactionResumeMessage, resolveTaskText, resumeMemoryTargetTokens, coversAll, buildRollingSeedMessage, memoryIndexLine, upsertMemoryIndex, scaleEstimateToRequestView, approxCharsOf, anchoredRequestEstimate, resolveCompactionHandoffQA, resolveHandoffQAMaxQuestions, runHandoffQA } from './compactionResume.js';
|
|
53
56
|
import { resolveCompactionTargetTokens } from './compactionTarget.js'; // R256
|
|
54
57
|
import { resolveToolResultPruneGate, pruneGateTokens, CompactionThrashTracker } from './contextPressure.js'; // R258
|
|
55
58
|
import { renderResumeMemoryPrompt, buildRebuildInstructions, resolveCheckpointBand, resolveRearmBand } from './compactionResumeTemplate.js';
|
|
56
59
|
import { resolveTurnStatusMode, turnStatusActive, buildTurnStatusLine, appendStatusToNewestToolResult } from './turnStatus.js';
|
|
60
|
+
import { browseCarryEnabled, browseCarryBlock, browseCarryApplies, lastUrlIn, sawBotWall, sawScriptedBrowser, BROWSER_WALL_LINE, countDirectBrowserCalls, directBrowseLine, directBrowseNudgeThreshold, looksMultiStepBrowseTask, directConfirmEnabled, DIRECT_CONFIRM_TEXT, browseDirectAvailable, withoutBrowseDirect, WEB_TOOL_NAMES, SOURCES_REMINDER_LINE, looksLikeReverification, reverifyGuardEnabled, REVERIFY_TEXT, REVERIFY_MAX, reverifyKey, subAgentEndTurnEnv, looksLikeProbe, probeGuardEnabled, PROBE_TEXT } from './browserWallNudge.js';
|
|
61
|
+
import { resolveEndTurnIntegrityMode } from './integrityVerification.js';
|
|
62
|
+
import { isEssentialMcpTool } from '../mcp/essentialMcpTools.js';
|
|
57
63
|
import { resolveCortexStateDir, cortexStatePath, resolveWorkspaceClean, cortexProjectDir } from '../utils/stateDir.js';
|
|
58
64
|
import { collectWorkspaceDelta, detectCheckCommand, runCheck, resolveJudgeGroundingConfig, classifyCheckRun, isInvestigateCommandAllowed, specCheckParses, readFileSlice, formatEvidenceRound } from '../training/judgeEvidence.js';
|
|
59
65
|
import { resolveMentorRoleConfig, describeMentorWire, describeMentorDelivery, mentorSurfaceTimeoutMs } from '../training/mentorRole.js';
|
|
60
66
|
import { resolveOuterToolDeadlineMs, resolveOuterToolTimeoutFloorMs } from './outerToolTimeout.js';
|
|
61
|
-
import { resolveSubAgentTimeoutMs } from './subAgentTimeout.js';
|
|
67
|
+
import { resolveSubAgentTimeoutMs, browseDelegateTimeoutMs } from './subAgentTimeout.js';
|
|
62
68
|
import { resolveHerdrReporting, HerdrReporter, sanitizeHerdrAgentName } from './herdrReporter.js';
|
|
63
69
|
import { runDelegateInHerdr, readSubAgentRuntimeLever, resolveSubAgentRuntime } from './HerdrSubAgentRunner.js';
|
|
64
70
|
import { randomBytes } from 'crypto';
|
|
@@ -85,7 +91,7 @@ import { classifyTask } from '../training/TaskClassifier.js';
|
|
|
85
91
|
import { closestToolMatches } from './toolNameMatcher.js';
|
|
86
92
|
import { classifyApiError } from './apiErrorClassifier.js';
|
|
87
93
|
import { pinStaticSystemPrompt } from './staticSystemPromptPin.js';
|
|
88
|
-
import {
|
|
94
|
+
import { filterAgentTools, isBlockedBaseToolCall, isBrowseAgent, mayNotSpawn, agentDepth, maxAgentDepth } from './toolAllowlist.js';
|
|
89
95
|
import { hasVisibleAssistantText, shouldForceSynthesis } from './assistantTextPresence.js';
|
|
90
96
|
import { resolveTurnContractEnforce, checkTurnFormat, buildFormatRejectMessage, resolveActionPlanFields, ACTION_PLAN_TOOLS, planFieldsMissing, stripPlanFields, buildPlanFieldsRejectMessage } from './turnContractValidator.js'; // HB-TURN-CONTRACT-ENFORCE / R172
|
|
91
97
|
import { computeToolBudgetSignal, isToolProgressStalled } from './toolBudgetSignal.js';
|
|
@@ -119,6 +125,28 @@ const OUTER_TIMEOUT_GRACE_MS = 30_000;
|
|
|
119
125
|
* Phase 2.2: Timeline, compaction, checkpoints
|
|
120
126
|
* Phase 2.3: Historical retrieval
|
|
121
127
|
*/
|
|
128
|
+
/**
|
|
129
|
+
* In an agent that may not spawn (the browse agent, or one at CORTEX_MAX_AGENT_DEPTH), a tool result that asks to start another
|
|
130
|
+
* sub-agent becomes an error and nothing is spawned — the one choke point every spawning tool (Browse, Task, WebSearch interactive,
|
|
131
|
+
* any future one) passes through (operator 2026-11-03).
|
|
132
|
+
*/
|
|
133
|
+
function refuseSpawnInBrowseAgent(result) {
|
|
134
|
+
const r = result;
|
|
135
|
+
if (!mayNotSpawn() || !r?.metadata?.shouldSpawnSubAgent)
|
|
136
|
+
return result;
|
|
137
|
+
const msg = isBrowseAgent()
|
|
138
|
+
? 'The browse agent cannot start another sub-agent. Use the nexus-browser__* tools directly.'
|
|
139
|
+
: `Sub-agents may nest at most ${maxAgentDepth()} level(s) deep and this agent is at depth ${agentDepth()}: do the work yourself with the tools you have.`;
|
|
140
|
+
return { ...result, success: false, llmContent: `Error: ${msg}`, returnDisplay: `Error: ${msg}`, error: msg,
|
|
141
|
+
metadata: { ...r.metadata, shouldSpawnSubAgent: false, refusedByAgentPolicy: true } };
|
|
142
|
+
}
|
|
143
|
+
/** Appends the user's original request to a browse delegate's task (once; trimmed) so its scope rules have the real scope to keep. */
|
|
144
|
+
export function withUserRequest(taskPrompt, userText) {
|
|
145
|
+
const u = (userText || '').trim();
|
|
146
|
+
if (!u || taskPrompt.includes(u.slice(0, 120)))
|
|
147
|
+
return taskPrompt;
|
|
148
|
+
return `${taskPrompt}\n\n---\nThe user's original request (your result must satisfy its scope — place, filters, dates — even when the task above does not repeat it):\n${u.slice(0, 1500)}`;
|
|
149
|
+
}
|
|
122
150
|
export class CortexOrchestrator {
|
|
123
151
|
adapterRegistry;
|
|
124
152
|
gatewayTranslation;
|
|
@@ -220,6 +248,10 @@ export class CortexOrchestrator {
|
|
|
220
248
|
// Until the first executed tool call, requests present only the anchor
|
|
221
249
|
// profile's schemas; the lift happens at the first tool_result boundary.
|
|
222
250
|
anchorLifted = false;
|
|
251
|
+
/** The model of the turn in progress (2026-11-02). The server passes the model PER REQUEST (options.modelId) and never sets
|
|
252
|
+
* currentModelId, so a sub-agent with model 'inherit' (the Browse delegate) fell back to the server default — Sonnet's Browse ran on
|
|
253
|
+
* deepseek-flash. Inherit resolves to this first. */
|
|
254
|
+
turnModelId = null;
|
|
223
255
|
/** The active model card's anchorProfile (captured at request assembly) —
|
|
224
256
|
* env CORTEX_TOOL_ANCHOR still overrides inside resolveToolAnchor. */
|
|
225
257
|
cardAnchorProfile = null;
|
|
@@ -420,6 +452,9 @@ export class CortexOrchestrator {
|
|
|
420
452
|
// choke point, but MCP tools (nexus-browser__*) join the list HERE, past it —
|
|
421
453
|
// strip them at this seam too so the web surface is gone from every request.
|
|
422
454
|
tools = tools.filter((t) => !webToolBlocked(t.name));
|
|
455
|
+
// Browse mode "direct" is for a person watching; headless sessions get the delegate only (browserWallNudge.ts browseDirectAvailable).
|
|
456
|
+
if (!browseDirectAvailable(this.approvalMode.autoApproveActions))
|
|
457
|
+
tools = tools.map((t) => (t.name === 'Browse' ? withoutBrowseDirect(t) : t));
|
|
423
458
|
if (!resolveHeadlessDropAskUser(process.env, model?.headlessDropAskUser))
|
|
424
459
|
return tools; // card > env > false
|
|
425
460
|
if (!this.approvalMode.autoApproveActions)
|
|
@@ -1058,7 +1093,7 @@ export class CortexOrchestrator {
|
|
|
1058
1093
|
const store = this.getDecisionStore();
|
|
1059
1094
|
const sessionId = this.currentSessionId ?? 'unknown';
|
|
1060
1095
|
try {
|
|
1061
|
-
const etInput = toolUses.find((t) => t.id === et.tool_use_id || t.name === 'EndTurn')?.input
|
|
1096
|
+
const etInput = normalizeEndTurnInput(toolUses.find((t) => t.id === et.tool_use_id || t.name === 'EndTurn')?.input); // stringified arrays parsed (endTurnGates.ts)
|
|
1062
1097
|
// HB-JUDGE-GROUNDING (2026-09-10): the LATEST outputs (the checks it ran before finishing), not the
|
|
1063
1098
|
// oldest 4K; plus the deliverable itself (workspace delta) and, when an evident check entry point
|
|
1064
1099
|
// exists, a real check result — the evidence the judge prompt already promises.
|
|
@@ -1198,6 +1233,8 @@ export class CortexOrchestrator {
|
|
|
1198
1233
|
const t0 = Date.now();
|
|
1199
1234
|
const evidenceRounds = []; // R170
|
|
1200
1235
|
let judgeEnvReport = ''; // the ENVIRONMENT REPORT the judge last saw (banked on the event — it is not in the session record)
|
|
1236
|
+
// Web-only turns (web results, no file writes) can be judged at lower effort: a review took 67–133 s at max (CORTEX_RESOLVER_WEB_EFFORT).
|
|
1237
|
+
const webJudgeEffort = webTurnEvidenced(ev) ? String(process.env.CORTEX_RESOLVER_WEB_EFFORT ?? '').trim() : '';
|
|
1201
1238
|
const judgeOnce = (reasoning, investigate) => withTimeout(this.helperMiddleware.evaluateEndTurn({
|
|
1202
1239
|
task,
|
|
1203
1240
|
liftPlan: this.liftPlanText || undefined,
|
|
@@ -1212,6 +1249,8 @@ export class CortexOrchestrator {
|
|
|
1212
1249
|
evidenceRounds: evidenceRounds.length ? evidenceRounds.slice() : undefined, // R170
|
|
1213
1250
|
investigate,
|
|
1214
1251
|
helperModelId: this.config.reactiveMentorship?.helperModelId,
|
|
1252
|
+
...(webJudgeEffort ? { effort: webJudgeEffort } : {}),
|
|
1253
|
+
webAnswer: webTurnEvidenced(ev) && resolverWebRuleEnabled(), // CORTEX_RESOLVER_WEB_RULE (2026-11-04)
|
|
1215
1254
|
}), reasoning === 'on' ? mentorSurfaceTimeoutMs('endturn-resolver', Math.max(timeoutMs, 180000)) : timeoutMs);
|
|
1216
1255
|
// R170 HB-JUDGE-TOOL-LOOP: up to cfg.toolRounds calls; while rounds and the aggregate budget remain the judge may
|
|
1217
1256
|
// INVESTIGATE (checks run via the existing runCheck surface behind a read-only denylist; file slices read in-workspace),
|
|
@@ -1551,7 +1590,7 @@ export class CortexOrchestrator {
|
|
|
1551
1590
|
et.content = acceptGateHold.message;
|
|
1552
1591
|
console.warn(`[EndTurnResolver] R260 ACCEPT-GATE HOLD — finish held (${this.acceptGateHolds}/${agc.max})`);
|
|
1553
1592
|
}
|
|
1554
|
-
else if ((action === 'accept-with-gap' || action === 'accept-low-confidence') &&
|
|
1593
|
+
else if ((action === 'accept-with-gap' || action === 'accept-low-confidence') && !webFinishStands(ev) &&
|
|
1555
1594
|
shouldFinishConfirm({ action, remainingFrac: resolverRemainingFrac, confirmsUsed: this.finishConfirms, cfg })) {
|
|
1556
1595
|
// R167 HB-FINISH-CONFIRM: the judge would accept with a gap and budget remains — hold ONCE with an informed
|
|
1557
1596
|
// confirmation (zero model calls): budget left, the reviewer's gap plan, the junior's own open_items. The next
|
|
@@ -1578,7 +1617,10 @@ export class CortexOrchestrator {
|
|
|
1578
1617
|
else if (action === 'accept' && verdict.meets) {
|
|
1579
1618
|
// R168 HB-MEETS-CONFIRM: a MEETS stands only when a proving check passed. Otherwise, with budget left, hold ONCE
|
|
1580
1619
|
// (shared counter with R167) showing what was (not) verified; the next EndTurn stands.
|
|
1581
|
-
|
|
1620
|
+
// A web turn's proof is the browser output the judge just read: no command can re-prove "the page shows X" (2026-11-03,
|
|
1621
|
+
// multi-site browser runs: the hold told a correct Parkers answer "named NO command", and the agent spent a whole extra Browse
|
|
1622
|
+
// delegate producing citable output). Web evidence + no file writes = evidenced (webTurnEvidenced; CORTEX_MEETS_CONFIRM_WEB).
|
|
1623
|
+
const evidencePassed = namedPassed > 0 || (!!checkResult && /→ PASSED/.test(checkResult)) || webTurnEvidenced(ev);
|
|
1582
1624
|
if (cfg.meetsConfirm && !evidencePassed)
|
|
1583
1625
|
this.judgeMeetsUnevidenced = true; // let the budget-continue / surrender nudges see this finish
|
|
1584
1626
|
if (shouldFinishConfirm({ action, remainingFrac: resolverRemainingFrac, confirmsUsed: this.finishConfirms, cfg, meets: true, evidencePassed })) {
|
|
@@ -1695,6 +1737,18 @@ export class CortexOrchestrator {
|
|
|
1695
1737
|
return tools;
|
|
1696
1738
|
return applyToolProfile(tools, anchor);
|
|
1697
1739
|
}
|
|
1740
|
+
/** Refusal text for a tool the guard blocks. While the turn-1 anchor is armed and the tool WILL be available once it lifts, say so —
|
|
1741
|
+
* 2026-11-02: Sonnet's first call was Browse; "not available under the active tool profile (bash-edit). Use the tools offered" read as
|
|
1742
|
+
* "there is no browser", and it did the whole task with curl, never calling Browse after the lift. */
|
|
1743
|
+
profileRefusal(name) {
|
|
1744
|
+
const profile = this.effectiveGuardProfile();
|
|
1745
|
+
const anchorArmed = !this.anchorLifted && anchorAppliesToProcess(process.env) && !!resolveToolAnchor(process.env, this.cardAnchorProfile);
|
|
1746
|
+
if (anchorArmed && isToolAllowedByProfile(name, (n) => toolFactory.getTool(n)?.discoveryTier, resolveToolProfile())) {
|
|
1747
|
+
return `Tool '${name}' is available, but not yet: this session opens with ${profile} only (the orient step). Make one Bash call to ` +
|
|
1748
|
+
`orient (e.g. pwd && ls), then call ${name} again — the full tool set, including ${name}, is offered from your next turn on.`;
|
|
1749
|
+
}
|
|
1750
|
+
return `Tool '${name}' is not available under the active tool profile (${profile}). Use the tools offered in this session.`;
|
|
1751
|
+
}
|
|
1698
1752
|
/** The profile the dispatch guard enforces RIGHT NOW: the anchor while
|
|
1699
1753
|
* armed (pre-lift calls to hidden tools are refused like any hidden
|
|
1700
1754
|
* tool), the session profile after. */
|
|
@@ -1716,6 +1770,10 @@ export class CortexOrchestrator {
|
|
|
1716
1770
|
checkpointManager;
|
|
1717
1771
|
fileCheckpointManager; // Created in createSession() with sessionId
|
|
1718
1772
|
mcpAutoInject = false;
|
|
1773
|
+
/** Browse mode:'direct' (2026-11-03): MCP servers connected mid-session for the MAIN model — pinned like CORTEX_MCP_AUTOCONNECT ones. */
|
|
1774
|
+
directPinnedServers = new Set();
|
|
1775
|
+
/** This turn's tool array, so tools connected mid-turn (Browse direct) are usable before the next user message. */
|
|
1776
|
+
currentTurnTools = null;
|
|
1719
1777
|
// Phase 2.7: Cache metrics tracking
|
|
1720
1778
|
cacheMetricsAccumulator = new CacheMetricsAccumulator();
|
|
1721
1779
|
// Sub-Agent System: Manages spawning and tracking of sub-agents as child processes
|
|
@@ -1821,7 +1879,8 @@ export class CortexOrchestrator {
|
|
|
1821
1879
|
const extras = [...this.getMcpManagementTools(), ...this.getContextManagementTools()];
|
|
1822
1880
|
const seen = new Set();
|
|
1823
1881
|
const catalogVision = this.getModel(this.currentModelId ?? '')?.vision === true;
|
|
1824
|
-
|
|
1882
|
+
// The catalog obeys the agent's allowlist too: a restricted delegate must not DISCOVER tools it may not use (2026-11-03).
|
|
1883
|
+
return [...this.applyBaseToolAllowlist(toolFactory.getAllTools()), ...extras]
|
|
1825
1884
|
.filter((t) => endTurnGateOn || t.name !== 'EndTurn')
|
|
1826
1885
|
.filter((t) => catalogVision || !!resolveVisionHelperModel(process.env) || t.name !== 'ReadImage')
|
|
1827
1886
|
.filter((t) => (seen.has(t.name) ? false : (seen.add(t.name), true)))
|
|
@@ -1972,6 +2031,7 @@ export class CortexOrchestrator {
|
|
|
1972
2031
|
}
|
|
1973
2032
|
modelId = recommended;
|
|
1974
2033
|
}
|
|
2034
|
+
this.turnModelId = modelId ?? null; // the model THIS turn runs (a sub-agent 'inherit' follows it — see turnModelId)
|
|
1975
2035
|
const model = this.getModel(modelId);
|
|
1976
2036
|
// Phase 1 & Phase 2: Get tools (used for both injection context and API request)
|
|
1977
2037
|
const endTurnGateOn = (process.env.CORTEX_ENDTURN_GATE) === 'true';
|
|
@@ -2000,6 +2060,7 @@ export class CortexOrchestrator {
|
|
|
2000
2060
|
const allTools = this.stripHeadlessOnlyTools(options.tools !== undefined
|
|
2001
2061
|
? [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools, ...options.tools]
|
|
2002
2062
|
: [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools], model);
|
|
2063
|
+
this.currentTurnTools = allTools; // Browse direct appends the browser tools here mid-turn
|
|
2003
2064
|
// Phase 2.3: Inject server-side tools when enabled and model supports them
|
|
2004
2065
|
// R20: provider-aware — XAI gets web/x/code; OpenAI gets web/code/file/image
|
|
2005
2066
|
// (Responses API hosted tools); other providers stay client-only.
|
|
@@ -2594,7 +2655,9 @@ export class CortexOrchestrator {
|
|
|
2594
2655
|
// loop detection, the consecutive-error breaker, and MAX_TOOL_ITERATIONS.
|
|
2595
2656
|
const TOOL_BUDGET_HARD = TOOL_BUDGET_SOFT > 0 ? TOOL_BUDGET_SOFT * 2 : Infinity; // force-synthesis cap
|
|
2596
2657
|
// #2 (2026-09-04): per-turn wall-clock budget (opt-in — 0 disables, no behaviour change when unset).
|
|
2597
|
-
const TURN_DEADLINE_MS = loopDefaults.turnDeadlineMs;
|
|
2658
|
+
const TURN_DEADLINE_MS = Number.isFinite(options.turnDeadlineMs) && options.turnDeadlineMs > 0 ? options.turnDeadlineMs : loopDefaults.turnDeadlineMs;
|
|
2659
|
+
if (this.config.debug && TURN_DEADLINE_MS > 0)
|
|
2660
|
+
console.log(`[Orchestrator] turn deadline ${TURN_DEADLINE_MS}ms (${Number(options.turnDeadlineMs) > 0 ? 'request' : 'config/env'})`);
|
|
2598
2661
|
const loopStartMs = Date.now();
|
|
2599
2662
|
this.turnLoopStartMs = loopStartMs;
|
|
2600
2663
|
this.turnDeadlineMsActive = TURN_DEADLINE_MS; // R133: sub-agent timeouts read the residual
|
|
@@ -2743,7 +2806,7 @@ export class CortexOrchestrator {
|
|
|
2743
2806
|
? classifyEmptyResponse(currentAssistantCanonicalMessage.content, lastStopReason, loopHasBudget)
|
|
2744
2807
|
: null;
|
|
2745
2808
|
const isEmptyContinueKind = EMPTY_TURN_CONTINUE && !!emptyClass
|
|
2746
|
-
&& (emptyClass.kind === 'truncated' || emptyClass.kind === 'reasoning_only_active');
|
|
2809
|
+
&& (emptyClass.kind === 'truncated' || emptyClass.kind === 'reasoning_only_active' || emptyClass.kind === 'empty_active');
|
|
2747
2810
|
const canRetryEmpty = isEmptyContinueKind
|
|
2748
2811
|
? emptyContinueCount < EMPTY_CONTINUE_MAX
|
|
2749
2812
|
: !emptyResponseRetryUsed;
|
|
@@ -3400,7 +3463,8 @@ export class CortexOrchestrator {
|
|
|
3400
3463
|
const processedToolUseIds = new Set();
|
|
3401
3464
|
try {
|
|
3402
3465
|
this.herdrReport('working', `tool:${toolUseBlocks[0]?.name ?? 'batch'}`); // R145
|
|
3403
|
-
|
|
3466
|
+
// Secrets vault Phase 2: stored values never reach the model or the screen («secret:NAME»); identity without a vault.
|
|
3467
|
+
const toolResults = maskToolResults(await this.handleToolCalls(toolUseBlocks, abortController.signal, structuredOutputState));
|
|
3404
3468
|
this.testPostExecThrow(); // test-only fault injection for the re-execution guard (inert in prod)
|
|
3405
3469
|
clearTimeout(timeoutId);
|
|
3406
3470
|
notePollResults(exactRepeatTracker, toolUseBlocks, toolResults); // R137: poll repeats count only on identical STALLED output
|
|
@@ -3725,6 +3789,9 @@ export class CortexOrchestrator {
|
|
|
3725
3789
|
}
|
|
3726
3790
|
// HB-COMPUTE-NUDGE (CORTEX_COMPUTE_NUDGE, dark): after a heavily-reasoned step, 'compute/test instead' at the same tail.
|
|
3727
3791
|
this.injectComputeNudge(toolCallIteration, false);
|
|
3792
|
+
this.injectBrowserWallNudge(toolResults, toolCallIteration, false, toolUseBlocks);
|
|
3793
|
+
this.injectDirectBrowseNudge(toolCallIteration, false, toolUseBlocks);
|
|
3794
|
+
this.injectSourcesReminder(toolResults);
|
|
3728
3795
|
// HB-TURN-STATUS (CORTEX_TURN_STATUS, dark): clock + context + bg shells at the TAIL of this round's newest tool_result.
|
|
3729
3796
|
this.injectTurnStatus(loopStartMs, TURN_DEADLINE_MS, effectiveModel, toolCallIteration);
|
|
3730
3797
|
// Ladder break (teach-then-break): the break instruction is already in
|
|
@@ -4388,6 +4455,7 @@ export class CortexOrchestrator {
|
|
|
4388
4455
|
if (!modelId) {
|
|
4389
4456
|
throw new Error('No model specified and no current model set');
|
|
4390
4457
|
}
|
|
4458
|
+
this.turnModelId = modelId;
|
|
4391
4459
|
// Central lookup — includes the back-compat alias fallback (deprecated
|
|
4392
4460
|
// names like deepseek-chat resolve to their successor cards).
|
|
4393
4461
|
const model = this.getModel(modelId);
|
|
@@ -4459,6 +4527,7 @@ export class CortexOrchestrator {
|
|
|
4459
4527
|
const allTools = this.stripHeadlessOnlyTools(options.tools !== undefined
|
|
4460
4528
|
? [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools, ...options.tools]
|
|
4461
4529
|
: [...factoryTools, ...mcpTools, ...mcpManagementTools, ...contextManagementTools], model);
|
|
4530
|
+
this.currentTurnTools = allTools; // Browse direct appends the browser tools here mid-turn
|
|
4462
4531
|
// Phase 2.3: Inject server-side tools when enabled and model supports them
|
|
4463
4532
|
// R20: provider-aware (streaming path mirror of non-streaming injection above).
|
|
4464
4533
|
if (isServerSideToolsEnabled() && modelSupportsServerSideTools(model)) {
|
|
@@ -4952,7 +5021,9 @@ export class CortexOrchestrator {
|
|
|
4952
5021
|
let pendingGateRequest = false; // Stage-1: a reminder was appended; run one continuation with NO tool batch
|
|
4953
5022
|
const streamEndTurnGateEnabled = process.env.CORTEX_ENDTURN_GATE === 'true';
|
|
4954
5023
|
// #2 (2026-09-04): per-turn wall-clock budget (opt-in — 0 disables; streaming parity with non-stream).
|
|
4955
|
-
const TURN_DEADLINE_MS = loopDefaults.turnDeadlineMs;
|
|
5024
|
+
const TURN_DEADLINE_MS = Number.isFinite(options.turnDeadlineMs) && options.turnDeadlineMs > 0 ? options.turnDeadlineMs : loopDefaults.turnDeadlineMs;
|
|
5025
|
+
if (this.config.debug && TURN_DEADLINE_MS > 0)
|
|
5026
|
+
console.log(`[Orchestrator] turn deadline ${TURN_DEADLINE_MS}ms (${Number(options.turnDeadlineMs) > 0 ? 'request' : 'config/env'})`);
|
|
4956
5027
|
const loopStartMs = Date.now();
|
|
4957
5028
|
this.turnLoopStartMs = loopStartMs;
|
|
4958
5029
|
this.turnDeadlineMsActive = TURN_DEADLINE_MS; // R133: sub-agent timeouts read the residual
|
|
@@ -5004,7 +5075,7 @@ export class CortexOrchestrator {
|
|
|
5004
5075
|
? classifyEmptyResponse(currentAssistantCanonicalMessage.content, lastStopReason, loopHasBudget)
|
|
5005
5076
|
: null;
|
|
5006
5077
|
const isEmptyContinueKind = EMPTY_TURN_CONTINUE && !!emptyClass
|
|
5007
|
-
&& (emptyClass.kind === 'truncated' || emptyClass.kind === 'reasoning_only_active');
|
|
5078
|
+
&& (emptyClass.kind === 'truncated' || emptyClass.kind === 'reasoning_only_active' || emptyClass.kind === 'empty_active');
|
|
5008
5079
|
const canRetryEmpty = isEmptyContinueKind
|
|
5009
5080
|
? emptyContinueCount < EMPTY_CONTINUE_MAX
|
|
5010
5081
|
: !emptyResponseRetryUsed;
|
|
@@ -5314,7 +5385,8 @@ export class CortexOrchestrator {
|
|
|
5314
5385
|
try {
|
|
5315
5386
|
// Execute tools (reuse existing method)
|
|
5316
5387
|
this.herdrReport('working', `tool:${toolUseBlocks[0]?.name ?? 'batch'}`); // R145
|
|
5317
|
-
|
|
5388
|
+
// Secrets vault Phase 2: stored values never reach the model or the screen («secret:NAME»); identity without a vault.
|
|
5389
|
+
const toolResults = maskToolResults(await this.handleToolCalls(toolUseBlocks, abortController.signal, structuredOutputState));
|
|
5318
5390
|
this.testPostExecThrow(); // test-only fault injection for the re-execution guard (inert in prod)
|
|
5319
5391
|
clearTimeout(timeoutId);
|
|
5320
5392
|
notePollResults(streamRepeatTracker, toolUseBlocks, toolResults); // R137 (streaming parity)
|
|
@@ -5583,6 +5655,9 @@ export class CortexOrchestrator {
|
|
|
5583
5655
|
}
|
|
5584
5656
|
// HB-COMPUTE-NUDGE (streaming parity).
|
|
5585
5657
|
this.injectComputeNudge(toolCallIteration, true);
|
|
5658
|
+
this.injectBrowserWallNudge(toolResults, toolCallIteration, true, toolUseBlocks);
|
|
5659
|
+
this.injectDirectBrowseNudge(toolCallIteration, true, toolUseBlocks);
|
|
5660
|
+
this.injectSourcesReminder(toolResults);
|
|
5586
5661
|
// HB-TURN-STATUS (streaming parity): same tail, same carrier.
|
|
5587
5662
|
this.injectTurnStatus(loopStartMs, TURN_DEADLINE_MS, effectiveModel, toolCallIteration);
|
|
5588
5663
|
// Ladder break (streaming parity): instruction already injected above;
|
|
@@ -6353,8 +6428,52 @@ export class CortexOrchestrator {
|
|
|
6353
6428
|
const elapsedMs = this.turnLoopStartMs > 0 ? Date.now() - this.turnLoopStartMs : 0;
|
|
6354
6429
|
return this.getFrameRunner().step({ action: input, task: this.lastRealUserText(), elapsedMs, deadlineMs: this.turnDeadlineMsActive, cwd: this.config.workingDirectory || process.cwd(), sessionId: this.currentSessionId ?? 'session', signal, predictorModel: 'generator' });
|
|
6355
6430
|
}
|
|
6431
|
+
/**
|
|
6432
|
+
* Browse mode:"direct": connect the nexus-browser MCP server (local or hosted per NEXUS_BROWSER_MODE — the registry entry decides)
|
|
6433
|
+
* into this session, pin its tools (essential tier) and append them to the current turn so the model can call them immediately.
|
|
6434
|
+
*/
|
|
6435
|
+
async enableBrowserDirect(url) {
|
|
6436
|
+
if (!this.mcpManager)
|
|
6437
|
+
return { text: 'Direct browsing needs MCP, which is disabled in this session. Use Browse without mode (a browse agent does the task).', isError: true };
|
|
6438
|
+
if (isNarrowProfile() || resolveFrameConfig(process.env).frame === 'terminus')
|
|
6439
|
+
return { text: 'Direct browsing is not available in this tool profile. Use Browse without mode.', isError: true };
|
|
6440
|
+
const name = 'nexus-browser';
|
|
6441
|
+
try {
|
|
6442
|
+
const connected = this.mcpManager.getServerInfo().some((s) => s.name === name && s.status === 'connected');
|
|
6443
|
+
if (!connected) {
|
|
6444
|
+
const def = this.getMcpServerRegistry()?.getServer(name);
|
|
6445
|
+
if (!def)
|
|
6446
|
+
return { text: 'nexus-browser is not in the MCP registry.', isError: true };
|
|
6447
|
+
this.mcpManager.addServerConfig(name, this.registryDefinitionToConfig(def));
|
|
6448
|
+
await this.mcpManager.connectToServer(name);
|
|
6449
|
+
await this.mcpManager.discoverAll();
|
|
6450
|
+
}
|
|
6451
|
+
this.directPinnedServers.add(name);
|
|
6452
|
+
this.mcpAutoInject = true;
|
|
6453
|
+
const tools = this.getMcpToolsAsCanonical().filter((t) => t.metadata?.serverName === name);
|
|
6454
|
+
if (!tools.length)
|
|
6455
|
+
return { text: 'Connected to nexus-browser but it reported no tools.', isError: true };
|
|
6456
|
+
if (this.currentTurnTools) {
|
|
6457
|
+
const have = new Set(this.currentTurnTools.map((t) => t.name));
|
|
6458
|
+
for (const t of tools)
|
|
6459
|
+
if (!have.has(t.name))
|
|
6460
|
+
this.currentTurnTools.push(t);
|
|
6461
|
+
}
|
|
6462
|
+
const backend = resolveBrowserBackend().using;
|
|
6463
|
+
const first = tools.find((t) => /__browse$/.test(t.name))?.name ?? `${name}__browse`;
|
|
6464
|
+
return { text: `Browser connected for you to drive directly (${backend}). ${tools.length} tools are now available, e.g. ${tools.slice(0, 8).map((t) => t.name).join(', ')}, … ` +
|
|
6465
|
+
`Next: call ${first}({ url${url ? `: ${JSON.stringify(url)}` : ''} }), then scan / get_content / click / type, and close_session when done. ` +
|
|
6466
|
+
`Before you report a result, check it fits the task's scope and scale: if the task names a place, store or other filter, confirm on the page that ` +
|
|
6467
|
+
`the RESULTS are limited to it (a distance / "this store" filter, the result cards' locations) — a location in the header is not proof, and a ` +
|
|
6468
|
+
`total equal to the site-wide filter counts is unfiltered.`, isError: false };
|
|
6469
|
+
}
|
|
6470
|
+
catch (e) {
|
|
6471
|
+
return { text: `Could not connect the browser: ${String(e?.message ?? e).slice(0, 300)}. Use Browse without mode (a browse agent) instead.`, isError: true };
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6356
6474
|
applyBaseToolAllowlist(tools) {
|
|
6357
|
-
|
|
6475
|
+
// R266: SearchTools + EndTurn always survive; the browse agent never gets a sub-agent-spawning tool (toolAllowlist.ts).
|
|
6476
|
+
return filterAgentTools(tools, this.config.allowedBaseTools);
|
|
6358
6477
|
}
|
|
6359
6478
|
/**
|
|
6360
6479
|
* Get adapter registry (for advanced use cases)
|
|
@@ -7623,6 +7742,8 @@ export class CortexOrchestrator {
|
|
|
7623
7742
|
config.args = def.defaultArgs;
|
|
7624
7743
|
if (def.defaultEnv)
|
|
7625
7744
|
config.env = def.defaultEnv;
|
|
7745
|
+
if (def.connectTimeoutMs)
|
|
7746
|
+
config.timeout = def.connectTimeoutMs;
|
|
7626
7747
|
return config;
|
|
7627
7748
|
}
|
|
7628
7749
|
// ============================================
|
|
@@ -7649,6 +7770,69 @@ export class CortexOrchestrator {
|
|
|
7649
7770
|
}).catch(() => { });
|
|
7650
7771
|
}
|
|
7651
7772
|
async handleToolCalls(toolUseBlocks, signal, structuredOutputState) {
|
|
7773
|
+
// Agent tool policy at EXECUTION: a base tool outside this agent's allowlist (or a spawning tool inside the browse agent) is refused even if the
|
|
7774
|
+
// model calls it unoffered — the offered list and the SearchTools catalog already exclude it (filterAgentTools). 2026-11-03.
|
|
7775
|
+
{
|
|
7776
|
+
const baseNames = toolFactory.getAllTools().map((t) => t.name);
|
|
7777
|
+
const refused = toolUseBlocks.filter((t) => isBlockedBaseToolCall(t.name, baseNames, this.config.allowedBaseTools));
|
|
7778
|
+
if (refused.length) {
|
|
7779
|
+
const rest = toolUseBlocks.filter((t) => !refused.includes(t));
|
|
7780
|
+
const restResults = rest.length ? await this.handleToolCalls(rest, signal, structuredOutputState) : [];
|
|
7781
|
+
const msg = (n) => `${n} is not available to this agent${isBrowseAgent() ? ' (the browse agent cannot start another sub-agent: use the nexus-browser__* tools directly)' : mayNotSpawn() ? ` (sub-agents nest at most ${maxAgentDepth()} level(s) deep)` : ''}. Use only the tools you were given.`;
|
|
7782
|
+
return [...refused.map((t) => ({ tool_use_id: t.id, tool_name: t.name, content: msg(t.name), is_error: true, metadata: { refusedByAgentPolicy: true } })), ...restResults];
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7785
|
+
// Re-verification of a completed browse report → one explanation instead of a delegate (browserWallNudge.ts REVERIFY_TEXT).
|
|
7786
|
+
// Probe-first: the turn's first Browse call only asks whether the site loads → one explanation (browserWallNudge.ts PROBE_TEXT).
|
|
7787
|
+
if (!this.browseCompletedThisTurn && !this.probeExplained && probeGuardEnabled()) {
|
|
7788
|
+
const probes = toolUseBlocks.filter((t) => t.name === 'Browse' && looksLikeProbe(t.input?.task));
|
|
7789
|
+
if (probes.length) {
|
|
7790
|
+
this.probeExplained = true;
|
|
7791
|
+
const store = this.getDecisionStore();
|
|
7792
|
+
if (store)
|
|
7793
|
+
void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', detail: { kinds: ['browse_probe'], task: String(probes[0].input?.task ?? '').slice(0, 200) } }).catch(() => { });
|
|
7794
|
+
const rest = toolUseBlocks.filter((t) => !probes.includes(t));
|
|
7795
|
+
const restResults = rest.length ? await this.handleToolCalls(rest, signal, structuredOutputState) : [];
|
|
7796
|
+
return [...probes.map((t) => ({ tool_use_id: t.id, tool_name: t.name, content: PROBE_TEXT, is_error: false, metadata: { browseProbe: true } })), ...restResults];
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
// Each NEW verification-shaped task is explained once (2026-11-03 val-W BBB: after the one-shot guard the agent re-worded four more
|
|
7800
|
+
// passes); re-sending the SAME task runs it; at most REVERIFY_MAX explanations per turn, so the agent can never be stuck.
|
|
7801
|
+
if (this.browseCompletedThisTurn && this.reverifyExplained.size < REVERIFY_MAX && reverifyGuardEnabled()) {
|
|
7802
|
+
const rev = toolUseBlocks.filter((t) => t.name === 'Browse' && looksLikeReverification(t.input?.task) && !this.reverifyExplained.has(reverifyKey(t.input?.task)));
|
|
7803
|
+
if (rev.length) {
|
|
7804
|
+
for (const t of rev)
|
|
7805
|
+
this.reverifyExplained.add(reverifyKey(t.input?.task));
|
|
7806
|
+
const store = this.getDecisionStore();
|
|
7807
|
+
if (store)
|
|
7808
|
+
void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', detail: { kinds: ['browse_reverify'], task: String(rev[0].input?.task ?? '').slice(0, 200) } }).catch(() => { });
|
|
7809
|
+
const rest = toolUseBlocks.filter((t) => !rev.includes(t));
|
|
7810
|
+
const restResults = rest.length ? await this.handleToolCalls(rest, signal, structuredOutputState) : [];
|
|
7811
|
+
return [...rev.map((t) => ({ tool_use_id: t.id, tool_name: t.name, content: REVERIFY_TEXT, is_error: false, metadata: { browseReverify: true } })), ...restResults];
|
|
7812
|
+
}
|
|
7813
|
+
}
|
|
7814
|
+
// Browse mode:"direct" — the MAIN model drives the browser itself (the SPA's browse anchor-lift pattern): connect nexus-browser into
|
|
7815
|
+
// THIS session and put its tools in the current turn, instead of dispatching a browse sub-agent. 2026-11-03.
|
|
7816
|
+
{
|
|
7817
|
+
// Headless: direct is withheld — a stray direct call is not intercepted here and runs as a delegate (executor site below).
|
|
7818
|
+
const direct = browseDirectAvailable(this.approvalMode.autoApproveActions)
|
|
7819
|
+
? toolUseBlocks.filter((t) => t.name === 'Browse' && t.input?.mode === 'direct') : [];
|
|
7820
|
+
if (direct.length) {
|
|
7821
|
+
const rest = toolUseBlocks.filter((t) => !direct.includes(t));
|
|
7822
|
+
const alreadyConnected = this.directPinnedServers.has('nexus-browser');
|
|
7823
|
+
if (!alreadyConnected && !this.directConfirmAsked && directConfirmEnabled() && looksMultiStepBrowseTask(direct[0].input?.task)) {
|
|
7824
|
+
this.directConfirmAsked = true;
|
|
7825
|
+
const store = this.getDecisionStore();
|
|
7826
|
+
if (store)
|
|
7827
|
+
void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', detail: { kinds: ['direct_browse_confirm'] } }).catch(() => { });
|
|
7828
|
+
const restResults = rest.length ? await this.handleToolCalls(rest, signal, structuredOutputState) : [];
|
|
7829
|
+
return [...direct.map((t) => ({ tool_use_id: t.id, tool_name: t.name, content: DIRECT_CONFIRM_TEXT, is_error: false, metadata: { browseDirect: 'confirm' } })), ...restResults];
|
|
7830
|
+
}
|
|
7831
|
+
const content = await this.enableBrowserDirect(direct[0].input?.url);
|
|
7832
|
+
const restResults = rest.length ? await this.handleToolCalls(rest, signal, structuredOutputState) : [];
|
|
7833
|
+
return [...direct.map((t, i) => ({ tool_use_id: t.id, tool_name: t.name, content: i === 0 ? content.text : 'Already handled by the first direct Browse call.', is_error: content.isError, metadata: { browseDirect: true } })), ...restResults];
|
|
7834
|
+
}
|
|
7835
|
+
}
|
|
7652
7836
|
// HB-TURN-CONTRACT-ENFORCE: a batch the loop rejected on format carries the re-prompt back as error results; nothing runs.
|
|
7653
7837
|
if (this.pendingFormatReject) {
|
|
7654
7838
|
const rej = this.pendingFormatReject;
|
|
@@ -7746,7 +7930,7 @@ export class CortexOrchestrator {
|
|
|
7746
7930
|
models: taskTools.map(t => String(t.input?.model ?? 'inherit')),
|
|
7747
7931
|
turn: this.turnNumber,
|
|
7748
7932
|
// R133 HB-SUBAGENT-TIMEOUT: the resolved per-dispatch timeout + the parent's residual budget at dispatch
|
|
7749
|
-
timeoutMs: taskTools.map(t => this.resolveSubAgentTimeoutFor(t.input).timeoutMs),
|
|
7933
|
+
timeoutMs: taskTools.map(t => this.resolveSubAgentTimeoutFor(t.input, t.name).timeoutMs),
|
|
7750
7934
|
remainingMs: this.remainingTurnMs(),
|
|
7751
7935
|
},
|
|
7752
7936
|
}).catch(() => { });
|
|
@@ -7883,7 +8067,7 @@ export class CortexOrchestrator {
|
|
|
7883
8067
|
results.push({
|
|
7884
8068
|
tool_use_id: toolUse.id,
|
|
7885
8069
|
tool_name: toolUse.name,
|
|
7886
|
-
content:
|
|
8070
|
+
content: this.profileRefusal(toolUse.name),
|
|
7887
8071
|
is_error: true,
|
|
7888
8072
|
});
|
|
7889
8073
|
continue;
|
|
@@ -8064,7 +8248,8 @@ export class CortexOrchestrator {
|
|
|
8064
8248
|
}
|
|
8065
8249
|
}
|
|
8066
8250
|
// Execute regular executor tool
|
|
8067
|
-
result = await this.executorRegistry.execute(toolUse.name, toolUse
|
|
8251
|
+
result = await this.executorRegistry.execute(toolUse.name, this.executorInputFor(toolUse), signal);
|
|
8252
|
+
result = refuseSpawnInBrowseAgent(result); // the browse agent may not start another sub-agent (toolAllowlist.ts)
|
|
8068
8253
|
// Sub-Agent Spawning (Task, Browse, etc. — any tool emitting
|
|
8069
8254
|
// shouldSpawnSubAgent). The dispatch gate is the metadata
|
|
8070
8255
|
// contract, not the tool name, so new entry tools can reuse it.
|
|
@@ -8080,7 +8265,17 @@ export class CortexOrchestrator {
|
|
|
8080
8265
|
const processManager = this.getOrCreateSubAgentProcessManager();
|
|
8081
8266
|
// Extract agent definition from metadata
|
|
8082
8267
|
const agentDef = result.metadata.agentDefinition;
|
|
8083
|
-
|
|
8268
|
+
let taskPrompt = result.metadata.taskPrompt;
|
|
8269
|
+
// CORTEX_BROWSE_CARRY (2026-11-04): a follow-up Browse after a COMPLETED report starts where that pass ended (browserWallNudge.ts).
|
|
8270
|
+
if (agentDef.name === 'browse-agent') {
|
|
8271
|
+
if (this.lastBrowseReport && browseCarryEnabled() && browseCarryApplies(this.lastBrowseReport, taskPrompt)) {
|
|
8272
|
+
taskPrompt = browseCarryBlock(this.lastBrowseReport, /^Start at: /.test(taskPrompt)) + taskPrompt;
|
|
8273
|
+
const store = this.getDecisionStore();
|
|
8274
|
+
if (store)
|
|
8275
|
+
void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', detail: { kinds: ['browse_carry'], url: lastUrlIn(this.lastBrowseReport.report) ?? null } }).catch(() => { });
|
|
8276
|
+
}
|
|
8277
|
+
this.lastBrowseTask = String(result.metadata.taskPrompt);
|
|
8278
|
+
}
|
|
8084
8279
|
// Per-fork env overrides let Browse enable MCP_AUTO_INJECT
|
|
8085
8280
|
// for its subagent without affecting the parent process.
|
|
8086
8281
|
const envOverrides = result.metadata.envOverrides;
|
|
@@ -8099,8 +8294,8 @@ export class CortexOrchestrator {
|
|
|
8099
8294
|
}
|
|
8100
8295
|
}
|
|
8101
8296
|
else {
|
|
8102
|
-
// No model or 'inherit' - use parent's current model
|
|
8103
|
-
resolvedModelId = this.currentModelId || this.config.defaultModelId;
|
|
8297
|
+
// No model or 'inherit' - use the model of the turn in progress (the request's), else the parent's current model
|
|
8298
|
+
resolvedModelId = this.turnModelId || this.currentModelId || this.config.defaultModelId;
|
|
8104
8299
|
}
|
|
8105
8300
|
if (this.config.debug) {
|
|
8106
8301
|
console.log(`[Orchestrator SubAgent] Model resolved: ${agentDef.model} -> ${resolvedModelId}`);
|
|
@@ -8114,7 +8309,7 @@ export class CortexOrchestrator {
|
|
|
8114
8309
|
// R133 HB-SUBAGENT-TIMEOUT: derive the child's limit from the parent's remaining turn budget (or the
|
|
8115
8310
|
// Task `timeout_ms`), and hand the child the SAME limit as its own turn deadline — it otherwise inherits
|
|
8116
8311
|
// the parent's full CORTEX_TURN_DEADLINE_MS via process.env and never converges before the kill.
|
|
8117
|
-
const { timeoutMs, source: timeoutSource } = this.resolveSubAgentTimeoutFor(toolUse.input);
|
|
8312
|
+
const { timeoutMs, source: timeoutSource } = this.resolveSubAgentTimeoutFor(toolUse.input, toolUse.name);
|
|
8118
8313
|
if (this.config.debug)
|
|
8119
8314
|
console.log(`[SubAgent] timeout ${timeoutMs}ms (${timeoutSource})`);
|
|
8120
8315
|
// R147 HB-HERDR-DELEGATES: runtime = herdr pane or forked IPC child (lever / Task `runtime`).
|
|
@@ -8245,7 +8440,7 @@ export class CortexOrchestrator {
|
|
|
8245
8440
|
* sides are individually env-gated and never block tool execution.
|
|
8246
8441
|
*/
|
|
8247
8442
|
async executeSingleToolCallWithTraining(toolUse, signal) {
|
|
8248
|
-
const result = await this.executeSingleToolCall(toolUse, signal);
|
|
8443
|
+
const result = await this.executeSingleToolCall(toolUse, signal); // masked at the top of processToolTraining
|
|
8249
8444
|
return await this.processToolTraining(toolUse, result);
|
|
8250
8445
|
}
|
|
8251
8446
|
async executeSingleToolCall(toolUse, signal) {
|
|
@@ -8314,7 +8509,7 @@ export class CortexOrchestrator {
|
|
|
8314
8509
|
return {
|
|
8315
8510
|
tool_use_id: toolUse.id,
|
|
8316
8511
|
tool_name: toolUse.name,
|
|
8317
|
-
content:
|
|
8512
|
+
content: this.profileRefusal(toolUse.name),
|
|
8318
8513
|
is_error: true,
|
|
8319
8514
|
};
|
|
8320
8515
|
}
|
|
@@ -8339,7 +8534,7 @@ export class CortexOrchestrator {
|
|
|
8339
8534
|
}
|
|
8340
8535
|
}
|
|
8341
8536
|
// Execute the tool (using executor registry)
|
|
8342
|
-
const result = await this.executorRegistry.execute(toolUse.name, toolUse
|
|
8537
|
+
const result = refuseSpawnInBrowseAgent(await this.executorRegistry.execute(toolUse.name, this.executorInputFor(toolUse), signal)); // the browse agent may not start another sub-agent
|
|
8343
8538
|
// Handle any sub-agent-spawning tool (Task, Browse, etc.). The dispatch
|
|
8344
8539
|
// gate is the metadata contract, not the tool name — that way Browse and
|
|
8345
8540
|
// future entry tools can reuse the same path without duplication.
|
|
@@ -8360,7 +8555,7 @@ export class CortexOrchestrator {
|
|
|
8360
8555
|
resolvedModelId = resolved?.modelId || this.currentModelId || this.config.defaultModelId;
|
|
8361
8556
|
}
|
|
8362
8557
|
else {
|
|
8363
|
-
resolvedModelId = this.currentModelId || this.config.defaultModelId;
|
|
8558
|
+
resolvedModelId = this.turnModelId || this.currentModelId || this.config.defaultModelId;
|
|
8364
8559
|
}
|
|
8365
8560
|
if (this.config.debug) {
|
|
8366
8561
|
console.log(`[Orchestrator] Spawning sub-agent: ${agentDef.name} with model ${resolvedModelId}${envOverrides ? ` (env: ${Object.keys(envOverrides).join(',')})` : ''}`);
|
|
@@ -8370,7 +8565,7 @@ export class CortexOrchestrator {
|
|
|
8370
8565
|
const progressListeners = this.setupSubAgentProgressListeners(eventEmitter, agentDef.name);
|
|
8371
8566
|
try {
|
|
8372
8567
|
// R133 HB-SUBAGENT-TIMEOUT: see the handleToolCalls site — residual-derived limit, mirrored into the child's turn deadline.
|
|
8373
|
-
const { timeoutMs, source: timeoutSource } = this.resolveSubAgentTimeoutFor(toolUse.input);
|
|
8568
|
+
const { timeoutMs, source: timeoutSource } = this.resolveSubAgentTimeoutFor(toolUse.input, toolUse.name);
|
|
8374
8569
|
if (this.config.debug)
|
|
8375
8570
|
console.log(`[SubAgent] timeout ${timeoutMs}ms (${timeoutSource})`);
|
|
8376
8571
|
// R147 HB-HERDR-DELEGATES: runtime = herdr pane or forked IPC child (lever / Task `runtime`).
|
|
@@ -8520,12 +8715,15 @@ export class CortexOrchestrator {
|
|
|
8520
8715
|
// R266: a server named in CORTEX_MCP_AUTOCONNECT was asked for explicitly (the Browse delegate's
|
|
8521
8716
|
// envOverrides) — its tools are ESSENTIAL so the deferred filter cannot strip them; other MCP tools
|
|
8522
8717
|
// keep no tier (deferred as before).
|
|
8523
|
-
const pinnedServers = new Set((process.env.CORTEX_MCP_AUTOCONNECT ?? '').split(',').map((x) => x.trim()).filter(Boolean));
|
|
8718
|
+
const pinnedServers = new Set([...(process.env.CORTEX_MCP_AUTOCONNECT ?? '').split(',').map((x) => x.trim()).filter(Boolean), ...this.directPinnedServers]);
|
|
8524
8719
|
return mcpTools.map(tool => ({
|
|
8525
8720
|
name: prefixMcpToolName(tool.serverName, tool.name),
|
|
8526
8721
|
description: tool.description || `MCP tool from ${tool.serverName}`,
|
|
8527
|
-
schema: tool.inputSchema || { type: 'object', properties: {} },
|
|
8528
|
-
|
|
8722
|
+
schema: normalizeMcpSchema(tool.inputSchema || { type: 'object', properties: {} }), // tuple items → providers reject (mcpSchemaCompat.ts)
|
|
8723
|
+
// Only a server's ESSENTIAL set is pinned (2026-11-02): pinning all 50 nexus-browser tools sent ~49k chars of schemas on EVERY
|
|
8724
|
+
// delegate request; the rest stay deferred and discoverable via SearchTools (the canonical progressive-discovery form the MCP
|
|
8725
|
+
// server itself uses). Servers without a curated set keep the old all-pinned behaviour.
|
|
8726
|
+
...(pinnedServers.has(tool.serverName) && isEssentialMcpTool(tool.serverName, tool.name) ? { discoveryTier: 'essential' } : {}),
|
|
8529
8727
|
metadata: {
|
|
8530
8728
|
source: 'mcp',
|
|
8531
8729
|
serverName: tool.serverName,
|
|
@@ -8877,12 +9075,16 @@ export class CortexOrchestrator {
|
|
|
8877
9075
|
return Math.max(0, this.turnDeadlineMsActive - (Date.now() - this.turnLoopStartMs));
|
|
8878
9076
|
}
|
|
8879
9077
|
/** R133: the sub-agent timeout for one Task/Browse tool input (`timeout_ms` request → residual deadline → env → 5 min). */
|
|
8880
|
-
resolveSubAgentTimeoutFor(input) {
|
|
8881
|
-
|
|
9078
|
+
resolveSubAgentTimeoutFor(input, toolName) {
|
|
9079
|
+
let requested = Number(input?.timeout_ms);
|
|
9080
|
+
// Browse delegates get their own default (2026-11-03): 4 of 8 cars.com/CarMax delegates hit the generic 5-min limit mid-task
|
|
9081
|
+
// (~1.1M input tokens each, nothing usable returned); the ones that finished took 3–4 min. A parent deadline still caps it.
|
|
9082
|
+
if (!(Number.isFinite(requested) && requested > 0) && toolName === 'Browse')
|
|
9083
|
+
requested = browseDelegateTimeoutMs(process.env);
|
|
8882
9084
|
return resolveSubAgentTimeoutMs({ requestedMs: Number.isFinite(requested) && requested > 0 ? requested : undefined, remainingMs: this.remainingTurnMs() });
|
|
8883
9085
|
}
|
|
8884
9086
|
/** R133: per-block lookup for resolveOuterToolDeadlineMs — a sub-agent-spawning block contributes its resolved timeout. */
|
|
8885
|
-
subAgentBlockTimeoutMs = (block) => block?.name === 'Task' || block?.name === 'Browse' ? this.resolveSubAgentTimeoutFor(block.input).timeoutMs : undefined;
|
|
9087
|
+
subAgentBlockTimeoutMs = (block) => block?.name === 'Task' || block?.name === 'Browse' ? this.resolveSubAgentTimeoutFor(block.input, block.name).timeoutMs : undefined;
|
|
8886
9088
|
/**
|
|
8887
9089
|
* R147 HB-HERDR-DELEGATES: herdr delegates are possible only inside a herdr pane (HERDR_ENV=1 +
|
|
8888
9090
|
* HERDR_PANE_ID + binary) with the herdr terminal backend resolving (socket reachable). Probed once.
|
|
@@ -8914,9 +9116,17 @@ export class CortexOrchestrator {
|
|
|
8914
9116
|
}
|
|
8915
9117
|
/** R147: run the sub-agent in a herdr pane (HerdrSubAgentRunner) or as the forked IPC child (unchanged path). */
|
|
8916
9118
|
async dispatchSubAgent(processManager, input, agentDef, taskPrompt, options) {
|
|
9119
|
+
// EndTurn review inside sub-agents (2026-11-03, multi-site browser pass): the resolver judge took 90 of 296 task-minutes INSIDE browse
|
|
9120
|
+
// delegates (67–133 s per review, holds, 5 timeouts at the 10-min cap) while the parent reviews the delegate's result anyway.
|
|
9121
|
+
options = { ...options, envOverrides: { ...subAgentEndTurnEnv(), ...options.envOverrides } };
|
|
8917
9122
|
const { runtime, reason } = await this.resolveSubAgentRuntimeFor(input);
|
|
8918
9123
|
if (this.config.debug)
|
|
8919
9124
|
console.log(`[SubAgent] runtime ${runtime} (${reason})`);
|
|
9125
|
+
// The browse agent always sees the USER's request, not only the parent's paraphrase (2026-11-03, Sonnet CarMax): a follow-up Browse
|
|
9126
|
+
// call ("open the URL in a fresh browser and report") dropped "near Kentwood", so that delegate kept no scope and the final answer
|
|
9127
|
+
// hedged between 20 (25 mi) and the nationwide 14,332. The parent's task still says WHAT to do; the request says what must hold.
|
|
9128
|
+
if (agentDef.name === 'browse-agent')
|
|
9129
|
+
taskPrompt = withUserRequest(taskPrompt, this.lastRealUserText());
|
|
8920
9130
|
// R266b: the delegate inherits the parent's approval mode — auto-approving parents (headless, one-shot, TUI yolo)
|
|
8921
9131
|
// must not leave a child blocked on an approval the operator never sees; interactive parents keep the IPC forward.
|
|
8922
9132
|
const permissionMode = this.approvalMode?.autoApproveActions === true ? 'auto' : 'interactive';
|
|
@@ -8956,6 +9166,10 @@ export class CortexOrchestrator {
|
|
|
8956
9166
|
}
|
|
8957
9167
|
formatSubAgentResultForLLM(result) {
|
|
8958
9168
|
const lines = [];
|
|
9169
|
+
if (result.agentName === 'browse-agent' && result.status === 'completed') {
|
|
9170
|
+
this.browseCompletedThisTurn = true;
|
|
9171
|
+
this.lastBrowseReport = { task: this.lastBrowseTask, report: String(result.fullResponse || result.summary || '') }; // CORTEX_BROWSE_CARRY
|
|
9172
|
+
}
|
|
8959
9173
|
// Header
|
|
8960
9174
|
lines.push(`# Sub-Agent Result: ${result.agentName}`);
|
|
8961
9175
|
lines.push('');
|
|
@@ -9005,7 +9219,7 @@ export class CortexOrchestrator {
|
|
|
9005
9219
|
lines.push(`- Input: ${result.cost.inputTokens.toLocaleString()}`);
|
|
9006
9220
|
lines.push(`- Output: ${result.cost.outputTokens.toLocaleString()}`);
|
|
9007
9221
|
if (result.cost.cacheHits > 0) {
|
|
9008
|
-
lines.push(`- Cache
|
|
9222
|
+
lines.push(`- Cache-read tokens: ${result.cost.cacheHits.toLocaleString()}`);
|
|
9009
9223
|
}
|
|
9010
9224
|
lines.push(`- Estimated Cost: $${result.cost.estimatedCost.toFixed(4)}`);
|
|
9011
9225
|
lines.push('');
|
|
@@ -9021,11 +9235,13 @@ export class CortexOrchestrator {
|
|
|
9021
9235
|
if (result.fullResponse) {
|
|
9022
9236
|
lines.push('## Full Response');
|
|
9023
9237
|
lines.push('');
|
|
9024
|
-
|
|
9238
|
+
// Keep the END (2026-11-02): fullResponse is the sub-agent's text across ALL its turns — narration first, the answer last. Keeping the
|
|
9239
|
+
// first 5000 chars dropped the answer itself (fuzzycortex: "the browser sub-agent's report was cut off partway"). Drop early narration.
|
|
9240
|
+
const maxLen = 8000;
|
|
9025
9241
|
if (result.fullResponse.length > maxLen) {
|
|
9026
|
-
lines.push(result.fullResponse.
|
|
9242
|
+
lines.push(`... (${result.fullResponse.length - maxLen} chars of earlier narration omitted — the sub-agent's final answer follows)`);
|
|
9027
9243
|
lines.push('');
|
|
9028
|
-
lines.push(
|
|
9244
|
+
lines.push(result.fullResponse.slice(-maxLen));
|
|
9029
9245
|
}
|
|
9030
9246
|
else {
|
|
9031
9247
|
lines.push(result.fullResponse);
|
|
@@ -9539,6 +9755,16 @@ export class CortexOrchestrator {
|
|
|
9539
9755
|
this.effortRampCalls = 0;
|
|
9540
9756
|
this.computeNudgeLastRound = -1_000_000;
|
|
9541
9757
|
this.computeNudgeFired = 0;
|
|
9758
|
+
this.browserWallNudged = false;
|
|
9759
|
+
this.directBrowseCalls = 0;
|
|
9760
|
+
this.directBrowseNudged = false;
|
|
9761
|
+
this.directConfirmAsked = false;
|
|
9762
|
+
this.sourcesReminded = false;
|
|
9763
|
+
this.browseCompletedThisTurn = false;
|
|
9764
|
+
this.lastBrowseReport = null;
|
|
9765
|
+
this.lastBrowseTask = '';
|
|
9766
|
+
this.reverifyExplained = new Set();
|
|
9767
|
+
this.probeExplained = false;
|
|
9542
9768
|
}
|
|
9543
9769
|
try {
|
|
9544
9770
|
const cfg = resolveEffortRamp();
|
|
@@ -9628,6 +9854,78 @@ export class CortexOrchestrator {
|
|
|
9628
9854
|
* append one 'compute/test instead of deliberating' line to the tail of the newest tool_result (the HB-TURN-STATUS carrier —
|
|
9629
9855
|
* earlier request prefixes stay byte-identical). Cooldown + per-turn cap; one compute_nudge event per firing. Fail-open.
|
|
9630
9856
|
*/
|
|
9857
|
+
browserWallNudged = false;
|
|
9858
|
+
directBrowseCalls = 0;
|
|
9859
|
+
directBrowseNudged = false;
|
|
9860
|
+
directConfirmAsked = false;
|
|
9861
|
+
sourcesReminded = false;
|
|
9862
|
+
browseCompletedThisTurn = false;
|
|
9863
|
+
/** CORTEX_BROWSE_CARRY: the last COMPLETED browse delegate's task + report this turn (reset per turn). */
|
|
9864
|
+
lastBrowseReport = null;
|
|
9865
|
+
lastBrowseTask = '';
|
|
9866
|
+
reverifyExplained = new Set();
|
|
9867
|
+
probeExplained = false;
|
|
9868
|
+
/** A Browse call naming mode "direct" where direct is withheld (headless) runs as a delegate — same task, no error round-trip. Used by
|
|
9869
|
+
* EVERY executor call site (2026-11-03: the batch path still passed mode:"direct" through and the executor answered with an error). */
|
|
9870
|
+
executorInputFor(toolUse) {
|
|
9871
|
+
if (toolUse.name === 'Browse' && toolUse.input?.mode === 'direct' && !browseDirectAvailable(this.approvalMode.autoApproveActions)) {
|
|
9872
|
+
const { mode: _m, ...rest } = toolUse.input;
|
|
9873
|
+
void _m;
|
|
9874
|
+
return rest;
|
|
9875
|
+
}
|
|
9876
|
+
return toolUse.input;
|
|
9877
|
+
}
|
|
9878
|
+
/** Stage 5 needs `sources` after web use; say so at the first web result of the turn instead of after a rejected EndTurn. */
|
|
9879
|
+
injectSourcesReminder(toolResults) {
|
|
9880
|
+
try {
|
|
9881
|
+
if (this.sourcesReminded || !resolveEndTurnIntegrityMode(process.env))
|
|
9882
|
+
return;
|
|
9883
|
+
if (!toolResults.some((r) => WEB_TOOL_NAMES.includes(String(r.tool_name))))
|
|
9884
|
+
return;
|
|
9885
|
+
if (!appendStatusToNewestToolResult(this.messageHistory, SOURCES_REMINDER_LINE))
|
|
9886
|
+
return;
|
|
9887
|
+
this.invalidateTailUnitConversions();
|
|
9888
|
+
this.sourcesReminded = true;
|
|
9889
|
+
}
|
|
9890
|
+
catch { /* fail-open */ }
|
|
9891
|
+
}
|
|
9892
|
+
/** Long direct browsing → once per turn, steer the rest of the task to the browse sub-agent (browserWallNudge.ts directBrowseLine). */
|
|
9893
|
+
injectDirectBrowseNudge(round, streaming, toolUses = []) {
|
|
9894
|
+
try {
|
|
9895
|
+
if (this.directBrowseNudged || process.env.CORTEX_BROWSE_AGENT === 'true')
|
|
9896
|
+
return;
|
|
9897
|
+
this.directBrowseCalls += countDirectBrowserCalls(toolUses);
|
|
9898
|
+
const threshold = directBrowseNudgeThreshold();
|
|
9899
|
+
if (threshold <= 0 || this.directBrowseCalls < threshold || webToolBlocked('Browse'))
|
|
9900
|
+
return;
|
|
9901
|
+
if (!appendStatusToNewestToolResult(this.messageHistory, directBrowseLine(this.directBrowseCalls)))
|
|
9902
|
+
return;
|
|
9903
|
+
this.invalidateTailUnitConversions(); // R221 safety net
|
|
9904
|
+
this.directBrowseNudged = true;
|
|
9905
|
+
const store = this.getDecisionStore();
|
|
9906
|
+
if (store)
|
|
9907
|
+
void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', detail: { kinds: ['direct_browse_long'], calls: this.directBrowseCalls, iteration: round, streaming } }).catch(() => { });
|
|
9908
|
+
}
|
|
9909
|
+
catch { /* fail-open */ }
|
|
9910
|
+
}
|
|
9911
|
+
/** Browser-wall nudge (browserWallNudge.ts): once per turn, when a Bash/WebFetch result is a bot-check page and Browse is enabled. */
|
|
9912
|
+
injectBrowserWallNudge(toolResults, round, streaming, toolUses = []) {
|
|
9913
|
+
try {
|
|
9914
|
+
if (this.browserWallNudged || webToolBlocked('Browse'))
|
|
9915
|
+
return;
|
|
9916
|
+
const trigger = sawBotWall(toolResults) ? 'wall' : sawScriptedBrowser(toolUses) ? 'scripted_browser' : null;
|
|
9917
|
+
if (!trigger)
|
|
9918
|
+
return;
|
|
9919
|
+
if (!appendStatusToNewestToolResult(this.messageHistory, BROWSER_WALL_LINE))
|
|
9920
|
+
return;
|
|
9921
|
+
this.invalidateTailUnitConversions(); // R221 safety net
|
|
9922
|
+
this.browserWallNudged = true;
|
|
9923
|
+
const store = this.getDecisionStore();
|
|
9924
|
+
if (store)
|
|
9925
|
+
void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'steering_injected', detail: { kinds: ['browser_wall'], trigger, iteration: round, streaming } }).catch(() => { });
|
|
9926
|
+
}
|
|
9927
|
+
catch { /* fail-open */ }
|
|
9928
|
+
}
|
|
9631
9929
|
injectComputeNudge(round, streaming) {
|
|
9632
9930
|
try {
|
|
9633
9931
|
const cfg = resolveComputeNudge();
|
|
@@ -9910,6 +10208,9 @@ export class CortexOrchestrator {
|
|
|
9910
10208
|
};
|
|
9911
10209
|
}
|
|
9912
10210
|
async processToolTraining(toolUse, result) {
|
|
10211
|
+
// Secrets vault Phase 2: mask FIRST — every tool path funnels through here, and the decision store below persists errorSnippet
|
|
10212
|
+
// (later re-injected into prompts as priors) and training records. Identity without a vault.
|
|
10213
|
+
result = maskToolResults([result])[0];
|
|
9913
10214
|
this.applySliceNudge(toolUse, result); // universal post-execution hook: every path funnels through processToolTraining
|
|
9914
10215
|
const store = this.getDecisionStore();
|
|
9915
10216
|
if (!store)
|
|
@@ -10516,7 +10817,7 @@ export class CortexOrchestrator {
|
|
|
10516
10817
|
if (process.env.DEBUG === 'true') {
|
|
10517
10818
|
console.log(`[Orchestrator] R30 recovery: found persisted sub-agent result for tool_use_id=${toolUse.id}`);
|
|
10518
10819
|
}
|
|
10519
|
-
const formatted = this.formatSubAgentResultForLLM(persisted);
|
|
10820
|
+
const formatted = maskSecretText(this.formatSubAgentResultForLLM(persisted)); // secrets vault Phase 2
|
|
10520
10821
|
return {
|
|
10521
10822
|
type: 'tool_result',
|
|
10522
10823
|
toolResult: {
|
|
@@ -11184,7 +11485,7 @@ export class CortexOrchestrator {
|
|
|
11184
11485
|
'Session & History': [
|
|
11185
11486
|
'ListSessions', 'LoadSession', 'SearchConversationHistory',
|
|
11186
11487
|
'GetConversationSegment', 'ListCompactionBoundaries', 'RequestHistoricalContext',
|
|
11187
|
-
'CanonListSessions', 'CanonPullSession',
|
|
11488
|
+
'CanonListSessions', 'CanonPullSession', 'SecretsList',
|
|
11188
11489
|
],
|
|
11189
11490
|
'Sandbox & Artifacts': [
|
|
11190
11491
|
'CreateArtifactTool', 'InteractWithSandbox', 'ModifySandbox',
|