fix(coding-agent): Clean up Path Handling (#4873)

This commit is contained in:
Armin Ronacher
2026-05-22 11:25:15 +02:00
committed by GitHub
parent bf56a86e1e
commit c100620bf4
23 changed files with 363 additions and 214 deletions

View File

@@ -1,5 +1,4 @@
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join, resolve, sep } from "node:path";
import chalk from "chalk";
import { CONFIG_DIR_NAME } from "../config.ts";
@@ -8,7 +7,7 @@ import type { ResourceDiagnostic } from "./diagnostics.ts";
export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.ts";
import { canonicalizePath, isLocalPath } from "../utils/paths.ts";
import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.ts";
import { createEventBus, type EventBus } from "./event-bus.ts";
import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./extensions/loader.ts";
import type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from "./extensions/types.ts";
@@ -77,8 +76,8 @@ export function loadProjectContextFiles(options: {
cwd: string;
agentDir: string;
}): Array<{ path: string; content: string }> {
const resolvedCwd = options.cwd;
const resolvedAgentDir = options.agentDir;
const resolvedCwd = resolvePath(options.cwd);
const resolvedAgentDir = resolvePath(options.agentDir);
const contextFiles: Array<{ path: string; content: string }> = [];
const seenPaths = new Set<string>();
@@ -205,8 +204,8 @@ export class DefaultResourceLoader implements ResourceLoader {
private lastThemePaths: string[];
constructor(options: DefaultResourceLoaderOptions) {
this.cwd = options.cwd;
this.agentDir = options.agentDir;
this.cwd = resolvePath(options.cwd);
this.agentDir = resolvePath(options.agentDir);
this.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);
this.eventBus = options.eventBus ?? createEventBus();
this.packageManager = new DefaultPackageManager({
@@ -409,8 +408,11 @@ export class DefaultResourceLoader implements ResourceLoader {
}
for (const p of this.additionalExtensionPaths) {
if (isLocalPath(p) && !existsSync(p)) {
extensionsResult.errors.push({ path: p, error: `Extension path does not exist: ${p}` });
if (isLocalPath(p)) {
const resolved = this.resolveResourcePath(p);
if (!existsSync(resolved)) {
extensionsResult.errors.push({ path: resolved, error: `Extension path does not exist: ${resolved}` });
}
}
}
this.extensionsResult = this.extensionsOverride ? this.extensionsOverride(extensionsResult) : extensionsResult;
@@ -423,8 +425,11 @@ export class DefaultResourceLoader implements ResourceLoader {
this.lastSkillPaths = skillPaths;
this.updateSkillsFromPaths(skillPaths, metadataByPath);
for (const p of this.additionalSkillPaths) {
if (isLocalPath(p) && !existsSync(p) && !this.skillDiagnostics.some((d) => d.path === p)) {
this.skillDiagnostics.push({ type: "error", message: "Skill path does not exist", path: p });
if (isLocalPath(p)) {
const resolved = this.resolveResourcePath(p);
if (!existsSync(resolved) && !this.skillDiagnostics.some((d) => d.path === resolved)) {
this.skillDiagnostics.push({ type: "error", message: "Skill path does not exist", path: resolved });
}
}
}
@@ -435,8 +440,15 @@ export class DefaultResourceLoader implements ResourceLoader {
this.lastPromptPaths = promptPaths;
this.updatePromptsFromPaths(promptPaths, metadataByPath);
for (const p of this.additionalPromptTemplatePaths) {
if (isLocalPath(p) && !existsSync(p) && !this.promptDiagnostics.some((d) => d.path === p)) {
this.promptDiagnostics.push({ type: "error", message: "Prompt template path does not exist", path: p });
if (isLocalPath(p)) {
const resolved = this.resolveResourcePath(p);
if (!existsSync(resolved) && !this.promptDiagnostics.some((d) => d.path === resolved)) {
this.promptDiagnostics.push({
type: "error",
message: "Prompt template path does not exist",
path: resolved,
});
}
}
}
@@ -447,8 +459,9 @@ export class DefaultResourceLoader implements ResourceLoader {
this.lastThemePaths = themePaths;
this.updateThemesFromPaths(themePaths, metadataByPath);
for (const p of this.additionalThemePaths) {
if (!existsSync(p) && !this.themeDiagnostics.some((d) => d.path === p)) {
this.themeDiagnostics.push({ type: "error", message: "Theme path does not exist", path: p });
const resolved = this.resolveResourcePath(p);
if (!existsSync(resolved) && !this.themeDiagnostics.some((d) => d.path === resolved)) {
this.themeDiagnostics.push({ type: "error", message: "Theme path does not exist", path: resolved });
}
}
@@ -478,10 +491,15 @@ export class DefaultResourceLoader implements ResourceLoader {
private normalizeExtensionPaths(
entries: Array<{ path: string; metadata: PathMetadata }>,
): Array<{ path: string; metadata: PathMetadata }> {
return entries.map((entry) => ({
path: this.resolveResourcePath(entry.path),
metadata: entry.metadata,
}));
return entries.map((entry) => {
const metadata = entry.metadata.baseDir
? { ...entry.metadata, baseDir: this.resolveResourcePath(entry.metadata.baseDir) }
: entry.metadata;
return {
path: this.resolveResourcePath(entry.path),
metadata,
};
});
}
private updateSkillsFromPaths(skillPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {
@@ -674,16 +692,7 @@ export class DefaultResourceLoader implements ResourceLoader {
}
private resolveResourcePath(p: string): string {
const trimmed = p.trim();
let expanded = trimmed;
if (trimmed === "~") {
expanded = homedir();
} else if (trimmed.startsWith("~/")) {
expanded = join(homedir(), trimmed.slice(2));
} else if (trimmed.startsWith("~")) {
expanded = join(homedir(), trimmed.slice(1));
}
return resolve(this.cwd, expanded);
return resolvePath(p, this.cwd, { trim: true });
}
private loadThemes(
@@ -704,7 +713,7 @@ export class DefaultResourceLoader implements ResourceLoader {
}
for (const p of paths) {
const resolved = resolve(this.cwd, p);
const resolved = this.resolveResourcePath(p);
if (!existsSync(resolved)) {
diagnostics.push({ type: "warning", message: "theme path does not exist", path: resolved });
continue;