mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 14:23:02 -07:00
feat(core): pass resolved plan directory context to tools and system prompt via activeExtensionName
This commit is contained in:
@@ -26,6 +26,9 @@ export interface AgentLoopContext {
|
||||
/** The unique ID for the parent session if this is a subagent. */
|
||||
readonly parentSessionId?: string;
|
||||
|
||||
/** The name of the active extension driving this context, if any. */
|
||||
readonly activeExtensionName?: string;
|
||||
|
||||
/** The registry of tools available to the agent in this context. */
|
||||
readonly toolRegistry: ToolRegistry;
|
||||
|
||||
|
||||
@@ -737,6 +737,17 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
private blockedEnvironmentVariables: string[];
|
||||
private readonly enableEnvironmentVariableRedaction: boolean;
|
||||
private _promptRegistry!: PromptRegistry;
|
||||
private _activeExtensionName?: string;
|
||||
|
||||
get activeExtensionName(): string | undefined {
|
||||
return (
|
||||
this._activeExtensionName || process.env['GEMINI_CLI_ACTIVE_EXTENSION']
|
||||
);
|
||||
}
|
||||
|
||||
setActiveExtensionName(name: string | undefined): void {
|
||||
this._activeExtensionName = name;
|
||||
}
|
||||
private _resourceRegistry!: ResourceRegistry;
|
||||
private agentRegistry!: AgentRegistry;
|
||||
private readonly acknowledgedAgentsService: AcknowledgedAgentsService;
|
||||
|
||||
@@ -192,12 +192,28 @@ export class PromptProvider {
|
||||
),
|
||||
planningWorkflow: this.withSection(
|
||||
'planningWorkflow',
|
||||
() => ({
|
||||
interactive: interactiveMode,
|
||||
planModeToolsList,
|
||||
plansDir: context.config.storage.getPlansDir(),
|
||||
approvedPlanPath: context.config.getApprovedPlanPath(),
|
||||
}),
|
||||
() => {
|
||||
let plansDir = '';
|
||||
const activeExt = context.config.activeExtensionName;
|
||||
let customDir: string | undefined;
|
||||
if (activeExt) {
|
||||
customDir = context.config.getExtensionSetting<string>(
|
||||
activeExt,
|
||||
'plan.directory',
|
||||
);
|
||||
}
|
||||
try {
|
||||
plansDir = context.config.storage.getPlansDir(customDir);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return {
|
||||
interactive: interactiveMode,
|
||||
planModeToolsList,
|
||||
plansDir,
|
||||
approvedPlanPath: context.config.getApprovedPlanPath(),
|
||||
};
|
||||
},
|
||||
isPlanMode,
|
||||
),
|
||||
operationalGuidelines: this.withSection(
|
||||
|
||||
@@ -466,10 +466,22 @@ class EditToolInvocation
|
||||
);
|
||||
if (this.config.isPlanMode()) {
|
||||
const safeFilename = path.basename(this.params.file_path);
|
||||
this.resolvedPath = path.join(
|
||||
this.config.storage.getPlansDir(),
|
||||
safeFilename,
|
||||
);
|
||||
let customDir: string | undefined;
|
||||
const activeExt = this.config.activeExtensionName;
|
||||
if (activeExt) {
|
||||
customDir = this.config.getExtensionSetting<string>(
|
||||
activeExt,
|
||||
'plan.directory',
|
||||
);
|
||||
}
|
||||
try {
|
||||
this.resolvedPath = path.join(
|
||||
this.config.storage.getPlansDir(customDir),
|
||||
safeFilename,
|
||||
);
|
||||
} catch {
|
||||
this.resolvedPath = ''; // Handled safely downstream
|
||||
}
|
||||
} else if (!path.isAbsolute(this.params.file_path)) {
|
||||
const result = correctPath(this.params.file_path, this.config);
|
||||
if (result.success) {
|
||||
|
||||
@@ -53,6 +53,18 @@ export class ExitPlanModeTool extends BaseDeclarativeTool<
|
||||
);
|
||||
}
|
||||
|
||||
private getResolvedPlansDir(): string {
|
||||
let customDir: string | undefined;
|
||||
const activeExt = this.config.activeExtensionName;
|
||||
if (activeExt) {
|
||||
customDir = this.config.getExtensionSetting<string>(
|
||||
activeExt,
|
||||
'plan.directory',
|
||||
);
|
||||
}
|
||||
return this.config.storage.getPlansDir(customDir);
|
||||
}
|
||||
|
||||
protected override validateToolParamValues(
|
||||
params: ExitPlanModeParams,
|
||||
): string | null {
|
||||
@@ -61,11 +73,14 @@ export class ExitPlanModeTool extends BaseDeclarativeTool<
|
||||
}
|
||||
|
||||
const safeFilename = path.basename(params.plan_filename);
|
||||
const plansDir = resolveToRealPath(this.config.storage.getPlansDir());
|
||||
const resolvedPath = path.join(
|
||||
this.config.storage.getPlansDir(),
|
||||
safeFilename,
|
||||
);
|
||||
let plansDir: string;
|
||||
let resolvedPath: string;
|
||||
try {
|
||||
plansDir = resolveToRealPath(this.getResolvedPlansDir());
|
||||
resolvedPath = path.join(this.getResolvedPlansDir(), safeFilename);
|
||||
} catch {
|
||||
return 'Failed to read plan directory: Path traversal attempt detected.';
|
||||
}
|
||||
|
||||
const realPath = resolveToRealPath(resolvedPath);
|
||||
|
||||
@@ -114,6 +129,18 @@ export class ExitPlanModeInvocation extends BaseToolInvocation<
|
||||
super(params, messageBus, toolName, toolDisplayName);
|
||||
}
|
||||
|
||||
private getResolvedPlansDir(): string {
|
||||
let customDir: string | undefined;
|
||||
const activeExt = this.config.activeExtensionName;
|
||||
if (activeExt) {
|
||||
customDir = this.config.getExtensionSetting<string>(
|
||||
activeExt,
|
||||
'plan.directory',
|
||||
);
|
||||
}
|
||||
return this.config.storage.getPlansDir(customDir);
|
||||
}
|
||||
|
||||
override async shouldConfirmExecute(
|
||||
abortSignal: AbortSignal,
|
||||
): Promise<ToolExitPlanModeConfirmationDetails | false> {
|
||||
@@ -121,7 +148,7 @@ export class ExitPlanModeInvocation extends BaseToolInvocation<
|
||||
|
||||
const pathError = await validatePlanPath(
|
||||
this.params.plan_filename,
|
||||
this.config.storage.getPlansDir(),
|
||||
this.getResolvedPlansDir(),
|
||||
);
|
||||
if (pathError) {
|
||||
this.planValidationError = pathError;
|
||||
@@ -171,7 +198,7 @@ export class ExitPlanModeInvocation extends BaseToolInvocation<
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return `Requesting plan approval for: ${path.join(this.config.storage.getPlansDir(), this.params.plan_filename)}`;
|
||||
return `Requesting plan approval for: ${path.join(this.getResolvedPlansDir(), this.params.plan_filename)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +207,7 @@ export class ExitPlanModeInvocation extends BaseToolInvocation<
|
||||
*/
|
||||
private getResolvedPlanPath(): string {
|
||||
const safeFilename = path.basename(this.params.plan_filename);
|
||||
return path.join(this.config.storage.getPlansDir(), safeFilename);
|
||||
return path.join(this.getResolvedPlansDir(), safeFilename);
|
||||
}
|
||||
|
||||
async execute({ abortSignal: _signal }: ExecuteOptions): Promise<ToolResult> {
|
||||
|
||||
@@ -634,7 +634,23 @@ export class ToolRegistry {
|
||||
*/
|
||||
getFunctionDeclarations(modelId?: string): FunctionDeclaration[] {
|
||||
const isPlanMode = this.config.getApprovalMode() === ApprovalMode.PLAN;
|
||||
const plansDir = this.config.storage.getPlansDir();
|
||||
|
||||
let plansDir: string | undefined;
|
||||
if (isPlanMode) {
|
||||
let customDir: string | undefined;
|
||||
const activeExt = this.config.activeExtensionName;
|
||||
if (activeExt) {
|
||||
customDir = this.config.getExtensionSetting<string>(
|
||||
activeExt,
|
||||
'plan.directory',
|
||||
);
|
||||
}
|
||||
try {
|
||||
plansDir = this.config.storage.getPlansDir(customDir);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const declarations: FunctionDeclaration[] = [];
|
||||
const seenNames = new Set<string>();
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface ExecuteOptions {
|
||||
updateOutput?: (output: ToolLiveOutput) => void;
|
||||
shellExecutionConfig?: ShellExecutionConfig;
|
||||
setExecutionIdCallback?: (executionId: number) => void;
|
||||
activeExtensionName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -169,10 +169,22 @@ class WriteFileToolInvocation extends BaseToolInvocation<
|
||||
|
||||
if (this.config.isPlanMode()) {
|
||||
const safeFilename = path.basename(this.params.file_path);
|
||||
this.resolvedPath = path.join(
|
||||
this.config.storage.getPlansDir(),
|
||||
safeFilename,
|
||||
);
|
||||
let customDir: string | undefined;
|
||||
const activeExt = this.config.activeExtensionName;
|
||||
if (activeExt) {
|
||||
customDir = this.config.getExtensionSetting<string>(
|
||||
activeExt,
|
||||
'plan.directory',
|
||||
);
|
||||
}
|
||||
try {
|
||||
this.resolvedPath = path.join(
|
||||
this.config.storage.getPlansDir(customDir),
|
||||
safeFilename,
|
||||
);
|
||||
} catch {
|
||||
this.resolvedPath = ''; // handled safely downstream
|
||||
}
|
||||
} else {
|
||||
this.resolvedPath = path.resolve(
|
||||
this.config.getTargetDir(),
|
||||
|
||||
Reference in New Issue
Block a user