@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.
Files changed (120) hide show
  1. package/dist/docs/changelogs/index.md +15 -0
  2. package/dist/docs/changelogs/latest.md +198 -262
  3. package/dist/docs/changelogs/preview.md +202 -181
  4. package/dist/docs/reference/configuration.md +24 -29
  5. package/dist/src/availability/policyCatalog.js +1 -1
  6. package/dist/src/availability/policyCatalog.js.map +1 -1
  7. package/dist/src/availability/policyCatalog.test.js +0 -2
  8. package/dist/src/availability/policyCatalog.test.js.map +1 -1
  9. package/dist/src/availability/policyHelpers.js +1 -5
  10. package/dist/src/availability/policyHelpers.js.map +1 -1
  11. package/dist/src/availability/policyHelpers.test.js +2 -4
  12. package/dist/src/availability/policyHelpers.test.js.map +1 -1
  13. package/dist/src/code_assist/experiments/flagNames.d.ts +0 -1
  14. package/dist/src/code_assist/experiments/flagNames.js +0 -1
  15. package/dist/src/code_assist/experiments/flagNames.js.map +1 -1
  16. package/dist/src/config/config.d.ts +0 -13
  17. package/dist/src/config/config.js +4 -27
  18. package/dist/src/config/config.js.map +1 -1
  19. package/dist/src/config/config.test.js +0 -10
  20. package/dist/src/config/config.test.js.map +1 -1
  21. package/dist/src/config/defaultModelConfigs.js +19 -25
  22. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  23. package/dist/src/config/models.d.ts +6 -6
  24. package/dist/src/config/models.js +30 -27
  25. package/dist/src/config/models.js.map +1 -1
  26. package/dist/src/config/models.test.js +81 -54
  27. package/dist/src/config/models.test.js.map +1 -1
  28. package/dist/src/context/chatCompressionService.js +6 -4
  29. package/dist/src/context/chatCompressionService.js.map +1 -1
  30. package/dist/src/context/config/configLoader.js +4 -1
  31. package/dist/src/context/config/configLoader.js.map +1 -1
  32. package/dist/src/context/config/profiles.d.ts +5 -0
  33. package/dist/src/context/config/profiles.js +84 -0
  34. package/dist/src/context/config/profiles.js.map +1 -1
  35. package/dist/src/context/config/types.d.ts +8 -1
  36. package/dist/src/context/contextManager.d.ts +9 -25
  37. package/dist/src/context/contextManager.incremental.test.d.ts +6 -0
  38. package/dist/src/context/contextManager.incremental.test.js +101 -0
  39. package/dist/src/context/contextManager.incremental.test.js.map +1 -0
  40. package/dist/src/context/contextManager.js +190 -145
  41. package/dist/src/context/contextManager.js.map +1 -1
  42. package/dist/src/context/contextManager.test.js +41 -3
  43. package/dist/src/context/contextManager.test.js.map +1 -1
  44. package/dist/src/context/eventBus.d.ts +7 -0
  45. package/dist/src/context/eventBus.js +6 -0
  46. package/dist/src/context/eventBus.js.map +1 -1
  47. package/dist/src/context/graph/render.js +19 -2
  48. package/dist/src/context/graph/render.js.map +1 -1
  49. package/dist/src/context/graph/render.test.js +10 -3
  50. package/dist/src/context/graph/render.test.js.map +1 -1
  51. package/dist/src/context/graph/toGraph.js +5 -4
  52. package/dist/src/context/graph/toGraph.js.map +1 -1
  53. package/dist/src/context/pipeline/orchestrator.d.ts +2 -1
  54. package/dist/src/context/pipeline/orchestrator.js +4 -4
  55. package/dist/src/context/pipeline/orchestrator.js.map +1 -1
  56. package/dist/src/context/pipeline/orchestrator.test.js +8 -4
  57. package/dist/src/context/pipeline/orchestrator.test.js.map +1 -1
  58. package/dist/src/context/system-tests/powerUserLifecycle.test.d.ts +6 -0
  59. package/dist/src/context/system-tests/powerUserLifecycle.test.js +91 -0
  60. package/dist/src/context/system-tests/powerUserLifecycle.test.js.map +1 -0
  61. package/dist/src/core/client.js +1 -1
  62. package/dist/src/core/client.js.map +1 -1
  63. package/dist/src/core/contentGenerator.js +1 -3
  64. package/dist/src/core/contentGenerator.js.map +1 -1
  65. package/dist/src/core/geminiChat.js +3 -4
  66. package/dist/src/core/geminiChat.js.map +1 -1
  67. package/dist/src/core/prompts.test.js +5 -5
  68. package/dist/src/core/prompts.test.js.map +1 -1
  69. package/dist/src/generated/git-commit.d.ts +2 -2
  70. package/dist/src/generated/git-commit.js +2 -2
  71. package/dist/src/generated/git-commit.js.map +1 -1
  72. package/dist/src/prompts/promptProvider.js +2 -2
  73. package/dist/src/prompts/promptProvider.js.map +1 -1
  74. package/dist/src/routing/strategies/approvalModeStrategy.js +3 -4
  75. package/dist/src/routing/strategies/approvalModeStrategy.js.map +1 -1
  76. package/dist/src/routing/strategies/approvalModeStrategy.test.js +0 -1
  77. package/dist/src/routing/strategies/approvalModeStrategy.test.js.map +1 -1
  78. package/dist/src/routing/strategies/classifierStrategy.js +10 -4
  79. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  80. package/dist/src/routing/strategies/classifierStrategy.test.js +62 -1
  81. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  82. package/dist/src/routing/strategies/defaultStrategy.js +1 -1
  83. package/dist/src/routing/strategies/defaultStrategy.js.map +1 -1
  84. package/dist/src/routing/strategies/fallbackStrategy.js +1 -1
  85. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  86. package/dist/src/routing/strategies/gemmaClassifierStrategy.js +3 -4
  87. package/dist/src/routing/strategies/gemmaClassifierStrategy.js.map +1 -1
  88. package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js +0 -1
  89. package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js.map +1 -1
  90. package/dist/src/routing/strategies/numericalClassifierStrategy.js +10 -3
  91. package/dist/src/routing/strategies/numericalClassifierStrategy.js.map +1 -1
  92. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js +67 -1
  93. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js.map +1 -1
  94. package/dist/src/routing/strategies/overrideStrategy.js +1 -1
  95. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  96. package/dist/src/sandbox/utils/commandUtils.js +1 -5
  97. package/dist/src/sandbox/utils/commandUtils.js.map +1 -1
  98. package/dist/src/scheduler/scheduler.js +4 -0
  99. package/dist/src/scheduler/scheduler.js.map +1 -1
  100. package/dist/src/scheduler/scheduler_parallel.test.js +37 -0
  101. package/dist/src/scheduler/scheduler_parallel.test.js.map +1 -1
  102. package/dist/src/services/modelConfigService.js +1 -5
  103. package/dist/src/services/modelConfigService.js.map +1 -1
  104. package/dist/src/services/shellExecutionService.js +6 -37
  105. package/dist/src/services/shellExecutionService.js.map +1 -1
  106. package/dist/src/services/shellExecutionService.test.js +4 -4
  107. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  108. package/dist/src/services/test-data/resolved-aliases-retry.golden.json +19 -7
  109. package/dist/src/services/test-data/resolved-aliases.golden.json +19 -7
  110. package/dist/src/utils/sessionUtils.js +5 -2
  111. package/dist/src/utils/sessionUtils.js.map +1 -1
  112. package/dist/src/utils/sessionUtils.test.js +26 -0
  113. package/dist/src/utils/sessionUtils.test.js.map +1 -1
  114. package/dist/src/utils/shell-utils.d.ts +1 -19
  115. package/dist/src/utils/shell-utils.js +4 -42
  116. package/dist/src/utils/shell-utils.js.map +1 -1
  117. package/dist/src/utils/shell-utils.test.js +1 -44
  118. package/dist/src/utils/shell-utils.test.js.map +1 -1
  119. package/dist/tsconfig.tsbuildinfo +1 -1
  120. 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
- private performHotStartCalibration;
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,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export {};
@@ -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":"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"}
@@ -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
- // Provide the orchestrator with a way to fetch the latest nodes from the live buffer
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 currentIds = new Set(this.buffer.nodes.map((n) => n.id));
44
- const allTargetsPresent = event.targets.every((t) => currentIds.has(t.id));
45
- if (!allTargetsPresent) {
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
- previewNodes = this.env.graphMapper.sync([pendingRequest]);
169
- const previewNodeIds = new Set(previewNodes.map((n) => n.id));
170
- previewNodes = await this.orchestrator.executeTriggerSync('new_message', previewNodes, previewNodeIds);
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 graphHash = nodes.map((n) => n.id).join('|');
197
- const headerHash = header ? JSON.stringify(header.parts) : 'no-header';
198
- const totalHash = `${graphHash}::${headerHash}`;
199
- if (this.lastRenderCache?.nodesHash === totalHash) {
200
- debugLogger.log('[ContextManager] Render cache hit. Skipping redundant render.');
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
- this.buffer = this.buffer.applyProcessorResult('sync_backstop', this.buffer.nodes, processedNodes.filter((n) => !previewNodeIds.has(n.id)));
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
- this.tracer.logEvent('ContextManager', 'Finished rendering');
215
- const allHistory = [...renderedHistory, ...pendingHistory];
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 firstPendingId = pendingHistory[0]?.id;
220
- let splitIndex = renderedHistory.length;
221
- if (firstPendingId) {
222
- const foundIndex = hardenedAllHistory.findIndex((h) => h.id === firstPendingId);
223
- if (foundIndex !== -1) {
224
- splitIndex = foundIndex;
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
- this.lastRenderCache = {
245
- nodesHash: totalHash,
246
- result,
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