fix(core,cli): properly resolve nested paths and plan dirs in exit_plan_mode

- **exit_plan_mode.ts**: Fixed an issue where `path.basename` incorrectly stripped nested directories (e.g., `tracks/123/index.md` to `index.md`) by adopting `resolveAndValidatePlanPath`.
- **UI Components**: Updated `ExitPlanModeDialog.tsx` and `planCommand.ts` to use `config.getPlansDir()` instead of `config.storage.getPlansDir()`. This ensures the UI respects active extension plan directories (like Conductor) rather than falling back to the default temporary directory.
- **write-file.ts**: Updated to use `config.getPlansDir()` for consistency.
This commit is contained in:
Moisés Gana Obregón
2026-04-17 17:31:32 +00:00
parent 9b5c3775e0
commit a2dda25890
4 changed files with 22 additions and 20 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ export const planCommand: SlashCommand = {
try {
const content = await processSingleFileContent(
approvedPlanPath,
config.storage.getPlansDir(),
config.getPlansDir(),
config.getFileSystemService(),
);
const fileName = path.basename(approvedPlanPath);
@@ -84,7 +84,7 @@ function usePlanContent(planPath: string, config: Config): PlanContentState {
try {
const pathError = await validatePlanPath(
planPath,
config.storage.getPlansDir(),
config.getPlansDir(),
);
if (ignore) return;
if (pathError) {
@@ -101,7 +101,7 @@ function usePlanContent(planPath: string, config: Config): PlanContentState {
const result = await processSingleFileContent(
planPath,
config.storage.getPlansDir(),
config.getPlansDir(),
config.getFileSystemService(),
);
+18 -16
View File
@@ -63,20 +63,20 @@ export class ExitPlanModeTool extends BaseDeclarativeTool<
if (!params.plan_filename || params.plan_filename.trim() === '') {
return 'plan_filename is required.';
}
try {
resolveAndValidatePlanPath(
params.plan_filename,
this.config.getPlansDir(),
);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Security violation')) {
return `Access denied: plan path (${path.join(
this.config.getPlansDir(),
params.plan_filename,
)}) must be within the designated plans directory (${this.config.getPlansDir()}).`;
}
return e instanceof Error ? e.message : String(e);
}
try {
resolveAndValidatePlanPath(
params.plan_filename,
this.config.getPlansDir(),
);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Security violation')) {
return `Access denied: plan path (${path.join(
this.config.getPlansDir(),
params.plan_filename,
)}) must be within the designated plans directory (${this.config.getPlansDir()}).`;
}
return e instanceof Error ? e.message : String(e);
}
return null;
}
@@ -184,8 +184,10 @@ export class ExitPlanModeInvocation extends BaseToolInvocation<
* Note: Validation is done in validateToolParamValues, so this assumes the path is valid.
*/
private getResolvedPlanPath(): string {
const safeFilename = path.basename(this.params.plan_filename);
return path.join(this.config.getPlansDir(), safeFilename);
return resolveAndValidatePlanPath(
this.params.plan_filename,
this.config.getPlansDir(),
);
}
async execute({ abortSignal: _signal }: ExecuteOptions): Promise<ToolResult> {
+1 -1
View File
@@ -504,7 +504,7 @@ export class WriteFileTool
try {
resolvedPath = resolveAndValidatePlanPath(
filePath,
this.config.storage.getPlansDir(),
this.config.getPlansDir(),
);
} catch (err) {
return err instanceof Error ? err.message : String(err);