test(agent): pin harness resource formatting
This commit is contained in:
@@ -169,7 +169,7 @@ function errorMessage(error: unknown, fallback: string): string {
|
|||||||
return error instanceof Error ? error.message : fallback;
|
return error instanceof Error ? error.message : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parse slash-command arguments using simple shell-style single and double quotes. */
|
/** Parse an argument string using simple shell-style single and double quotes. */
|
||||||
export function parseCommandArgs(argsString: string): string[] {
|
export function parseCommandArgs(argsString: string): string[] {
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
let current = "";
|
let current = "";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export function formatSkillsForSystemPrompt(skills: Skill[]): string {
|
|||||||
|
|
||||||
const lines = [
|
const lines = [
|
||||||
"The following skills provide specialized instructions for specific tasks.",
|
"The following skills provide specialized instructions for specific tasks.",
|
||||||
"Use the read tool to load a skill's file when the task matches its description.",
|
"Read the full skill file when the task matches its description.",
|
||||||
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
||||||
"",
|
"",
|
||||||
"<available_skills>",
|
"<available_skills>",
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ import type { ImageContent, Model, TextContent } from "@earendil-works/pi-ai";
|
|||||||
import type { AgentEvent, AgentMessage, AgentTool, ThinkingLevel } from "../index.js";
|
import type { AgentEvent, AgentMessage, AgentTool, ThinkingLevel } from "../index.js";
|
||||||
import type { Session } from "./session/session.js";
|
import type { Session } from "./session/session.js";
|
||||||
|
|
||||||
/** Model-visible skill loaded from a `SKILL.md` file or provided by an application. */
|
/**
|
||||||
|
* Skill loaded from a `SKILL.md` file or provided by an application.
|
||||||
|
*
|
||||||
|
* `name`, `description`, and `filePath` are inserted into the system prompt in an XML-formatted block as suggested by agentskills.io.
|
||||||
|
* Use {@link formatSkillsForSystemPrompt} to generate the spec-compatible system prompt block.
|
||||||
|
*/
|
||||||
export interface Skill {
|
export interface Skill {
|
||||||
/** Skill command name. */
|
/** Stable skill name used for lookup and model-visible listings. */
|
||||||
name: string;
|
name: string;
|
||||||
/** Short model-visible description of when to use the skill. */
|
/** Short model-visible description of when to use the skill. */
|
||||||
description: string;
|
description: string;
|
||||||
@@ -16,19 +21,19 @@ export interface Skill {
|
|||||||
disableModelInvocation?: boolean;
|
disableModelInvocation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Prompt template that can expand slash-command text before a prompt is sent. */
|
/** Prompt template that can be formatted into a prompt for explicit invocation. */
|
||||||
export interface PromptTemplate {
|
export interface PromptTemplate {
|
||||||
/** Slash-command name without the leading `/`. */
|
/** Stable template name used for lookup or application command routing. */
|
||||||
name: string;
|
name: string;
|
||||||
/** Optional description for command lists or autocomplete. */
|
/** Optional description for command lists or autocomplete. */
|
||||||
description?: string;
|
description?: string;
|
||||||
/** Template content. Argument placeholders are expanded by `formatPromptTemplateInvocation`. */
|
/** Template content. Argument placeholders are formatted by `formatPromptTemplateInvocation`. */
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resources made available to harness prompt expansion and system-prompt callbacks. */
|
/** Resources made available to explicit invocation methods and system-prompt callbacks. */
|
||||||
export interface AgentHarnessResources {
|
export interface AgentHarnessResources {
|
||||||
/** Prompt templates used to expand `/template args` input. */
|
/** Prompt templates available for explicit invocation. */
|
||||||
promptTemplates?: PromptTemplate[];
|
promptTemplates?: PromptTemplate[];
|
||||||
/** Skills available to the model and explicit skill invocation. */
|
/** Skills available to the model and explicit skill invocation. */
|
||||||
skills?: Skill[];
|
skills?: Skill[];
|
||||||
|
|||||||
@@ -1,45 +1,66 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { formatSkillsForSystemPrompt } from "../../src/harness/system-prompt.js";
|
import { formatSkillsForSystemPrompt } from "../../src/harness/system-prompt.js";
|
||||||
|
|
||||||
|
const visibleSkill = {
|
||||||
|
name: "visible",
|
||||||
|
description: "Use <this> & that",
|
||||||
|
content: "visible content",
|
||||||
|
filePath: "/skills/visible/SKILL.md",
|
||||||
|
};
|
||||||
|
|
||||||
|
const secondSkill = {
|
||||||
|
name: "second",
|
||||||
|
description: "Second skill",
|
||||||
|
content: "second content",
|
||||||
|
filePath: "/skills/second/SKILL.md",
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabledSkill = {
|
||||||
|
name: "hidden",
|
||||||
|
description: "Hidden",
|
||||||
|
content: "hidden content",
|
||||||
|
filePath: "/skills/hidden/SKILL.md",
|
||||||
|
disableModelInvocation: true,
|
||||||
|
};
|
||||||
|
|
||||||
describe("formatSkillsForSystemPrompt", () => {
|
describe("formatSkillsForSystemPrompt", () => {
|
||||||
it("formats visible skills and skips model-disabled skills", () => {
|
it("formats visible skills in order and skips model-disabled skills", () => {
|
||||||
|
expect(formatSkillsForSystemPrompt([visibleSkill, disabledSkill, secondSkill])).toBe(
|
||||||
|
`The following skills provide specialized instructions for specific tasks.
|
||||||
|
Read the full skill file when the task matches its description.
|
||||||
|
When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.
|
||||||
|
|
||||||
|
<available_skills>
|
||||||
|
<skill>
|
||||||
|
<name>visible</name>
|
||||||
|
<description>Use <this> & that</description>
|
||||||
|
<location>/skills/visible/SKILL.md</location>
|
||||||
|
</skill>
|
||||||
|
<skill>
|
||||||
|
<name>second</name>
|
||||||
|
<description>Second skill</description>
|
||||||
|
<location>/skills/second/SKILL.md</location>
|
||||||
|
</skill>
|
||||||
|
</available_skills>`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an empty string when no skills are model-visible", () => {
|
||||||
|
expect(formatSkillsForSystemPrompt([disabledSkill])).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("escapes XML in all model-visible skill fields", () => {
|
||||||
expect(
|
expect(
|
||||||
formatSkillsForSystemPrompt([
|
formatSkillsForSystemPrompt([
|
||||||
{
|
{
|
||||||
name: "visible",
|
name: "a&b",
|
||||||
description: "Use <this> & that",
|
description: `Quote "double" and 'single'`,
|
||||||
content: "visible content",
|
content: "content",
|
||||||
filePath: "/skills/visible/SKILL.md",
|
filePath: '/skills/<bad>&"quote"/SKILL.md',
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "hidden",
|
|
||||||
description: "Hidden",
|
|
||||||
content: "hidden content",
|
|
||||||
filePath: "/skills/hidden/SKILL.md",
|
|
||||||
disableModelInvocation: true,
|
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
).toContain("<name>visible</name>");
|
).toContain(
|
||||||
expect(
|
"<name>a&b</name>\n <description>Quote "double" and 'single'</description>\n <location>/skills/<bad>&"quote"/SKILL.md</location>",
|
||||||
formatSkillsForSystemPrompt([
|
);
|
||||||
{
|
|
||||||
name: "visible",
|
|
||||||
description: "Use <this> & that",
|
|
||||||
content: "visible content",
|
|
||||||
filePath: "/skills/visible/SKILL.md",
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
).toContain("Use <this> & that");
|
|
||||||
expect(
|
|
||||||
formatSkillsForSystemPrompt([
|
|
||||||
{
|
|
||||||
name: "hidden",
|
|
||||||
description: "Hidden",
|
|
||||||
content: "hidden content",
|
|
||||||
filePath: "/skills/hidden/SKILL.md",
|
|
||||||
disableModelInvocation: true,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
).toBe("");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user