mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-03 13:41:05 -07:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c31205cad0 | |||
| b3f8c90972 | |||
| b3f4fd2515 | |||
| 938d53eeb3 | |||
| 6b47b0a8f8 |
@@ -2,7 +2,8 @@
|
||||
"experimental": {
|
||||
"plan": true,
|
||||
"extensionReloading": true,
|
||||
"modelSteering": true
|
||||
"modelSteering": true,
|
||||
"reflection": true
|
||||
},
|
||||
"general": {
|
||||
"devtools": true
|
||||
|
||||
@@ -776,6 +776,7 @@ export async function loadCliConfig(
|
||||
skillsSupport: settings.skills?.enabled ?? true,
|
||||
disabledSkills: settings.skills?.disabled,
|
||||
experimentalJitContext: settings.experimental?.jitContext,
|
||||
experimentalReflection: settings.experimental?.reflection,
|
||||
modelSteering: settings.experimental?.modelSteering,
|
||||
toolOutputMasking: settings.experimental?.toolOutputMasking,
|
||||
noBrowser: !!process.env['NO_BROWSER'],
|
||||
|
||||
@@ -1798,6 +1798,16 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Enable Just-In-Time (JIT) context loading.',
|
||||
showInDialog: false,
|
||||
},
|
||||
reflection: {
|
||||
type: 'boolean',
|
||||
label: 'Continuous Learning (Reflection)',
|
||||
category: 'Experimental',
|
||||
requiresRestart: false,
|
||||
default: false,
|
||||
description:
|
||||
'Enable the agent to periodically reflect on the session and propose new skills or memories.',
|
||||
showInDialog: true,
|
||||
},
|
||||
useOSC52Paste: {
|
||||
type: 'boolean',
|
||||
label: 'Use OSC 52 Paste',
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
} from '../scheduler/types.js';
|
||||
import type { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import type { EditorType } from '../utils/editor.js';
|
||||
import { PolicyDecision } from '../policy/types.js';
|
||||
|
||||
/**
|
||||
* Options for scheduling agent tools.
|
||||
@@ -29,6 +30,8 @@ export interface AgentSchedulingOptions {
|
||||
getPreferredEditor?: () => EditorType | undefined;
|
||||
/** Optional function to be notified when the scheduler is waiting for user confirmation. */
|
||||
onWaitingForConfirmation?: (waiting: boolean) => void;
|
||||
/** Optional list of tools to automatically approve for this agent. */
|
||||
allowedTools?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +54,7 @@ export async function scheduleAgentTools(
|
||||
signal,
|
||||
getPreferredEditor,
|
||||
onWaitingForConfirmation,
|
||||
allowedTools,
|
||||
} = options;
|
||||
|
||||
// Create a proxy/override of the config to provide the agent-specific tool registry.
|
||||
@@ -59,6 +63,38 @@ export async function scheduleAgentTools(
|
||||
agentConfig.getToolRegistry = () => toolRegistry;
|
||||
agentConfig.getMessageBus = () => toolRegistry.getMessageBus();
|
||||
|
||||
if (allowedTools && allowedTools.length > 0) {
|
||||
const existingAllowed = config.getAllowedTools() || [];
|
||||
const mergedAllowed = Array.from(
|
||||
new Set([...existingAllowed, ...allowedTools]),
|
||||
);
|
||||
agentConfig.getAllowedTools = () => mergedAllowed;
|
||||
|
||||
const originalPolicyEngine = config.getPolicyEngine();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const proxyPolicyEngine = Object.create(originalPolicyEngine);
|
||||
proxyPolicyEngine.check = async (
|
||||
toolCall: { name: string; args?: Record<string, unknown> },
|
||||
serverName?: string,
|
||||
toolAnnotations?: Record<string, unknown>,
|
||||
) => {
|
||||
if (allowedTools.includes(toolCall.name)) {
|
||||
return {
|
||||
decision: PolicyDecision.ALLOW,
|
||||
rule: {
|
||||
toolName: toolCall.name,
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: 999,
|
||||
source: 'Agent Allowed Tools',
|
||||
},
|
||||
};
|
||||
}
|
||||
return originalPolicyEngine.check(toolCall, serverName, toolAnnotations);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
agentConfig.getPolicyEngine = () => proxyPolicyEngine;
|
||||
}
|
||||
|
||||
const scheduler = new Scheduler({
|
||||
config: agentConfig,
|
||||
messageBus: toolRegistry.getMessageBus(),
|
||||
|
||||
@@ -880,10 +880,6 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
taskCompleted: boolean;
|
||||
aborted: boolean;
|
||||
}> {
|
||||
const allowedToolNames = new Set(this.toolRegistry.getAllToolNames());
|
||||
// Always allow the completion tool
|
||||
allowedToolNames.add(TASK_COMPLETE_TOOL_NAME);
|
||||
|
||||
let submittedOutput: string | null = null;
|
||||
let taskCompleted = false;
|
||||
let aborted = false;
|
||||
@@ -1057,7 +1053,7 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
}
|
||||
|
||||
// Handle standard tools
|
||||
if (!allowedToolNames.has(toolName)) {
|
||||
if (!this.toolRegistry.getTool(toolName)) {
|
||||
const error = createUnauthorizedToolError(toolName);
|
||||
debugLogger.warn(`[LocalAgentExecutor] Blocked call: ${error}`);
|
||||
|
||||
@@ -1100,6 +1096,7 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
toolRegistry: this.toolRegistry,
|
||||
signal,
|
||||
onWaitingForConfirmation,
|
||||
allowedTools: this.definition.toolConfig?.allowedTools,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { LocalAgentDefinition } from './types.js';
|
||||
import {
|
||||
GLOB_TOOL_NAME,
|
||||
GREP_TOOL_NAME,
|
||||
LS_TOOL_NAME,
|
||||
READ_FILE_TOOL_NAME,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
EDIT_TOOL_NAME,
|
||||
GET_SESSION_HISTORY_TOOL_NAME,
|
||||
} from '../tools/tool-names.js';
|
||||
import { DEFAULT_GEMINI_MODEL } from '../config/models.js';
|
||||
import { z } from 'zod';
|
||||
import type { Config } from '../config/config.js';
|
||||
|
||||
// Define a type that matches the outputConfig schema for type safety.
|
||||
const ReflectAgentReportSchema = z.object({
|
||||
SummaryOfFindings: z
|
||||
.string()
|
||||
.describe(
|
||||
'A summary of what was learned during reflection, including any new skills or memories created.',
|
||||
),
|
||||
CreatedSkills: z
|
||||
.array(z.string())
|
||||
.describe('A list of skill files created or updated.'),
|
||||
AddedMemories: z
|
||||
.array(z.string())
|
||||
.describe('A list of global memories added to GEMINI.md.'),
|
||||
});
|
||||
|
||||
/**
|
||||
* A subagent specialized in reflecting on session history and preserving
|
||||
* reusable knowledge as skills or memories.
|
||||
*/
|
||||
export const ReflectAgent = (
|
||||
_config: Config,
|
||||
): LocalAgentDefinition<typeof ReflectAgentReportSchema> => ({
|
||||
name: 'reflect_agent',
|
||||
kind: 'local',
|
||||
displayName: 'Reflect Agent',
|
||||
description: `A specialized agent that reads the current chat history, identifies reusable knowledge or workflows, and saves them as skills or memories.`,
|
||||
inputConfig: {
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
outputConfig: {
|
||||
outputName: 'report',
|
||||
description: 'The final reflection report as a JSON object.',
|
||||
schema: ReflectAgentReportSchema,
|
||||
},
|
||||
|
||||
processOutput: (output) => JSON.stringify(output, null, 2),
|
||||
|
||||
modelConfig: {
|
||||
model: DEFAULT_GEMINI_MODEL,
|
||||
generateContentConfig: {
|
||||
temperature: 0.1,
|
||||
topP: 0.95,
|
||||
},
|
||||
},
|
||||
|
||||
runConfig: {
|
||||
maxTimeMinutes: 3,
|
||||
maxTurns: 10,
|
||||
},
|
||||
|
||||
toolConfig: {
|
||||
tools: [
|
||||
GET_SESSION_HISTORY_TOOL_NAME,
|
||||
LS_TOOL_NAME,
|
||||
READ_FILE_TOOL_NAME,
|
||||
GLOB_TOOL_NAME,
|
||||
GREP_TOOL_NAME,
|
||||
WRITE_FILE_TOOL_NAME,
|
||||
EDIT_TOOL_NAME,
|
||||
],
|
||||
allowedTools: [GET_SESSION_HISTORY_TOOL_NAME],
|
||||
},
|
||||
|
||||
promptConfig: {
|
||||
query: `Please review the current session history and save any valuable learnings as skills or memories.`,
|
||||
systemPrompt: `You are the **Reflect Agent**, a specialized AI agent responsible for continuous learning. Your purpose is to review the current session's chat history, identify high-value, reusable knowledge or workflows, and persist them.
|
||||
|
||||
## What is considered to be high-value information
|
||||
High value information includes but is not limited to information that:
|
||||
- You do not know already from your training set, memories, or available skills.
|
||||
- Provides a valuable capability that aids in foreseeable future tasks.
|
||||
- Knowledge that will help you avoid turns lost to exploration, unproductive strategies, learning tools, or obscure codebase details.
|
||||
- Tips for authoring, debugging, and validating changes effectively.
|
||||
- Refinements and improvements for existing skills, knowledge, etc.
|
||||
- Command line examples that would have made the session history pass with fewer turns, errors, or false starts.
|
||||
- Non-trivial scripts (greater than 5 lines) that are reusable as part of a skill.
|
||||
|
||||
## Maintenance and consolidation
|
||||
Memories and skills have an associated cost and benefit. Use the following guidance to maintain
|
||||
a cohesive and high value set of memories and skills.
|
||||
- High value memories and skills will improve agent performance.
|
||||
- Low value or invalid memories and skills will degrade performance.
|
||||
- Reference existing docs, like 'README.md', and other markdown files to avoid the need to duplicate that information in a skill or memory, unless duplication leads to a better outcome.
|
||||
- Look for and remove invalid memories as you make changes.
|
||||
- Group skills in high level bundles related to a feature area or task.
|
||||
- If a skill grows too large, consider refactoring its core SKILL.md file into separate linked markdown files.
|
||||
- In some cases it may make sense to split a skill into 2 or more skills, particularly if it has grown to encompass multiple distinct skillsets, or knowledge which is rarely used together.
|
||||
- In some rare cases it may make sense to recommend a change to user documentation, like README.md, contents of the docs folder, etc. This must only be done when the change is obvious, high value, and likely to be accepted by the user and you must prompt with 'ask_user' before making the change.
|
||||
- Always use 'ask_user' tool to ask permission before deleting a skill, deleting a significant amount of memories, or refactoring a skill (merging, splitting, etc).
|
||||
|
||||
## Core Directives
|
||||
1. **Retrieve History:** Your very first action MUST be to call the \`get_session_history\` tool to read what happened during this session.
|
||||
2. **Analyze & Extract:** Look for complex workflows, specific project conventions, repeated commands, or user preferences that the agent should remember for future tasks.
|
||||
3. **Persist Knowledge:**
|
||||
- **Review Memories and Skills:** Then, review memories (GEMINI.md files) and skills (.gemini/skills). Determine whether you need to add new memories or skills or update existing ones.
|
||||
- **Memories:** For general, workspace-level preferences (e.g., "always use 2 spaces", "use strict typescript"), update the \`GEMINI.md\` file using \`${WRITE_FILE_TOOL_NAME}\` or \`${EDIT_TOOL_NAME}\`.
|
||||
- **Skills:** For complex, task-specific scripts or workflows, create a new skill in the \`.gemini/skills/\` directory. A skill requires a \`SKILL.md\` file containing the rules and resources.
|
||||
4. **Local Changes Only:** Only modify files in the \`.gemini/\` directory or the \`GEMINI.md\` file. Do NOT modify the main application source code.
|
||||
5. Do a Maintenance and consolidation pass.
|
||||
6. **Format Report:** Once you have saved the learnings, call the \`complete_task\` tool with a structured JSON report detailing what you found and what you saved.
|
||||
`,
|
||||
},
|
||||
});
|
||||
@@ -272,6 +272,7 @@ describe('AgentRegistry', () => {
|
||||
codebase_investigator: { enabled: false },
|
||||
cli_help: { enabled: false },
|
||||
generalist: { enabled: false },
|
||||
reflect_agent: { enabled: false },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import { loadAgentsFromDirectory } from './agentLoader.js';
|
||||
import { CodebaseInvestigatorAgent } from './codebase-investigator.js';
|
||||
import { CliHelpAgent } from './cli-help-agent.js';
|
||||
import { GeneralistAgent } from './generalist-agent.js';
|
||||
import { ReflectAgent } from './reflect-agent.js';
|
||||
import { BrowserAgentDefinition } from './browser/browserAgentDefinition.js';
|
||||
import { A2AClientManager } from './a2a-client-manager.js';
|
||||
import { A2AAuthProviderFactory } from './auth-provider/factory.js';
|
||||
@@ -242,6 +243,7 @@ export class AgentRegistry {
|
||||
this.registerLocalAgent(CodebaseInvestigatorAgent(this.config));
|
||||
this.registerLocalAgent(CliHelpAgent(this.config));
|
||||
this.registerLocalAgent(GeneralistAgent(this.config));
|
||||
this.registerLocalAgent(ReflectAgent(this.config));
|
||||
|
||||
// Register the browser agent if enabled in settings.
|
||||
// Tools are configured dynamically at invocation time via browserAgentFactory.
|
||||
|
||||
@@ -186,6 +186,7 @@ export interface PromptConfig {
|
||||
*/
|
||||
export interface ToolConfig {
|
||||
tools: Array<string | FunctionDeclaration | AnyDeclarativeTool>;
|
||||
allowedTools?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@ import { GrepTool } from '../tools/grep.js';
|
||||
import { canUseRipgrep, RipGrepTool } from '../tools/ripGrep.js';
|
||||
import { GlobTool } from '../tools/glob.js';
|
||||
import { ActivateSkillTool } from '../tools/activate-skill.js';
|
||||
import { GetSessionHistoryTool } from '../tools/get-session-history.js';
|
||||
import { EditTool } from '../tools/edit.js';
|
||||
import { ShellTool } from '../tools/shell.js';
|
||||
import { WriteFileTool } from '../tools/write-file.js';
|
||||
@@ -579,6 +580,7 @@ export interface ConfigParameters {
|
||||
disabledSkills?: string[];
|
||||
adminSkillsEnabled?: boolean;
|
||||
experimentalJitContext?: boolean;
|
||||
experimentalReflection?: boolean;
|
||||
toolOutputMasking?: Partial<ToolOutputMaskingConfig>;
|
||||
disableLLMCorrection?: boolean;
|
||||
plan?: boolean;
|
||||
@@ -794,6 +796,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
private readonly adminSkillsEnabled: boolean;
|
||||
|
||||
private readonly experimentalJitContext: boolean;
|
||||
private readonly experimentalReflection: boolean;
|
||||
private readonly disableLLMCorrection: boolean;
|
||||
private readonly planEnabled: boolean;
|
||||
private readonly trackerEnabled: boolean;
|
||||
@@ -895,6 +898,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this.adminSkillsEnabled = params.adminSkillsEnabled ?? true;
|
||||
this.modelAvailabilityService = new ModelAvailabilityService();
|
||||
this.experimentalJitContext = params.experimentalJitContext ?? false;
|
||||
this.experimentalReflection = params.experimentalReflection ?? false;
|
||||
this.modelSteering = params.modelSteering ?? false;
|
||||
this.userHintService = new UserHintService(() =>
|
||||
this.isModelSteeringEnabled(),
|
||||
@@ -1934,6 +1938,10 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
return this.experimentalJitContext;
|
||||
}
|
||||
|
||||
isReflectionEnabled(): boolean {
|
||||
return this.experimentalReflection;
|
||||
}
|
||||
|
||||
isModelSteeringEnabled(): boolean {
|
||||
return this.modelSteering;
|
||||
}
|
||||
@@ -2847,6 +2855,9 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
maybeRegister(ActivateSkillTool, () =>
|
||||
registry.registerTool(new ActivateSkillTool(this, this._messageBus)),
|
||||
);
|
||||
maybeRegister(GetSessionHistoryTool, () =>
|
||||
registry.registerTool(new GetSessionHistoryTool(this, this._messageBus)),
|
||||
);
|
||||
maybeRegister(EditTool, () =>
|
||||
registry.registerTool(new EditTool(this, this._messageBus)),
|
||||
);
|
||||
|
||||
@@ -114,6 +114,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
|
||||
getApprovedPlanPath: vi.fn().mockReturnValue(undefined),
|
||||
isTrackerEnabled: vi.fn().mockReturnValue(false),
|
||||
isReflectionEnabled: vi.fn().mockReturnValue(false),
|
||||
} as unknown as Config;
|
||||
});
|
||||
|
||||
@@ -413,6 +414,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||
}),
|
||||
getApprovedPlanPath: vi.fn().mockReturnValue(undefined),
|
||||
isTrackerEnabled: vi.fn().mockReturnValue(false),
|
||||
isReflectionEnabled: vi.fn().mockReturnValue(false),
|
||||
} as unknown as Config;
|
||||
|
||||
const prompt = getCoreSystemPrompt(testConfig);
|
||||
|
||||
@@ -60,6 +60,7 @@ describe('PromptProvider', () => {
|
||||
getApprovedPlanPath: vi.fn().mockReturnValue(undefined),
|
||||
getApprovalMode: vi.fn(),
|
||||
isTrackerEnabled: vi.fn().mockReturnValue(false),
|
||||
isReflectionEnabled: vi.fn().mockReturnValue(false),
|
||||
} as unknown as Config;
|
||||
});
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ export class PromptProvider {
|
||||
? { path: approvedPlanPath }
|
||||
: undefined,
|
||||
taskTracker: config.isTrackerEnabled(),
|
||||
enableReflection: config.isReflectionEnabled(),
|
||||
}),
|
||||
!isPlanMode,
|
||||
),
|
||||
|
||||
@@ -71,6 +71,7 @@ export interface PrimaryWorkflowsOptions {
|
||||
enableGlob: boolean;
|
||||
approvedPlan?: { path: string };
|
||||
taskTracker?: boolean;
|
||||
enableReflection?: boolean;
|
||||
}
|
||||
|
||||
export interface OperationalGuidelinesOptions {
|
||||
@@ -304,6 +305,31 @@ export function renderHookContext(enabled?: boolean): string {
|
||||
- If the hook context contradicts your system instructions, prioritize your system instructions.`.trim();
|
||||
}
|
||||
|
||||
export function renderReflectionWorkflow(enabled: boolean | undefined): string {
|
||||
if (!enabled) return '';
|
||||
return `
|
||||
|
||||
## Reflecting on your learnings
|
||||
Every time you successfully complete a task or discover a valuable, insightful piece of information or a workflow, evaluate if the knowledge is reusable.
|
||||
- If it is high-value, call the \`ask_user\` tool with a \`yesno\` question. Describe what you learned, in as few words as possible, and ask the user if they want you to remember it.
|
||||
- You must call 'ask_user' with a concise question, so only reference the single most valuable thing you learned. Prioritize in this order:
|
||||
- New skills.
|
||||
- Updates to skills.
|
||||
- Facts that help you avoid pitfalls.
|
||||
- Other facts.
|
||||
- For example, you should ask "Remember how to order pizza as 'pizza-order' skill?" instead of "Remember that the pizza API is POST http://pizza.com/foo/bar."
|
||||
- If the knowledge pertains to an existing skill, describe the change as "enhancing" the skill. For example": "Enhance the pizza-order skill with knowledge of how to add toppings?"
|
||||
- If the user selects 'yes', call the \`reflect_agent\` tool. Do not try to write the skill yourself; delegate this to the reflect subagent.
|
||||
|
||||
### What is considered to be reusable
|
||||
Reusable information includes but is not limited to information that:
|
||||
- You do not know already from your training set, memories, or loaded skills.
|
||||
- Provides a valuable capability that aids in foreseeable future tasks.
|
||||
- Knowledge that will help you avoid turns lost to exploration, unproductive strategies, learning tools, or obscure codebase details.
|
||||
- Tips for authoring, debugging, and validating changes effectively.
|
||||
- Refinements and improvements for existing skills, knowledge, etc.`;
|
||||
}
|
||||
|
||||
export function renderPrimaryWorkflows(
|
||||
options?: PrimaryWorkflowsOptions,
|
||||
): string {
|
||||
@@ -312,7 +338,9 @@ export function renderPrimaryWorkflows(
|
||||
# Primary Workflows
|
||||
|
||||
## Development Lifecycle
|
||||
Operate using a **Research -> Strategy -> Execution** lifecycle. For the Execution phase, resolve each sub-task through an iterative **Plan -> Act -> Validate** cycle.
|
||||
Operate using a **Research -> Strategy -> Execution** lifecycle. For the Execution phase, resolve each sub-task through an iterative **Plan -> Act -> Validate -> Reflect** cycle.
|
||||
|
||||
${renderReflectionWorkflow(options.enableReflection)}
|
||||
|
||||
${workflowStepResearch(options)}
|
||||
${workflowStepStrategy(options)}
|
||||
@@ -323,6 +351,8 @@ ${workflowStepStrategy(options)}
|
||||
|
||||
**Validation is the only path to finality.** Never assume success or settle for unverified changes. Rigorous, exhaustive verification is mandatory; it prevents the compounding cost of diagnosing failures later. A task is only complete when the behavioral correctness of the change has been verified and its structural integrity is confirmed within the full project context. Prioritize comprehensive validation above all else, utilizing redirection and focused analysis to manage high-output tasks without sacrificing depth. Never sacrifice validation rigor for the sake of brevity or to minimize tool-call overhead; partial or isolated checks are insufficient when more comprehensive validation is possible.
|
||||
|
||||
**Reflect** -- **CRITICAL** always perform the 'Reflecting on your learnings' workflow after you are done working to preserve your knowledge for subsequent sessions.
|
||||
|
||||
## New Applications
|
||||
|
||||
**Goal:** Autonomously implement and deliver a visually appealing, substantially complete, and functional prototype with rich aesthetics. Users judge applications by their visual impact; ensure they feel modern, "alive," and polished through consistent spacing, interactive feedback, and platform-appropriate design.
|
||||
|
||||
@@ -264,6 +264,8 @@ Skip this step only if the skill being developed already exists, and iteration o
|
||||
|
||||
When creating a new skill from scratch, always run the `init_skill.cjs` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.
|
||||
|
||||
**Note for In-Repo Skills:** If you are creating a skill specifically for the current repository, you should initialize it in the `.gemini/skills` directory of the repository (e.g., `--path .gemini/skills`). This allows Gemini CLI to automatically discover and use the skill without it needing to be packaged and installed globally or at the workspace level.
|
||||
|
||||
**Note:** Use the absolute path to the script as provided in the `available_resources` section.
|
||||
|
||||
Usage:
|
||||
@@ -323,11 +325,15 @@ Do not include any other fields in YAML frontmatter.
|
||||
|
||||
Write instructions for using the skill and its bundled resources.
|
||||
|
||||
### Step 5: Packaging a Skill
|
||||
### Step 5: Packaging a Skill (Optional for In-Repo Skills)
|
||||
|
||||
Once development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first (checking YAML and ensuring no TODOs remain) to ensure it meets all requirements:
|
||||
Once development of the skill is complete, it can be packaged into a distributable .skill file that gets shared with the user.
|
||||
|
||||
**Note:** Use the absolute path to the script as provided in the `available_resources` section.
|
||||
**Note for In-Repo Skills:** If you are creating a skill specifically for the current repository and it is located in the \`.gemini/skills/\` directory, you DO NOT need to package or install it. Gemini CLI automatically loads skills from this directory. You can skip to Step 7.
|
||||
|
||||
If you do need to package the skill for distribution:
|
||||
|
||||
**Note:** Use the absolute path to the script as provided in the \`available_resources\` section.
|
||||
|
||||
```bash
|
||||
node <path-to-skill-creator>/scripts/package_skill.cjs <path/to/skill-folder>
|
||||
@@ -347,15 +353,15 @@ The packaging script will:
|
||||
- Description completeness and quality
|
||||
- File organization and resource references
|
||||
|
||||
2. **Package** the skill if validation passes, creating a .skill file named after the skill (e.g., `my-skill.skill`) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension.
|
||||
2. **Package** the skill if validation passes, creating a .skill file named after the skill (e.g., \`my-skill.skill\`) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension.
|
||||
|
||||
If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again.
|
||||
|
||||
### Step 6: Installing and Reloading a Skill
|
||||
|
||||
Once the skill is packaged into a `.skill` file, offer to install it for the user. Ask whether they would like to install it locally in the current folder (workspace scope) or at the user level (user scope).
|
||||
If you packaged the skill into a \`.skill\` file, offer to install it for the user. Ask whether they would like to install it locally in the current folder (workspace scope) or at the user level (user scope).
|
||||
|
||||
If the user agrees to an installation, perform it immediately using the `run_shell_command` tool:
|
||||
If the user agrees to an installation, perform it immediately using the \`run_shell_command\` tool:
|
||||
|
||||
- **Locally (workspace scope)**:
|
||||
```bash
|
||||
@@ -366,9 +372,11 @@ If the user agrees to an installation, perform it immediately using the `run_she
|
||||
gemini skills install <path/to/skill-name.skill> --scope user
|
||||
```
|
||||
|
||||
**Important:** After the installation is complete, notify the user that they MUST manually execute the `/skills reload` command in their interactive Gemini CLI session to enable the new skill. They can then verify the installation by running `/skills list`.
|
||||
**Important:** After the installation is complete, notify the user that they MUST manually execute the \`/skills reload\` command in their interactive Gemini CLI session to enable the new skill. They can then verify the installation by running \`/skills list\`.
|
||||
|
||||
Note: You (the agent) cannot execute the `/skills reload` command yourself; it must be done by the user in an interactive instance of Gemini CLI. Do not attempt to run it on their behalf.
|
||||
For in-repo skills created in \`.gemini/skills/\`, notify the user that they must execute \`/skills reload\` to enable the new skill.
|
||||
|
||||
Note: You (the agent) cannot execute the \`/skills reload\` command yourself; it must be done by the user in an interactive instance of Gemini CLI. Do not attempt to run it on their behalf.
|
||||
|
||||
### Step 7: Iterate
|
||||
|
||||
|
||||
@@ -122,3 +122,6 @@ export const EXIT_PLAN_PARAM_PLAN_PATH = 'plan_path';
|
||||
// -- enter_plan_mode --
|
||||
export const ENTER_PLAN_MODE_TOOL_NAME = 'enter_plan_mode';
|
||||
export const PLAN_MODE_PARAM_REASON = 'reason';
|
||||
|
||||
// -- get_session_history --
|
||||
export const GET_SESSION_HISTORY_TOOL_NAME = 'get_session_history';
|
||||
|
||||
@@ -38,6 +38,7 @@ export {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
GET_SESSION_HISTORY_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
@@ -221,6 +222,13 @@ export const ENTER_PLAN_MODE_DEFINITION: ToolDefinition = {
|
||||
overrides: (modelId) => getToolSet(modelId).enter_plan_mode,
|
||||
};
|
||||
|
||||
export const GET_SESSION_HISTORY_DEFINITION: ToolDefinition = {
|
||||
get base() {
|
||||
return DEFAULT_LEGACY_SET.get_session_history;
|
||||
},
|
||||
overrides: (modelId) => getToolSet(modelId).get_session_history,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// DYNAMIC TOOL DEFINITIONS (LEGACY EXPORTS)
|
||||
// ============================================================================
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
GET_INTERNAL_DOCS_TOOL_NAME,
|
||||
ASK_USER_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
GET_SESSION_HISTORY_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
@@ -732,6 +733,15 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
},
|
||||
},
|
||||
|
||||
get_session_history: {
|
||||
name: GET_SESSION_HISTORY_TOOL_NAME,
|
||||
description: 'Retrieves the complete chat history of the current session.',
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
|
||||
exit_plan_mode: (plansDir) => getExitPlanModeDeclaration(plansDir),
|
||||
activate_skill: (skillNames) => getActivateSkillDeclaration(skillNames),
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
GET_INTERNAL_DOCS_TOOL_NAME,
|
||||
ASK_USER_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
GET_SESSION_HISTORY_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
@@ -707,6 +708,15 @@ The agent did not use the todo list because this task could be completed by a ti
|
||||
},
|
||||
},
|
||||
|
||||
get_session_history: {
|
||||
name: GET_SESSION_HISTORY_TOOL_NAME,
|
||||
description: 'Retrieves the complete chat history of the current session.',
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
|
||||
exit_plan_mode: (plansDir) => getExitPlanModeDeclaration(plansDir),
|
||||
activate_skill: (skillNames) => getActivateSkillDeclaration(skillNames),
|
||||
};
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface CoreToolSet {
|
||||
get_internal_docs: FunctionDeclaration;
|
||||
ask_user: FunctionDeclaration;
|
||||
enter_plan_mode: FunctionDeclaration;
|
||||
get_session_history: FunctionDeclaration;
|
||||
exit_plan_mode: (plansDir: string) => FunctionDeclaration;
|
||||
activate_skill: (skillNames: string[]) => FunctionDeclaration;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
BaseDeclarativeTool,
|
||||
BaseToolInvocation,
|
||||
Kind,
|
||||
type ToolResult,
|
||||
} from './tools.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { GET_SESSION_HISTORY_TOOL_NAME } from './tool-names.js';
|
||||
import { GET_SESSION_HISTORY_DEFINITION } from './definitions/coreTools.js';
|
||||
import { resolveToolDeclaration } from './definitions/resolver.js';
|
||||
|
||||
class GetSessionHistoryInvocation extends BaseToolInvocation<
|
||||
Record<string, never>,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
params: Record<string, never>,
|
||||
messageBus: MessageBus,
|
||||
toolName?: string,
|
||||
displayName?: string,
|
||||
) {
|
||||
super(params, messageBus, toolName, displayName);
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return 'Retrieving current session chat history';
|
||||
}
|
||||
|
||||
async execute(_signal: AbortSignal): Promise<ToolResult> {
|
||||
const client = this.config.getGeminiClient();
|
||||
if (!client) {
|
||||
throw new Error('GeminiClient not initialized.');
|
||||
}
|
||||
|
||||
const history = client.getHistory();
|
||||
let historyText = '';
|
||||
|
||||
for (const turn of history) {
|
||||
historyText += `\n--- Role: ${turn.role} ---\n`;
|
||||
if (turn.parts) {
|
||||
for (const part of turn.parts) {
|
||||
if (part.text) {
|
||||
historyText += `${part.text}\n`;
|
||||
} else if (part.functionCall) {
|
||||
historyText += `[Tool Call: ${part.functionCall.name} with args: ${JSON.stringify(part.functionCall.args)}]\n`;
|
||||
} else if (part.functionResponse) {
|
||||
// Include function response safely
|
||||
const responseText = JSON.stringify(part.functionResponse.response);
|
||||
historyText += `[Tool Response: ${part.functionResponse.name} - ${responseText.substring(0, 1000)}${responseText.length > 1000 ? '...' : ''}]\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
llmContent: historyText || 'No history found.',
|
||||
returnDisplay: 'Successfully retrieved session history.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GetSessionHistoryTool extends BaseDeclarativeTool<
|
||||
Record<string, never>,
|
||||
ToolResult
|
||||
> {
|
||||
static readonly Name = GET_SESSION_HISTORY_TOOL_NAME;
|
||||
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
messageBus: MessageBus,
|
||||
) {
|
||||
super(
|
||||
GetSessionHistoryTool.Name,
|
||||
'GetSessionHistory',
|
||||
GET_SESSION_HISTORY_DEFINITION.base.description!,
|
||||
Kind.Think,
|
||||
GET_SESSION_HISTORY_DEFINITION.base.parametersJsonSchema,
|
||||
messageBus,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: Record<string, never>,
|
||||
messageBus: MessageBus,
|
||||
toolName?: string,
|
||||
displayName?: string,
|
||||
) {
|
||||
return new GetSessionHistoryInvocation(
|
||||
this.config,
|
||||
params,
|
||||
messageBus,
|
||||
toolName ?? this.name,
|
||||
displayName ?? this.displayName,
|
||||
);
|
||||
}
|
||||
|
||||
override getSchema(modelId?: string) {
|
||||
return resolveToolDeclaration(GET_SESSION_HISTORY_DEFINITION, modelId);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
GET_SESSION_HISTORY_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
@@ -95,6 +96,7 @@ export {
|
||||
ASK_USER_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
GET_SESSION_HISTORY_TOOL_NAME,
|
||||
// Shared parameter names
|
||||
PARAM_FILE_PATH,
|
||||
PARAM_DIR_PATH,
|
||||
|
||||
@@ -472,6 +472,9 @@ export class ToolRegistry {
|
||||
) ?? new Set([]);
|
||||
const activeTools: AnyDeclarativeTool[] = [];
|
||||
for (const tool of this.allKnownTools.values()) {
|
||||
if (tool.name === 'get_session_history') {
|
||||
continue;
|
||||
}
|
||||
if (this.isActiveTool(tool, excludedTools)) {
|
||||
activeTools.push(tool);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user