@google/gemini-cli-core 0.44.0 → 0.45.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/changelogs/index.md +15 -0
- package/dist/docs/changelogs/latest.md +198 -262
- package/dist/docs/changelogs/preview.md +202 -181
- package/dist/docs/reference/configuration.md +24 -29
- package/dist/src/availability/policyCatalog.js +1 -1
- package/dist/src/availability/policyCatalog.js.map +1 -1
- package/dist/src/availability/policyCatalog.test.js +0 -2
- package/dist/src/availability/policyCatalog.test.js.map +1 -1
- package/dist/src/availability/policyHelpers.js +1 -5
- package/dist/src/availability/policyHelpers.js.map +1 -1
- package/dist/src/availability/policyHelpers.test.js +2 -4
- package/dist/src/availability/policyHelpers.test.js.map +1 -1
- package/dist/src/code_assist/experiments/flagNames.d.ts +0 -1
- package/dist/src/code_assist/experiments/flagNames.js +0 -1
- package/dist/src/code_assist/experiments/flagNames.js.map +1 -1
- package/dist/src/config/config.d.ts +0 -13
- package/dist/src/config/config.js +4 -27
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +0 -10
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.js +19 -25
- package/dist/src/config/defaultModelConfigs.js.map +1 -1
- package/dist/src/config/models.d.ts +6 -6
- package/dist/src/config/models.js +30 -27
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/models.test.js +81 -54
- package/dist/src/config/models.test.js.map +1 -1
- package/dist/src/context/chatCompressionService.js +6 -4
- package/dist/src/context/chatCompressionService.js.map +1 -1
- package/dist/src/context/config/configLoader.js +4 -1
- package/dist/src/context/config/configLoader.js.map +1 -1
- package/dist/src/context/config/profiles.d.ts +5 -0
- package/dist/src/context/config/profiles.js +84 -0
- package/dist/src/context/config/profiles.js.map +1 -1
- package/dist/src/context/config/types.d.ts +8 -1
- package/dist/src/context/contextManager.d.ts +9 -25
- package/dist/src/context/contextManager.incremental.test.d.ts +6 -0
- package/dist/src/context/contextManager.incremental.test.js +101 -0
- package/dist/src/context/contextManager.incremental.test.js.map +1 -0
- package/dist/src/context/contextManager.js +190 -145
- package/dist/src/context/contextManager.js.map +1 -1
- package/dist/src/context/contextManager.test.js +41 -3
- package/dist/src/context/contextManager.test.js.map +1 -1
- package/dist/src/context/eventBus.d.ts +7 -0
- package/dist/src/context/eventBus.js +6 -0
- package/dist/src/context/eventBus.js.map +1 -1
- package/dist/src/context/graph/render.js +19 -2
- package/dist/src/context/graph/render.js.map +1 -1
- package/dist/src/context/graph/render.test.js +10 -3
- package/dist/src/context/graph/render.test.js.map +1 -1
- package/dist/src/context/graph/toGraph.js +5 -4
- package/dist/src/context/graph/toGraph.js.map +1 -1
- package/dist/src/context/pipeline/orchestrator.d.ts +2 -1
- package/dist/src/context/pipeline/orchestrator.js +4 -4
- package/dist/src/context/pipeline/orchestrator.js.map +1 -1
- package/dist/src/context/pipeline/orchestrator.test.js +8 -4
- package/dist/src/context/pipeline/orchestrator.test.js.map +1 -1
- package/dist/src/context/system-tests/powerUserLifecycle.test.d.ts +6 -0
- package/dist/src/context/system-tests/powerUserLifecycle.test.js +91 -0
- package/dist/src/context/system-tests/powerUserLifecycle.test.js.map +1 -0
- package/dist/src/core/client.js +1 -1
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/contentGenerator.js +1 -3
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/geminiChat.js +3 -4
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/prompts.test.js +5 -5
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/prompts/promptProvider.js +2 -2
- package/dist/src/prompts/promptProvider.js.map +1 -1
- package/dist/src/routing/strategies/approvalModeStrategy.js +3 -4
- package/dist/src/routing/strategies/approvalModeStrategy.js.map +1 -1
- package/dist/src/routing/strategies/approvalModeStrategy.test.js +0 -1
- package/dist/src/routing/strategies/approvalModeStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.js +10 -4
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js +62 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/defaultStrategy.js +1 -1
- package/dist/src/routing/strategies/defaultStrategy.js.map +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.js +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
- package/dist/src/routing/strategies/gemmaClassifierStrategy.js +3 -4
- package/dist/src/routing/strategies/gemmaClassifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js +0 -1
- package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/numericalClassifierStrategy.js +10 -3
- package/dist/src/routing/strategies/numericalClassifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/numericalClassifierStrategy.test.js +67 -1
- package/dist/src/routing/strategies/numericalClassifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/overrideStrategy.js +1 -1
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
- package/dist/src/sandbox/utils/commandUtils.js +1 -5
- package/dist/src/sandbox/utils/commandUtils.js.map +1 -1
- package/dist/src/scheduler/scheduler.js +4 -0
- package/dist/src/scheduler/scheduler.js.map +1 -1
- package/dist/src/scheduler/scheduler_parallel.test.js +37 -0
- package/dist/src/scheduler/scheduler_parallel.test.js.map +1 -1
- package/dist/src/services/modelConfigService.js +1 -5
- package/dist/src/services/modelConfigService.js.map +1 -1
- package/dist/src/services/shellExecutionService.js +6 -37
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +4 -4
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/services/test-data/resolved-aliases-retry.golden.json +19 -7
- package/dist/src/services/test-data/resolved-aliases.golden.json +19 -7
- package/dist/src/utils/sessionUtils.js +5 -2
- package/dist/src/utils/sessionUtils.js.map +1 -1
- package/dist/src/utils/sessionUtils.test.js +26 -0
- package/dist/src/utils/sessionUtils.test.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +1 -19
- package/dist/src/utils/shell-utils.js +4 -42
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +1 -44
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import type { ContextProcessor, AsyncContextProcessor } from '../pipeline.js';
|
|
7
|
-
export type PipelineTrigger = 'new_message' | 'retained_exceeded' | 'gc_backstop' | 'nodes_added' | 'nodes_aged_out' | {
|
|
7
|
+
export type PipelineTrigger = 'new_message' | 'retained_exceeded' | 'normalized_exceeded' | 'gc_backstop' | 'nodes_added' | 'nodes_aged_out' | {
|
|
8
8
|
type: 'timer';
|
|
9
9
|
intervalMs: number;
|
|
10
10
|
};
|
|
@@ -20,6 +20,7 @@ export interface AsyncPipelineDef {
|
|
|
20
20
|
}
|
|
21
21
|
export interface ContextBudget {
|
|
22
22
|
retainedTokens: number;
|
|
23
|
+
normalizedTokens?: number;
|
|
23
24
|
maxTokens: number;
|
|
24
25
|
/**
|
|
25
26
|
* Only trigger background consolidation (snapshots) when at least this many
|
|
@@ -33,6 +34,12 @@ export interface ContextBudget {
|
|
|
33
34
|
export interface ContextManagementConfig {
|
|
34
35
|
/** Defines the token ceilings and limits for the pipeline. */
|
|
35
36
|
budget: ContextBudget;
|
|
37
|
+
/**
|
|
38
|
+
* Strategy for the GC backstop when maxTokens is exceeded.
|
|
39
|
+
* 'bulk' (default): Processes all nodes that have aged out of retainedTokens.
|
|
40
|
+
* 'incremental': Processes only the oldest nodes necessary to get back under maxTokens.
|
|
41
|
+
*/
|
|
42
|
+
gcStrategy?: 'bulk' | 'incremental';
|
|
36
43
|
/**
|
|
37
44
|
* Dynamic hyperparameter overrides for individual ContextProcessors and AsyncProcessors.
|
|
38
45
|
* Keys are named identifiers (e.g. "gentleTruncation").
|
|
@@ -23,33 +23,11 @@ export declare class ContextManager {
|
|
|
23
23
|
private readonly orchestrator;
|
|
24
24
|
private readonly evaluatedNodeIds;
|
|
25
25
|
private lastTriggeredDeficit;
|
|
26
|
+
private lastTriggeredNormalizeDeficit;
|
|
26
27
|
private lastRenderCache?;
|
|
27
28
|
private hasPerformedHotStart;
|
|
28
29
|
constructor(sidecar: ContextProfile, env: ContextEnvironment, tracer: ContextTracer, orchestrator: PipelineOrchestrator, chatHistory: AgentChatHistory, advancedTokenCalculator: AdvancedTokenCalculator, headerProvider?: (() => Promise<Content | undefined>) | undefined);
|
|
29
|
-
|
|
30
|
-
* Returns a promise that resolves when all currently executing async pipelines have finished.
|
|
31
|
-
*/
|
|
32
|
-
waitForPipelines(): Promise<void>;
|
|
33
|
-
/**
|
|
34
|
-
* Safely stops background async pipelines and clears event listeners.
|
|
35
|
-
*/
|
|
36
|
-
shutdown(): void;
|
|
37
|
-
/**
|
|
38
|
-
* Evaluates if the current working buffer exceeds configured budget thresholds,
|
|
39
|
-
* firing consolidation events if necessary.
|
|
40
|
-
*/
|
|
41
|
-
private evaluateTriggers;
|
|
42
|
-
private getProtectedNodeIds;
|
|
43
|
-
getPristineGraph(): readonly ConcreteNode[];
|
|
44
|
-
getNodes(): readonly ConcreteNode[];
|
|
45
|
-
/**
|
|
46
|
-
* Generates a virtual view of the pristine graph, substituting in variants
|
|
47
|
-
* up to the configured token budget.
|
|
48
|
-
*/
|
|
49
|
-
renderHistory(pendingRequest?: {
|
|
50
|
-
id: string;
|
|
51
|
-
content: Content;
|
|
52
|
-
}, activeTaskIds?: Set<string>, abortSignal?: AbortSignal): Promise<{
|
|
30
|
+
renderHistory(pendingRequest?: HistoryTurn, activeTaskIds?: Set<string>, abortSignal?: AbortSignal): Promise<{
|
|
53
31
|
history: HistoryTurn[];
|
|
54
32
|
apiHistory: Content[];
|
|
55
33
|
pendingApiHistory: Content[];
|
|
@@ -57,6 +35,12 @@ export declare class ContextManager {
|
|
|
57
35
|
baseUnits: number;
|
|
58
36
|
processedNodes: readonly ConcreteNode[];
|
|
59
37
|
}>;
|
|
60
|
-
|
|
38
|
+
waitForPipelines(): Promise<void>;
|
|
39
|
+
shutdown(): void;
|
|
40
|
+
getNodes(): readonly ConcreteNode[];
|
|
61
41
|
getEnvironment(): ContextEnvironment;
|
|
42
|
+
getPristineGraph(): readonly ConcreteNode[];
|
|
43
|
+
private evaluateTriggers;
|
|
44
|
+
private getProtectedNodeIds;
|
|
45
|
+
private performHotStartCalibration;
|
|
62
46
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
import { ContextManager } from './contextManager.js';
|
|
8
|
+
import { createMockEnvironment, createDummyNode, } from './testing/contextTestUtils.js';
|
|
9
|
+
import { NodeType } from './graph/types.js';
|
|
10
|
+
describe('ContextManager - Multi-stage and Incremental GC', () => {
|
|
11
|
+
let mockEnv;
|
|
12
|
+
let mockOrchestrator;
|
|
13
|
+
let mockChatHistory;
|
|
14
|
+
let mockAdvancedTokenCalculator;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockEnv = createMockEnvironment();
|
|
17
|
+
mockOrchestrator = {
|
|
18
|
+
setNodeProvider: vi.fn(),
|
|
19
|
+
waitForPipelines: vi.fn().mockResolvedValue(undefined),
|
|
20
|
+
executeTriggerSync: vi
|
|
21
|
+
.fn()
|
|
22
|
+
.mockImplementation(async (trigger, buffer) => buffer),
|
|
23
|
+
executeIngestionPipeline: vi
|
|
24
|
+
.fn()
|
|
25
|
+
.mockImplementation(async (nodes) => nodes),
|
|
26
|
+
shutdown: vi.fn(),
|
|
27
|
+
};
|
|
28
|
+
mockChatHistory = {
|
|
29
|
+
all: vi.fn().mockReturnValue([]),
|
|
30
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
31
|
+
get: vi.fn().mockReturnValue([]),
|
|
32
|
+
subscribe: vi.fn(),
|
|
33
|
+
};
|
|
34
|
+
mockAdvancedTokenCalculator = {
|
|
35
|
+
getRawBaseUnits: vi.fn().mockReturnValue(0),
|
|
36
|
+
getRawBaseUnitsForContent: vi.fn().mockReturnValue(0),
|
|
37
|
+
calculateTokensAndBaseUnits: vi.fn(),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
const setupManager = (config) => {
|
|
41
|
+
const sidecar = {
|
|
42
|
+
name: 'test',
|
|
43
|
+
config,
|
|
44
|
+
buildPipelines: () => [],
|
|
45
|
+
buildAsyncPipelines: () => [],
|
|
46
|
+
};
|
|
47
|
+
return new ContextManager(sidecar, mockEnv, mockEnv.tracer, mockOrchestrator, mockChatHistory, mockAdvancedTokenCalculator);
|
|
48
|
+
};
|
|
49
|
+
it('should emit NormalizeNeeded when normalizedTokens budget is exceeded', async () => {
|
|
50
|
+
const manager = setupManager({
|
|
51
|
+
budget: {
|
|
52
|
+
retainedTokens: 100,
|
|
53
|
+
normalizedTokens: 150,
|
|
54
|
+
maxTokens: 300,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const normalizeSpy = vi.fn();
|
|
58
|
+
mockEnv.eventBus.onNormalizeNeeded(normalizeSpy);
|
|
59
|
+
const consolidationSpy = vi.fn();
|
|
60
|
+
mockEnv.eventBus.onConsolidationNeeded(consolidationSpy);
|
|
61
|
+
// Mock token calculator for evaluateTriggers
|
|
62
|
+
mockEnv.tokenCalculator.calculateConcreteListTokens = vi
|
|
63
|
+
.fn()
|
|
64
|
+
.mockImplementation((nodes) => nodes.reduce((sum, n) =>
|
|
65
|
+
// Look for the mock tokens we attached to the dummy node
|
|
66
|
+
sum + (n._mockTokens || 0), 0));
|
|
67
|
+
const createNodeWithTokens = (id, type, tokens) => {
|
|
68
|
+
const node = createDummyNode(id, type);
|
|
69
|
+
// @ts-expect-error - attaching mock tokens for test
|
|
70
|
+
node._mockTokens = tokens;
|
|
71
|
+
return node;
|
|
72
|
+
};
|
|
73
|
+
// Create 4 nodes, each 80 tokens. Total = 320 tokens.
|
|
74
|
+
// Node 1 (oldest): prior=240. 240 > 150 -> Normalization (Archiving trigger)
|
|
75
|
+
// Node 2: prior=160. 160 > 150 -> Normalization
|
|
76
|
+
// Node 3: prior=80. 80 <= 100 -> Retained
|
|
77
|
+
// Node 4 (newest): prior=0. 0 <= 100 -> Retained
|
|
78
|
+
const nodes = [
|
|
79
|
+
createNodeWithTokens('ep1', NodeType.USER_PROMPT, 80),
|
|
80
|
+
createNodeWithTokens('ep2', NodeType.AGENT_THOUGHT, 80),
|
|
81
|
+
createNodeWithTokens('ep3', NodeType.TOOL_EXECUTION, 80),
|
|
82
|
+
createNodeWithTokens('ep4', NodeType.TOOL_EXECUTION, 80),
|
|
83
|
+
];
|
|
84
|
+
// @ts-expect-error - access private method for testing
|
|
85
|
+
manager.buffer = { nodes };
|
|
86
|
+
// Trigger evaluation manually with a dummy "new node" to bypass the empty check
|
|
87
|
+
// @ts-expect-error - access private method for testing
|
|
88
|
+
await manager.evaluateTriggers(nodes, new Set([nodes[3].id]), new Set());
|
|
89
|
+
// Nodes 3 and 4 are retained.
|
|
90
|
+
// Node 2 and Node 1 both fall out of normalizedTokens (160 > 150, 240 > 150).
|
|
91
|
+
// Therefore they should trigger NormalizeNeeded. They should NOT trigger ConsolidationNeeded
|
|
92
|
+
// because they exceeded normalized budget, so they skip the retained fallback.
|
|
93
|
+
expect(consolidationSpy).not.toHaveBeenCalled();
|
|
94
|
+
expect(normalizeSpy).toHaveBeenCalledOnce();
|
|
95
|
+
const normalizeEvent = normalizeSpy.mock.calls[0][0];
|
|
96
|
+
expect(normalizeEvent.targetNodeIds.has(nodes[0].id)).toBe(true);
|
|
97
|
+
expect(normalizeEvent.targetNodeIds.has(nodes[1].id)).toBe(true);
|
|
98
|
+
expect(normalizeEvent.targetNodeIds.has(nodes[2].id)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=contextManager.incremental.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextManager.incremental.test.js","sourceRoot":"","sources":["../../../src/context/contextManager.incremental.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,qBAAqB,EACrB,eAAe,GAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAqB,MAAM,kBAAkB,CAAC;AAQ/D,QAAQ,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC/D,IAAI,OAAiD,CAAC;IACtD,IAAI,gBAAsC,CAAC;IAC3C,IAAI,eAAiC,CAAC;IACtC,IAAI,2BAAoD,CAAC;IAEzD,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,qBAAqB,EAAE,CAAC;QAElC,gBAAgB,GAAG;YACjB,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE;YACxB,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;YACtD,kBAAkB,EAAE,EAAE;iBACnB,EAAE,EAAE;iBACJ,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC;YACxD,wBAAwB,EAAE,EAAE;iBACzB,EAAE,EAAE;iBACJ,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC;YAC7C,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE;SACiB,CAAC;QAErC,eAAe,GAAG;YAChB,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC;YAChC,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC;YACvC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC;YAChC,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;SACY,CAAC;QAEjC,2BAA2B,GAAG;YAC5B,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;YAC3C,yBAAyB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;YACrD,2BAA2B,EAAE,EAAE,CAAC,EAAE,EAAE;SACC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,CAAC,MAA+B,EAAE,EAAE;QACvD,MAAM,OAAO,GAAmB;YAC9B,IAAI,EAAE,MAAM;YACZ,MAAM;YACN,cAAc,EAAE,GAAG,EAAE,CAAC,EAAE;YACxB,mBAAmB,EAAE,GAAG,EAAE,CAAC,EAAE;SAC9B,CAAC;QACF,OAAO,IAAI,cAAc,CACvB,OAAO,EACP,OAAwC,EACxC,OAAO,CAAC,MAAM,EACd,gBAAgB,EAChB,eAAe,EACf,2BAA2B,CAC5B,CAAC;IACJ,CAAC,CAAC;IAEF,EAAE,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;QACpF,MAAM,OAAO,GAAG,YAAY,CAAC;YAC3B,MAAM,EAAE;gBACN,cAAc,EAAE,GAAG;gBACnB,gBAAgB,EAAE,GAAG;gBACrB,SAAS,EAAE,GAAG;aACf;SACoC,CAAC,CAAC;QAEzC,MAAM,YAAY,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,gBAAgB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;QAEzD,6CAA6C;QAC7C,OAAO,CAAC,eAAe,CAAC,2BAA2B,GAAG,EAAE;aACrD,EAAE,EAAE;aACJ,kBAAkB,CAAC,CAAC,KAAqB,EAAE,EAAE,CAC5C,KAAK,CAAC,MAAM,CACV,CAAC,GAAW,EAAE,CAAe,EAAE,EAAE;QAC/B,yDAAyD;QACzD,GAAG,GAAG,CAAE,CAAwC,CAAC,WAAW,IAAI,CAAC,CAAC,EACpE,CAAC,CACF,CACF,CAAC;QAEJ,MAAM,oBAAoB,GAAG,CAC3B,EAAU,EACV,IAAc,EACd,MAAc,EACd,EAAE;YACF,MAAM,IAAI,GAAG,eAAe,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACvC,oDAAoD;YACpD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QAEF,sDAAsD;QACtD,6EAA6E;QAC7E,gDAAgD;QAChD,0CAA0C;QAC1C,iDAAiD;QACjD,MAAM,KAAK,GAAG;YACZ,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;YACrD,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC;YACvD,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC;YACxD,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC;SACzD,CAAC;QAEF,uDAAuD;QACvD,OAAO,CAAC,MAAM,GAAG,EAAE,KAAK,EAAyC,CAAC;QAElE,gFAAgF;QAChF,uDAAuD;QACvD,MAAM,OAAO,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAEzE,8BAA8B;QAC9B,8EAA8E;QAC9E,6FAA6F;QAC7F,+EAA+E;QAC/E,MAAM,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAEhD,MAAM,CAAC,YAAY,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAC5C,MAAM,cAAc,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjE,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjE,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -24,6 +24,7 @@ export class ContextManager {
|
|
|
24
24
|
evaluatedNodeIds = new Set();
|
|
25
25
|
// Hysteresis tracking to prevent utility call churn
|
|
26
26
|
lastTriggeredDeficit = 0;
|
|
27
|
+
lastTriggeredNormalizeDeficit = 0;
|
|
27
28
|
// Cache for Anomaly 3 (Redundant Renders)
|
|
28
29
|
lastRenderCache;
|
|
29
30
|
hasPerformedHotStart = false;
|
|
@@ -36,121 +37,22 @@ export class ContextManager {
|
|
|
36
37
|
this.headerProvider = headerProvider;
|
|
37
38
|
this.eventBus = env.eventBus;
|
|
38
39
|
this.orchestrator = orchestrator;
|
|
39
|
-
//
|
|
40
|
+
// Direct synchronization: ContextManager is the "Pull Master"
|
|
41
|
+
// and tells the orchestrator what to do.
|
|
40
42
|
this.orchestrator.setNodeProvider(() => this.buffer.nodes);
|
|
41
43
|
this.eventBus.onProcessorResult((event) => {
|
|
42
44
|
// Defensive: Verify all targets are still present in the buffer.
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
debugLogger.log(`[ContextManager] Dropping stale processor result from ${event.processorId}. One or more targets were already removed.`);
|
|
45
|
+
const bufferIds = new Set(this.buffer.nodes.map((n) => n.id));
|
|
46
|
+
if (!event.targets.every((t) => bufferIds.has(t.id))) {
|
|
47
|
+
debugLogger.warn(`[ContextManager] Dropping processor result from ${event.processorId}: targets no longer in buffer.`);
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
this.buffer = this.buffer.applyProcessorResult(event.processorId, event.targets, event.returnedNodes);
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Returns a promise that resolves when all currently executing async pipelines have finished.
|
|
54
|
-
*/
|
|
55
|
-
async waitForPipelines() {
|
|
56
|
-
return this.orchestrator.waitForPipelines();
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Safely stops background async pipelines and clears event listeners.
|
|
60
|
-
*/
|
|
61
|
-
shutdown() {
|
|
62
|
-
this.orchestrator.shutdown();
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Evaluates if the current working buffer exceeds configured budget thresholds,
|
|
66
|
-
* firing consolidation events if necessary.
|
|
67
|
-
*/
|
|
68
|
-
async evaluateTriggers(newNodes) {
|
|
69
|
-
if (!this.sidecar.config.budget)
|
|
70
|
-
return;
|
|
71
|
-
if (newNodes.size > 0) {
|
|
72
|
-
await this.orchestrator.executeTriggerSync('new_message', this.buffer.nodes, newNodes);
|
|
73
|
-
}
|
|
74
|
-
const currentTokens = this.env.tokenCalculator.calculateConcreteListTokens(this.buffer.nodes);
|
|
75
|
-
if (currentTokens > this.sidecar.config.budget.retainedTokens) {
|
|
76
|
-
const agedOutNodes = new Set();
|
|
77
|
-
let rollingTokens = 0;
|
|
78
|
-
// Identify nodes that must NEVER be truncated
|
|
79
|
-
const protectedIds = this.getProtectedNodeIds(this.buffer.nodes);
|
|
80
|
-
// Walk backwards finding nodes that fall out of the retained budget
|
|
81
|
-
for (let i = this.buffer.nodes.length - 1; i >= 0; i--) {
|
|
82
|
-
const node = this.buffer.nodes[i];
|
|
83
|
-
const priorTokens = rollingTokens;
|
|
84
|
-
rollingTokens += this.env.tokenCalculator.calculateConcreteListTokens([
|
|
85
|
-
node,
|
|
86
|
-
]);
|
|
87
|
-
if (priorTokens > this.sidecar.config.budget.retainedTokens) {
|
|
88
|
-
if (!protectedIds.has(node.id)) {
|
|
89
|
-
agedOutNodes.add(node.id);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (agedOutNodes.size > 0) {
|
|
94
|
-
const targetDeficit = currentTokens - this.sidecar.config.budget.retainedTokens;
|
|
95
|
-
if (targetDeficit < this.lastTriggeredDeficit) {
|
|
96
|
-
this.lastTriggeredDeficit = targetDeficit;
|
|
97
|
-
}
|
|
98
|
-
const threshold = this.sidecar.config.budget.coalescingThresholdTokens || 0;
|
|
99
|
-
const growthSinceLast = targetDeficit - this.lastTriggeredDeficit;
|
|
100
|
-
if (targetDeficit >= threshold &&
|
|
101
|
-
(growthSinceLast >= threshold || this.lastTriggeredDeficit === 0)) {
|
|
102
|
-
this.lastTriggeredDeficit = targetDeficit;
|
|
103
|
-
this.env.tokenCalculator.garbageCollectCache(new Set(this.buffer.nodes.map((n) => n.id)));
|
|
104
|
-
// Trigger synchronous consolidation for budget deficit
|
|
105
|
-
await this.orchestrator.executeTriggerSync('nodes_aged_out', this.buffer.nodes, agedOutNodes, new Set(protectedIds.keys()));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
this.lastTriggeredDeficit = 0;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
getProtectedNodeIds(nodes, extraProtectedIds = new Set()) {
|
|
114
|
-
const protectionMap = new Map();
|
|
115
|
-
if (nodes.length === 0)
|
|
116
|
-
return protectionMap;
|
|
117
|
-
const lastNode = nodes[nodes.length - 1];
|
|
118
|
-
const lastTurnId = lastNode.turnId;
|
|
119
|
-
const envTurnId = `turn_${deriveStableId(['environment-context'])}`;
|
|
120
|
-
for (const node of nodes) {
|
|
121
|
-
if (node.turnId === lastTurnId) {
|
|
122
|
-
protectionMap.set(node.id, 'recent_turn');
|
|
123
|
-
}
|
|
124
|
-
else if (node.turnId === envTurnId) {
|
|
125
|
-
protectionMap.set(node.id, 'environment_context');
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
for (const id of extraProtectedIds) {
|
|
129
|
-
protectionMap.set(id, 'external_active_task');
|
|
130
|
-
}
|
|
131
|
-
return protectionMap;
|
|
132
|
-
}
|
|
133
|
-
getPristineGraph() {
|
|
134
|
-
const pristineSet = new Map();
|
|
135
|
-
for (const node of this.buffer.nodes) {
|
|
136
|
-
const roots = this.buffer.getPristineNodes(node.id);
|
|
137
|
-
for (const root of roots) {
|
|
138
|
-
pristineSet.set(root.id, root);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return Array.from(pristineSet.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
142
|
-
}
|
|
143
|
-
getNodes() {
|
|
144
|
-
return [...this.buffer.nodes];
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Generates a virtual view of the pristine graph, substituting in variants
|
|
148
|
-
* up to the configured token budget.
|
|
149
|
-
*/
|
|
150
53
|
async renderHistory(pendingRequest, activeTaskIds = new Set(), abortSignal) {
|
|
151
54
|
this.tracer.logEvent('ContextManager', 'Starting rendering of LLM context');
|
|
152
55
|
// 1. Explicit Sync with the durable history.
|
|
153
|
-
// This replaces the background HistoryObserver.
|
|
154
56
|
const currentHistory = this.chatHistory.get();
|
|
155
57
|
const pristineNodes = this.env.graphMapper.sync(currentHistory);
|
|
156
58
|
this.buffer = this.buffer.syncPristineHistory(pristineNodes);
|
|
@@ -165,12 +67,12 @@ export class ContextManager {
|
|
|
165
67
|
// 2. Preview the pending request.
|
|
166
68
|
let previewNodes = [];
|
|
167
69
|
if (pendingRequest) {
|
|
168
|
-
|
|
169
|
-
const previewNodeIds = new Set(
|
|
170
|
-
|
|
70
|
+
const syncedNodes = this.env.graphMapper.sync([pendingRequest]);
|
|
71
|
+
const previewNodeIds = new Set(syncedNodes.map((n) => n.id));
|
|
72
|
+
const previewBuffer = ContextWorkingBufferImpl.initialize(syncedNodes);
|
|
73
|
+
const processedPreviewBuffer = await this.orchestrator.executeTriggerSync('new_message', previewBuffer, previewNodeIds);
|
|
74
|
+
previewNodes = processedPreviewBuffer.nodes;
|
|
171
75
|
}
|
|
172
|
-
// 3. Trigger evaluation (Sync budget management).
|
|
173
|
-
await this.evaluateTriggers(newPrimalNodes);
|
|
174
76
|
// --- Hot Start Calibration ---
|
|
175
77
|
const hotStartPromise = (async () => {
|
|
176
78
|
if (!this.hasPerformedHotStart) {
|
|
@@ -181,23 +83,30 @@ export class ContextManager {
|
|
|
181
83
|
}
|
|
182
84
|
}
|
|
183
85
|
})();
|
|
86
|
+
// 3. Synchronous Pressure Barrier
|
|
184
87
|
await Promise.all([this.orchestrator.waitForPipelines(), hotStartPromise]);
|
|
185
88
|
let nodes = this.buffer.nodes;
|
|
186
89
|
const previewNodeIds = new Set();
|
|
187
90
|
if (previewNodes.length > 0) {
|
|
188
|
-
for (const n of previewNodes) {
|
|
189
|
-
previewNodeIds.add(n.id);
|
|
190
|
-
}
|
|
191
91
|
nodes = [...nodes, ...previewNodes];
|
|
92
|
+
for (const node of previewNodes) {
|
|
93
|
+
previewNodeIds.add(node.id);
|
|
94
|
+
}
|
|
192
95
|
}
|
|
96
|
+
// 4. Trigger Management (GC/Distillation/Normalization)
|
|
97
|
+
await this.evaluateTriggers(nodes, newPrimalNodes, activeTaskIds);
|
|
98
|
+
// Re-fetch nodes from buffer (master) and combine with ephemeral previews
|
|
99
|
+
nodes = [...this.buffer.nodes, ...previewNodes];
|
|
100
|
+
// 5. Final Render
|
|
193
101
|
const header = this.headerProvider
|
|
194
102
|
? await this.headerProvider()
|
|
195
103
|
: undefined;
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
104
|
+
const nodesHash = deriveStableId([
|
|
105
|
+
...nodes.map((n) => n.id),
|
|
106
|
+
header ? JSON.stringify(header.parts) : 'no-header',
|
|
107
|
+
]);
|
|
108
|
+
if (this.lastRenderCache?.nodesHash === nodesHash) {
|
|
109
|
+
this.tracer.logEvent('ContextManager', 'Render Cache Hit', { nodesHash });
|
|
201
110
|
return this.lastRenderCache.result;
|
|
202
111
|
}
|
|
203
112
|
const protectionReasons = this.getProtectedNodeIds(nodes, activeTaskIds);
|
|
@@ -208,45 +117,184 @@ export class ContextManager {
|
|
|
208
117
|
});
|
|
209
118
|
const { history: renderedHistory, pendingHistory, didApplyManagement, baseUnits, processedNodes, } = renderResult;
|
|
210
119
|
if (didApplyManagement) {
|
|
211
|
-
|
|
120
|
+
// Commit the GC backstop results back to the master buffer.
|
|
121
|
+
// We must be careful to only apply results to the nodes that belong to the master buffer.
|
|
122
|
+
const masterIdsInResult = new Set(this.buffer.nodes.map((n) => n.id));
|
|
123
|
+
const processedMasterNodes = processedNodes.filter((n) => !previewNodeIds.has(n.id) || masterIdsInResult.has(n.id));
|
|
124
|
+
this.buffer = this.buffer.applyProcessorResult('sync_backstop', this.buffer.nodes, processedMasterNodes);
|
|
212
125
|
}
|
|
126
|
+
// Structural validation
|
|
213
127
|
checkContextInvariants(this.buffer.nodes, 'RenderHistory');
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
const hardenedAllHistory = hardenHistory(allHistory, {
|
|
128
|
+
const fullHistoryToHarden = [...renderedHistory, ...pendingHistory];
|
|
129
|
+
const hardenedFullHistory = hardenHistory(fullHistoryToHarden, {
|
|
217
130
|
sentinels: this.sidecar.sentinels,
|
|
218
131
|
});
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
132
|
+
const envContextId = deriveStableId(['environment-context']);
|
|
133
|
+
const pendingIds = new Set(pendingHistory.map((t) => t.id));
|
|
134
|
+
const resultHistory = [];
|
|
135
|
+
const resultPending = [];
|
|
136
|
+
let foundPending = false;
|
|
137
|
+
for (const turn of hardenedFullHistory) {
|
|
138
|
+
if (!foundPending &&
|
|
139
|
+
(pendingIds.has(turn.id) ||
|
|
140
|
+
(turn.id.startsWith('turn_') &&
|
|
141
|
+
pendingIds.has(turn.id.substring(5)))) &&
|
|
142
|
+
turn.id !== envContextId &&
|
|
143
|
+
turn.id !== `turn_${envContextId}`) {
|
|
144
|
+
foundPending = true;
|
|
145
|
+
}
|
|
146
|
+
if (foundPending) {
|
|
147
|
+
resultPending.push(turn);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
resultHistory.push(turn);
|
|
225
151
|
}
|
|
226
|
-
}
|
|
227
|
-
const apiHistory = hardenedAllHistory
|
|
228
|
-
.slice(0, splitIndex)
|
|
229
|
-
.map((h) => h.content);
|
|
230
|
-
const pendingApiHistory = hardenedAllHistory
|
|
231
|
-
.slice(splitIndex)
|
|
232
|
-
.map((h) => h.content);
|
|
233
|
-
if (header) {
|
|
234
|
-
apiHistory.unshift(header);
|
|
235
152
|
}
|
|
236
153
|
const result = {
|
|
237
154
|
history: renderedHistory,
|
|
238
|
-
apiHistory,
|
|
239
|
-
pendingApiHistory,
|
|
155
|
+
apiHistory: resultHistory.map((h) => h.content),
|
|
156
|
+
pendingApiHistory: resultPending.map((h) => h.content),
|
|
240
157
|
didApplyManagement,
|
|
241
158
|
baseUnits,
|
|
242
159
|
processedNodes,
|
|
243
160
|
};
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
161
|
+
if (header) {
|
|
162
|
+
result.apiHistory.unshift(header);
|
|
163
|
+
}
|
|
164
|
+
this.lastRenderCache = { nodesHash, result };
|
|
165
|
+
this.tracer.logEvent('ContextManager', 'Rendering Complete', {
|
|
166
|
+
historySize: renderedHistory.length,
|
|
167
|
+
pendingSize: pendingHistory.length,
|
|
168
|
+
didApplyManagement,
|
|
169
|
+
});
|
|
248
170
|
return result;
|
|
249
171
|
}
|
|
172
|
+
async waitForPipelines() {
|
|
173
|
+
await this.orchestrator.waitForPipelines();
|
|
174
|
+
}
|
|
175
|
+
shutdown() {
|
|
176
|
+
this.orchestrator.shutdown();
|
|
177
|
+
}
|
|
178
|
+
getNodes() {
|
|
179
|
+
return this.buffer.nodes;
|
|
180
|
+
}
|
|
181
|
+
getEnvironment() {
|
|
182
|
+
return this.env;
|
|
183
|
+
}
|
|
184
|
+
getPristineGraph() {
|
|
185
|
+
const pristineSet = new Map();
|
|
186
|
+
for (const node of this.buffer.nodes) {
|
|
187
|
+
const roots = this.buffer.getPristineNodes(node.id);
|
|
188
|
+
for (const root of roots) {
|
|
189
|
+
pristineSet.set(root.id, root);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return Array.from(pristineSet.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
193
|
+
}
|
|
194
|
+
async evaluateTriggers(nodes, newPrimalNodes, activeTaskIds) {
|
|
195
|
+
if (newPrimalNodes.size > 0) {
|
|
196
|
+
this.buffer = await this.orchestrator.executeTriggerSync('nodes_added', this.buffer, newPrimalNodes);
|
|
197
|
+
}
|
|
198
|
+
// Identify ephemeral preview nodes that are NOT in the master buffer.
|
|
199
|
+
const bufferIds = new Set(this.buffer.nodes.map((n) => n.id));
|
|
200
|
+
const previewNodes = nodes.filter((n) => !bufferIds.has(n.id));
|
|
201
|
+
const currentNodes = [...this.buffer.nodes, ...previewNodes];
|
|
202
|
+
const currentTokens = this.env.tokenCalculator.calculateConcreteListTokens(currentNodes);
|
|
203
|
+
if (currentTokens > this.sidecar.config.budget.retainedTokens) {
|
|
204
|
+
const agedOutRetainedNodes = new Set();
|
|
205
|
+
const agedOutNormalizedNodes = new Set();
|
|
206
|
+
const protectionMap = this.getProtectedNodeIds(currentNodes, activeTaskIds);
|
|
207
|
+
const protectedIds = new Set(protectionMap.keys());
|
|
208
|
+
// Also pin Turn 0 (Environment Context)
|
|
209
|
+
const envTurnId = `turn_${deriveStableId(['environment-context'])}`;
|
|
210
|
+
const turn0Nodes = currentNodes.filter((n) => n.turnId === envTurnId);
|
|
211
|
+
for (const n of turn0Nodes) {
|
|
212
|
+
protectedIds.add(n.id);
|
|
213
|
+
}
|
|
214
|
+
let rollingTokens = 0;
|
|
215
|
+
for (let i = currentNodes.length - 1; i >= 0; i--) {
|
|
216
|
+
const node = currentNodes[i];
|
|
217
|
+
const priorTokens = rollingTokens;
|
|
218
|
+
rollingTokens += this.env.tokenCalculator.calculateConcreteListTokens([
|
|
219
|
+
node,
|
|
220
|
+
]);
|
|
221
|
+
if (priorTokens > this.sidecar.config.budget.retainedTokens) {
|
|
222
|
+
if (!protectedIds.has(node.id)) {
|
|
223
|
+
const hasNormalizedTier = this.sidecar.config.budget.normalizedTokens !== undefined;
|
|
224
|
+
if (!hasNormalizedTier ||
|
|
225
|
+
priorTokens <= this.sidecar.config.budget.normalizedTokens) {
|
|
226
|
+
agedOutRetainedNodes.add(node.id);
|
|
227
|
+
}
|
|
228
|
+
if (hasNormalizedTier &&
|
|
229
|
+
priorTokens > this.sidecar.config.budget.normalizedTokens) {
|
|
230
|
+
agedOutNormalizedNodes.add(node.id);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (agedOutRetainedNodes.size > 0) {
|
|
236
|
+
const targetDeficit = currentTokens - this.sidecar.config.budget.retainedTokens;
|
|
237
|
+
const threshold = this.sidecar.config.budget.coalescingThresholdTokens || 0;
|
|
238
|
+
if (targetDeficit < this.lastTriggeredDeficit) {
|
|
239
|
+
this.lastTriggeredDeficit = targetDeficit;
|
|
240
|
+
}
|
|
241
|
+
if (targetDeficit > this.lastTriggeredDeficit + threshold) {
|
|
242
|
+
this.lastTriggeredDeficit = targetDeficit;
|
|
243
|
+
this.eventBus.emitConsolidationNeeded({
|
|
244
|
+
nodes: this.buffer.nodes,
|
|
245
|
+
targetDeficit,
|
|
246
|
+
targetNodeIds: agedOutRetainedNodes,
|
|
247
|
+
});
|
|
248
|
+
this.env.tokenCalculator.garbageCollectCache(new Set(this.buffer.nodes.map((n) => n.id)));
|
|
249
|
+
this.buffer = await this.orchestrator.executeTriggerSync('nodes_aged_out', this.buffer, agedOutRetainedNodes, protectedIds);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
this.lastTriggeredDeficit = 0;
|
|
254
|
+
}
|
|
255
|
+
if (agedOutNormalizedNodes.size > 0) {
|
|
256
|
+
const targetDeficit = currentTokens - this.sidecar.config.budget.normalizedTokens;
|
|
257
|
+
const threshold = this.sidecar.config.budget.coalescingThresholdTokens || 0;
|
|
258
|
+
if (targetDeficit < this.lastTriggeredNormalizeDeficit) {
|
|
259
|
+
this.lastTriggeredNormalizeDeficit = targetDeficit;
|
|
260
|
+
}
|
|
261
|
+
if (targetDeficit > this.lastTriggeredNormalizeDeficit + threshold) {
|
|
262
|
+
this.lastTriggeredNormalizeDeficit = targetDeficit;
|
|
263
|
+
this.eventBus.emitNormalizeNeeded({
|
|
264
|
+
nodes: this.buffer.nodes,
|
|
265
|
+
targetDeficit,
|
|
266
|
+
targetNodeIds: agedOutNormalizedNodes,
|
|
267
|
+
});
|
|
268
|
+
this.buffer = await this.orchestrator.executeTriggerSync('normalized_exceeded', this.buffer, agedOutNormalizedNodes, protectedIds);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
this.lastTriggeredNormalizeDeficit = 0;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
getProtectedNodeIds(nodes, extraProtectedIds = new Set()) {
|
|
277
|
+
const protectionMap = new Map();
|
|
278
|
+
if (nodes.length === 0)
|
|
279
|
+
return protectionMap;
|
|
280
|
+
const lastNode = nodes[nodes.length - 1];
|
|
281
|
+
const lastTurnId = lastNode.turnId;
|
|
282
|
+
// Identify Environment Context (Turn 0) for pinning
|
|
283
|
+
const envContextId = deriveStableId(['environment-context']);
|
|
284
|
+
const envContextTurnId = `turn_${envContextId}`;
|
|
285
|
+
for (const node of nodes) {
|
|
286
|
+
if (node.turnId === envContextTurnId || node.turnId === envContextId) {
|
|
287
|
+
protectionMap.set(node.id, 'environment_context');
|
|
288
|
+
}
|
|
289
|
+
if (node.turnId === lastTurnId) {
|
|
290
|
+
protectionMap.set(node.id, 'recent_turn');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const id of extraProtectedIds) {
|
|
294
|
+
protectionMap.set(id, 'external_active_task');
|
|
295
|
+
}
|
|
296
|
+
return protectionMap;
|
|
297
|
+
}
|
|
250
298
|
async performHotStartCalibration(nodes, abortSignal) {
|
|
251
299
|
const history = this.env.graphMapper.fromGraph(nodes);
|
|
252
300
|
const contents = history.map((h) => h.content);
|
|
@@ -267,8 +315,5 @@ export class ContextManager {
|
|
|
267
315
|
debugLogger.warn('[ContextManager] Hot start calibration failed', e);
|
|
268
316
|
}
|
|
269
317
|
}
|
|
270
|
-
getEnvironment() {
|
|
271
|
-
return this.env;
|
|
272
|
-
}
|
|
273
318
|
}
|
|
274
319
|
//# sourceMappingURL=contextManager.js.map
|