@yeaft/webchat-agent 1.0.504 → 1.0.506
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/conversation.js +4 -2
- package/local-runtime/server/handlers/agent-conversation.js +11 -9
- package/local-runtime/server/handlers/client-conversation.js +28 -2
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +99 -94
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +4 -3
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/local-runtime/web/vendor/katex/LICENSE +21 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/local-runtime/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/local-runtime/web/vendor/katex/katex.min.css +1 -0
- package/local-runtime/web/vendor.bundle.js +1 -0
- package/local-runtime/web/vendor.bundle.js.gz +0 -0
- package/package.json +1 -1
- package/yeaft/cli.js +3 -0
- package/yeaft/config-api.js +16 -5
- package/yeaft/config.js +11 -2
- package/yeaft/conversation/history-index-worker.js +151 -0
- package/yeaft/conversation/history-index.js +96 -1
- package/yeaft/conversation/persist.js +21 -10
- package/yeaft/conversation/recall-relevance.js +103 -0
- package/yeaft/conversation/search.js +2 -1
- package/yeaft/debug-trace.js +15 -4
- package/yeaft/effort.js +159 -6
- package/yeaft/engine.js +180 -41
- package/yeaft/history-window.js +430 -5
- package/yeaft/llm/adapter.js +3 -2
- package/yeaft/llm/anthropic.js +97 -25
- package/yeaft/llm/openai-responses.js +71 -7
- package/yeaft/llm/provider-state.js +242 -0
- package/yeaft/llm/router.js +14 -2
- package/yeaft/llm/usage-accounting.js +3 -0
- package/yeaft/models.js +4 -1
- package/yeaft/snapshot-filter.js +1 -0
- package/yeaft/sub-agent/output-log.js +4 -0
- package/yeaft/sub-agent/prompt-queue.js +3 -0
- package/yeaft/sub-agent/runner.js +13 -2
- package/yeaft/tools/agent.js +3 -0
- package/yeaft/tools/send-message.js +2 -0
- package/yeaft/web-bridge.js +24 -17
package/yeaft/engine.js
CHANGED
|
@@ -33,7 +33,9 @@ import {
|
|
|
33
33
|
DEFAULT_PROJECT_DOC_MAX_BYTES,
|
|
34
34
|
} from './sessions/project-doc.js';
|
|
35
35
|
import { archiveToolResults } from './archive/tool-results.js';
|
|
36
|
-
import { trimSnapshotForBudget } from './history-window.js';
|
|
36
|
+
import { trimSnapshotForBudget, estimateMessageTokens, buildHistoryBuckets } from './history-window.js';
|
|
37
|
+
import { recallConversationTurns } from './conversation/history-index.js';
|
|
38
|
+
import { parseSeqFromId } from './conversation/persist.js';
|
|
37
39
|
import { isVpForeign, readContent as readScopeContent } from './memory/store.js';
|
|
38
40
|
import { ActiveMemorySet } from './memory/ams.js';
|
|
39
41
|
import { cleanMemoryPromptText } from './memory/prompt-cleanup.js';
|
|
@@ -42,7 +44,8 @@ import { perfNowMs, recordAgentPerfTrace } from './perf-trace.js';
|
|
|
42
44
|
// Default thread marker for legacy / non-group flows. Group VP runtime may
|
|
43
45
|
// pass a real threadId per (sessionId, vpId, threadId) engine instance.
|
|
44
46
|
const MAIN_THREAD_ID = 'main';
|
|
45
|
-
import { pickEffort, parseEffortPrefix } from './effort.js';
|
|
47
|
+
import { pickEffort, parseEffortPrefix, snapshotEffortDecision } from './effort.js';
|
|
48
|
+
import { bindProviderState } from './llm/provider-state.js';
|
|
46
49
|
import { DEFAULT_CONTEXT_WINDOW, normalizeEffort, resolveContextWindow, resolveModel } from './models.js';
|
|
47
50
|
import { lookupModelLimitSync } from './llm/models-dev.js';
|
|
48
51
|
import { attachRouterPlan, extractPriorPlan, stripMetaForWire } from './router/continuity.js';
|
|
@@ -90,7 +93,8 @@ const MAX_CONTINUE_TURNS = 3;
|
|
|
90
93
|
* network, or subprocess reads. Only tools whose metadata explicitly declares
|
|
91
94
|
* both read-only and concurrency-safe execution enter this lane.
|
|
92
95
|
*/
|
|
93
|
-
|
|
96
|
+
// Safe shared tools run together within the finite provider batch. Unsafe tools
|
|
97
|
+
// form exclusive barriers; resource-specific limits belong to the owning tool.
|
|
94
98
|
|
|
95
99
|
/** Maximum silence while a visible turn waits for a result-producing task. */
|
|
96
100
|
const DEFAULT_ASYNC_TASK_WAIT_TIMEOUT_MS = 120_000;
|
|
@@ -1390,6 +1394,8 @@ export class Engine {
|
|
|
1390
1394
|
#buildToolContext(signal, vpCtx) {
|
|
1391
1395
|
return {
|
|
1392
1396
|
signal,
|
|
1397
|
+
effortDecision: snapshotEffortDecision(vpCtx?.effortDecision),
|
|
1398
|
+
requestIdentity: vpCtx?.requestIdentity,
|
|
1393
1399
|
yeaftDir: this.#yeaftDir,
|
|
1394
1400
|
managedCliReady: this.#managedCliReady,
|
|
1395
1401
|
runtimePlatform: getRuntimePlatformInfo(),
|
|
@@ -1589,6 +1595,7 @@ export class Engine {
|
|
|
1589
1595
|
if (message.toolCallId) record.toolCallId = message.toolCallId;
|
|
1590
1596
|
if (Array.isArray(message.toolCalls) && message.toolCalls.length > 0) record.toolCalls = message.toolCalls;
|
|
1591
1597
|
if (Array.isArray(message.thinkingBlocks) && message.thinkingBlocks.length > 0) record.thinkingBlocks = message.thinkingBlocks;
|
|
1598
|
+
if (message.providerState) record.providerState = message.providerState;
|
|
1592
1599
|
if (message.isError) record.isError = true;
|
|
1593
1600
|
if (message.imageAssetAnchor) record.imageAssetAnchor = true;
|
|
1594
1601
|
if (message._reflection) record._reflection = true;
|
|
@@ -1623,7 +1630,7 @@ export class Engine {
|
|
|
1623
1630
|
: message.content != null;
|
|
1624
1631
|
const hasToolCalls = Array.isArray(message.toolCalls) && message.toolCalls.length > 0;
|
|
1625
1632
|
const hasThinking = Array.isArray(message.thinkingBlocks) && message.thinkingBlocks.length > 0;
|
|
1626
|
-
if (!hasContent && !hasToolCalls && !hasThinking && message.role !== 'tool') return null;
|
|
1633
|
+
if (!hasContent && !hasToolCalls && !hasThinking && !message.providerState && message.role !== 'tool') return null;
|
|
1627
1634
|
return this.#conversationStore.append(this.#conversationRecord(message, context));
|
|
1628
1635
|
}
|
|
1629
1636
|
|
|
@@ -1875,7 +1882,7 @@ export class Engine {
|
|
|
1875
1882
|
}
|
|
1876
1883
|
}
|
|
1877
1884
|
|
|
1878
|
-
async *#queryLifecycle({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, causalRootId = null, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null } = {}) {
|
|
1885
|
+
async *#queryLifecycle({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', isSubAgent = false, parentEffortDecision = null, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, currentUserMessage = null, causalRootId = null, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null } = {}) {
|
|
1879
1886
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
1880
1887
|
const error = new Error('prompt is required and must be a non-empty string');
|
|
1881
1888
|
yield {
|
|
@@ -1967,7 +1974,7 @@ export class Engine {
|
|
|
1967
1974
|
try {
|
|
1968
1975
|
this.#currentThreadId = threadId || MAIN_THREAD_ID;
|
|
1969
1976
|
this.#currentCausalRootId = effectiveCausalRootId;
|
|
1970
|
-
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: explicitUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds, projectInstruction, projectLabel, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted, causalRootId: effectiveCausalRootId, getCurrentTodos, setCurrentTodos, askUser, threadId: this.#currentThreadId, vpTurnId, drainPendingUserMessages, prepareProviderRequest, startProviderRequest, finishProviderRequest, failProviderRequest, closePendingUserInput, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName, retryLifecycle });
|
|
1977
|
+
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: explicitUserEffort, scenario, isSubAgent, parentEffortDecision, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds, projectInstruction, projectLabel, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted, currentUserMessage, causalRootId: effectiveCausalRootId, getCurrentTodos, setCurrentTodos, askUser, threadId: this.#currentThreadId, vpTurnId, drainPendingUserMessages, prepareProviderRequest, startProviderRequest, finishProviderRequest, failProviderRequest, closePendingUserInput, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName, retryLifecycle });
|
|
1971
1978
|
} finally {
|
|
1972
1979
|
// Closing the async generator at a visible retry boundary means the
|
|
1973
1980
|
// continuation never reached a provider. Keep it out of history and
|
|
@@ -2022,7 +2029,7 @@ export class Engine {
|
|
|
2022
2029
|
* in a try/finally without indenting the whole loop.
|
|
2023
2030
|
* @private
|
|
2024
2031
|
*/
|
|
2025
|
-
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, causalRootId = null, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null, explicitSkillName = null, retryLifecycle }) {
|
|
2032
|
+
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', isSubAgent = false, parentEffortDecision = null, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, currentUserMessage = null, causalRootId = null, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null, explicitSkillName = null, retryLifecycle }) {
|
|
2026
2033
|
|
|
2027
2034
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
2028
2035
|
? collabToolPolicy
|
|
@@ -2047,6 +2054,15 @@ export class Engine {
|
|
|
2047
2054
|
// standalone/CLI callers pass it per query.
|
|
2048
2055
|
this.#sessionId = runtimeSessionId || null;
|
|
2049
2056
|
this.#currentThreadId = runtimeThreadId;
|
|
2057
|
+
const requestIdentity = Object.freeze({
|
|
2058
|
+
instanceScope: this.#yeaftDir || '',
|
|
2059
|
+
ownerScope: this.#yeaftDir ? 'instance-local-owner' : '',
|
|
2060
|
+
sessionId: runtimeSessionId || this.#chatId || '',
|
|
2061
|
+
vpId: this.#vpId || senderVpId || vpPersona?.vpId || 'default',
|
|
2062
|
+
threadId: runtimeThreadId,
|
|
2063
|
+
});
|
|
2064
|
+
const effortConstraint = isSubAgent || scenario === 'sub_agent' || vpPersona?.subAgent
|
|
2065
|
+
? Object.freeze({ parentDecision: snapshotEffortDecision(parentEffortDecision) }) : null;
|
|
2050
2066
|
const queryStartedAt = Date.now();
|
|
2051
2067
|
const userQuestionPreview = String(prompt || '').slice(0, 200);
|
|
2052
2068
|
const queryVpId = vpPersona && typeof vpPersona === 'object'
|
|
@@ -2079,10 +2095,59 @@ export class Engine {
|
|
|
2079
2095
|
// memory pre-flow or provider request can fail. The Web Session bridge
|
|
2080
2096
|
// already writes one shared user row before multi-VP fan-out, so those
|
|
2081
2097
|
// callers set userAlreadyPersisted and every VP skips this append.
|
|
2098
|
+
const internalTrigger = !!inboundEnvelope?.msg?.meta?.injectedBy;
|
|
2099
|
+
let persistedQueryUser = currentUserMessage;
|
|
2082
2100
|
if (!userAlreadyPersisted) {
|
|
2083
|
-
this.#persistConversationMessage({
|
|
2084
|
-
|
|
2085
|
-
|
|
2101
|
+
persistedQueryUser = this.#persistConversationMessage({
|
|
2102
|
+
role: internalTrigger ? 'assistant' : 'user', content: prompt,
|
|
2103
|
+
...(internalTrigger ? { internal: true } : { userAuthored: true }),
|
|
2104
|
+
}, { sessionId: runtimeSessionId });
|
|
2105
|
+
}
|
|
2106
|
+
// Native Session history, including internal wakeups, never comes from
|
|
2107
|
+
// Dream. Work Center and scoped child agents keep their memory contracts.
|
|
2108
|
+
const useMessageHistory = scenario !== 'work-item' && !!runtimeSessionId && !vpPersona?.subAgent;
|
|
2109
|
+
const useDreamMemory = scenario === 'work-item' || !!vpPersona?.subAgent
|
|
2110
|
+
|| (!runtimeSessionId && !internalTrigger);
|
|
2111
|
+
const recentTurnCap = this.#config.yeaft?.recentTurnsLimit ?? 20;
|
|
2112
|
+
const relatedTurnCap = this.#config.yeaft?.relatedTurnsLimit ?? 8;
|
|
2113
|
+
let relatedHistoryTurns = [];
|
|
2114
|
+
let historyRecallMeta = { source: 'messages', status: 'disabled' };
|
|
2115
|
+
if (useMessageHistory && !internalTrigger && this.#conversationStore?.loadRecentBySession) {
|
|
2116
|
+
// Bounded canonical rows, not the lossy bridge cache. Threads share the
|
|
2117
|
+
// Session transcript; only tool arcs and thinking are VP-private.
|
|
2118
|
+
const clientId = inboundEnvelope?.msg?.meta?.clientMessageId;
|
|
2119
|
+
if (!persistedQueryUser && clientId) {
|
|
2120
|
+
// Compatibility only: bridge callers carry the durable row directly.
|
|
2121
|
+
// A missing identity outside this bounded lookup fails closed below.
|
|
2122
|
+
persistedQueryUser = this.#conversationStore.loadRecentBySession(runtimeSessionId, recentTurnCap + 1)
|
|
2123
|
+
.find(m => m.role === 'user' && m.clientMessageId === clientId);
|
|
2124
|
+
}
|
|
2125
|
+
const beforeSeq = Number.isFinite(persistedQueryUser?.seq)
|
|
2126
|
+
? persistedQueryUser.seq : parseSeqFromId(persistedQueryUser?.id);
|
|
2127
|
+
if (Number.isFinite(beforeSeq)) {
|
|
2128
|
+
const tail = this.#conversationStore.loadRecentBySession(runtimeSessionId, recentTurnCap, { beforeSeq });
|
|
2129
|
+
messages = tail.filter(m => parseSeqFromId(m.id) < beforeSeq
|
|
2130
|
+
&& (m.role !== 'tool' || !queryVpId || m.speakerVpId === queryVpId))
|
|
2131
|
+
.map(m => {
|
|
2132
|
+
if (m.role !== 'assistant' || !queryVpId || m.speakerVpId === queryVpId) return m;
|
|
2133
|
+
const { toolCalls, thinkingBlocks, ...textOnly } = m;
|
|
2134
|
+
return textOnly;
|
|
2135
|
+
});
|
|
2136
|
+
if (relatedTurnCap > 0 && this.#yeaftDir) {
|
|
2137
|
+
try {
|
|
2138
|
+
const recalled = await recallConversationTurns(this.#yeaftDir, runtimeSessionId, prompt, {
|
|
2139
|
+
beforeSeq, limit: relatedTurnCap,
|
|
2140
|
+
});
|
|
2141
|
+
relatedHistoryTurns = recalled.turns || [];
|
|
2142
|
+
historyRecallMeta = { source: 'messages', status: 'ready', ...recalled.meta };
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
// Cold/stale index degrades to recent history, never a full scan.
|
|
2145
|
+
historyRecallMeta = { source: 'messages', status: error?.code || 'unavailable' };
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
} else {
|
|
2149
|
+
historyRecallMeta = { source: 'messages', status: 'missing_current_user_fence' };
|
|
2150
|
+
}
|
|
2086
2151
|
}
|
|
2087
2152
|
|
|
2088
2153
|
const perfTraceId = typeof inboundEnvelope?._perfTraceId === 'string' && inboundEnvelope._perfTraceId.trim()
|
|
@@ -2111,7 +2176,7 @@ export class Engine {
|
|
|
2111
2176
|
let memoryInjection = '';
|
|
2112
2177
|
let recallEntryCount = 0;
|
|
2113
2178
|
|
|
2114
|
-
const topicScopesForMemory = await this.#loadSessionTopicScopes(sessionId);
|
|
2179
|
+
const topicScopesForMemory = !useDreamMemory ? [] : await this.#loadSessionTopicScopes(sessionId);
|
|
2115
2180
|
const projectScopesForMemory = Array.isArray(projectSessionIds)
|
|
2116
2181
|
? projectSessionIds.flatMap(id => [
|
|
2117
2182
|
`sessions/${id}`,
|
|
@@ -2119,7 +2184,7 @@ export class Engine {
|
|
|
2119
2184
|
`group/${id}`,
|
|
2120
2185
|
])
|
|
2121
2186
|
: [];
|
|
2122
|
-
const recallResult = await this.#recallMemory(prompt, {
|
|
2187
|
+
const recallResult = !useDreamMemory ? { entries: [], meta: {} } : await this.#recallMemory(prompt, {
|
|
2123
2188
|
sessionId,
|
|
2124
2189
|
vpId: vpPersona && typeof vpPersona === 'object' && typeof vpPersona.vpId === 'string'
|
|
2125
2190
|
? vpPersona.vpId
|
|
@@ -2148,7 +2213,7 @@ export class Engine {
|
|
|
2148
2213
|
|
|
2149
2214
|
// Load canonical content only for scopes selected by ranked FTS records.
|
|
2150
2215
|
// summary.md remains catalog metadata and never enters the prompt.
|
|
2151
|
-
const summaries = await this.#loadLayerASummaries({
|
|
2216
|
+
const summaries = !useDreamMemory ? {} : await this.#loadLayerASummaries({
|
|
2152
2217
|
sessionId,
|
|
2153
2218
|
vpId: vpPersona && typeof vpPersona === 'object' && typeof vpPersona.vpId === 'string'
|
|
2154
2219
|
? vpPersona.vpId
|
|
@@ -2167,7 +2232,7 @@ export class Engine {
|
|
|
2167
2232
|
&& typeof vpPersona.vpId === 'string'
|
|
2168
2233
|
? vpPersona.vpId
|
|
2169
2234
|
: (typeof senderVpId === 'string' ? senderVpId : null);
|
|
2170
|
-
const amsContext = this.#prepareAms({
|
|
2235
|
+
const amsContext = !useDreamMemory ? null : this.#prepareAms({
|
|
2171
2236
|
sessionId,
|
|
2172
2237
|
ownVpId: ownVpIdForAms,
|
|
2173
2238
|
summaries,
|
|
@@ -2179,7 +2244,8 @@ export class Engine {
|
|
|
2179
2244
|
}
|
|
2180
2245
|
const loadedMemoryForDebug = loadedMemoryDebugEntries(amsContext?.snapshot);
|
|
2181
2246
|
const loadedMemoryMetaForDebug = {
|
|
2182
|
-
|
|
2247
|
+
...(useMessageHistory ? historyRecallMeta : {}),
|
|
2248
|
+
recallLimit: useMessageHistory ? relatedTurnCap : resolveMemoryRecallLimit(this.#config),
|
|
2183
2249
|
recallCandidates: Number.isFinite(recallResult?.meta?.hitCount)
|
|
2184
2250
|
? recallResult.meta.hitCount
|
|
2185
2251
|
: (recallResult && Array.isArray(recallResult.entries) ? recallResult.entries.length : 0),
|
|
@@ -2224,7 +2290,7 @@ export class Engine {
|
|
|
2224
2290
|
envelope: inboundEnvelope || null,
|
|
2225
2291
|
};
|
|
2226
2292
|
|
|
2227
|
-
|
|
2293
|
+
let projectDocSource = this.#getProjectDocBlock(workDir);
|
|
2228
2294
|
let projectDocLoadedPathHints = [];
|
|
2229
2295
|
let projectDocContext = selectProjectDocContext(projectDocSource, {
|
|
2230
2296
|
prompt,
|
|
@@ -2356,11 +2422,14 @@ export class Engine {
|
|
|
2356
2422
|
: prompt;
|
|
2357
2423
|
}
|
|
2358
2424
|
const conversationMessages = [
|
|
2359
|
-
...trimSnapshotForBudget(messages, {
|
|
2425
|
+
...(useMessageHistory ? messages : trimSnapshotForBudget(messages, {
|
|
2360
2426
|
messageTokenBudget: this.#config.messageTokenBudget,
|
|
2361
2427
|
language: this.#config.language,
|
|
2362
|
-
}),
|
|
2363
|
-
{ role: 'user', content: finalUserContent
|
|
2428
|
+
})),
|
|
2429
|
+
{ role: 'user', content: finalUserContent,
|
|
2430
|
+
...(persistedQueryUser?.id ? { id: persistedQueryUser.id, seq: parseSeqFromId(persistedQueryUser.id) } : {}),
|
|
2431
|
+
...(persistedQueryUser?.clientMessageId ? { clientMessageId: persistedQueryUser.clientMessageId } : {}),
|
|
2432
|
+
},
|
|
2364
2433
|
];
|
|
2365
2434
|
|
|
2366
2435
|
const groupReflectionGate = shouldAllowGroupReflection({
|
|
@@ -2620,7 +2689,9 @@ export class Engine {
|
|
|
2620
2689
|
});
|
|
2621
2690
|
};
|
|
2622
2691
|
const toolCalls = [];
|
|
2623
|
-
const thinkingBlocks = []; //
|
|
2692
|
+
const thinkingBlocks = []; // Legacy adapter compatibility only.
|
|
2693
|
+
let providerState = null;
|
|
2694
|
+
let requestEffortDecision = snapshotEffortDecision();
|
|
2624
2695
|
let stopReason = 'end_turn';
|
|
2625
2696
|
const totalUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, cacheInputDeltaTokens: 0 };
|
|
2626
2697
|
// Raw provider exchange is diagnostic source data, not model context or
|
|
@@ -2741,7 +2812,7 @@ export class Engine {
|
|
|
2741
2812
|
// routerPlan, the VP's role default, and the global config all
|
|
2742
2813
|
// outrank the scenario picker for `'high'|'max'`. UI/userEffort
|
|
2743
2814
|
// is already honoured by pickEffort (highest precedence).
|
|
2744
|
-
if (vpPersona && vpPersona.vpId) {
|
|
2815
|
+
if (!requestUserEffort && vpPersona && vpPersona.vpId) {
|
|
2745
2816
|
const priorPlan = extractPriorPlan(conversationMessages, vpPersona.vpId);
|
|
2746
2817
|
const thinkingCfg = (this.#config && this.#config.thinking) || {};
|
|
2747
2818
|
// PR-I: live routerPlan.thinking — when the dispatcher passes
|
|
@@ -2784,10 +2855,28 @@ export class Engine {
|
|
|
2784
2855
|
// query tape remain complete; no summary is generated and no history
|
|
2785
2856
|
// rows are rewritten. This also protects later tool-loop requests,
|
|
2786
2857
|
// not just the initial snapshot assembled by the bridge.
|
|
2787
|
-
const
|
|
2858
|
+
const continuationCost = pendingContinuationForRequest
|
|
2859
|
+
? estimateMessageTokens(pendingContinuationForRequest) : 0;
|
|
2860
|
+
const historyBudget = Math.max(1, Math.min(
|
|
2861
|
+
requestConfig.messageTokenBudget || 32768,
|
|
2862
|
+
Math.floor(currentContextWindow * 0.75) - estimateMessagesTokens(systemPrompt, []),
|
|
2863
|
+
) - continuationCost);
|
|
2864
|
+
const buckets = useMessageHistory ? buildHistoryBuckets(conversationMessages, {
|
|
2865
|
+
prompt,
|
|
2866
|
+
relatedTurns: relatedHistoryTurns,
|
|
2867
|
+
recentTurnCap: requestConfig.yeaft?.recentTurnsLimit ?? 20,
|
|
2868
|
+
relatedTurnCap: requestConfig.yeaft?.relatedTurnsLimit ?? 8,
|
|
2869
|
+
messageTokenBudget: historyBudget,
|
|
2870
|
+
currentTurnStartIndex: turnStartIdx,
|
|
2871
|
+
language: requestConfig.language,
|
|
2872
|
+
}) : null;
|
|
2873
|
+
const requestHistory = buckets?.messages || trimSnapshotForBudget(conversationMessages, {
|
|
2788
2874
|
messageTokenBudget: requestConfig.messageTokenBudget,
|
|
2789
2875
|
language: requestConfig.language,
|
|
2790
2876
|
});
|
|
2877
|
+
if (buckets) this.#trace.log?.('history_buckets', {
|
|
2878
|
+
sessionId: runtimeSessionId, turnId: queryTurnId, ...historyRecallMeta, ...buckets.meta,
|
|
2879
|
+
});
|
|
2791
2880
|
let wireMessages = stripMetaForWire(pendingContinuationForRequest
|
|
2792
2881
|
? [...requestHistory, pendingContinuationForRequest]
|
|
2793
2882
|
: requestHistory);
|
|
@@ -2896,6 +2985,10 @@ export class Engine {
|
|
|
2896
2985
|
tools: toolDefs.length > 0 ? toolDefs : undefined,
|
|
2897
2986
|
maxTokens: requestConfig.maxOutputTokens || 16384,
|
|
2898
2987
|
effort: resolvedEffort,
|
|
2988
|
+
effortConstraint,
|
|
2989
|
+
requestIdentity,
|
|
2990
|
+
onEffortDecision: decision => { requestEffortDecision = snapshotEffortDecision(decision); },
|
|
2991
|
+
onProviderDiagnostics: info => traceRequest('llm.capabilities', { detail: info }),
|
|
2899
2992
|
effortSource: requestUserEffort ? 'user' : 'auto',
|
|
2900
2993
|
signal,
|
|
2901
2994
|
onRawExchange: captureRawExchange,
|
|
@@ -2956,6 +3049,9 @@ export class Engine {
|
|
|
2956
3049
|
responseText += event.text;
|
|
2957
3050
|
yield event;
|
|
2958
3051
|
break;
|
|
3052
|
+
case 'provider_state':
|
|
3053
|
+
providerState = event.providerState;
|
|
3054
|
+
break;
|
|
2959
3055
|
case 'thinking_delta':
|
|
2960
3056
|
yield event;
|
|
2961
3057
|
break;
|
|
@@ -2993,6 +3089,9 @@ export class Engine {
|
|
|
2993
3089
|
const cacheInputDeltaTokens = event.cacheTokensAreIncludedInInput ? 0 : cacheReadTokens + cacheWriteTokens;
|
|
2994
3090
|
totalUsage.inputTokens += inputTokens;
|
|
2995
3091
|
totalUsage.outputTokens += outputTokens;
|
|
3092
|
+
if (Number.isFinite(event.reasoningTokens) && event.reasoningTokens >= 0) {
|
|
3093
|
+
totalUsage.reasoningTokens = (totalUsage.reasoningTokens || 0) + event.reasoningTokens;
|
|
3094
|
+
}
|
|
2996
3095
|
totalUsage.cacheReadTokens += cacheReadTokens;
|
|
2997
3096
|
totalUsage.cacheWriteTokens += cacheWriteTokens;
|
|
2998
3097
|
totalUsage.cacheInputDeltaTokens += cacheInputDeltaTokens;
|
|
@@ -3089,6 +3188,7 @@ export class Engine {
|
|
|
3089
3188
|
usage: {
|
|
3090
3189
|
inputTokens: totalUsage.inputTokens || 0,
|
|
3091
3190
|
outputTokens: totalUsage.outputTokens || 0,
|
|
3191
|
+
...(totalUsage.reasoningTokens !== undefined ? { reasoningTokens: totalUsage.reasoningTokens } : {}),
|
|
3092
3192
|
cacheReadTokens: totalUsage.cacheReadTokens || 0,
|
|
3093
3193
|
cacheWriteTokens: totalUsage.cacheWriteTokens || 0,
|
|
3094
3194
|
totalInputTokens: (totalUsage.inputTokens || 0) + (totalUsage.cacheInputDeltaTokens || 0),
|
|
@@ -3335,6 +3435,7 @@ export class Engine {
|
|
|
3335
3435
|
usage: {
|
|
3336
3436
|
inputTokens: totalUsage.inputTokens || 0,
|
|
3337
3437
|
outputTokens: totalUsage.outputTokens || 0,
|
|
3438
|
+
...(totalUsage.reasoningTokens !== undefined ? { reasoningTokens: totalUsage.reasoningTokens } : {}),
|
|
3338
3439
|
cacheReadTokens: totalUsage.cacheReadTokens || 0,
|
|
3339
3440
|
cacheWriteTokens: totalUsage.cacheWriteTokens || 0,
|
|
3340
3441
|
totalInputTokens: (totalUsage.inputTokens || 0) + (totalUsage.cacheInputDeltaTokens || 0),
|
|
@@ -3361,6 +3462,7 @@ export class Engine {
|
|
|
3361
3462
|
usage: {
|
|
3362
3463
|
inputTokens: totalUsage.inputTokens || 0,
|
|
3363
3464
|
outputTokens: errLoopOutputTokens,
|
|
3465
|
+
...(totalUsage.reasoningTokens !== undefined ? { reasoningTokens: totalUsage.reasoningTokens } : {}),
|
|
3364
3466
|
cacheReadTokens: totalUsage.cacheReadTokens || 0,
|
|
3365
3467
|
cacheWriteTokens: totalUsage.cacheWriteTokens || 0,
|
|
3366
3468
|
totalInputTokens: errLoopInputTokens,
|
|
@@ -3438,6 +3540,7 @@ export class Engine {
|
|
|
3438
3540
|
usage: {
|
|
3439
3541
|
inputTokens: totalUsage.inputTokens || 0,
|
|
3440
3542
|
outputTokens: totalUsage.outputTokens || 0,
|
|
3543
|
+
...(totalUsage.reasoningTokens !== undefined ? { reasoningTokens: totalUsage.reasoningTokens } : {}),
|
|
3441
3544
|
cacheReadTokens: totalUsage.cacheReadTokens || 0,
|
|
3442
3545
|
cacheWriteTokens: totalUsage.cacheWriteTokens || 0,
|
|
3443
3546
|
totalInputTokens: turnInputTokens,
|
|
@@ -3460,13 +3563,10 @@ export class Engine {
|
|
|
3460
3563
|
input: tc.input,
|
|
3461
3564
|
}));
|
|
3462
3565
|
}
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
: { thinking: tb.thinking, signature: tb.signature }
|
|
3468
|
-
));
|
|
3469
|
-
}
|
|
3566
|
+
const boundProviderState = bindProviderState(providerState, assistantMsg);
|
|
3567
|
+
if (boundProviderState) assistantMsg.providerState = boundProviderState;
|
|
3568
|
+
// New private reasoning is persisted only with verified provider ownership;
|
|
3569
|
+
// never downgrade it to the origin-free legacy thinkingBlocks format.
|
|
3470
3570
|
if (vpPersona && vpPersona.vpId) {
|
|
3471
3571
|
const planForThisVp = (vpPlan && typeof vpPlan === 'object'
|
|
3472
3572
|
&& typeof vpPlan.vpId === 'string' && vpPlan.vpId === vpPersona.vpId)
|
|
@@ -3536,6 +3636,7 @@ export class Engine {
|
|
|
3536
3636
|
usage: {
|
|
3537
3637
|
inputTokens: totalUsage.inputTokens || 0,
|
|
3538
3638
|
outputTokens: loopOutputTokens,
|
|
3639
|
+
...(totalUsage.reasoningTokens !== undefined ? { reasoningTokens: totalUsage.reasoningTokens } : {}),
|
|
3539
3640
|
cacheReadTokens: totalUsage.cacheReadTokens || 0,
|
|
3540
3641
|
cacheWriteTokens: totalUsage.cacheWriteTokens || 0,
|
|
3541
3642
|
totalInputTokens: loopInputTokens,
|
|
@@ -3841,6 +3942,8 @@ export class Engine {
|
|
|
3841
3942
|
// We re-create the closure each iteration because endTurnRequested
|
|
3842
3943
|
// is a per-query local (reset implicitly at the top of #runQuery).
|
|
3843
3944
|
const toolCtx = this.#buildToolContext(signal, {
|
|
3945
|
+
effortDecision: snapshotEffortDecision(requestEffortDecision),
|
|
3946
|
+
requestIdentity,
|
|
3844
3947
|
router,
|
|
3845
3948
|
senderVpId,
|
|
3846
3949
|
sessionId: runtimeSessionId,
|
|
@@ -4021,9 +4124,8 @@ export class Engine {
|
|
|
4021
4124
|
&& duplicatePolicyForCall(tc) !== 'suppress'
|
|
4022
4125
|
&& !mayMutateWorkspaceAfterReturn(this, tc.name, tc.input)) {
|
|
4023
4126
|
const parallelCalls = [];
|
|
4024
|
-
const segmentCacheKeys = new Set();
|
|
4025
4127
|
for (let candidateIndex = toolCallIndex;
|
|
4026
|
-
candidateIndex < toolCalls.length
|
|
4128
|
+
candidateIndex < toolCalls.length;
|
|
4027
4129
|
candidateIndex += 1) {
|
|
4028
4130
|
const candidate = toolCalls[candidateIndex];
|
|
4029
4131
|
if (!toolAllowedForRequest(candidate)
|
|
@@ -4032,21 +4134,27 @@ export class Engine {
|
|
|
4032
4134
|
|| mayMutateWorkspaceAfterReturn(this, candidate.name, candidate.input)) break;
|
|
4033
4135
|
const candidateKey = `${candidate.name}\u001f${argsHashOf(candidate.input)}`;
|
|
4034
4136
|
const candidateCacheable = isCacheableTool(this, candidate.name, candidate.input);
|
|
4035
|
-
//
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
&& (readOnlyToolResults.has(candidateKey) || segmentCacheKeys.has(candidateKey))) break;
|
|
4137
|
+
// Already committed reads reuse their result on the commit path.
|
|
4138
|
+
if (!readOnlyToolReuseDisabled && candidateCacheable
|
|
4139
|
+
&& readOnlyToolResults.has(candidateKey)) break;
|
|
4039
4140
|
parallelCalls.push(candidate);
|
|
4040
|
-
if (candidateCacheable) segmentCacheKeys.add(candidateKey);
|
|
4041
4141
|
}
|
|
4042
4142
|
|
|
4043
4143
|
if (parallelCalls.length > 1) {
|
|
4044
4144
|
const executions = [];
|
|
4145
|
+
const inFlightReads = new Map();
|
|
4045
4146
|
for (const call of parallelCalls) {
|
|
4046
|
-
if (signal?.aborted) {
|
|
4047
|
-
abortedDuringTools = true;
|
|
4147
|
+
if (signal?.aborted || toolBatchBarrier) {
|
|
4148
|
+
if (signal?.aborted) abortedDuringTools = true;
|
|
4048
4149
|
break;
|
|
4049
4150
|
}
|
|
4151
|
+
const cacheKey = `${call.name}\u001f${argsHashOf(call.input)}`;
|
|
4152
|
+
const cacheable = !readOnlyToolReuseDisabled && isCacheableTool(this, call.name, call.input);
|
|
4153
|
+
const sharedExecution = cacheable ? inFlightReads.get(cacheKey) : null;
|
|
4154
|
+
if (sharedExecution) {
|
|
4155
|
+
executions.push(sharedExecution.then(result => ({ ...result, call, shared: true })));
|
|
4156
|
+
continue;
|
|
4157
|
+
}
|
|
4050
4158
|
announcedParallelToolCalls.add(call.id);
|
|
4051
4159
|
yield {
|
|
4052
4160
|
type: 'tool_start',
|
|
@@ -4059,7 +4167,8 @@ export class Engine {
|
|
|
4059
4167
|
abortedDuringTools = true;
|
|
4060
4168
|
break;
|
|
4061
4169
|
}
|
|
4062
|
-
|
|
4170
|
+
if (toolBatchBarrier) break;
|
|
4171
|
+
const execution = (async () => {
|
|
4063
4172
|
const startedAt = Date.now();
|
|
4064
4173
|
const callContext = toolContextForCall(call);
|
|
4065
4174
|
const toolErrorOutput = this.#toolRegistry
|
|
@@ -4073,7 +4182,9 @@ export class Engine {
|
|
|
4073
4182
|
} catch (error) {
|
|
4074
4183
|
return { call, startedAt, durationMs: Date.now() - startedAt, error, toolErrorOutput };
|
|
4075
4184
|
}
|
|
4076
|
-
})()
|
|
4185
|
+
})();
|
|
4186
|
+
executions.push(execution);
|
|
4187
|
+
if (cacheable) inFlightReads.set(cacheKey, execution);
|
|
4077
4188
|
}
|
|
4078
4189
|
const completed = await Promise.all(executions);
|
|
4079
4190
|
for (const execution of completed) {
|
|
@@ -4130,7 +4241,12 @@ export class Engine {
|
|
|
4130
4241
|
const missingProjectDocScopes = hasTool && !readOnlyTool
|
|
4131
4242
|
? projectDocWriteScopesNeedingReload(projectDocContext, toolProjectDocPathHints)
|
|
4132
4243
|
: new Set();
|
|
4133
|
-
|
|
4244
|
+
// A changed rule source was not present in the request that generated
|
|
4245
|
+
// this write, even when its scope label is unchanged. Never replay it.
|
|
4246
|
+
const freshProjectDocSource = hasTool && !readOnlyTool
|
|
4247
|
+
? this.#getProjectDocBlock(workDir) : projectDocSource;
|
|
4248
|
+
const projectDocSourceChanged = freshProjectDocSource !== projectDocSource;
|
|
4249
|
+
const needsProjectDocReload = missingProjectDocScopes.size > 0 || projectDocSourceChanged;
|
|
4134
4250
|
|
|
4135
4251
|
if (abortSkipped) {
|
|
4136
4252
|
output = `Skipped ${tc.name} because the turn was aborted before this tool started.`;
|
|
@@ -4173,6 +4289,7 @@ export class Engine {
|
|
|
4173
4289
|
isError = true;
|
|
4174
4290
|
yield { type: 'tool_end', id: tc.id, name: tc.name, output, isError: true, threadId: this.currentThreadId };
|
|
4175
4291
|
} else if (needsProjectDocReload) {
|
|
4292
|
+
projectDocSource = freshProjectDocSource;
|
|
4176
4293
|
projectDocLoadedPathHints = [...new Set([
|
|
4177
4294
|
...projectDocLoadedPathHints,
|
|
4178
4295
|
...toolProjectDocPathHints,
|
|
@@ -4319,6 +4436,15 @@ export class Engine {
|
|
|
4319
4436
|
|
|
4320
4437
|
currentToolCallForAsyncTask = null;
|
|
4321
4438
|
|
|
4439
|
+
// Record concrete inputs, never tool/file prose. Apply only after the
|
|
4440
|
+
// whole batch: a read cannot authorize a same-response write whose
|
|
4441
|
+
// model has not yet received the newly selected rules.
|
|
4442
|
+
if (hasTool && readOnlyTool && !skipped && !isError) {
|
|
4443
|
+
projectDocLoadedPathHints = [...new Set([
|
|
4444
|
+
...projectDocLoadedPathHints, ...toolProjectDocPathHints,
|
|
4445
|
+
])].slice(-128);
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4322
4448
|
if (!skipped && !duplicateCallSuppressed && !isError && !reusedReadOnlyResult
|
|
4323
4449
|
&& duplicateCallPolicy !== 'allow') {
|
|
4324
4450
|
const nextDuplicateCount = successfulDuplicateCount + 1;
|
|
@@ -4446,6 +4572,19 @@ export class Engine {
|
|
|
4446
4572
|
if (fatalToolError) throw fatalToolError;
|
|
4447
4573
|
}
|
|
4448
4574
|
|
|
4575
|
+
// Preload rules for observed read paths before the next provider input.
|
|
4576
|
+
// No tool from the just-completed response can benefit retroactively.
|
|
4577
|
+
projectDocSource = this.#getProjectDocBlock(workDir);
|
|
4578
|
+
const nextProjectDocContext = selectProjectDocContext(projectDocSource, {
|
|
4579
|
+
prompt, messages, pathHints: projectDocLoadedPathHints,
|
|
4580
|
+
forcedScopes: [...projectDocContext.selectedScopes],
|
|
4581
|
+
language: this.#config.language || 'en',
|
|
4582
|
+
});
|
|
4583
|
+
if (nextProjectDocContext.text !== projectDocContext.text) {
|
|
4584
|
+
projectDocContext = nextProjectDocContext;
|
|
4585
|
+
systemPrompt = buildCurrentSystemPrompt();
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4449
4588
|
// PR-L: flush any duplicate-call reminders queued during the batch.
|
|
4450
4589
|
// Pushed AFTER the for-loop so the tool_use → tool_result pairing
|
|
4451
4590
|
// is intact; the next adapter.stream() will see the reminder as a
|