feat(core): Introduce AgentLoopContext. (#21198)

This commit is contained in:
joshualitt
2026-03-09 09:02:20 -07:00
committed by GitHub
parent 7837194ab5
commit 96b939f63a
14 changed files with 196 additions and 66 deletions
@@ -0,0 +1,27 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { GeminiClient } from '../core/client.js';
import type { MessageBus } from '../confirmation-bus/message-bus.js';
import type { ToolRegistry } from '../tools/tool-registry.js';
/**
* AgentLoopContext represents the execution-scoped view of the world for a single
* agent turn or sub-agent loop.
*/
export interface AgentLoopContext {
/** The unique ID for the current user turn or agent thought loop. */
readonly promptId: string;
/** The registry of tools available to the agent in this context. */
readonly toolRegistry: ToolRegistry;
/** The bus for user confirmations and messages in this context. */
readonly messageBus: MessageBus;
/** The client used to communicate with the LLM in this context. */
readonly geminiClient: GeminiClient;
}
+74 -49
View File
@@ -96,6 +96,7 @@ import type {
import { ModelAvailabilityService } from '../availability/modelAvailabilityService.js';
import { ModelRouterService } from '../routing/modelRouterService.js';
import { OutputFormat } from '../output/types.js';
//import { type AgentLoopContext } from './agent-loop-context.js';
import {
ModelConfigService,
type ModelConfig,
@@ -154,6 +155,7 @@ import { CheckerRunner } from '../safety/checker-runner.js';
import { ContextBuilder } from '../safety/context-builder.js';
import { CheckerRegistry } from '../safety/registry.js';
import { ConsecaSafetyChecker } from '../safety/conseca/conseca.js';
import type { AgentLoopContext } from './agent-loop-context.js';
export interface AccessibilitySettings {
/** @deprecated Use ui.loadingPhrases instead. */
@@ -598,8 +600,8 @@ export interface ConfigParameters {
};
}
export class Config implements McpContext {
private toolRegistry!: ToolRegistry;
export class Config implements McpContext, AgentLoopContext {
private _toolRegistry!: ToolRegistry;
private mcpClientManager?: McpClientManager;
private allowedMcpServers: string[];
private blockedMcpServers: string[];
@@ -611,7 +613,7 @@ export class Config implements McpContext {
private agentRegistry!: AgentRegistry;
private readonly acknowledgedAgentsService: AcknowledgedAgentsService;
private skillManager!: SkillManager;
private sessionId: string;
private _sessionId: string;
private clientVersion: string;
private fileSystemService: FileSystemService;
private trackerService?: TrackerService;
@@ -645,7 +647,7 @@ export class Config implements McpContext {
private readonly accessibility: AccessibilitySettings;
private readonly telemetrySettings: TelemetrySettings;
private readonly usageStatisticsEnabled: boolean;
private geminiClient!: GeminiClient;
private _geminiClient!: GeminiClient;
private baseLlmClient!: BaseLlmClient;
private localLiteRtLmClient?: LocalLiteRtLmClient;
private modelRouterService: ModelRouterService;
@@ -740,7 +742,7 @@ export class Config implements McpContext {
private readonly fileExclusions: FileExclusions;
private readonly eventEmitter?: EventEmitter;
private readonly useWriteTodos: boolean;
private readonly messageBus: MessageBus;
private readonly _messageBus: MessageBus;
private readonly policyEngine: PolicyEngine;
private policyUpdateConfirmationRequest:
| PolicyUpdateConfirmationRequest
@@ -806,7 +808,7 @@ export class Config implements McpContext {
private approvedPlanPath: string | undefined;
constructor(params: ConfigParameters) {
this.sessionId = params.sessionId;
this._sessionId = params.sessionId;
this.clientVersion = params.clientVersion ?? 'unknown';
this.approvedPlanPath = undefined;
this.embeddingModel =
@@ -961,7 +963,7 @@ export class Config implements McpContext {
(params.shellToolInactivityTimeout ?? 300) * 1000; // 5 minutes
this.extensionManagement = params.extensionManagement ?? true;
this.enableExtensionReloading = params.enableExtensionReloading ?? false;
this.storage = new Storage(this.targetDir, this.sessionId);
this.storage = new Storage(this.targetDir, this._sessionId);
this.storage.setCustomPlansDir(params.planSettings?.directory);
this.fakeResponses = params.fakeResponses;
@@ -997,7 +999,7 @@ export class Config implements McpContext {
ConsecaSafetyChecker.getInstance().setConfig(this);
}
this.messageBus = new MessageBus(this.policyEngine, this.debugMode);
this._messageBus = new MessageBus(this.policyEngine, this.debugMode);
this.acknowledgedAgentsService = new AcknowledgedAgentsService();
this.skillManager = new SkillManager();
this.outputSettings = {
@@ -1057,7 +1059,7 @@ export class Config implements McpContext {
);
}
}
this.geminiClient = new GeminiClient(this);
this._geminiClient = new GeminiClient(this);
this.modelRouterService = new ModelRouterService(this);
// HACK: The settings loading logic doesn't currently merge the default
@@ -1142,11 +1144,11 @@ export class Config implements McpContext {
coreEvents.on(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
this.toolRegistry = await this.createToolRegistry();
this._toolRegistry = await this.createToolRegistry();
discoverToolsHandle?.end();
this.mcpClientManager = new McpClientManager(
this.clientVersion,
this.toolRegistry,
this._toolRegistry,
this,
this.eventEmitter,
);
@@ -1181,7 +1183,7 @@ export class Config implements McpContext {
if (this.getSkillManager().getSkills().length > 0) {
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
this.getToolRegistry().registerTool(
new ActivateSkillTool(this, this.messageBus),
new ActivateSkillTool(this, this._messageBus),
);
}
}
@@ -1198,7 +1200,7 @@ export class Config implements McpContext {
await this.contextManager.refresh();
}
await this.geminiClient.initialize();
await this._geminiClient.initialize();
this.initialized = true;
}
@@ -1222,7 +1224,7 @@ export class Config implements McpContext {
authMethod !== AuthType.USE_GEMINI
) {
// Restore the conversation history to the new client
this.geminiClient.stripThoughtsFromHistory();
this._geminiClient.stripThoughtsFromHistory();
}
// Reset availability status when switching auth (e.g. from limited key to OAuth)
@@ -1343,12 +1345,28 @@ export class Config implements McpContext {
return this.localLiteRtLmClient;
}
get promptId(): string {
return this._sessionId;
}
get toolRegistry(): ToolRegistry {
return this._toolRegistry;
}
get messageBus(): MessageBus {
return this._messageBus;
}
get geminiClient(): GeminiClient {
return this._geminiClient;
}
getSessionId(): string {
return this.sessionId;
return this.promptId;
}
setSessionId(sessionId: string): void {
this.sessionId = sessionId;
this._sessionId = sessionId;
}
setTerminalBackground(terminalBackground: string | undefined): void {
@@ -1613,6 +1631,7 @@ export class Config implements McpContext {
return this.acknowledgedAgentsService;
}
/** @deprecated Use toolRegistry getter */
getToolRegistry(): ToolRegistry {
return this.toolRegistry;
}
@@ -1889,9 +1908,9 @@ export class Config implements McpContext {
);
await refreshServerHierarchicalMemory(this);
}
if (this.geminiClient?.isInitialized()) {
await this.geminiClient.setTools();
this.geminiClient.updateSystemInstruction();
if (this._geminiClient?.isInitialized()) {
await this._geminiClient.setTools();
this._geminiClient.updateSystemInstruction();
}
}
@@ -2045,8 +2064,8 @@ export class Config implements McpContext {
(currentMode === ApprovalMode.YOLO || mode === ApprovalMode.YOLO);
if (isPlanModeTransition || isYoloModeTransition) {
if (this.geminiClient?.isInitialized()) {
this.geminiClient.setTools().catch((err) => {
if (this._geminiClient?.isInitialized()) {
this._geminiClient.setTools().catch((err) => {
debugLogger.error('Failed to update tools', err);
});
}
@@ -2142,6 +2161,7 @@ export class Config implements McpContext {
return this.telemetrySettings.useCliAuth ?? false;
}
/** @deprecated Use geminiClient getter */
getGeminiClient(): GeminiClient {
return this.geminiClient;
}
@@ -2577,7 +2597,7 @@ export class Config implements McpContext {
if (this.getSkillManager().getSkills().length > 0) {
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
this.getToolRegistry().registerTool(
new ActivateSkillTool(this, this.messageBus),
new ActivateSkillTool(this, this._messageBus),
);
} else {
this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
@@ -2703,6 +2723,7 @@ export class Config implements McpContext {
return this.fileExclusions;
}
/** @deprecated Use messageBus getter */
getMessageBus(): MessageBus {
return this.messageBus;
}
@@ -2760,7 +2781,7 @@ export class Config implements McpContext {
}
async createToolRegistry(): Promise<ToolRegistry> {
const registry = new ToolRegistry(this, this.messageBus);
const registry = new ToolRegistry(this, this._messageBus);
// helper to create & register core tools that are enabled
const maybeRegister = (
@@ -2790,10 +2811,10 @@ export class Config implements McpContext {
};
maybeRegister(LSTool, () =>
registry.registerTool(new LSTool(this, this.messageBus)),
registry.registerTool(new LSTool(this, this._messageBus)),
);
maybeRegister(ReadFileTool, () =>
registry.registerTool(new ReadFileTool(this, this.messageBus)),
registry.registerTool(new ReadFileTool(this, this._messageBus)),
);
if (this.getUseRipgrep()) {
@@ -2806,81 +2827,85 @@ export class Config implements McpContext {
}
if (useRipgrep) {
maybeRegister(RipGrepTool, () =>
registry.registerTool(new RipGrepTool(this, this.messageBus)),
registry.registerTool(new RipGrepTool(this, this._messageBus)),
);
} else {
logRipgrepFallback(this, new RipgrepFallbackEvent(errorString));
maybeRegister(GrepTool, () =>
registry.registerTool(new GrepTool(this, this.messageBus)),
registry.registerTool(new GrepTool(this, this._messageBus)),
);
}
} else {
maybeRegister(GrepTool, () =>
registry.registerTool(new GrepTool(this, this.messageBus)),
registry.registerTool(new GrepTool(this, this._messageBus)),
);
}
maybeRegister(GlobTool, () =>
registry.registerTool(new GlobTool(this, this.messageBus)),
registry.registerTool(new GlobTool(this, this._messageBus)),
);
maybeRegister(ActivateSkillTool, () =>
registry.registerTool(new ActivateSkillTool(this, this.messageBus)),
registry.registerTool(new ActivateSkillTool(this, this._messageBus)),
);
maybeRegister(EditTool, () =>
registry.registerTool(new EditTool(this, this.messageBus)),
registry.registerTool(new EditTool(this, this._messageBus)),
);
maybeRegister(WriteFileTool, () =>
registry.registerTool(new WriteFileTool(this, this.messageBus)),
registry.registerTool(new WriteFileTool(this, this._messageBus)),
);
maybeRegister(WebFetchTool, () =>
registry.registerTool(new WebFetchTool(this, this.messageBus)),
registry.registerTool(new WebFetchTool(this, this._messageBus)),
);
maybeRegister(ShellTool, () =>
registry.registerTool(new ShellTool(this, this.messageBus)),
registry.registerTool(new ShellTool(this, this._messageBus)),
);
maybeRegister(MemoryTool, () =>
registry.registerTool(new MemoryTool(this.messageBus)),
registry.registerTool(new MemoryTool(this._messageBus)),
);
maybeRegister(WebSearchTool, () =>
registry.registerTool(new WebSearchTool(this, this.messageBus)),
registry.registerTool(new WebSearchTool(this, this._messageBus)),
);
maybeRegister(AskUserTool, () =>
registry.registerTool(new AskUserTool(this.messageBus)),
registry.registerTool(new AskUserTool(this._messageBus)),
);
if (this.getUseWriteTodos()) {
maybeRegister(WriteTodosTool, () =>
registry.registerTool(new WriteTodosTool(this.messageBus)),
registry.registerTool(new WriteTodosTool(this._messageBus)),
);
}
if (this.isPlanEnabled()) {
maybeRegister(ExitPlanModeTool, () =>
registry.registerTool(new ExitPlanModeTool(this, this.messageBus)),
registry.registerTool(new ExitPlanModeTool(this, this._messageBus)),
);
maybeRegister(EnterPlanModeTool, () =>
registry.registerTool(new EnterPlanModeTool(this, this.messageBus)),
registry.registerTool(new EnterPlanModeTool(this, this._messageBus)),
);
}
if (this.isTrackerEnabled()) {
maybeRegister(TrackerCreateTaskTool, () =>
registry.registerTool(new TrackerCreateTaskTool(this, this.messageBus)),
registry.registerTool(
new TrackerCreateTaskTool(this, this._messageBus),
),
);
maybeRegister(TrackerUpdateTaskTool, () =>
registry.registerTool(new TrackerUpdateTaskTool(this, this.messageBus)),
registry.registerTool(
new TrackerUpdateTaskTool(this, this._messageBus),
),
);
maybeRegister(TrackerGetTaskTool, () =>
registry.registerTool(new TrackerGetTaskTool(this, this.messageBus)),
registry.registerTool(new TrackerGetTaskTool(this, this._messageBus)),
);
maybeRegister(TrackerListTasksTool, () =>
registry.registerTool(new TrackerListTasksTool(this, this.messageBus)),
registry.registerTool(new TrackerListTasksTool(this, this._messageBus)),
);
maybeRegister(TrackerAddDependencyTool, () =>
registry.registerTool(
new TrackerAddDependencyTool(this, this.messageBus),
new TrackerAddDependencyTool(this, this._messageBus),
),
);
maybeRegister(TrackerVisualizeTool, () =>
registry.registerTool(new TrackerVisualizeTool(this, this.messageBus)),
registry.registerTool(new TrackerVisualizeTool(this, this._messageBus)),
);
}
@@ -3007,8 +3032,8 @@ export class Config implements McpContext {
}
private onAgentsRefreshed = async () => {
if (this.toolRegistry) {
this.registerSubAgentTools(this.toolRegistry);
if (this._toolRegistry) {
this.registerSubAgentTools(this._toolRegistry);
}
// Propagate updates to the active chat session
const client = this.getGeminiClient();
@@ -3029,7 +3054,7 @@ export class Config implements McpContext {
this.logCurrentModeDuration(this.getApprovalMode());
coreEvents.off(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
this.agentRegistry?.dispose();
this.geminiClient?.dispose();
this._geminiClient?.dispose();
if (this.mcpClientManager) {
await this.mcpClientManager.stop();
}