refactor(agent): make resource invocation explicit

This commit is contained in:
Mario Zechner
2026-05-08 13:35:54 +02:00
parent 3680418251
commit e1647aaa00
5 changed files with 46 additions and 30 deletions

View File

@@ -5,6 +5,7 @@ import type { AgentEvent, AgentMessage, AgentTool, ThinkingLevel } from "../type
import { collectEntriesForBranchSummary, generateBranchSummary } from "./compaction/branch-summarization.js";
import { compact, DEFAULT_COMPACTION_SETTINGS, prepareCompaction } from "./compaction/compaction.js";
import { expandPromptTemplate } from "./prompt-templates.js";
import { expandSkillCommand } from "./skills.js";
import type {
AbortResult,
AgentHarnessContext,
@@ -18,7 +19,6 @@ import type {
ExecutionEnv,
NavigateTreeResult,
Session,
Skill,
} from "./types.js";
function createUserMessage(text: string, images?: ImageContent[]): AgentMessage {
@@ -297,11 +297,7 @@ export class AgentHarness {
this.operation.idle = false;
this.operation.liveOperationId = randomUUID();
const resources = await this.resolveResources(this.agent.signal);
const expanded = this.expandSkillCommand(
expandPromptTemplate(text, resources.promptTemplates ?? []),
resources.skills ?? [],
);
let messages: AgentMessage[] = [createUserMessage(expanded, options?.images)];
let messages: AgentMessage[] = [createUserMessage(text, options?.images)];
if (this.conversation.nextTurnQueue.length > 0) {
messages = [messages[0]!, ...this.conversation.nextTurnQueue];
this.conversation.nextTurnQueue = [];
@@ -312,7 +308,7 @@ export class AgentHarness {
"before_agent_start",
{
type: "before_agent_start",
prompt: expanded,
prompt: text,
images: options?.images,
systemPrompt: this.agent.state.systemPrompt,
resources,
@@ -335,12 +331,18 @@ export class AgentHarness {
return response;
}
async skill(name: string, args?: string): Promise<AssistantMessage> {
async skill(name: string, additionalInstructions?: string): Promise<AssistantMessage> {
const resources = await this.resolveResources();
const skill = (resources.skills ?? []).find((candidate) => candidate.name === name);
if (!skill) throw new Error(`Unknown skill: ${name}`);
const prompt = args ? `${skill.content}\n\n${args}` : skill.content;
return await this.prompt(prompt);
return await this.prompt(expandSkillCommand(skill, additionalInstructions));
}
async promptFromTemplate(name: string, args: string[] = []): Promise<AssistantMessage> {
const resources = await this.resolveResources();
const template = (resources.promptTemplates ?? []).find((candidate) => candidate.name === name);
if (!template) throw new Error(`Unknown prompt template: ${name}`);
return await this.prompt(expandPromptTemplate(template, args));
}
steer(message: AgentMessage): void {
@@ -601,14 +603,4 @@ export class AgentHarness {
handlers.add(handler as any);
return () => handlers!.delete(handler as any);
}
private expandSkillCommand(text: string, skills: Skill[]): string {
if (!text.startsWith("/skill:")) return text;
const spaceIndex = text.indexOf(" ");
const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
const skill = skills.find((candidate) => candidate.name === skillName);
if (!skill) return text;
return args ? `${skill.content}\n\n${args}` : skill.content;
}
}

View File

@@ -211,13 +211,7 @@ export function substituteArgs(content: string, args: string[]): string {
return result;
}
/** Expand `/template args` text using the matching prompt template, or return the original text when no template matches. */
export function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {
if (!text.startsWith("/")) return text;
const spaceIndex = text.indexOf(" ");
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
const template = templates.find((candidate) => candidate.name === commandName);
if (!template) return text;
return substituteArgs(template.content, parseCommandArgs(argsString));
/** Expand a prompt template with positional command arguments. */
export function expandPromptTemplate(template: PromptTemplate, args: string[] = []): string {
return substituteArgs(template.content, args);
}

View File

@@ -25,6 +25,12 @@ interface SkillFrontmatter {
[key: string]: unknown;
}
/** Expand a skill into a prompt, optionally appending additional user instructions. */
export function expandSkillCommand(skill: Skill, additionalInstructions?: string): string {
const skillBlock = `<skill name="${skill.name}" location="${skill.filePath}">\nReferences are relative to ${dirnameEnvPath(skill.filePath)}.\n\n${skill.content}\n</skill>`;
return additionalInstructions ? `${skillBlock}\n\n${additionalInstructions}` : skillBlock;
}
/**
* Load skills from one or more directories.
*

View File

@@ -83,7 +83,7 @@ describe("loadPromptTemplates", () => {
describe("expandPromptTemplate", () => {
it("substitutes command arguments", () => {
const content = "$1 $" + "{@:2} $ARGUMENTS";
expect(expandPromptTemplate('/one "hello world" test', [{ name: "one", content }])).toBe(
expect(expandPromptTemplate({ name: "one", content }, ["hello world", "test"])).toBe(
"hello world test hello world test",
);
});

View File

@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { expandPromptTemplate } from "../../src/harness/prompt-templates.js";
import { expandSkillCommand } from "../../src/harness/skills.js";
describe("resource expansion helpers", () => {
it("expands skills with additional instructions", () => {
const skill = {
name: "inspect",
description: "Inspect things",
content: "Use inspection tools.",
filePath: "/project/.pi/skills/inspect/SKILL.md",
};
expect(expandSkillCommand(skill, "Check errors.")).toBe(
'<skill name="inspect" location="/project/.pi/skills/inspect/SKILL.md">\nReferences are relative to /project/.pi/skills/inspect.\n\nUse inspection tools.\n</skill>\n\nCheck errors.',
);
});
it("expands prompt templates with positional arguments", () => {
expect(expandPromptTemplate({ name: "review", content: "Review $1 with $ARGUMENTS" }, ["a.ts", "care"])).toBe(
"Review a.ts with a.ts care",
);
});
});