diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md
index 223f142e97..8040c34186 100644
--- a/docs/get-started/configuration.md
+++ b/docs/get-started/configuration.md
@@ -836,6 +836,11 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `"auto"`
- **Requires restart:** Yes
+- **`experimental.introspectionAgentSettings.enabled`** (boolean):
+ - **Description:** Enable the Introspection Agent.
+ - **Default:** `false`
+ - **Requires restart:** Yes
+
#### `hooks`
- **`hooks.disabled`** (array):
diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts
index 20bc8b670e..74f9fb3f49 100755
--- a/packages/cli/src/config/config.ts
+++ b/packages/cli/src/config/config.ts
@@ -679,6 +679,8 @@ export async function loadCliConfig(
enableMessageBusIntegration,
codebaseInvestigatorSettings:
settings.experimental?.codebaseInvestigatorSettings,
+ introspectionAgentSettings:
+ settings.experimental?.introspectionAgentSettings,
fakeResponses: argv.fakeResponses,
recordResponses: argv.recordResponses,
retryFetchErrors: settings.general?.retryFetchErrors ?? false,
diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts
index 821699b8e3..9145918a48 100644
--- a/packages/cli/src/config/settingsSchema.ts
+++ b/packages/cli/src/config/settingsSchema.ts
@@ -1403,6 +1403,26 @@ const SETTINGS_SCHEMA = {
},
},
},
+ introspectionAgentSettings: {
+ type: 'object',
+ label: 'Introspection Agent Settings',
+ category: 'Experimental',
+ requiresRestart: true,
+ default: {},
+ description: 'Configuration for Introspection Agent.',
+ showInDialog: false,
+ properties: {
+ enabled: {
+ type: 'boolean',
+ label: 'Enable Introspection Agent',
+ category: 'Experimental',
+ requiresRestart: true,
+ default: false,
+ description: 'Enable the Introspection Agent.',
+ showInDialog: true,
+ },
+ },
+ },
},
},
diff --git a/packages/core/src/agents/delegate-to-agent-tool.test.ts b/packages/core/src/agents/delegate-to-agent-tool.test.ts
index 009b6a0010..0075cb03a7 100644
--- a/packages/core/src/agents/delegate-to-agent-tool.test.ts
+++ b/packages/core/src/agents/delegate-to-agent-tool.test.ts
@@ -47,6 +47,7 @@ describe('DelegateToAgentTool', () => {
beforeEach(() => {
config = {
getDebugMode: () => false,
+ getActiveModel: () => 'test-model',
modelConfigService: {
registerRuntimeModelConfig: vi.fn(),
},
diff --git a/packages/core/src/agents/introspection-agent.test.ts b/packages/core/src/agents/introspection-agent.test.ts
new file mode 100644
index 0000000000..3e28659390
--- /dev/null
+++ b/packages/core/src/agents/introspection-agent.test.ts
@@ -0,0 +1,59 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { describe, it, expect } from 'vitest';
+import { IntrospectionAgent } from './introspection-agent.js';
+import { GetInternalDocsTool } from '../tools/get-internal-docs.js';
+import { GEMINI_MODEL_ALIAS_FLASH } from '../config/models.js';
+import type { LocalAgentDefinition } from './types.js';
+
+describe('IntrospectionAgent', () => {
+ const localAgent = IntrospectionAgent as LocalAgentDefinition;
+
+ it('should have the correct agent definition metadata', () => {
+ expect(localAgent.name).toBe('introspection_agent');
+ expect(localAgent.kind).toBe('local');
+ expect(localAgent.displayName).toBe('Introspection Agent');
+ expect(localAgent.description).toContain('Gemini CLI');
+ });
+
+ it('should have correctly configured inputs and outputs', () => {
+ expect(localAgent.inputConfig.inputs['question']).toBeDefined();
+ expect(localAgent.inputConfig.inputs['question'].required).toBe(true);
+
+ expect(localAgent.outputConfig?.outputName).toBe('report');
+ expect(localAgent.outputConfig?.description).toBeDefined();
+ });
+
+ it('should use the correct model and tools', () => {
+ expect(localAgent.modelConfig?.model).toBe(GEMINI_MODEL_ALIAS_FLASH);
+
+ const tools = localAgent.toolConfig?.tools || [];
+ const hasInternalDocsTool = tools.some(
+ (t) => t instanceof GetInternalDocsTool,
+ );
+ expect(hasInternalDocsTool).toBe(true);
+ });
+
+ it('should have expected prompt placeholders', () => {
+ const systemPrompt = localAgent.promptConfig.systemPrompt || '';
+ expect(systemPrompt).toContain('${cliVersion}');
+ expect(systemPrompt).toContain('${activeModel}');
+ expect(systemPrompt).toContain('${today}');
+
+ const query = localAgent.promptConfig.query || '';
+ expect(query).toContain('${question}');
+ });
+
+ it('should process output to a formatted JSON string', () => {
+ const mockOutput = {
+ answer: 'This is the answer.',
+ sources: ['file1.md', 'file2.md'],
+ };
+ const processed = localAgent.processOutput?.(mockOutput);
+ expect(processed).toBe(JSON.stringify(mockOutput, null, 2));
+ });
+});
diff --git a/packages/core/src/agents/introspection-agent.ts b/packages/core/src/agents/introspection-agent.ts
new file mode 100644
index 0000000000..413caa28a6
--- /dev/null
+++ b/packages/core/src/agents/introspection-agent.ts
@@ -0,0 +1,85 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type { AgentDefinition } from './types.js';
+import { GetInternalDocsTool } from '../tools/get-internal-docs.js';
+import { GEMINI_MODEL_ALIAS_FLASH } from '../config/models.js';
+import { z } from 'zod';
+
+const IntrospectionReportSchema = z.object({
+ answer: z
+ .string()
+ .describe('The detailed answer to the user question about Gemini CLI.'),
+ sources: z
+ .array(z.string())
+ .describe('The documentation files used to answer the question.'),
+});
+
+/**
+ * An agent specialized in answering questions about Gemini CLI itself,
+ * using its own documentation and runtime state.
+ */
+export const IntrospectionAgent: AgentDefinition<
+ typeof IntrospectionReportSchema
+> = {
+ name: 'introspection_agent',
+ kind: 'local',
+ displayName: 'Introspection Agent',
+ description:
+ 'Specialized in answering questions about yourself (Gemini CLI): features, documentation, and current runtime configuration.',
+ inputConfig: {
+ inputs: {
+ question: {
+ description: 'The specific question about Gemini CLI.',
+ type: 'string',
+ required: true,
+ },
+ },
+ },
+ outputConfig: {
+ outputName: 'report',
+ description: 'The final answer and sources as a JSON object.',
+ schema: IntrospectionReportSchema,
+ },
+
+ processOutput: (output) => JSON.stringify(output, null, 2),
+
+ modelConfig: {
+ model: GEMINI_MODEL_ALIAS_FLASH,
+ temp: 0.1,
+ top_p: 0.95,
+ thinkingBudget: -1,
+ },
+
+ runConfig: {
+ max_time_minutes: 3,
+ max_turns: 10,
+ },
+
+ toolConfig: {
+ tools: [new GetInternalDocsTool()],
+ },
+
+ promptConfig: {
+ query:
+ 'Your task is to answer the following question about Gemini CLI:\n' +
+ '\n' +
+ '${question}\n' +
+ '',
+ systemPrompt:
+ "You are **Introspection Agent**, an expert on Gemini CLI. Your purpose is to provide accurate information about Gemini CLI's features, configuration, and current state.\n\n" +
+ '### Runtime Context\n' +
+ '- **CLI Version:** ${cliVersion}\n' +
+ '- **Active Model:** ${activeModel}\n' +
+ "- **Today's Date:** ${today}\n\n" +
+ '### Instructions\n' +
+ "1. **Explore Documentation**: Use the `get_internal_docs` tool to find answers. If you don't know where to start, call `get_internal_docs()` without arguments to see the full list of available documentation files.\n" +
+ '2. **Be Precise**: Use the provided runtime context and documentation to give exact answers.\n' +
+ '3. **Cite Sources**: Always include the specific documentation files you used in your final report.\n' +
+ '4. **Non-Interactive**: You operate in a loop and cannot ask the user for more info. If the question is ambiguous, answer as best as you can with the information available.\n\n' +
+ 'You MUST call `complete_task` with a JSON report containing your `answer` and the `sources` you used.',
+ },
+};
diff --git a/packages/core/src/agents/local-executor.test.ts b/packages/core/src/agents/local-executor.test.ts
index aba89e0afe..1a25d8bd7a 100644
--- a/packages/core/src/agents/local-executor.test.ts
+++ b/packages/core/src/agents/local-executor.test.ts
@@ -100,6 +100,10 @@ vi.mock('../core/nonInteractiveToolExecutor.js', () => ({
executeToolCall: mockExecuteToolCall,
}));
+vi.mock('../utils/version.js', () => ({
+ getVersion: vi.fn().mockResolvedValue('1.2.3'),
+}));
+
vi.mock('../utils/environmentContext.js');
vi.mock('../telemetry/loggers.js', () => ({
diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts
index 6cb78c5f11..cadf384e69 100644
--- a/packages/core/src/agents/local-executor.ts
+++ b/packages/core/src/agents/local-executor.ts
@@ -44,6 +44,7 @@ import { type z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { debugLogger } from '../utils/debugLogger.js';
import { getModelConfigAlias } from './registry.js';
+import { getVersion } from '../utils/version.js';
import { ApprovalMode } from '../policy/types.js';
/** A callback function to report on agent activity. */
@@ -209,7 +210,6 @@ export class LocalAgentExecutor {
const { nextMessage, submittedOutput, taskCompleted } =
await this.processFunctionCalls(functionCalls, combinedSignal, promptId);
-
if (taskCompleted) {
const finalResult = submittedOutput ?? 'Task completed successfully.';
return {
@@ -373,10 +373,18 @@ export class LocalAgentExecutor {
let chat: GeminiChat | undefined;
let tools: FunctionDeclaration[] | undefined;
try {
+ // Inject standard runtime context into inputs
+ const augmentedInputs = {
+ ...inputs,
+ cliVersion: await getVersion(),
+ activeModel: this.runtimeContext.getActiveModel(),
+ today: new Date().toLocaleDateString(),
+ };
+
tools = this.prepareToolsList();
- chat = await this.createChatObject(inputs, tools);
+ chat = await this.createChatObject(augmentedInputs, tools);
const query = this.definition.promptConfig.query
- ? templateString(this.definition.promptConfig.query, inputs)
+ ? templateString(this.definition.promptConfig.query, augmentedInputs)
: 'Get Started!';
let currentMessage: Content = { role: 'user', parts: [{ text: query }] };
@@ -866,18 +874,12 @@ export class LocalAgentExecutor {
// Create a promise for the tool execution
const executionPromise = (async () => {
- // Force YOLO mode for subagents to prevent hanging on confirmation
- const contextProxy = new Proxy(this.runtimeContext, {
- get(target, prop, receiver) {
- if (prop === 'getApprovalMode') {
- return () => ApprovalMode.YOLO;
- }
- return Reflect.get(target, prop, receiver);
- },
- });
+ const agentContext = Object.create(this.runtimeContext);
+ agentContext.getToolRegistry = () => this.toolRegistry;
+ agentContext.getApprovalMode = () => ApprovalMode.YOLO;
const { response: toolResponse } = await executeToolCall(
- contextProxy,
+ agentContext,
requestInfo,
signal,
);
diff --git a/packages/core/src/agents/registry.test.ts b/packages/core/src/agents/registry.test.ts
index 022e1feb2c..3719d132e9 100644
--- a/packages/core/src/agents/registry.test.ts
+++ b/packages/core/src/agents/registry.test.ts
@@ -212,6 +212,28 @@ describe('AgentRegistry', () => {
vi.mocked(tomlLoader.loadAgentsFromDirectory),
).not.toHaveBeenCalled();
});
+
+ it('should register introspection agent if enabled', async () => {
+ const config = makeFakeConfig({
+ introspectionAgentSettings: { enabled: true },
+ });
+ const registry = new TestableAgentRegistry(config);
+
+ await registry.initialize();
+
+ expect(registry.getDefinition('introspection_agent')).toBeDefined();
+ });
+
+ it('should NOT register introspection agent if disabled', async () => {
+ const config = makeFakeConfig({
+ introspectionAgentSettings: { enabled: false },
+ });
+ const registry = new TestableAgentRegistry(config);
+
+ await registry.initialize();
+
+ expect(registry.getDefinition('introspection_agent')).toBeUndefined();
+ });
});
describe('registration logic', () => {
diff --git a/packages/core/src/agents/registry.ts b/packages/core/src/agents/registry.ts
index d42de545eb..bdbdb216d4 100644
--- a/packages/core/src/agents/registry.ts
+++ b/packages/core/src/agents/registry.ts
@@ -10,6 +10,7 @@ import type { Config } from '../config/config.js';
import type { AgentDefinition } from './types.js';
import { loadAgentsFromDirectory } from './toml-loader.js';
import { CodebaseInvestigatorAgent } from './codebase-investigator.js';
+import { IntrospectionAgent } from './introspection-agent.js';
import { type z } from 'zod';
import { debugLogger } from '../utils/debugLogger.js';
import {
@@ -98,6 +99,7 @@ export class AgentRegistry {
private loadBuiltInAgents(): void {
const investigatorSettings = this.config.getCodebaseInvestigatorSettings();
+ const introspectionSettings = this.config.getIntrospectionAgentSettings();
// Only register the agent if it's enabled in the settings.
if (investigatorSettings?.enabled) {
@@ -135,6 +137,11 @@ export class AgentRegistry {
};
this.registerAgent(agentDef);
}
+
+ // Register the introspection agent if it's explicitly enabled.
+ if (introspectionSettings.enabled) {
+ this.registerAgent(IntrospectionAgent);
+ }
}
private refreshAgents(): void {
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts
index 56bb2ac3c5..dc78a78966 100644
--- a/packages/core/src/config/config.ts
+++ b/packages/core/src/config/config.ts
@@ -134,6 +134,10 @@ export interface CodebaseInvestigatorSettings {
model?: string;
}
+export interface IntrospectionAgentSettings {
+ enabled?: boolean;
+}
+
/**
* All information required in CLI to handle an extension. Defined in Core so
* that the collection of loaded, active, and inactive extensions can be passed
@@ -307,6 +311,7 @@ export interface ConfigParameters {
enableMessageBusIntegration?: boolean;
disableModelRouterForAuth?: AuthType[];
codebaseInvestigatorSettings?: CodebaseInvestigatorSettings;
+ introspectionAgentSettings?: IntrospectionAgentSettings;
continueOnFailedApiCall?: boolean;
retryFetchErrors?: boolean;
enableShellOutputEfficiency?: boolean;
@@ -429,6 +434,7 @@ export class Config {
private readonly outputSettings: OutputSettings;
private readonly enableMessageBusIntegration: boolean;
private readonly codebaseInvestigatorSettings: CodebaseInvestigatorSettings;
+ private readonly introspectionAgentSettings: IntrospectionAgentSettings;
private readonly continueOnFailedApiCall: boolean;
private readonly retryFetchErrors: boolean;
private readonly enableShellOutputEfficiency: boolean;
@@ -578,6 +584,9 @@ export class Config {
DEFAULT_THINKING_MODE,
model: params.codebaseInvestigatorSettings?.model,
};
+ this.introspectionAgentSettings = {
+ enabled: params.introspectionAgentSettings?.enabled ?? false,
+ };
this.continueOnFailedApiCall = params.continueOnFailedApiCall ?? true;
this.enableShellOutputEfficiency =
params.enableShellOutputEfficiency ?? true;
@@ -1550,6 +1559,10 @@ export class Config {
return this.codebaseInvestigatorSettings;
}
+ getIntrospectionAgentSettings(): IntrospectionAgentSettings {
+ return this.introspectionAgentSettings;
+ }
+
async createToolRegistry(): Promise {
const registry = new ToolRegistry(this);
diff --git a/packages/core/src/tools/get-internal-docs.test.ts b/packages/core/src/tools/get-internal-docs.test.ts
new file mode 100644
index 0000000000..40a47b6477
--- /dev/null
+++ b/packages/core/src/tools/get-internal-docs.test.ts
@@ -0,0 +1,71 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { describe, it, expect, beforeEach } from 'vitest';
+import { GetInternalDocsTool } from './get-internal-docs.js';
+import { ToolErrorType } from './tool-error.js';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+
+describe('GetInternalDocsTool (Integration)', () => {
+ let tool: GetInternalDocsTool;
+ const abortSignal = new AbortController().signal;
+
+ beforeEach(() => {
+ tool = new GetInternalDocsTool();
+ });
+
+ it('should find the documentation root and list files', async () => {
+ const invocation = tool.build({});
+ const result = await invocation.execute(abortSignal);
+
+ expect(result.error).toBeUndefined();
+ // Verify we found some files
+ expect(result.returnDisplay).toMatch(/Found \d+ documentation files/);
+
+ // Check for a known file that should exist in the docs
+ // We assume 'index.md' or 'sidebar.json' exists in docs/
+ const content = result.llmContent as string;
+ expect(content).toContain('index.md');
+ });
+
+ it('should read a specific documentation file', async () => {
+ // Read the actual index.md from the real file system to compare
+ // We need to resolve the path relative to THIS test file to find the expected content
+ // Test file is in packages/core/src/tools/
+ // Docs are in docs/ (root)
+ const expectedDocsPath = path.resolve(
+ __dirname,
+ '../../../../docs/index.md',
+ );
+ const expectedContent = await fs.readFile(expectedDocsPath, 'utf8');
+
+ const invocation = tool.build({ path: 'index.md' });
+ const result = await invocation.execute(abortSignal);
+
+ expect(result.error).toBeUndefined();
+ expect(result.llmContent).toBe(expectedContent);
+ expect(result.returnDisplay).toContain('index.md');
+ });
+
+ it('should prevent access to files outside the docs directory (Path Traversal)', async () => {
+ // Attempt to read package.json from the root
+ const invocation = tool.build({ path: '../package.json' });
+ const result = await invocation.execute(abortSignal);
+
+ expect(result.error).toBeDefined();
+ expect(result.error?.type).toBe(ToolErrorType.EXECUTION_FAILED);
+ expect(result.error?.message).toContain('Access denied');
+ });
+
+ it('should handle non-existent files', async () => {
+ const invocation = tool.build({ path: 'this-file-does-not-exist.md' });
+ const result = await invocation.execute(abortSignal);
+
+ expect(result.error).toBeDefined();
+ expect(result.error?.type).toBe(ToolErrorType.EXECUTION_FAILED);
+ });
+});
diff --git a/packages/core/src/tools/get-internal-docs.ts b/packages/core/src/tools/get-internal-docs.ts
new file mode 100644
index 0000000000..aec44a5272
--- /dev/null
+++ b/packages/core/src/tools/get-internal-docs.ts
@@ -0,0 +1,187 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ BaseDeclarativeTool,
+ BaseToolInvocation,
+ Kind,
+ type ToolInvocation,
+ type ToolResult,
+ type ToolCallConfirmationDetails,
+} from './tools.js';
+import { GET_INTERNAL_DOCS_TOOL_NAME } from './tool-names.js';
+import type { MessageBus } from '../confirmation-bus/message-bus.js';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { glob } from 'glob';
+import { ToolErrorType } from './tool-error.js';
+
+/**
+ * Parameters for the GetInternalDocs tool.
+ */
+export interface GetInternalDocsParams {
+ /**
+ * The relative path to a specific documentation file (e.g., 'cli/commands.md').
+ * If omitted, the tool will return a list of all available documentation paths.
+ */
+ path?: string;
+}
+
+/**
+ * Helper to find the absolute path to the documentation directory.
+ */
+async function getDocsRoot(): Promise {
+ const currentFile = fileURLToPath(import.meta.url);
+ const currentDir = path.dirname(currentFile);
+
+ const isDocsDir = async (dir: string) => {
+ try {
+ const stats = await fs.stat(dir);
+ if (stats.isDirectory()) {
+ const marker = path.join(dir, 'sidebar.json');
+ await fs.access(marker);
+ return true;
+ }
+ } catch {
+ // Not a valid docs directory
+ }
+ return false;
+ };
+
+ // 1. Check for documentation in the distributed package (dist/docs)
+ // Path: dist/src/tools/get-internal-docs.js -> dist/docs
+ const distDocsPath = path.resolve(currentDir, '..', '..', 'docs');
+ if (await isDocsDir(distDocsPath)) {
+ return distDocsPath;
+ }
+
+ // 2. Check for documentation in the repository root (development)
+ // Path: packages/core/src/tools/get-internal-docs.ts -> docs/
+ const repoDocsPath = path.resolve(currentDir, '..', '..', '..', '..', 'docs');
+ if (await isDocsDir(repoDocsPath)) {
+ return repoDocsPath;
+ }
+
+ // 3. Check for documentation in the bundle directory (bundle/docs)
+ // Path: bundle/gemini.js -> bundle/docs
+ const bundleDocsPath = path.join(currentDir, 'docs');
+ if (await isDocsDir(bundleDocsPath)) {
+ return bundleDocsPath;
+ }
+
+ throw new Error('Could not find Gemini CLI documentation directory.');
+}
+
+class GetInternalDocsInvocation extends BaseToolInvocation<
+ GetInternalDocsParams,
+ ToolResult
+> {
+ constructor(params: GetInternalDocsParams, messageBus?: MessageBus) {
+ super(params, messageBus, GET_INTERNAL_DOCS_TOOL_NAME);
+ }
+
+ override async shouldConfirmExecute(
+ _abortSignal: AbortSignal,
+ ): Promise {
+ return false;
+ }
+
+ getDescription(): string {
+ if (this.params.path) {
+ return `Reading internal documentation: ${this.params.path}`;
+ }
+ return 'Listing all available internal documentation.';
+ }
+
+ async execute(_signal: AbortSignal): Promise {
+ try {
+ const docsRoot = await getDocsRoot();
+
+ if (!this.params.path) {
+ // List all .md files recursively
+ const files = await glob('**/*.md', { cwd: docsRoot, posix: true });
+ files.sort();
+
+ const fileList = files.map((f) => `- ${f}`).join('\n');
+ const resultContent = `Available Gemini CLI documentation files:\n\n${fileList}`;
+
+ return {
+ llmContent: resultContent,
+ returnDisplay: `Found ${files.length} documentation files.`,
+ };
+ }
+
+ // Read a specific file
+ // Security: Prevent path traversal by resolving and verifying it stays within docsRoot
+ const resolvedPath = path.resolve(docsRoot, this.params.path);
+ if (!resolvedPath.startsWith(docsRoot)) {
+ throw new Error(
+ 'Access denied: Requested path is outside the documentation directory.',
+ );
+ }
+
+ const content = await fs.readFile(resolvedPath, 'utf8');
+
+ return {
+ llmContent: content,
+ returnDisplay: `Successfully read documentation: ${this.params.path}`,
+ };
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ return {
+ llmContent: `Error accessing internal documentation: ${errorMessage}`,
+ returnDisplay: `Failed to access documentation: ${errorMessage}`,
+ error: {
+ message: errorMessage,
+ type: ToolErrorType.EXECUTION_FAILED,
+ },
+ };
+ }
+ }
+}
+
+/**
+ * A tool that provides access to Gemini CLI's internal documentation.
+ * If no path is provided, it returns a list of all available documentation files.
+ * If a path is provided, it returns the content of that specific file.
+ */
+export class GetInternalDocsTool extends BaseDeclarativeTool<
+ GetInternalDocsParams,
+ ToolResult
+> {
+ static readonly Name = GET_INTERNAL_DOCS_TOOL_NAME;
+
+ constructor(messageBus?: MessageBus) {
+ super(
+ GetInternalDocsTool.Name,
+ 'GetInternalDocs',
+ 'Returns the content of Gemini CLI internal documentation files. If no path is provided, returns a list of all available documentation paths.',
+ Kind.Think,
+ {
+ type: 'object',
+ properties: {
+ path: {
+ description:
+ "The relative path to the documentation file (e.g., 'cli/commands.md'). If omitted, lists all available documentation.",
+ type: 'string',
+ },
+ },
+ },
+ /* isOutputMarkdown */ true,
+ /* canUpdateOutput */ false,
+ messageBus,
+ );
+ }
+
+ protected createInvocation(
+ params: GetInternalDocsParams,
+ messageBus?: MessageBus,
+ ): ToolInvocation {
+ return new GetInternalDocsInvocation(params, messageBus);
+ }
+}
diff --git a/packages/core/src/tools/tool-names.ts b/packages/core/src/tools/tool-names.ts
index cec12de72c..db2967405e 100644
--- a/packages/core/src/tools/tool-names.ts
+++ b/packages/core/src/tools/tool-names.ts
@@ -20,6 +20,7 @@ export const READ_MANY_FILES_TOOL_NAME = 'read_many_files';
export const READ_FILE_TOOL_NAME = 'read_file';
export const LS_TOOL_NAME = 'list_directory';
export const MEMORY_TOOL_NAME = 'save_memory';
+export const GET_INTERNAL_DOCS_TOOL_NAME = 'get_internal_docs';
export const EDIT_TOOL_NAMES = new Set([EDIT_TOOL_NAME, WRITE_FILE_TOOL_NAME]);
export const DELEGATE_TO_AGENT_TOOL_NAME = 'delegate_to_agent';
diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json
index 258c8c0864..8e88a88119 100644
--- a/schemas/settings.schema.json
+++ b/schemas/settings.schema.json
@@ -1380,6 +1380,23 @@
}
},
"additionalProperties": false
+ },
+ "introspectionAgentSettings": {
+ "title": "Introspection Agent Settings",
+ "description": "Configuration for Introspection Agent.",
+ "markdownDescription": "Configuration for Introspection Agent.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `{}`",
+ "default": {},
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "title": "Enable Introspection Agent",
+ "description": "Enable the Introspection Agent.",
+ "markdownDescription": "Enable the Introspection Agent.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`",
+ "default": false,
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": false
}
},
"additionalProperties": false
diff --git a/scripts/build_package.js b/scripts/build_package.js
index 73f73861e9..c201333d2c 100644
--- a/scripts/build_package.js
+++ b/scripts/build_package.js
@@ -18,20 +18,32 @@
// limitations under the License.
import { execSync } from 'node:child_process';
-import { writeFileSync } from 'node:fs';
-import { join } from 'node:path';
+import { writeFileSync, existsSync, cpSync } from 'node:fs';
+import { join, basename } from 'node:path';
if (!process.cwd().includes('packages')) {
console.error('must be invoked from a package directory');
process.exit(1);
}
+const packageName = basename(process.cwd());
+
// build typescript files
execSync('tsc --build', { stdio: 'inherit' });
// copy .{md,json} files
execSync('node ../../scripts/copy_files.js', { stdio: 'inherit' });
+// Copy documentation for the core package
+if (packageName === 'core') {
+ const docsSource = join(process.cwd(), '..', '..', 'docs');
+ const docsTarget = join(process.cwd(), 'dist', 'docs');
+ if (existsSync(docsSource)) {
+ cpSync(docsSource, docsTarget, { recursive: true, dereference: true });
+ console.log('Copied documentation to dist/docs');
+ }
+}
+
// touch dist/.last_build
writeFileSync(join(process.cwd(), 'dist', '.last_build'), '');
process.exit(0);
diff --git a/scripts/copy_bundle_assets.js b/scripts/copy_bundle_assets.js
index 1dbca40296..afd2b35ea4 100644
--- a/scripts/copy_bundle_assets.js
+++ b/scripts/copy_bundle_assets.js
@@ -17,7 +17,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
+import { copyFileSync, existsSync, mkdirSync, cpSync } from 'node:fs';
import { dirname, join, basename } from 'node:path';
import { fileURLToPath } from 'node:url';
import { glob } from 'glob';
@@ -53,4 +53,13 @@ for (const file of policyFiles) {
}
console.log(`Copied ${policyFiles.length} policy files to bundle/policies/`);
+
+// 3. Copy Documentation (docs/)
+const docsSrc = join(root, 'docs');
+const docsDest = join(bundleDir, 'docs');
+if (existsSync(docsSrc)) {
+ cpSync(docsSrc, docsDest, { recursive: true, dereference: true });
+ console.log('Copied docs to bundle/docs/');
+}
+
console.log('Assets copied to bundle/');