diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index ebb56eb94d..23f4e36ac5 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -44,6 +44,7 @@ import { AskUserTool } from '../tools/ask-user.js'; import { UpdateTopicTool } from '../tools/topicTool.js'; import { TopicState } from './topicState.js'; import { AgentTool } from '../agents/agent-tool.js'; +import { WatcherTool } from '../tools/watcherTool.js'; import { ExitPlanModeTool } from '../tools/exit-plan-mode.js'; import { EnterPlanModeTool } from '../tools/enter-plan-mode.js'; import { @@ -3650,6 +3651,12 @@ export class Config implements McpContext, AgentLoopContext { registry.registerTool(new AgentTool(this, this.messageBus)), ); + if (this.isExperimentalWatcherEnabled()) { + maybeRegister(WatcherTool, () => + registry.registerTool(new WatcherTool(this, this.messageBus)), + ); + } + await registry.discoverAllTools(); registry.sortTools(); return registry; diff --git a/packages/core/src/tools/definitions/base-declarations.ts b/packages/core/src/tools/definitions/base-declarations.ts index 89a5aa1614..0da88514bf 100644 --- a/packages/core/src/tools/definitions/base-declarations.ts +++ b/packages/core/src/tools/definitions/base-declarations.ts @@ -137,3 +137,8 @@ export const TOPIC_PARAM_STRATEGIC_INTENT = 'strategic_intent'; // -- complete_task -- export const COMPLETE_TASK_TOOL_NAME = 'complete_task'; export const COMPLETE_TASK_DISPLAY_NAME = 'Complete Task'; + +// -- watcher -- +export const WATCHER_TOOL_NAME = 'watcher'; +export const WATCHER_DISPLAY_NAME = 'Watcher'; +export const WATCHER_PARAM_RECENT_HISTORY = 'recentHistory'; diff --git a/packages/core/src/tools/watcherTool.ts b/packages/core/src/tools/watcherTool.ts new file mode 100644 index 0000000000..b87679c45f --- /dev/null +++ b/packages/core/src/tools/watcherTool.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + BaseDeclarativeTool, + type ToolResult, + Kind, + type ToolInvocation, +} from './tools.js'; +import { type AgentLoopContext } from '../config/agent-loop-context.js'; +import type { MessageBus } from '../confirmation-bus/message-bus.js'; +import { + WATCHER_TOOL_NAME, + WATCHER_DISPLAY_NAME, +} from './definitions/base-declarations.js'; +import { LocalSubagentInvocation } from '../agents/local-invocation.js'; +import { WatcherAgent } from '../agents/watcher-agent.js'; +import type { AgentInputs } from '../agents/types.js'; + +/** + * A specialized tool for the internal Watcher agent loop. + * + * This tool wraps the Watcher sub-agent to allow it to be discovered and + * executed by the GeminiClient's internal monitoring loop. + */ +export class WatcherTool extends BaseDeclarativeTool< + AgentInputs, + ToolResult +> { + static readonly Name = WATCHER_TOOL_NAME; + + constructor( + private readonly context: AgentLoopContext, + messageBus: MessageBus, + ) { + const definition = WatcherAgent(context.config); + super( + WATCHER_TOOL_NAME, + WATCHER_DISPLAY_NAME, + definition.description, + Kind.Agent, + definition.inputConfig.inputSchema, + messageBus, + /* isOutputMarkdown */ true, + /* canUpdateOutput */ true, + ); + } + + protected createInvocation( + params: AgentInputs, + messageBus: MessageBus, + _toolName?: string, + _toolDisplayName?: string, + ): ToolInvocation { + const definition = WatcherAgent(this.context.config); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion + return new LocalSubagentInvocation( + definition as any, + this.context, + params, + messageBus, + _toolName, + _toolDisplayName, + ) as any; + } +}