fix(coding-agent): scope managed skills to profiles
This commit is contained in:
@@ -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 });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user