feat(nixos): build vendored pi
This commit is contained in:
@@ -1,287 +0,0 @@
|
|||||||
// installed by herdr
|
|
||||||
// managed by herdr; reinstalling or updating the integration overwrites this file.
|
|
||||||
// add custom hooks/plugins beside this file instead of editing it.
|
|
||||||
// HERDR_INTEGRATION_ID=pi
|
|
||||||
// HERDR_INTEGRATION_VERSION=6
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import net from "node:net";
|
|
||||||
|
|
||||||
const HERDR_ENV = process.env.HERDR_ENV;
|
|
||||||
const socketPath = process.env.HERDR_SOCKET_PATH;
|
|
||||||
const socketEndpoint =
|
|
||||||
process.platform === "win32" && socketPath ? `\\\\.\\pipe\\${socketPath}` : socketPath;
|
|
||||||
const paneId = process.env.HERDR_PANE_ID;
|
|
||||||
const source = "herdr:pi";
|
|
||||||
|
|
||||||
function enabled() {
|
|
||||||
return HERDR_ENV === "1" && !!socketPath && !!paneId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendRequestAttempt(request: unknown, timeoutMs: number): Promise<boolean> {
|
|
||||||
if (!enabled()) {
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
let done = false;
|
|
||||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
const finish = (delivered: boolean) => {
|
|
||||||
if (done) return;
|
|
||||||
done = true;
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
socket.destroy();
|
|
||||||
resolve(delivered);
|
|
||||||
};
|
|
||||||
|
|
||||||
const socket = net.createConnection(socketEndpoint!);
|
|
||||||
socket.on("error", () => finish(false));
|
|
||||||
socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`));
|
|
||||||
socket.on("data", () => finish(true));
|
|
||||||
socket.on("end", () => finish(false));
|
|
||||||
timeout = setTimeout(() => finish(false), timeoutMs);
|
|
||||||
timeout.unref?.();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendRequest(request: unknown): Promise<void> {
|
|
||||||
if (await sendRequestAttempt(request, 500)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await sendRequestAttempt(request, 1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentState = "working" | "blocked" | "idle";
|
|
||||||
|
|
||||||
type QueuedState = {
|
|
||||||
state: AgentState;
|
|
||||||
message?: string;
|
|
||||||
seq: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
let reportSeq = Date.now() * 1000;
|
|
||||||
let currentAgentSessionId: string | undefined;
|
|
||||||
let currentAgentSessionPath: string | undefined;
|
|
||||||
|
|
||||||
function nextReportSeq(): number {
|
|
||||||
reportSeq += 1;
|
|
||||||
return reportSeq;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSessionRef(ctx: any): void {
|
|
||||||
try {
|
|
||||||
const file = ctx?.sessionManager?.getSessionFile?.();
|
|
||||||
currentAgentSessionPath =
|
|
||||||
typeof file === "string" && file.startsWith("/") ? file : undefined;
|
|
||||||
} catch {
|
|
||||||
currentAgentSessionPath = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const id = ctx?.sessionManager?.getSessionId?.();
|
|
||||||
currentAgentSessionId = typeof id === "string" && id.length > 0 ? id : undefined;
|
|
||||||
} catch {
|
|
||||||
currentAgentSessionId = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function withSessionRef(params: Record<string, unknown>): Record<string, unknown> {
|
|
||||||
if (currentAgentSessionPath) {
|
|
||||||
return { ...params, agent_session_path: currentAgentSessionPath };
|
|
||||||
}
|
|
||||||
if (currentAgentSessionId) {
|
|
||||||
return { ...params, agent_session_id: currentAgentSessionId };
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
function currentSessionRef(): Record<string, unknown> | undefined {
|
|
||||||
if (currentAgentSessionPath) {
|
|
||||||
return { agent_session_path: currentAgentSessionPath };
|
|
||||||
}
|
|
||||||
if (currentAgentSessionId) {
|
|
||||||
return { agent_session_id: currentAgentSessionId };
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportSession(sessionStartSource?: string): Promise<void> {
|
|
||||||
const sessionRef = currentSessionRef();
|
|
||||||
if (!sessionRef) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sendRequest({
|
|
||||||
id: `${source}:session:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
||||||
method: "pane.report_agent_session",
|
|
||||||
params: {
|
|
||||||
pane_id: paneId,
|
|
||||||
source,
|
|
||||||
agent: "pi",
|
|
||||||
seq: nextReportSeq(),
|
|
||||||
session_start_source: sessionStartSource,
|
|
||||||
...sessionRef,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendState(state: AgentState, message?: string, seq = nextReportSeq()): Promise<void> {
|
|
||||||
return sendRequest({
|
|
||||||
id: `${source}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
||||||
method: "pane.report_agent",
|
|
||||||
params: withSessionRef({
|
|
||||||
pane_id: paneId,
|
|
||||||
source,
|
|
||||||
agent: "pi",
|
|
||||||
state,
|
|
||||||
message,
|
|
||||||
seq,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function releaseAgent(): Promise<void> {
|
|
||||||
return sendRequest({
|
|
||||||
id: `${source}:release:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
||||||
method: "pane.release_agent",
|
|
||||||
params: {
|
|
||||||
pane_id: paneId,
|
|
||||||
source,
|
|
||||||
agent: "pi",
|
|
||||||
seq: nextReportSeq(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldReleaseOnSessionShutdown(event: any): boolean {
|
|
||||||
// Pi tears down and rebinds extension runtimes for internal lifecycle actions
|
|
||||||
// such as /reload, /new, /resume, and /fork. Those do not mean the pane's
|
|
||||||
// agent process has exited, and releasing hook authority there can suppress
|
|
||||||
// legitimate reports from the replacement runtime. Only a user/process quit
|
|
||||||
// should release Herdr's full-lifecycle authority.
|
|
||||||
const reason = event?.reason;
|
|
||||||
return reason === "quit";
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendInFlight = false;
|
|
||||||
let queuedState: QueuedState | undefined;
|
|
||||||
|
|
||||||
function queueState(state: AgentState, message?: string): void {
|
|
||||||
queuedState = { state, message, seq: nextReportSeq() };
|
|
||||||
if (!sendInFlight) {
|
|
||||||
void drainStateQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function drainStateQueue(): Promise<void> {
|
|
||||||
if (sendInFlight) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendInFlight = true;
|
|
||||||
try {
|
|
||||||
while (queuedState) {
|
|
||||||
const next = queuedState;
|
|
||||||
queuedState = undefined;
|
|
||||||
await sendState(next.state, next.message, next.seq);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
sendInFlight = false;
|
|
||||||
if (queuedState) {
|
|
||||||
void drainStateQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (pi) {
|
|
||||||
if (!enabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let agentActive = false;
|
|
||||||
let blockedCount = 0;
|
|
||||||
let blockedMessage: string | undefined;
|
|
||||||
let lastState: AgentState | undefined;
|
|
||||||
let lastMessage: string | undefined;
|
|
||||||
let rootSession = false;
|
|
||||||
|
|
||||||
function desiredState() {
|
|
||||||
if (blockedCount > 0) {
|
|
||||||
return { state: "blocked" as const, message: blockedMessage };
|
|
||||||
}
|
|
||||||
if (agentActive) {
|
|
||||||
return { state: "working" as const, message: undefined };
|
|
||||||
}
|
|
||||||
return { state: "idle" as const, message: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
function publishState(force = false) {
|
|
||||||
const next = desiredState();
|
|
||||||
if (!force && next.state === lastState && next.message === lastMessage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastState = next.state;
|
|
||||||
lastMessage = next.message;
|
|
||||||
queueState(next.state, next.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
pi.events.on("herdr:blocked", (data) => {
|
|
||||||
if (!rootSession) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!data?.active) {
|
|
||||||
blockedCount = Math.max(0, blockedCount - 1);
|
|
||||||
if (blockedCount === 0) {
|
|
||||||
blockedMessage = undefined;
|
|
||||||
}
|
|
||||||
publishState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockedCount += 1;
|
|
||||||
blockedMessage = data.label;
|
|
||||||
publishState();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_start", async (event, ctx) => {
|
|
||||||
if (ctx?.hasUI !== true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rootSession = true;
|
|
||||||
updateSessionRef(ctx);
|
|
||||||
await reportSession(event?.reason);
|
|
||||||
// A reload can replace this extension mid-run without emitting another agent_start.
|
|
||||||
agentActive = ctx?.isIdle?.() === false;
|
|
||||||
publishState(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("agent_start", (_event, ctx) => {
|
|
||||||
if (!rootSession) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
updateSessionRef(ctx);
|
|
||||||
void reportSession();
|
|
||||||
agentActive = true;
|
|
||||||
publishState();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("agent_settled", (_event, ctx) => {
|
|
||||||
if (!rootSession || ctx?.isIdle?.() !== true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
agentActive = false;
|
|
||||||
publishState();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_shutdown", async (event) => {
|
|
||||||
if (!rootSession) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (shouldReleaseOnSessionShutdown(event)) {
|
|
||||||
await releaseAgent();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -11,12 +11,10 @@ let
|
|||||||
cfg = config.modules.shell.pi;
|
cfg = config.modules.shell.pi;
|
||||||
user = config.user.name;
|
user = config.user.name;
|
||||||
piDir = "${config.users.users.${user}.home}/.pi/agent";
|
piDir = "${config.users.users.${user}.home}/.pi/agent";
|
||||||
patchedPi = pkgs.pi-coding-agent.overrideAttrs (old: {
|
vendoredPi = pkgs.pi-coding-agent.overrideAttrs {
|
||||||
patches = (old.patches or [ ]) ++ [
|
src = ../../../../../third_party/pi;
|
||||||
./patches/pi-project-resource-ancestors.patch
|
patches = [ ];
|
||||||
./patches/pi-tool-lookup-validation.patch
|
};
|
||||||
];
|
|
||||||
});
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.shell.pi = {
|
options.modules.shell.pi = {
|
||||||
@@ -28,7 +26,7 @@ in
|
|||||||
home-manager.users.${user} = {
|
home-manager.users.${user} = {
|
||||||
programs.pi-coding-agent = {
|
programs.pi-coding-agent = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = patchedPi;
|
package = vendoredPi;
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
defaultProvider = "openai-codex";
|
defaultProvider = "openai-codex";
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
diff --git a/packages/coding-agent/src/core/package-manager.ts b/packages/coding-agent/src/core/package-manager.ts
|
|
||||||
--- a/packages/coding-agent/src/core/package-manager.ts
|
|
||||||
+++ b/packages/coding-agent/src/core/package-manager.ts
|
|
||||||
@@ -459,14 +459,14 @@ function findGitRepoRoot(startDir: string): string | null {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-function collectAncestorAgentsSkillDirs(startDir: string): string[] {
|
|
||||||
- const skillDirs: string[] = [];
|
|
||||||
+function collectAncestorConfigDirs(startDir: string, configDirName: string): string[] {
|
|
||||||
+ const configDirs: string[] = [];
|
|
||||||
const resolvedStartDir = resolve(startDir);
|
|
||||||
const gitRepoRoot = findGitRepoRoot(resolvedStartDir);
|
|
||||||
|
|
||||||
let dir = resolvedStartDir;
|
|
||||||
while (true) {
|
|
||||||
- skillDirs.push(join(dir, ".agents", "skills"));
|
|
||||||
+ configDirs.push(join(dir, configDirName));
|
|
||||||
if (gitRepoRoot && dir === gitRepoRoot) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
@@ -477,7 +477,7 @@ function collectAncestorAgentsSkillDirs(startDir: string): string[] {
|
|
||||||
dir = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
- return skillDirs;
|
|
||||||
+ return configDirs;
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectAutoPromptEntries(dir: string): string[] {
|
|
||||||
@@ -2388,16 +2388,13 @@ export class DefaultPackageManager implements PackageManager {
|
|
||||||
prompts: join(globalBaseDir, "prompts"),
|
|
||||||
themes: join(globalBaseDir, "themes"),
|
|
||||||
};
|
|
||||||
- const projectDirs = {
|
|
||||||
- extensions: join(projectBaseDir, "extensions"),
|
|
||||||
- skills: join(projectBaseDir, "skills"),
|
|
||||||
- prompts: join(projectBaseDir, "prompts"),
|
|
||||||
- themes: join(projectBaseDir, "themes"),
|
|
||||||
- };
|
|
||||||
const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
|
|
||||||
const projectTrusted = this.settingsManager.isProjectTrusted();
|
|
||||||
- const projectAgentsSkillDirs = projectTrusted
|
|
||||||
- ? collectAncestorAgentsSkillDirs(this.cwd).filter((dir) => resolve(dir) !== resolve(userAgentsSkillsDir))
|
|
||||||
+ const projectConfigDirs = projectTrusted
|
|
||||||
+ ? collectAncestorConfigDirs(this.cwd, CONFIG_DIR_NAME).filter((dir) => resolve(dir) !== resolve(globalBaseDir))
|
|
||||||
+ : [];
|
|
||||||
+ const projectAgentsDirs = projectTrusted
|
|
||||||
+ ? collectAncestorConfigDirs(this.cwd, ".agents").filter((dir) => resolve(dir) !== resolve(dirname(userAgentsSkillsDir)))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const addResources = (
|
|
||||||
@@ -2414,56 +2411,54 @@ export class DefaultPackageManager implements PackageManager {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
- if (projectTrusted) {
|
|
||||||
- // Project extensions from .pi/
|
|
||||||
+ // Project resources from .pi/ in cwd and ancestors.
|
|
||||||
+ for (const configDir of projectConfigDirs) {
|
|
||||||
+ const configMetadata: PathMetadata = {
|
|
||||||
+ ...projectMetadata,
|
|
||||||
+ baseDir: configDir,
|
|
||||||
+ };
|
|
||||||
addResources(
|
|
||||||
"extensions",
|
|
||||||
- collectAutoExtensionEntries(projectDirs.extensions),
|
|
||||||
- projectMetadata,
|
|
||||||
+ collectAutoExtensionEntries(join(configDir, "extensions")),
|
|
||||||
+ configMetadata,
|
|
||||||
projectOverrides.extensions,
|
|
||||||
- projectBaseDir,
|
|
||||||
+ configDir,
|
|
||||||
);
|
|
||||||
-
|
|
||||||
- // Project skills from .pi/
|
|
||||||
addResources(
|
|
||||||
"skills",
|
|
||||||
- collectAutoSkillEntries(projectDirs.skills, "pi"),
|
|
||||||
- projectMetadata,
|
|
||||||
+ collectAutoSkillEntries(join(configDir, "skills"), "pi"),
|
|
||||||
+ configMetadata,
|
|
||||||
projectOverrides.skills,
|
|
||||||
- projectBaseDir,
|
|
||||||
+ configDir,
|
|
||||||
+ );
|
|
||||||
+ addResources(
|
|
||||||
+ "prompts",
|
|
||||||
+ collectAutoPromptEntries(join(configDir, "prompts")),
|
|
||||||
+ configMetadata,
|
|
||||||
+ projectOverrides.prompts,
|
|
||||||
+ configDir,
|
|
||||||
+ );
|
|
||||||
+ addResources(
|
|
||||||
+ "themes",
|
|
||||||
+ collectAutoThemeEntries(join(configDir, "themes")),
|
|
||||||
+ configMetadata,
|
|
||||||
+ projectOverrides.themes,
|
|
||||||
+ configDir,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
- // Project skills from .agents/ (each with its own baseDir)
|
|
||||||
- for (const agentsSkillsDir of projectAgentsSkillDirs) {
|
|
||||||
- const agentsBaseDir = dirname(agentsSkillsDir); // the .agents directory
|
|
||||||
+ // Project skills from .agents/ in cwd and ancestors.
|
|
||||||
+ for (const agentsDir of projectAgentsDirs) {
|
|
||||||
const agentsMetadata: PathMetadata = {
|
|
||||||
...projectMetadata,
|
|
||||||
- baseDir: agentsBaseDir,
|
|
||||||
+ baseDir: agentsDir,
|
|
||||||
};
|
|
||||||
addResources(
|
|
||||||
"skills",
|
|
||||||
- collectAutoSkillEntries(agentsSkillsDir, "agents"),
|
|
||||||
+ collectAutoSkillEntries(join(agentsDir, "skills"), "agents"),
|
|
||||||
agentsMetadata,
|
|
||||||
projectOverrides.skills,
|
|
||||||
- agentsBaseDir,
|
|
||||||
- );
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- if (projectTrusted) {
|
|
||||||
- addResources(
|
|
||||||
- "prompts",
|
|
||||||
- collectAutoPromptEntries(projectDirs.prompts),
|
|
||||||
- projectMetadata,
|
|
||||||
- projectOverrides.prompts,
|
|
||||||
- projectBaseDir,
|
|
||||||
- );
|
|
||||||
- addResources(
|
|
||||||
- "themes",
|
|
||||||
- collectAutoThemeEntries(projectDirs.themes),
|
|
||||||
- projectMetadata,
|
|
||||||
- projectOverrides.themes,
|
|
||||||
- projectBaseDir,
|
|
||||||
+ agentsDir,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-agent/src/utils/tools-manager.ts
|
|
||||||
--- a/packages/coding-agent/src/utils/tools-manager.ts 2026-08-01 18:41:36.970496010 -0400
|
|
||||||
+++ b/packages/coding-agent/src/utils/tools-manager.ts 2026-08-01 18:41:37.028186009 -0400
|
|
||||||
@@ -74,8 +74,8 @@
|
|
||||||
function commandExists(cmd: string): boolean {
|
|
||||||
try {
|
|
||||||
const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
|
|
||||||
- // Check for ENOENT error (command not found)
|
|
||||||
- return result.error === undefined || result.error === null;
|
|
||||||
+ // Downloaded generic Linux binaries can exist but fail on NixOS before they print a version.
|
|
||||||
+ return (result.error === undefined || result.error === null) && result.status === 0;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -88,7 +87,7 @@
|
|
||||||
|
|
||||||
// Check our tools directory first
|
|
||||||
const localPath = join(TOOLS_DIR, config.binaryName + (platform() === "win32" ? ".exe" : ""));
|
|
||||||
- if (existsSync(localPath)) {
|
|
||||||
+ if (existsSync(localPath) && commandExists(localPath)) {
|
|
||||||
return localPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -459,14 +459,14 @@ function findGitRepoRoot(startDir: string): string | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectAncestorAgentsSkillDirs(startDir: string): string[] {
|
function collectAncestorConfigDirs(startDir: string, configDirName: string): string[] {
|
||||||
const skillDirs: string[] = [];
|
const configDirs: string[] = [];
|
||||||
const resolvedStartDir = resolve(startDir);
|
const resolvedStartDir = resolve(startDir);
|
||||||
const gitRepoRoot = findGitRepoRoot(resolvedStartDir);
|
const gitRepoRoot = findGitRepoRoot(resolvedStartDir);
|
||||||
|
|
||||||
let dir = resolvedStartDir;
|
let dir = resolvedStartDir;
|
||||||
while (true) {
|
while (true) {
|
||||||
skillDirs.push(join(dir, ".agents", "skills"));
|
configDirs.push(join(dir, configDirName));
|
||||||
if (gitRepoRoot && dir === gitRepoRoot) {
|
if (gitRepoRoot && dir === gitRepoRoot) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -477,7 +477,7 @@ function collectAncestorAgentsSkillDirs(startDir: string): string[] {
|
|||||||
dir = parent;
|
dir = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return skillDirs;
|
return configDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectAutoPromptEntries(dir: string): string[] {
|
function collectAutoPromptEntries(dir: string): string[] {
|
||||||
@@ -2388,16 +2388,13 @@ export class DefaultPackageManager implements PackageManager {
|
|||||||
prompts: join(globalBaseDir, "prompts"),
|
prompts: join(globalBaseDir, "prompts"),
|
||||||
themes: join(globalBaseDir, "themes"),
|
themes: join(globalBaseDir, "themes"),
|
||||||
};
|
};
|
||||||
const projectDirs = {
|
|
||||||
extensions: join(projectBaseDir, "extensions"),
|
|
||||||
skills: join(projectBaseDir, "skills"),
|
|
||||||
prompts: join(projectBaseDir, "prompts"),
|
|
||||||
themes: join(projectBaseDir, "themes"),
|
|
||||||
};
|
|
||||||
const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
|
const userAgentsSkillsDir = join(getHomeDir(), ".agents", "skills");
|
||||||
const projectTrusted = this.settingsManager.isProjectTrusted();
|
const projectTrusted = this.settingsManager.isProjectTrusted();
|
||||||
const projectAgentsSkillDirs = projectTrusted
|
const projectConfigDirs = projectTrusted
|
||||||
? collectAncestorAgentsSkillDirs(this.cwd).filter((dir) => resolve(dir) !== resolve(userAgentsSkillsDir))
|
? collectAncestorConfigDirs(this.cwd, CONFIG_DIR_NAME).filter((dir) => resolve(dir) !== resolve(globalBaseDir))
|
||||||
|
: [];
|
||||||
|
const projectAgentsDirs = projectTrusted
|
||||||
|
? collectAncestorConfigDirs(this.cwd, ".agents").filter((dir) => resolve(dir) !== resolve(dirname(userAgentsSkillsDir)))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const addResources = (
|
const addResources = (
|
||||||
@@ -2414,56 +2411,54 @@ export class DefaultPackageManager implements PackageManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (projectTrusted) {
|
// Project resources from .pi/ in cwd and ancestors.
|
||||||
// Project extensions from .pi/
|
for (const configDir of projectConfigDirs) {
|
||||||
addResources(
|
const configMetadata: PathMetadata = {
|
||||||
"extensions",
|
|
||||||
collectAutoExtensionEntries(projectDirs.extensions),
|
|
||||||
projectMetadata,
|
|
||||||
projectOverrides.extensions,
|
|
||||||
projectBaseDir,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Project skills from .pi/
|
|
||||||
addResources(
|
|
||||||
"skills",
|
|
||||||
collectAutoSkillEntries(projectDirs.skills, "pi"),
|
|
||||||
projectMetadata,
|
|
||||||
projectOverrides.skills,
|
|
||||||
projectBaseDir,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Project skills from .agents/ (each with its own baseDir)
|
|
||||||
for (const agentsSkillsDir of projectAgentsSkillDirs) {
|
|
||||||
const agentsBaseDir = dirname(agentsSkillsDir); // the .agents directory
|
|
||||||
const agentsMetadata: PathMetadata = {
|
|
||||||
...projectMetadata,
|
...projectMetadata,
|
||||||
baseDir: agentsBaseDir,
|
baseDir: configDir,
|
||||||
};
|
};
|
||||||
addResources(
|
addResources(
|
||||||
"skills",
|
"extensions",
|
||||||
collectAutoSkillEntries(agentsSkillsDir, "agents"),
|
collectAutoExtensionEntries(join(configDir, "extensions")),
|
||||||
agentsMetadata,
|
configMetadata,
|
||||||
projectOverrides.skills,
|
projectOverrides.extensions,
|
||||||
agentsBaseDir,
|
configDir,
|
||||||
|
);
|
||||||
|
addResources(
|
||||||
|
"skills",
|
||||||
|
collectAutoSkillEntries(join(configDir, "skills"), "pi"),
|
||||||
|
configMetadata,
|
||||||
|
projectOverrides.skills,
|
||||||
|
configDir,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (projectTrusted) {
|
|
||||||
addResources(
|
addResources(
|
||||||
"prompts",
|
"prompts",
|
||||||
collectAutoPromptEntries(projectDirs.prompts),
|
collectAutoPromptEntries(join(configDir, "prompts")),
|
||||||
projectMetadata,
|
configMetadata,
|
||||||
projectOverrides.prompts,
|
projectOverrides.prompts,
|
||||||
projectBaseDir,
|
configDir,
|
||||||
);
|
);
|
||||||
addResources(
|
addResources(
|
||||||
"themes",
|
"themes",
|
||||||
collectAutoThemeEntries(projectDirs.themes),
|
collectAutoThemeEntries(join(configDir, "themes")),
|
||||||
projectMetadata,
|
configMetadata,
|
||||||
projectOverrides.themes,
|
projectOverrides.themes,
|
||||||
projectBaseDir,
|
configDir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project skills from .agents/ in cwd and ancestors.
|
||||||
|
for (const agentsDir of projectAgentsDirs) {
|
||||||
|
const agentsMetadata: PathMetadata = {
|
||||||
|
...projectMetadata,
|
||||||
|
baseDir: agentsDir,
|
||||||
|
};
|
||||||
|
addResources(
|
||||||
|
"skills",
|
||||||
|
collectAutoSkillEntries(join(agentsDir, "skills"), "agents"),
|
||||||
|
agentsMetadata,
|
||||||
|
projectOverrides.skills,
|
||||||
|
agentsDir,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,8 +72,8 @@ const TOOLS: Record<string, ToolConfig> = {
|
|||||||
function commandExists(cmd: string): boolean {
|
function commandExists(cmd: string): boolean {
|
||||||
try {
|
try {
|
||||||
const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
|
const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
|
||||||
// Check for ENOENT error (command not found)
|
// Downloaded generic Linux binaries can exist but fail on NixOS before they print a version.
|
||||||
return result.error === undefined || result.error === null;
|
return (result.error === undefined || result.error === null) && result.status === 0;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ export function getToolPath(tool: "fd" | "rg"): string | null {
|
|||||||
|
|
||||||
// Check our tools directory first
|
// Check our tools directory first
|
||||||
const localPath = join(TOOLS_DIR, config.binaryName + (platform() === "win32" ? ".exe" : ""));
|
const localPath = join(TOOLS_DIR, config.binaryName + (platform() === "win32" ? ".exe" : ""));
|
||||||
if (existsSync(localPath)) {
|
if (existsSync(localPath) && commandExists(localPath)) {
|
||||||
return localPath;
|
return localPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user