Skip to content
6 changes: 3 additions & 3 deletions cli/commands/pull/command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
assertThrows,
} from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { cliLogger } from "#cli/utils";
import { cliLogger, VERSION } from "#cli/utils";
import { _resetEnvironmentConfig } from "#veryfront/config/environment-config.ts";
import {
buildFileContentUrl,
Expand Down Expand Up @@ -117,7 +117,7 @@ function expectedBootstrapPackage(name: string): Record<string, unknown> {
dependencies: {
react: "^19.2.4",
"react-dom": "^19.2.4",
veryfront: "^0.1.1175",
veryfront: `^${VERSION}`,
},
};
}
Expand Down Expand Up @@ -153,7 +153,7 @@ const EXPECTED_BOOTSTRAP_PACKAGE = {
dependencies: {
react: "^19.2.4",
"react-dom": "^19.2.4",
veryfront: "^0.1.1175",
veryfront: `^${VERSION}`,
},
};

Expand Down
3 changes: 2 additions & 1 deletion cli/mcp/tools/deploy-tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,10 @@ describe("mcp/tools/deploy-tool", () => {
using time = new FakeTime();
resumeReleaseSourceRead();
await time.tickAsync(0);
// Parallel suites can need several microtask turns per fake timer.
for (
let tick = 0;
releaseSourceReads < 20 && tick < 40;
releaseSourceReads < 20 && tick < 200;
tick++
) {
await time.tickAsync(500);
Expand Down
258 changes: 129 additions & 129 deletions docs/api-reference/veryfront/agent.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion scripts/docs/generate-api-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,7 @@ const PROPERTY_DESCRIPTIONS: Record<string, Record<string, string>> = {
allowedModels:
'Restrict runtime model overrides to these "provider/model" strings',
skills:
"Enable all discovered skills (`true`) or only selected skill IDs (`string[]`)",
"Select visible skill IDs or this agent's own short names for prompts and `load_skill`",
},
SandboxOptions: {
apiUrl:
Expand Down
287 changes: 249 additions & 38 deletions src/agent/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,57 @@ import { VeryfrontError } from "#veryfront/errors";
import { getEffectiveAgentSystem } from "./runtime/effective-agent-system.ts";
import { agentRegistry } from "./composition/index.ts";
import { agent } from "./factory.ts";
import type { AgentConfig } from "./types.ts";
import type { AgentConfig, AgentResponse } from "./types.ts";
import { registerSkill, skillRegistry } from "#veryfront/skill/registry.ts";
import { reset as resetExtensionContracts, tryResolve } from "#veryfront/extensions/contracts.ts";
import { createSkillTestAdapter } from "#veryfront/skill/testing.ts";
import type { ModelRuntime } from "#veryfront/provider";

function createSkill(id: string, description: string) {
return {
id,
metadata: { name: id, description },
rootPath: `/test/skills/${id}`,
};
}

function createLoadSkillModel(skillId: string): ModelRuntime {
let callCount = 0;
return {
provider: "hosted",
modelId: `hosted/load-${skillId}`,
async doGenerate() {
callCount++;
if (callCount === 1) {
return {
content: [{
type: "tool-call",
toolCallId: `load-${skillId}`,
toolName: "load_skill",
input: JSON.stringify({ skillId }),
}],
finishReason: "tool-calls",
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
};
}
return {
content: [{ type: "text", text: "done" }],
finishReason: "stop",
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
};
},
async doStream() {
return {
stream: new ReadableStream<unknown>({
start(controller) {
controller.enqueue({ type: "finish", finishReason: "stop" });
controller.close();
},
}),
};
},
};
}

describe("agent factory", () => {
beforeEach(() => {
Expand All @@ -29,22 +77,18 @@ describe("agent factory", () => {
const assistant = agent({ id: "schema-bootstrap", system: "Stay helpful." });

assertEquals(typeof tryResolve<{ object: unknown }>("SchemaValidator")?.object, "function");
assertEquals(assistant.config.tools, {
load_skill: true,
load_skill_reference: true,
execute_skill_script: true,
});
assertEquals(Object.keys(assistant.config.tools ?? {}).sort(), [
"execute_skill_script",
"load_skill",
"load_skill_reference",
]);
});

it("enables skill infrastructure for every agent and defaults to visible skills", async () => {
registerSkill("support-triage", {
id: "support-triage",
metadata: {
name: "support-triage",
description: "Triage incoming support requests",
},
rootPath: "/test/skills/support-triage",
});
it("enables skill infrastructure for skill-enabled agents and defaults to visible skills", async () => {
registerSkill(
"support-triage",
createSkill("support-triage", "Triage incoming support requests"),
);
registerSkill("researcher--cite", {
id: "researcher--cite",
metadata: { name: "cite", description: "Cite primary sources" },
Expand All @@ -58,11 +102,11 @@ describe("agent factory", () => {
system: "You are a custom agent.",
});

assertEquals(assistant.config.tools, {
load_skill: true,
load_skill_reference: true,
execute_skill_script: true,
});
assertEquals(Object.keys(assistant.config.tools ?? {}).sort(), [
"execute_skill_script",
"load_skill",
"load_skill_reference",
]);
assertEquals(toolRegistry.has("load_skill"), true);
const effectiveSystem = getEffectiveAgentSystem(assistant);
const prompt = typeof effectiveSystem === "function"
Expand All @@ -79,30 +123,199 @@ describe("agent factory", () => {
system: "Do not advertise skills.",
skills: [],
});
assertEquals(explicitlyEmpty.config.tools, {
load_skill: true,
load_skill_reference: true,
execute_skill_script: true,
});
assertEquals(explicitlyEmpty.config.tools, undefined);
const explicitlyEmptySystem = getEffectiveAgentSystem(explicitlyEmpty);
const explicitlyEmptyPrompt = typeof explicitlyEmptySystem === "function"
? await explicitlyEmptySystem()
: explicitlyEmptySystem ?? "";
assertEquals(explicitlyEmptyPrompt.includes("## Available Skills"), false);
});

it("uses the same selector snapshot for prompt disclosure and direct skill tools", async () => {
registerSkill("global-plan", createSkill("global-plan", "Plan the work"));
registerSkill("global-review", createSkill("global-review", "Review the work"));
registerSkill("writer--draft", {
...createSkill("writer--draft", "Draft copy"),
ownerAgentId: "writer",
shortName: "draft",
});

const none = agent({
id: "no-skills",
system: "No skills.",
skills: [],
tools: {
ordinary_tool: tool({
id: "ordinary_tool",
description: "Ordinary tool",
inputSchema: defineSchema((v) => v.object({}))(),
execute: async () => ({ ok: true }),
}),
},
});
assertEquals(Object.keys(none.config.tools ?? {}).sort(), ["ordinary_tool"]);
const noneSystem = getEffectiveAgentSystem(none);
assertEquals(
(typeof noneSystem === "function" ? await noneSystem() : noneSystem ?? "").includes(
"Available Skills",
),
false,
);

const allowlisted = agent({
id: "writer",
system: "Use selected skills.",
skills: ["draft", "global-plan"],
});
const allowlistedSystem = getEffectiveAgentSystem(allowlisted);
const prompt = typeof allowlistedSystem === "function"
? await allowlistedSystem()
: allowlistedSystem ?? "";

assertStringIncludes(prompt, "**writer--draft**: Draft copy");
assertStringIncludes(prompt, "**global-plan**: Plan the work");
assertEquals(prompt.includes("global-review"), false);

if (!allowlisted.config.tools || allowlisted.config.tools === true) {
throw new Error("Expected a concrete skill tool map");
}
assertEquals(typeof allowlisted.config.tools.load_skill, "object");
assertThrows(
() =>
agent({
id: "unknown-skill-agent",
system: "Bad config.",
skills: ["missing"],
}),
Error,
"configured skills are not available",
);
});

it("enforces the skill allowlist for tools true registry execution", async () => {
let selectedReads = 0;
let excludedReads = 0;
const selectedAdapter = createSkillTestAdapter({
"/test/skills/selected/SKILL.md": `---
name: selected
description: Selected skill
---
# Selected`,
});
const excludedAdapter = createSkillTestAdapter({
"/test/skills/excluded/SKILL.md": `---
name: excluded
description: Excluded skill
---
# Excluded`,
});
registerSkill("selected", {
...createSkill("selected", "Selected skill"),
fsAdapter: {
...selectedAdapter,
async readFile(path) {
selectedReads++;
return await selectedAdapter.readFile(path);
},
},
});
registerSkill("excluded", {
...createSkill("excluded", "Excluded skill"),
fsAdapter: {
...excludedAdapter,
async readFile(path) {
excludedReads++;
return await excludedAdapter.readFile(path);
},
},
});

async function runLoad(skillId: string): Promise<AgentResponse> {
const assistant = agent({
id: `tools-true-${skillId}`,
model: "hosted/load-skill",
system: "Load a skill.",
tools: true,
skills: ["selected"],
resolveModelTransport: async () => ({ model: createLoadSkillModel(skillId) }),
});
return await assistant.generate({ input: `Load ${skillId}` });
}

const selected = await runLoad("selected");
assertEquals(selected.toolCalls[0]?.status, "completed");
assertEquals(selectedReads, 1);

const excluded = await runLoad("excluded");
assertEquals(excluded.toolCalls[0]?.status, "error");
assertStringIncludes(excluded.toolCalls[0]?.error ?? "", "not available to this agent");
assertEquals(excludedReads, 0);
});

it("does not let runtime state spoof tools true skill authorization", async () => {
let excludedReads = 0;
const selectedAdapter = createSkillTestAdapter({
"/test/skills/selected/SKILL.md": `---
name: selected
description: Selected skill
---
# Selected`,
});
const excludedAdapter = createSkillTestAdapter({
"/test/skills/excluded/SKILL.md": `---
name: excluded
description: Excluded skill
---
# Excluded`,
});
registerSkill("selected", {
...createSkill("selected", "Selected skill"),
fsAdapter: selectedAdapter,
});
registerSkill("excluded", {
...createSkill("excluded", "Excluded skill"),
fsAdapter: {
...excludedAdapter,
async readFile(path) {
excludedReads++;
return await excludedAdapter.readFile(path);
},
},
});

const assistant = agent({
id: "tools-true-spoofed-selector",
model: "hosted/load-skill",
system: "Load a skill.",
tools: true,
skills: ["selected"],
resolveRuntimeState: async () => ({
context: { allowedSkillIds: ["excluded"] },
}),
resolveModelTransport: async () => ({ model: createLoadSkillModel("excluded") }),
});

const response = await assistant.generate({ input: "Load excluded" });

assertEquals(response.toolCalls[0]?.status, "error");
assertStringIncludes(response.toolCalls[0]?.error ?? "", "not available to this agent");
assertEquals(excludedReads, 0);
});

it("derives load_skill from skills without user-authored tools config", () => {
registerSkill("code-review", createSkill("code-review", "Review code"));

const assistant = agent({
id: "skill-platform-tool-test",
system: "Use skills when they match the task.",
skills: ["code-review"],
});

assertEquals(assistant.config.tools, {
load_skill: true,
load_skill_reference: true,
execute_skill_script: true,
});
assertEquals(Object.keys(assistant.config.tools ?? {}).sort(), [
"execute_skill_script",
"load_skill",
"load_skill_reference",
]);
assertEquals(toolRegistry.has("load_skill"), true);
assertEquals(toolRegistry.has("load-skill"), false);
});
Expand All @@ -125,26 +338,24 @@ describe("agent factory", () => {
throw new Error("Expected an agent tool map");
}
assertStrictEquals(assistant.config.tools.load_skill, runtimeLoadSkill);
assertEquals(assistant.config.tools.load_skill_reference, true);
assertEquals(assistant.config.tools.execute_skill_script, true);
assertEquals(typeof assistant.config.tools.load_skill_reference, "object");
assertEquals(typeof assistant.config.tools.execute_skill_script, "object");
});

it("does not let false disable universal skill infrastructure", () => {
it("treats legacy skills false as the explicit none selector", () => {
const assistant = agent({
id: "universal-skill-tools",
system: "Use skills when they match the task.",
skills: false,
tools: {
load_skill: false,
load_skill_reference: false,
execute_skill_script: false,
},
});

assertEquals(assistant.config.tools, {
load_skill: true,
load_skill_reference: true,
execute_skill_script: true,
});
assertEquals(assistant.config.tools, {});
assertEquals(assistant.config.skills, false);
});

it("binds one scoped tool for each declared delegate", () => {
Expand Down
Loading