refactor(agent): finalize harness resource config
This commit is contained in:
82
packages/agent/test/harness/agent-harness.test.ts
Normal file
82
packages/agent/test/harness/agent-harness.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { getModel } from "@earendil-works/pi-ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AgentHarness } from "../../src/harness/agent-harness.js";
|
||||
import { NodeExecutionEnv } from "../../src/harness/execution-env.js";
|
||||
import { Session } from "../../src/harness/session/session.js";
|
||||
import { InMemorySessionStorage } from "../../src/harness/session/storage/memory.js";
|
||||
import type { PromptTemplate, Skill } from "../../src/harness/types.js";
|
||||
import type { AgentTool } from "../../src/types.js";
|
||||
|
||||
interface AppSkill extends Skill {
|
||||
source: "project" | "user";
|
||||
}
|
||||
|
||||
interface AppPromptTemplate extends PromptTemplate {
|
||||
source: "project" | "user";
|
||||
}
|
||||
|
||||
interface AppTool extends AgentTool {
|
||||
source: "builtin" | "extension";
|
||||
}
|
||||
|
||||
describe("AgentHarness", () => {
|
||||
it("constructs directly and exposes queue modes", () => {
|
||||
const session = new Session(new InMemorySessionStorage());
|
||||
const env = new NodeExecutionEnv({ cwd: process.cwd() });
|
||||
const initialModel = getModel("anthropic", "claude-sonnet-4-5");
|
||||
const harness = new AgentHarness({
|
||||
env,
|
||||
session,
|
||||
model: initialModel,
|
||||
systemPrompt: "You are helpful.",
|
||||
steeringMode: "all",
|
||||
followUpMode: "all",
|
||||
});
|
||||
expect(harness.env).toBe(env);
|
||||
expect(harness.agent.state.model).toBe(initialModel);
|
||||
expect(harness.steeringMode).toBe("all");
|
||||
expect(harness.followUpMode).toBe("all");
|
||||
harness.steeringMode = "one-at-a-time";
|
||||
harness.followUpMode = "one-at-a-time";
|
||||
expect(harness.agent.steeringMode).toBe("one-at-a-time");
|
||||
expect(harness.agent.followUpMode).toBe("one-at-a-time");
|
||||
});
|
||||
|
||||
it("preserves app resource types for getters and update events", async () => {
|
||||
const session = new Session(new InMemorySessionStorage());
|
||||
const env = new NodeExecutionEnv({ cwd: process.cwd() });
|
||||
const model = getModel("anthropic", "claude-sonnet-4-5");
|
||||
const harness = new AgentHarness<AppSkill, AppPromptTemplate, AppTool>({ env, session, model });
|
||||
const skill: AppSkill = {
|
||||
name: "inspect",
|
||||
description: "Inspect things",
|
||||
content: "Use inspection tools.",
|
||||
filePath: "/skills/inspect/SKILL.md",
|
||||
source: "project",
|
||||
};
|
||||
const promptTemplate: AppPromptTemplate = { name: "review", content: "Review $1", source: "user" };
|
||||
const resources = { skills: [skill], promptTemplates: [promptTemplate] };
|
||||
const updates: Array<{ resourcesSource?: string; previousSource?: string }> = [];
|
||||
harness.subscribe((event) => {
|
||||
if (event.type === "resources_update") {
|
||||
updates.push({
|
||||
resourcesSource: event.resources.skills?.[0]?.source,
|
||||
previousSource: event.previousResources.skills?.[0]?.source,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await harness.setResources(resources);
|
||||
await harness.setResources(resources);
|
||||
const resolved = harness.getResources();
|
||||
|
||||
expect(updates).toEqual([
|
||||
{ resourcesSource: "project", previousSource: undefined },
|
||||
{ resourcesSource: "project", previousSource: "project" },
|
||||
]);
|
||||
expect(resolved.skills?.[0]?.source).toBe("project");
|
||||
expect(resolved.promptTemplates?.[0]?.source).toBe("user");
|
||||
expect(resolved.skills).not.toBe(resources.skills);
|
||||
expect(resolved.promptTemplates).not.toBe(resources.promptTemplates);
|
||||
});
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import { getModel } from "@earendil-works/pi-ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { NodeExecutionEnv } from "../../src/harness/execution-env.js";
|
||||
import { createAgentHarness, createSession } from "../../src/harness/factory.js";
|
||||
import { InMemorySessionStorage } from "../../src/harness/session/storage/memory.js";
|
||||
|
||||
describe("harness factories", () => {
|
||||
it("creates sessions from storage", async () => {
|
||||
const storage = new InMemorySessionStorage({
|
||||
metadata: { id: "session-1", createdAt: "2026-01-01T00:00:00.000Z" },
|
||||
});
|
||||
const session = createSession(storage);
|
||||
expect(session.getStorage()).toBe(storage);
|
||||
expect(await session.getMetadata()).toEqual({ id: "session-1", createdAt: "2026-01-01T00:00:00.000Z" });
|
||||
});
|
||||
|
||||
it("creates agent harnesses", () => {
|
||||
const session = createSession(new InMemorySessionStorage());
|
||||
const env = new NodeExecutionEnv({ cwd: process.cwd() });
|
||||
const initialModel = getModel("anthropic", "claude-sonnet-4-5");
|
||||
const harness = createAgentHarness({
|
||||
env,
|
||||
session,
|
||||
model: initialModel,
|
||||
systemPrompt: "You are helpful.",
|
||||
steeringMode: "all",
|
||||
followUpMode: "all",
|
||||
});
|
||||
expect(harness.env).toBe(env);
|
||||
expect(harness.agent.state.model).toBe(initialModel);
|
||||
expect(harness.steeringMode).toBe("all");
|
||||
expect(harness.followUpMode).toBe("all");
|
||||
harness.steeringMode = "one-at-a-time";
|
||||
harness.followUpMode = "one-at-a-time";
|
||||
expect(harness.agent.steeringMode).toBe("one-at-a-time");
|
||||
expect(harness.agent.followUpMode).toBe("one-at-a-time");
|
||||
});
|
||||
|
||||
it("updates and reads concrete resources", async () => {
|
||||
const session = createSession(new InMemorySessionStorage());
|
||||
const env = new NodeExecutionEnv({ cwd: process.cwd() });
|
||||
const model = getModel("anthropic", "claude-sonnet-4-5");
|
||||
const harness = createAgentHarness({ env, session, model });
|
||||
const skill = {
|
||||
name: "inspect",
|
||||
description: "Inspect things",
|
||||
content: "Use inspection tools.",
|
||||
filePath: "/skills/inspect/SKILL.md",
|
||||
};
|
||||
const resources = { skills: [skill], promptTemplates: [{ name: "review", content: "Review $1" }] };
|
||||
const updates: unknown[] = [];
|
||||
harness.subscribe((event) => {
|
||||
if (event.type === "resources_update") updates.push(event.resources);
|
||||
});
|
||||
|
||||
await harness.setResources(resources);
|
||||
const resolved = harness.getResources();
|
||||
|
||||
expect(updates).toEqual([resources]);
|
||||
expect(resolved).toEqual(resources);
|
||||
expect(resolved.skills).not.toBe(resources.skills);
|
||||
expect(resolved.promptTemplates).not.toBe(resources.promptTemplates);
|
||||
});
|
||||
});
|
||||
@@ -3,30 +3,39 @@ import { join } from "node:path";
|
||||
import { getModel } from "@earendil-works/pi-ai";
|
||||
import { InMemorySessionStorage } from "../../src/harness/session/storage/memory.js";
|
||||
import {
|
||||
createAgentHarness,
|
||||
AgentHarness,
|
||||
formatSkillsForSystemPrompt,
|
||||
loadSourcedPromptTemplates,
|
||||
loadSourcedSkills,
|
||||
NodeExecutionEnv,
|
||||
type PromptTemplate,
|
||||
Session,
|
||||
type Skill,
|
||||
} from "../../src/index.js";
|
||||
|
||||
type Source = { type: "project" | "user" | "path"; dir: string };
|
||||
type SourcedSkill = Skill & { source: Source };
|
||||
type SourcedPromptTemplate = PromptTemplate & { source: Source };
|
||||
|
||||
const env = new NodeExecutionEnv({ cwd: process.cwd() });
|
||||
const source = (type: Source["type"], dir: string) => ({ path: dir, source: { type, dir } });
|
||||
const { skills: sourcedSkills } = await loadSourcedSkills<Source>(env, [
|
||||
source("project", join(env.cwd, ".pi/skills")),
|
||||
source("user", join(homedir(), ".pi/agent/skills")),
|
||||
source("path", join(env.cwd, "../../../pi-skills")),
|
||||
]);
|
||||
const { promptTemplates: sourcedPromptTemplates } = await loadSourcedPromptTemplates<Source>(env, [
|
||||
source("project", join(env.cwd, ".pi/prompts")),
|
||||
source("user", join(homedir(), ".pi/agent/prompts")),
|
||||
]);
|
||||
const { skills: sourcedSkills } = await loadSourcedSkills<Source, SourcedSkill>(
|
||||
env,
|
||||
[
|
||||
source("project", join(env.cwd, ".pi/skills")),
|
||||
source("user", join(homedir(), ".pi/agent/skills")),
|
||||
source("path", join(env.cwd, "../../../pi-skills")),
|
||||
],
|
||||
(skill, source) => ({ ...skill, source }),
|
||||
);
|
||||
const { promptTemplates: sourcedPromptTemplates } = await loadSourcedPromptTemplates<Source, SourcedPromptTemplate>(
|
||||
env,
|
||||
[source("project", join(env.cwd, ".pi/prompts")), source("user", join(homedir(), ".pi/agent/prompts"))],
|
||||
(promptTemplate, source) => ({ ...promptTemplate, source }),
|
||||
);
|
||||
|
||||
const session = new Session(new InMemorySessionStorage());
|
||||
const agent = createAgentHarness({
|
||||
const agent = new AgentHarness({
|
||||
env,
|
||||
session,
|
||||
model: getModel("openai", "gpt-5.5"),
|
||||
|
||||
Reference in New Issue
Block a user