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
+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);