fa64097992
User-invoked skills (typed /skill:, steered, follow-up, interrupted/ resumed via compaction, ACP, RPC) only appended a bare "Skill: <path>" line, so the model neither learned the user had invoked that specific skill nor where the skill directory was. Relative paths in skill bodies (scripts/, templates/) could not be resolved. Route all user-invoked paths through a self-identifying, baseDir-aware prompt template; keep hidden autoload skills on the minimal non-user format. Interactive skillCommands now carries the loaded Skill object instead of a bare path so baseDir flows through without reconstruction. The invocation kind defaults to "user" to keep buildSkillPromptMessage source-compatible. Op: correct Restores: spec:user-invoked-skill-prompt-self-identifies-and-exposes-skill-directory
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import * as fs from "node:fs/promises";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { tryRunRpcSkillCommand } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-mode";
|
|
import { type CustomMessage, SKILL_PROMPT_MESSAGE_TYPE } from "@oh-my-pi/pi-coding-agent/session/messages";
|
|
import { removeWithRetries, Snowflake } from "@oh-my-pi/pi-utils";
|
|
|
|
describe("tryRunRpcSkillCommand", () => {
|
|
test("dispatches registered /skill commands as skill prompt messages", async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), `omp-rpc-skill-${Snowflake.next()}-`));
|
|
const skillPath = path.join(dir, "SKILL.md");
|
|
await Bun.write(
|
|
skillPath,
|
|
"---\nname: reviewer\ndescription: Review code\n---\n\nReview the supplied code carefully.\n",
|
|
);
|
|
|
|
let message: Pick<CustomMessage, "attribution" | "content" | "customType" | "details" | "display"> | undefined;
|
|
|
|
const handled = await tryRunRpcSkillCommand(
|
|
{
|
|
skillsSettings: { enableSkillCommands: true },
|
|
skills: [
|
|
{ name: "reviewer", description: "Review code", filePath: skillPath, baseDir: dir, source: "project" },
|
|
],
|
|
async promptCustomMessage(nextMessage: typeof message) {
|
|
message = nextMessage;
|
|
},
|
|
},
|
|
"/skill:reviewer focus on risks",
|
|
);
|
|
|
|
expect(handled).toEqual({ agentInvoked: true });
|
|
expect(message?.customType).toBe(SKILL_PROMPT_MESSAGE_TYPE);
|
|
expect(message?.content).toContain("Review the supplied code carefully.");
|
|
expect(message?.content).toContain('The user has invoked the "reviewer" skill');
|
|
expect(message?.content).toContain(`[Skill directory: ${dir}]`);
|
|
expect(message?.content).toMatch(/[Rr]esolve any relative paths/);
|
|
expect(message?.content).toContain("User: focus on risks");
|
|
expect(message?.display).toBe(true);
|
|
expect(message?.attribution).toBe("user");
|
|
|
|
await removeWithRetries(dir);
|
|
});
|
|
|
|
test("ignores unknown skill commands so normal prompt handling can continue", async () => {
|
|
const handled = await tryRunRpcSkillCommand(
|
|
{
|
|
skillsSettings: { enableSkillCommands: true },
|
|
skills: [],
|
|
async promptCustomMessage() {
|
|
throw new Error("should not dispatch unknown skills");
|
|
},
|
|
},
|
|
"/skill:missing",
|
|
);
|
|
|
|
expect(handled).toBe(false);
|
|
});
|
|
});
|