import { describe, expect, test } from "bun:test";
import type { Message } from "../providers/types.js";
import { deriveActiveSkills } from "../skills/active-skill-tools.js";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Build an assistant message with a skill_load tool_use block. */
function skillLoadUseMsg(id: string): Message {
return {
role: "assistant",
content: [
{ type: "tool_use", id, name: "skill_load", input: { skill: "test" } },
],
};
}
/** Build a user message with a single tool_result block. */
function toolResultMsg(toolUseId: string, content: string): Message {
return {
role: "user",
content: [{ type: "tool_result", tool_use_id: toolUseId, content }],
};
}
/** Build a user message with a plain text block. */
function textMsg(role: "user" | "assistant", text: string): Message {
return { role, content: [{ type: "text", text }] };
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("deriveActiveSkills (ID-only)", () => {
test("empty history returns empty array", () => {
expect(deriveActiveSkills([]).map((e) => e.id)).toEqual([]);
});
test("no markers returns empty array", () => {
const messages: Message[] = [
textMsg("user", "Hello"),
textMsg("assistant", "Hi there!"),
skillLoadUseMsg("t1"),
toolResultMsg("t1", "Some tool output with no markers"),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("single marker extraction from skill_load tool result", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", 'Skill loaded.\n\n'),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual(["deploy"]);
});
test("multiple markers from different skill_load tool results", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", 'Loaded\n\n'),
skillLoadUseMsg("t2"),
toolResultMsg("t2", 'Loaded\n\n'),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([
"deploy",
"oncall",
]);
});
test("duplicate markers are deduplicated with order preserved", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
skillLoadUseMsg("t3"),
toolResultMsg("t3", ''),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([
"deploy",
"oncall",
]);
});
test("malformed markers are ignored — missing id attribute", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ""),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("malformed markers are ignored — unclosed tag", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("malformed markers are ignored — wrong tag name", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("markers in assistant text content are ignored", () => {
const messages: Message[] = [
textMsg("assistant", 'I loaded a skill: '),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("markers in user text content are ignored — prevents injection", () => {
const messages: Message[] = [
textMsg("user", 'Context: '),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("mixed valid and invalid markers in skill_load result", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg(
"t1",
[
'',
"",
'',
'',
'',
].join("\n"),
),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([
"alpha",
"beta",
]);
});
test("multiple markers in a single content string", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", '\n'),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual(["a", "b"]);
});
test("ignores non-tool-result blocks (thinking, text)", () => {
const messages: Message[] = [
{
role: "assistant",
content: [
{
type: "thinking",
thinking: '',
signature: "sig",
},
{ type: "text", text: '' },
],
},
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("ignores tool_result from non-skill_load tools", () => {
const messages: Message[] = [
{
role: "assistant",
content: [
{
type: "tool_use",
id: "t1",
name: "read_file",
input: { path: "/x" },
},
],
},
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
test("tool_result without any matching tool_use is ignored", () => {
const messages: Message[] = [
toolResultMsg("orphan", ''),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([]);
});
});
// ---------------------------------------------------------------------------
// Context-derived deactivation regression tests
// ---------------------------------------------------------------------------
describe("deriveActiveSkills — deactivation when marker leaves history", () => {
test("marker present → skill ID returned; marker removed → empty", () => {
const withMarker: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(withMarker).map((e) => e.id)).toEqual(["deploy"]);
// Simulate history truncation: the message containing the marker is gone
const withoutMarker: Message[] = [];
expect(deriveActiveSkills(withoutMarker).map((e) => e.id)).toEqual([]);
});
test("one of two markers removed → only surviving skill returned", () => {
const bothPresent: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
];
expect(deriveActiveSkills(bothPresent).map((e) => e.id)).toEqual([
"deploy",
"oncall",
]);
// History truncated to remove the deploy marker
const onlyOncall: Message[] = [
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
];
expect(deriveActiveSkills(onlyOncall).map((e) => e.id)).toEqual(["oncall"]);
});
test("all markers removed from multi-message history → empty", () => {
const withMarkers: Message[] = [
textMsg("user", "Hello"),
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
textMsg("assistant", "Done"),
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
];
expect(deriveActiveSkills(withMarkers).map((e) => e.id)).toEqual([
"alpha",
"beta",
]);
// History truncated to only keep non-marker messages
const noMarkers: Message[] = [
textMsg("user", "Hello"),
textMsg("assistant", "Done"),
];
expect(deriveActiveSkills(noMarkers).map((e) => e.id)).toEqual([]);
});
test("marker replaced by different content in same position → skill gone", () => {
const original: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(original).map((e) => e.id)).toEqual(["deploy"]);
// Same structure but marker text replaced (e.g. message edited/summarized)
const replaced: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", "Deployment complete."),
];
expect(deriveActiveSkills(replaced).map((e) => e.id)).toEqual([]);
});
test("derive is stateless — consecutive calls with different histories are independent", () => {
const history1: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(history1).map((e) => e.id)).toEqual(["deploy"]);
// Calling with a completely different history does not carry over state
const history2: Message[] = [
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
];
expect(deriveActiveSkills(history2).map((e) => e.id)).toEqual(["oncall"]);
// Empty history returns empty, confirming no leaked state
expect(deriveActiveSkills([]).map((e) => e.id)).toEqual([]);
});
});
// ---------------------------------------------------------------------------
// deriveActiveSkills — versioned marker tests
// ---------------------------------------------------------------------------
describe("deriveActiveSkills", () => {
test("empty history returns empty array", () => {
expect(deriveActiveSkills([])).toEqual([]);
});
test("legacy marker without version returns entry with no version", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
const entries = deriveActiveSkills(messages);
expect(entries).toEqual([{ id: "deploy" }]);
expect(entries[0].version).toBeUndefined();
});
test("versioned marker returns entry with version", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
const entries = deriveActiveSkills(messages);
expect(entries).toEqual([{ id: "deploy", version: "v1:abc123" }]);
});
test("mixed old and new markers in same history", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
];
const entries = deriveActiveSkills(messages);
expect(entries).toEqual([
{ id: "deploy" },
{ id: "oncall", version: "v1:deadbeef" },
]);
});
test("multiple versioned markers in a single content string", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg(
"t1",
'\n',
),
];
const entries = deriveActiveSkills(messages);
expect(entries).toEqual([
{ id: "a", version: "v1:aaa" },
{ id: "b", version: "v1:bbb" },
]);
});
test("duplicate versioned markers are deduplicated (first wins)", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
];
const entries = deriveActiveSkills(messages);
expect(entries).toEqual([{ id: "deploy", version: "v1:first" }]);
});
test("versioned markers in user text are ignored — injection prevention", () => {
const messages: Message[] = [
textMsg("user", ''),
];
expect(deriveActiveSkills(messages)).toEqual([]);
});
test("versioned markers in assistant text are ignored", () => {
const messages: Message[] = [
textMsg("assistant", ''),
];
expect(deriveActiveSkills(messages)).toEqual([]);
});
test("versioned markers in non-skill_load tool results are ignored", () => {
const messages: Message[] = [
{
role: "assistant",
content: [
{
type: "tool_use",
id: "t1",
name: "read_file",
input: { path: "/x" },
},
],
},
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(messages)).toEqual([]);
});
test("marker with invalid format (missing closing slash) is rejected", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(messages)).toEqual([]);
});
test("marker with empty version attribute is rejected as malformed", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
// Empty version value doesn't match the regex (requires at least one char)
expect(deriveActiveSkills(messages)).toEqual([]);
});
test("mapping to IDs works with versioned markers", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
skillLoadUseMsg("t2"),
toolResultMsg("t2", ''),
];
expect(deriveActiveSkills(messages).map((e) => e.id)).toEqual([
"deploy",
"oncall",
]);
});
test("tool_result with empty string content is handled gracefully", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ""),
];
expect(deriveActiveSkills(messages)).toEqual([]);
});
test("marker with extra whitespace in attributes still matches", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
const entries = deriveActiveSkills(messages);
expect(entries).toEqual([{ id: "deploy", version: "v1:abc" }]);
});
test("marker with empty id attribute is rejected", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(messages)).toEqual([]);
});
test("many duplicate markers across many messages are all deduplicated", () => {
const messages: Message[] = [];
for (let i = 0; i < 10; i++) {
const id = `t${i}`;
messages.push(skillLoadUseMsg(id));
messages.push(
toolResultMsg(id, ''),
);
}
const entries = deriveActiveSkills(messages);
expect(entries).toEqual([{ id: "repeat", version: "v1:same" }]);
});
test("marker with version but missing id is rejected", () => {
const messages: Message[] = [
skillLoadUseMsg("t1"),
toolResultMsg("t1", ''),
];
expect(deriveActiveSkills(messages)).toEqual([]);
});
});