chore(context): remove dead initialization trigger and unawaited promise

This commit is contained in:
Your Name
2026-05-13 00:54:24 +00:00
parent cdd482c2e0
commit 78a0e5d457
9 changed files with 309 additions and 4 deletions
+149
View File
@@ -0,0 +1,149 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as fs from 'node:fs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe('Context Management Resume E2E', () => {
let rig: TestRig;
beforeEach(() => {
rig = new TestRig();
});
afterEach(async () => await rig.cleanup());
it('should preserve and utilize GC snapshot boundaries when resuming a session', async () => {
const snapshotResponse = {
method: 'generateContent',
response: {
candidates: [
{
content: {
parts: [
{
text: JSON.stringify({
new_facts: ['GC Triggered.'],
new_constraints: [],
new_tasks: [],
resolved_task_ids: [],
obsolete_fact_indices: [],
obsolete_constraint_indices: [],
chronological_summary: 'Snapshot created.',
}),
},
],
role: 'model',
},
finishReason: 'STOP',
index: 0,
},
],
},
};
const countTokensResponse = {
method: 'countTokens',
response: { totalTokens: 50000 },
};
const streamResponse = (text: string) => ({
method: 'generateContentStream',
response: [
{
candidates: [
{
content: { parts: [{ text }], role: 'model' },
finishReason: 'STOP',
index: 0,
},
],
},
],
});
const setupResponses = (fileName: string, mocks: any[]) => {
const filePath = path.join(rig.testDir!, fileName);
fs.writeFileSync(
filePath,
mocks.map((m) => JSON.stringify(m)).join('\n'),
);
return filePath;
};
await rig.setup('resume-gc-snapshot', {
settings: {
experimental: {
stressTestProfile: true,
},
},
});
const massivePayload = 'X'.repeat(40000);
const logFile = path.join(rig.testDir!, 'debug.log');
const traceDir = path.join(rig.testDir!, 'traces');
fs.mkdirSync(traceDir, { recursive: true });
const traceLog = path.join(traceDir, 'trace.log');
const commonEnv = {
GEMINI_API_KEY: 'mock-key',
GEMINI_DEBUG_LOG_FILE: logFile,
GEMINI_CONTEXT_TRACE_DIR: traceDir,
};
// Provide a massive pool of responses to prevent exhaustion
const runMocks = [streamResponse('Acknowledged block.')];
for (let i = 0; i < 50; i++) {
runMocks.push(snapshotResponse);
runMocks.push(countTokensResponse);
}
console.log('=== STARTING RUN 1 ===');
await rig.run({
args: [
'--debug',
'--fake-responses-non-strict',
setupResponses('resp1.json', runMocks),
'Turn 1: ' + massivePayload,
],
env: commonEnv,
});
console.log('=== STARTING RUN 2 ===');
await rig.run({
args: [
'--debug',
'--resume',
'latest',
'--fake-responses-non-strict',
setupResponses('resp2.json', runMocks),
'Turn 2: ' + massivePayload,
],
env: commonEnv,
});
console.log('=== STARTING RUN 3 ===');
const result3 = await rig.run({
args: [
'--debug',
'--resume',
'latest',
'--fake-responses-non-strict',
setupResponses('resp3.json', runMocks),
'continue',
],
env: commonEnv,
});
expect(result3).toContain('Acknowledged block');
const traces = fs.readFileSync(traceLog, 'utf-8');
expect(traces).toContain('Hitting Synchronous Pressure Barrier');
console.log('GC Trigger Verification: SUCCESS');
expect(traces).toContain('GC Triggered.');
console.log('Snapshot Utilization Verification: SUCCESS');
});
});
+8
View File
@@ -103,6 +103,7 @@ export interface CliArgs {
useWriteTodos: boolean | undefined;
outputFormat: string | undefined;
fakeResponses: string | undefined;
fakeResponsesNonStrict?: string | undefined;
recordResponses: string | undefined;
startupMessages?: string[];
rawOutput: boolean | undefined;
@@ -474,6 +475,12 @@ export async function parseArguments(
description: 'Path to a file with fake model responses for testing.',
hidden: true,
})
.option('fake-responses-non-strict', {
type: 'string',
description:
'Path to a file with fake model responses for testing (non-strict mode).',
hidden: true,
})
.option('record-responses', {
type: 'string',
description: 'Path to a file to record model responses for testing.',
@@ -1074,6 +1081,7 @@ export async function loadCliConfig(
gemmaModelRouter: settings.experimental?.gemmaModelRouter,
adk: settings.experimental?.adk,
fakeResponses: argv.fakeResponses,
fakeResponsesNonStrict: argv.fakeResponsesNonStrict,
recordResponses: argv.recordResponses,
retryFetchErrors: settings.general?.retryFetchErrors,
billing: settings.billing,
+3
View File
@@ -688,6 +688,7 @@ export interface ConfigParameters {
enableShellOutputEfficiency?: boolean;
shellToolInactivityTimeout?: number;
fakeResponses?: string;
fakeResponsesNonStrict?: string;
recordResponses?: string;
ptyInfo?: string;
disableYoloMode?: boolean;
@@ -919,6 +920,7 @@ export class Config implements McpContext, AgentLoopContext {
private readonly enableShellOutputEfficiency: boolean;
private readonly shellToolInactivityTimeout: number;
readonly fakeResponses?: string;
readonly fakeResponsesNonStrict?: string;
readonly recordResponses?: string;
private readonly disableYoloMode: boolean;
private readonly disableAlwaysAllow: boolean;
@@ -1299,6 +1301,7 @@ export class Config implements McpContext, AgentLoopContext {
this.storage.setCustomPlansDir(params.planSettings?.directory);
this.fakeResponses = params.fakeResponses;
this.fakeResponsesNonStrict = params.fakeResponsesNonStrict;
this.recordResponses = params.recordResponses;
this.fileExclusions = new FileExclusions(this);
this.eventEmitter = params.eventEmitter;
@@ -7,7 +7,6 @@
import type { ContextProcessor, AsyncContextProcessor } from '../pipeline.js';
export type PipelineTrigger =
| 'initialization'
| 'new_message'
| 'retained_exceeded'
| 'gc_backstop'
+1 -1
View File
@@ -437,7 +437,7 @@ export class ContextManager {
return SnapshotStateHelper.exportState(this.buffer.nodes);
}
async restoreState(state: ContextEngineState): Promise<void> {
restoreState(state: ContextEngineState): void {
if (!state) return;
SnapshotStateHelper.restoreState(state, this.env.inbox);
}
@@ -90,6 +90,9 @@ export function createStateSnapshotProcessor(
const isValid = consumedIds.every((id) => targetIds.has(id));
if (isValid) {
debugLogger.log(
`[StateSnapshotProcessor] Successfully spliced PROPOSED_SNAPSHOT from Inbox into Graph. Consumed ${consumedIds.length} nodes.`,
);
// If valid, apply it!
const newId = randomUUID();
@@ -120,6 +123,10 @@ export function createStateSnapshotProcessor(
inbox.consume(proposed.id);
return returnedNodes;
} else {
debugLogger.log(
`[StateSnapshotProcessor] Rejected PROPOSED_SNAPSHOT from Inbox because one or more target IDs were missing from the current graph window.`,
);
}
}
}
@@ -82,12 +82,21 @@ export function findLatestSnapshotBaseline(
import type { LiveInbox } from '../pipeline/inbox.js';
import type { ContextEngineState } from '../../services/chatRecordingTypes.js';
import { debugLogger } from '../../utils/debugLogger.js';
export const SnapshotStateHelper = {
exportState(nodes: readonly ConcreteNode[]): ContextEngineState {
const baseline = findLatestSnapshotBaseline(nodes);
if (!baseline) return {};
if (!baseline) {
debugLogger.log(
'[SnapshotStateHelper] exportState: No snapshot baseline found in current nodes.',
);
return {};
}
debugLogger.log(
`[SnapshotStateHelper] exportState: Exporting snapshot ID ${baseline.id} representing ${baseline.abstractsIds.length} consumed nodes.`,
);
return {
snapshot: {
text: baseline.text,
@@ -98,18 +107,30 @@ export const SnapshotStateHelper = {
},
restoreState(state: ContextEngineState, inbox: LiveInbox): void {
if (!state.snapshot) return;
if (!state.snapshot) {
debugLogger.log(
'[SnapshotStateHelper] restoreState: No snapshot found in provided ContextEngineState.',
);
return;
}
if (
typeof state.snapshot.text === 'string' &&
Array.isArray(state.snapshot.consumedIds)
) {
debugLogger.log(
`[SnapshotStateHelper] restoreState: Publishing hydrated snapshot to LiveInbox with ${state.snapshot.consumedIds.length} consumed IDs.`,
);
inbox.publish('PROPOSED_SNAPSHOT', {
newText: state.snapshot.text,
consumedIds: state.snapshot.consumedIds,
type: 'accumulate',
timestamp: state.snapshot.timestamp ?? Date.now(),
});
} else {
debugLogger.log(
'[SnapshotStateHelper] restoreState: Invalid snapshot structural format.',
);
}
},
};
@@ -23,6 +23,7 @@ import type { UserTierId, GeminiUserTier } from '../code_assist/types.js';
import { LoggingContentGenerator } from './loggingContentGenerator.js';
import { InstallationManager } from '../utils/installationManager.js';
import { FakeContentGenerator } from './fakeContentGenerator.js';
import { NonStrictFakeContentGenerator } from './nonStrictFakeContentGenerator.js';
import { parseCustomHeaders } from '../utils/customHeaderUtils.js';
import { determineSurface } from '../utils/surface.js';
import { RecordingContentGenerator } from './recordingContentGenerator.js';
@@ -193,6 +194,12 @@ export async function createContentGenerator(
sessionId?: string,
): Promise<ContentGenerator> {
const generator = await (async () => {
if (gcConfig.fakeResponsesNonStrict) {
const fakeGenerator = await NonStrictFakeContentGenerator.fromFile(
gcConfig.fakeResponsesNonStrict,
);
return new LoggingContentGenerator(fakeGenerator, gcConfig);
}
if (gcConfig.fakeResponses) {
const fakeGenerator = await FakeContentGenerator.fromFile(
gcConfig.fakeResponses,
@@ -0,0 +1,111 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
GenerateContentResponse,
type CountTokensResponse,
type GenerateContentParameters,
type CountTokensParameters,
EmbedContentResponse,
type EmbedContentParameters,
} from '@google/genai';
import { promises } from 'node:fs';
import type { ContentGenerator } from './contentGenerator.js';
import type { UserTierId, GeminiUserTier } from '../code_assist/types.js';
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
import type { LlmRole } from '../telemetry/types.js';
import type { FakeResponse } from './fakeContentGenerator.js';
/**
* A ContentGenerator that responds with canned responses, but unlike FakeContentGenerator,
* it is "non-strict": it will find and use the first available response that matches
* the requested method, rather than strictly following the input order.
*
* This is useful for testing asynchronous or non-deterministic background tasks
* (like token calibration or background snapshots) that might fire out-of-order.
*/
export class NonStrictFakeContentGenerator implements ContentGenerator {
userTier?: UserTierId;
userTierName?: string;
paidTier?: GeminiUserTier;
constructor(private readonly responses: FakeResponse[]) {}
static async fromFile(
filePath: string,
): Promise<NonStrictFakeContentGenerator> {
const fileContent = await promises.readFile(filePath, 'utf-8');
const responses = fileContent
.split('\n')
.filter((line) => line.trim() !== '')
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
.map((line) => JSON.parse(line) as FakeResponse);
return new NonStrictFakeContentGenerator(responses);
}
private getNextResponse<
M extends FakeResponse['method'],
R = Extract<FakeResponse, { method: M }>['response'],
>(method: M, request: unknown): R {
const index = this.responses.findIndex((r) => r.method === method);
if (index === -1) {
throw new Error(
`No more mock responses for ${method}, got request:\n` +
safeJsonStringify(request),
);
}
const response = this.responses.splice(index, 1)[0];
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return response.response as R;
}
async generateContent(
request: GenerateContentParameters,
_userPromptId: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
role: LlmRole,
): Promise<GenerateContentResponse> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return Object.setPrototypeOf(
this.getNextResponse('generateContent', request),
GenerateContentResponse.prototype,
);
}
async generateContentStream(
request: GenerateContentParameters,
_userPromptId: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
role: LlmRole,
): Promise<AsyncGenerator<GenerateContentResponse>> {
const responses = this.getNextResponse('generateContentStream', request);
async function* stream() {
for (const response of responses) {
yield Object.setPrototypeOf(
response,
GenerateContentResponse.prototype,
);
}
}
return stream();
}
async countTokens(
request: CountTokensParameters,
): Promise<CountTokensResponse> {
return this.getNextResponse('countTokens', request);
}
async embedContent(
request: EmbedContentParameters,
): Promise<EmbedContentResponse> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return Object.setPrototypeOf(
this.getNextResponse('embedContent', request),
EmbedContentResponse.prototype,
);
}
}