A skill directory may hold INSTRUCTIONS.md, with or without SKILL.md. Its body (frontmatter cut) goes into every system prompt in <skill_instructions>, re-read on every run: 16 KB per file, 64 KB in total, truncation marked with the file's path. /skills marks skills with instructions and lists instruction-only dirs.
71 lines
3.2 KiB
C#
71 lines
3.2 KiB
C#
using Volcanic.AgenticCore.Telegram.Tests.Support;
|
||
using Volcanic.AgenticCore.Testing;
|
||
|
||
namespace Volcanic.AgenticCore.Telegram.Tests;
|
||
|
||
public sealed class SkillsChatTests
|
||
{
|
||
private static void WriteSkill(string workspace, string dir, string name, string description)
|
||
{
|
||
var path = Path.Combine(workspace, ".volcanic", "skills", dir);
|
||
Directory.CreateDirectory(path);
|
||
File.WriteAllText(Path.Combine(path, "SKILL.md"), $"---\nname: {name}\ndescription: {description}\n---\nInstructions");
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Skills_lists_only_this_chats_workspace_skills()
|
||
{
|
||
await using var host = new ChatHost();
|
||
await host.SendAsync("/skills");
|
||
Assert.Equal("No skills yet. Ask me to write one, or put it in .volcanic/skills/<name>/SKILL.md.", host.LastReply());
|
||
|
||
WriteSkill(host.WorkspaceOf(), "trips", "trip-planner", "Plans trips");
|
||
WriteSkill(host.WorkspaceOf(2002), "secret", "other-chat", "Belongs to another chat");
|
||
await host.SendAsync("/skills");
|
||
|
||
Assert.Equal("Skills (.volcanic/skills/<name>/SKILL.md)\n• trip-planner – Plans trips", host.LastReply());
|
||
}
|
||
|
||
[Fact]
|
||
public async Task The_model_sees_skills_written_during_the_session_on_the_next_run()
|
||
{
|
||
await using var host = new ChatHost();
|
||
host.Faux.Enqueue(FauxTurn.Say("one"), FauxTurn.Say("two"));
|
||
await host.SendAsync("hi");
|
||
await host.SettleAsync();
|
||
Assert.Contains("No skills yet.", host.Faux.Requests.Last().SystemPrompt);
|
||
|
||
WriteSkill(host.WorkspaceOf(), "trips", "trip-planner", "Plans trips");
|
||
WriteSkill(host.WorkspaceOf(2002), "secret", "other-chat", "Belongs to another chat");
|
||
await host.SendAsync("again");
|
||
await host.SettleAsync();
|
||
|
||
var prompt = host.Faux.Requests.Last().SystemPrompt!;
|
||
Assert.Contains("<name>trip-planner</name>", prompt);
|
||
Assert.Contains(Path.Combine(host.WorkspaceOf(), ".volcanic", "skills", "trips", "SKILL.md"), prompt);
|
||
Assert.DoesNotContain("other-chat", prompt);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Skills_marks_instructions_and_lists_instruction_only_folders()
|
||
{
|
||
await using var host = new ChatHost();
|
||
var root = Path.Combine(host.WorkspaceOf(), ".volcanic", "skills");
|
||
WriteSkill(host.WorkspaceOf(), "trips", "trip-planner", "Plans trips");
|
||
WriteSkill(host.WorkspaceOf(), "mail", "mailer", "Sends mail");
|
||
File.WriteAllText(Path.Combine(root, "trips", "INSTRUCTIONS.md"), "Prices in EUR.");
|
||
Directory.CreateDirectory(Path.Combine(root, "house-rules"));
|
||
File.WriteAllText(Path.Combine(root, "house-rules", "Instructions.md"), "Be brief.");
|
||
host.Faux.Enqueue(FauxTurn.Say("ok"));
|
||
|
||
await host.SendAsync("/skills");
|
||
Assert.Equal("Skills (.volcanic/skills/<name>/SKILL.md)\n• mailer – Sends mail\n• trip-planner – Plans trips (+ INSTRUCTIONS.md)\n• house-rules – INSTRUCTIONS.md only", host.LastReply());
|
||
|
||
await host.SendAsync("hi");
|
||
await host.SettleAsync();
|
||
var prompt = host.Faux.Requests.Last().SystemPrompt!;
|
||
Assert.Contains("<instructions skill=\"house-rules\"", prompt);
|
||
Assert.Contains("Prices in EUR.", prompt);
|
||
}
|
||
}
|