feat(core): pass resolved plan directory context to tools and system prompt via activeExtensionName

This commit is contained in:
Mahima Shanware
2026-04-14 07:21:09 +00:00
parent ece2b0c3a0
commit f94dbbeb46
8 changed files with 121 additions and 23 deletions
@@ -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;
+11
View File
@@ -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;
+22 -6
View File
@@ -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(
+16 -4
View File
@@ -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) {
+35 -8
View File
@@ -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> {
+17 -1
View File
@@ -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>();
+1
View File
@@ -38,6 +38,7 @@ export interface ExecuteOptions {
updateOutput?: (output: ToolLiveOutput) => void;
shellExecutionConfig?: ShellExecutionConfig;
setExecutionIdCallback?: (executionId: number) => void;
activeExtensionName?: string;
}
/**
+16 -4
View File
@@ -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(),