fix(coding-agent): handle git/npm extension paths in CLI resolution (#2845)
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
- RpcClient now forwards subprocess stderr to parent process in real-time ([#2805](https://github.com/badlogic/pi-mono/issues/2805))
|
- RpcClient now forwards subprocess stderr to parent process in real-time ([#2805](https://github.com/badlogic/pi-mono/issues/2805))
|
||||||
- Theme file watcher now handles async `fs.watch` error events instead of crashing the process ([#2791](https://github.com/badlogic/pi-mono/issues/2791))
|
- Theme file watcher now handles async `fs.watch` error events instead of crashing the process ([#2791](https://github.com/badlogic/pi-mono/issues/2791))
|
||||||
|
- Fixed CLI extension paths like `git:gist.github.com/...` being incorrectly resolved against cwd instead of being passed through to the package manager ([#2845](https://github.com/badlogic/pi-mono/pull/2845) by [@aliou](https://github.com/aliou))
|
||||||
|
|
||||||
## [0.65.0] - 2026-04-03
|
## [0.65.0] - 2026-04-03
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ignore from "ignore";
|
|||||||
import { minimatch } from "minimatch";
|
import { minimatch } from "minimatch";
|
||||||
import { CONFIG_DIR_NAME } from "../config.js";
|
import { CONFIG_DIR_NAME } from "../config.js";
|
||||||
import { type GitSource, parseGitUrl } from "../utils/git.js";
|
import { type GitSource, parseGitUrl } from "../utils/git.js";
|
||||||
|
import { isLocalPath } from "../utils/paths.js";
|
||||||
import { isStdoutTakenOver } from "./output-guard.js";
|
import { isStdoutTakenOver } from "./output-guard.js";
|
||||||
import type { PackageSource, SettingsManager } from "./settings-manager.js";
|
import type { PackageSource, SettingsManager } from "./settings-manager.js";
|
||||||
|
|
||||||
@@ -1258,15 +1259,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = source.trim();
|
if (isLocalPath(source)) {
|
||||||
const isWindowsAbsolutePath = /^[A-Za-z]:[\\/]|^\\\\/.test(trimmed);
|
|
||||||
const isLocalPathLike =
|
|
||||||
trimmed.startsWith(".") ||
|
|
||||||
trimmed.startsWith("/") ||
|
|
||||||
trimmed === "~" ||
|
|
||||||
trimmed.startsWith("~/") ||
|
|
||||||
isWindowsAbsolutePath;
|
|
||||||
if (isLocalPathLike) {
|
|
||||||
return { type: "local", path: source };
|
return { type: "local", path: source };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { ResourceDiagnostic } from "./diagnostics.js";
|
|||||||
|
|
||||||
export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.js";
|
export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.js";
|
||||||
|
|
||||||
|
import { isLocalPath } from "../utils/paths.js";
|
||||||
import { createEventBus, type EventBus } from "./event-bus.js";
|
import { createEventBus, type EventBus } from "./event-bus.js";
|
||||||
import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./extensions/loader.js";
|
import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./extensions/loader.js";
|
||||||
import type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from "./extensions/types.js";
|
import type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from "./extensions/types.js";
|
||||||
@@ -404,7 +405,7 @@ export class DefaultResourceLoader implements ResourceLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const p of this.additionalExtensionPaths) {
|
for (const p of this.additionalExtensionPaths) {
|
||||||
if (!existsSync(p)) {
|
if (isLocalPath(p) && !existsSync(p)) {
|
||||||
extensionsResult.errors.push({ path: p, error: `Extension path does not exist: ${p}` });
|
extensionsResult.errors.push({ path: p, error: `Extension path does not exist: ${p}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,7 +419,7 @@ export class DefaultResourceLoader implements ResourceLoader {
|
|||||||
this.lastSkillPaths = skillPaths;
|
this.lastSkillPaths = skillPaths;
|
||||||
this.updateSkillsFromPaths(skillPaths, metadataByPath);
|
this.updateSkillsFromPaths(skillPaths, metadataByPath);
|
||||||
for (const p of this.additionalSkillPaths) {
|
for (const p of this.additionalSkillPaths) {
|
||||||
if (!existsSync(p) && !this.skillDiagnostics.some((d) => d.path === p)) {
|
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 });
|
this.skillDiagnostics.push({ type: "error", message: "Skill path does not exist", path: p });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,7 +431,7 @@ export class DefaultResourceLoader implements ResourceLoader {
|
|||||||
this.lastPromptPaths = promptPaths;
|
this.lastPromptPaths = promptPaths;
|
||||||
this.updatePromptsFromPaths(promptPaths, metadataByPath);
|
this.updatePromptsFromPaths(promptPaths, metadataByPath);
|
||||||
for (const p of this.additionalPromptTemplatePaths) {
|
for (const p of this.additionalPromptTemplatePaths) {
|
||||||
if (!existsSync(p) && !this.promptDiagnostics.some((d) => d.path === p)) {
|
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 });
|
this.promptDiagnostics.push({ type: "error", message: "Prompt template path does not exist", path: p });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { runMigrations, showDeprecationWarnings } from "./migrations.js";
|
|||||||
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
||||||
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
||||||
import { handleConfigCommand, handlePackageCommand } from "./package-manager-cli.js";
|
import { handleConfigCommand, handlePackageCommand } from "./package-manager-cli.js";
|
||||||
|
import { isLocalPath } from "./utils/paths.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read all content from piped stdin.
|
* Read all content from piped stdin.
|
||||||
@@ -371,7 +372,7 @@ function buildSessionOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined {
|
function resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined {
|
||||||
return paths?.map((value) => resolve(cwd, value));
|
return paths?.map((value) => (isLocalPath(value) ? resolve(cwd, value) : value));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function main(args: string[]) {
|
export async function main(args: string[]) {
|
||||||
|
|||||||
20
packages/coding-agent/src/utils/paths.ts
Normal file
20
packages/coding-agent/src/utils/paths.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Returns true if the value is NOT a package source (npm:, git:, etc.)
|
||||||
|
* or a URL protocol. Bare names and relative paths without ./ prefix
|
||||||
|
* are considered local.
|
||||||
|
*/
|
||||||
|
export function isLocalPath(value: string): boolean {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
// Known non-local prefixes
|
||||||
|
if (
|
||||||
|
trimmed.startsWith("npm:") ||
|
||||||
|
trimmed.startsWith("git:") ||
|
||||||
|
trimmed.startsWith("github:") ||
|
||||||
|
trimmed.startsWith("http:") ||
|
||||||
|
trimmed.startsWith("https:") ||
|
||||||
|
trimmed.startsWith("ssh:")
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user