fix(coding-agent): scope managed skills to profiles

This commit is contained in:
Ogrodev
2026-06-14 20:49:05 -03:00
parent f9bc96e96c
commit 87da6d373f
6 changed files with 24 additions and 6 deletions
+1
View File
@@ -19,6 +19,7 @@
- Fixed a crash in subagent task execution and extensions when a string (instead of a string array) was returned or set for the system prompt. Gracefully wrap string values in arrays.
- Fixed profile bootstrap so an extension-shadowed `--plan` flag no longer swallows a following global `--profile`.
- Fixed MCP OAuth URL-keyed credentials to stay profile-scoped under shared auth-broker storage and to clear discovered definition-only server auth during `/mcp unauth`.
- Fixed auto-learn managed skills to use the active profile's agent directory, so authored profile skills keep priority over managed fallbacks.
## [15.13.0] - 2026-06-14
@@ -9,11 +9,9 @@
*/
import { constants as fsConstants, type Stats } from "node:fs";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { isEnoent } from "@oh-my-pi/pi-utils";
import { getAgentDir, isEnoent } from "@oh-my-pi/pi-utils";
import { YAML } from "bun";
import { SOURCE_PATHS } from "../discovery/helpers";
/** Provider id stamped on discovered managed skills (distinguishes them from authored). */
export const MANAGED_SKILLS_PROVIDER_ID = "omp-managed";
@@ -24,8 +22,8 @@ export const MAX_MANAGED_SKILL_BYTES = 64_000;
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
/** Resolve the isolated managed-skills directory (`~/.omp/agent/managed-skills`). */
export function getManagedSkillsDir(home: string = os.homedir()): string {
return path.join(home, SOURCE_PATHS.native.userAgent, "managed-skills");
export function getManagedSkillsDir(agentDir: string = getAgentDir()): string {
return path.join(agentDir, "managed-skills");
}
/**
@@ -303,7 +303,7 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
const MANAGED_SKILLS_PRIORITY = 5;
async function loadManagedSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
return scanSkillsFromDir(ctx, {
dir: getManagedSkillsDir(ctx.home),
dir: getManagedSkillsDir(),
providerId: MANAGED_SKILLS_PROVIDER_ID,
level: "user",
requireDescription: true,
@@ -5,6 +5,7 @@ import * as path from "node:path";
import { getManagedSkillsDir } from "@oh-my-pi/pi-coding-agent/autolearn/managed-skills";
import "@oh-my-pi/pi-coding-agent/discovery";
import { loadSkills } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
import { getAgentDir, setAgentDir } from "@oh-my-pi/pi-utils/dirs";
async function writeSkill(dir: string, name: string, description: string): Promise<void> {
const file = path.join(dir, name, "SKILL.md");
@@ -18,13 +19,16 @@ describe("managed-skills discovery", () => {
let managedDir: string;
let authoredDir: string;
let originalAgentDir: string;
beforeEach(async () => {
originalAgentDir = getAgentDir();
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "omp-managed-disco-home-"));
// cwd MUST live under the fake home so loadSkills' ancestor walk is bounded
// and cannot pick up ambient /tmp/.omp or /.omp fixtures (full-suite-safe).
tempCwd = path.join(tempHome, "work");
await fs.mkdir(tempCwd, { recursive: true });
spyOn(os, "homedir").mockReturnValue(tempHome);
setAgentDir(path.join(tempHome, ".omp", "agent"));
managedDir = getManagedSkillsDir();
// Authored user skills live in the sibling `skills/` dir under .../agent.
authoredDir = path.join(path.dirname(managedDir), "skills");
@@ -32,6 +36,7 @@ describe("managed-skills discovery", () => {
afterEach(async () => {
spyOn(os, "homedir").mockRestore();
setAgentDir(originalAgentDir);
await fs.rm(tempHome, { recursive: true, force: true });
});
@@ -11,17 +11,22 @@ import {
writeManagedSkill,
} from "@oh-my-pi/pi-coding-agent/autolearn/managed-skills";
import { parseFrontmatter } from "@oh-my-pi/pi-utils";
import { getAgentDir, setAgentDir } from "@oh-my-pi/pi-utils/dirs";
describe("managed-skills primitives", () => {
let tempHome: string;
let originalAgentDir: string;
beforeEach(async () => {
originalAgentDir = getAgentDir();
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "omp-managed-skills-"));
spyOn(os, "homedir").mockReturnValue(tempHome);
setAgentDir(path.join(tempHome, ".omp", "agent"));
});
afterEach(async () => {
spyOn(os, "homedir").mockRestore();
setAgentDir(originalAgentDir);
await fs.rm(tempHome, { recursive: true, force: true });
});
@@ -10,6 +10,7 @@ import type { MnemopiSessionState } from "@oh-my-pi/pi-coding-agent/mnemopi/stat
import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { LearnTool } from "@oh-my-pi/pi-coding-agent/tools/learn";
import { ManageSkillTool } from "@oh-my-pi/pi-coding-agent/tools/manage-skill";
import { getAgentDir, setAgentDir } from "@oh-my-pi/pi-utils/dirs";
function makeSession(
settingsOverrides: Partial<Record<SettingPath, unknown>> = {},
@@ -104,14 +105,18 @@ describe("autolearn tool gating", () => {
describe("manage_skill execute", () => {
let tempHome: string;
let originalAgentDir: string;
beforeEach(async () => {
originalAgentDir = getAgentDir();
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "omp-manage-skill-"));
spyOn(os, "homedir").mockReturnValue(tempHome);
setAgentDir(path.join(tempHome, ".omp", "agent"));
});
afterEach(async () => {
spyOn(os, "homedir").mockRestore();
setAgentDir(originalAgentDir);
resetActiveSkillsForTests();
await fs.rm(tempHome, { recursive: true, force: true });
});
@@ -178,6 +183,7 @@ describe("manage_skill execute", () => {
describe("learn execute", () => {
let tempHome: string;
let remembered: string[];
let originalAgentDir: string;
function learnSession(): ToolSession {
const fakeState = {
@@ -195,13 +201,16 @@ describe("learn execute", () => {
}
beforeEach(async () => {
originalAgentDir = getAgentDir();
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "omp-learn-"));
spyOn(os, "homedir").mockReturnValue(tempHome);
setAgentDir(path.join(tempHome, ".omp", "agent"));
remembered = [];
});
afterEach(async () => {
spyOn(os, "homedir").mockRestore();
setAgentDir(originalAgentDir);
await fs.rm(tempHome, { recursive: true, force: true });
});