feat(agent): add structured outputs and media archive support
This commit is contained in:
80
tests/lib/agent-definitions.test.ts
Normal file
80
tests/lib/agent-definitions.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
AGENT_DEFINITIONS,
|
||||
getAgentDefinition,
|
||||
} from "@/lib/agent-definitions";
|
||||
|
||||
describe("agent definitions", () => {
|
||||
it("registers exactly one runtime definition for now", () => {
|
||||
expect(AGENT_DEFINITIONS.map((definition) => definition.id)).toEqual([
|
||||
"campaign-distributor",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns campaign distributor with runtime metadata and blueprint contract", () => {
|
||||
const definition = getAgentDefinition("campaign-distributor");
|
||||
|
||||
expect(definition?.metadata.name).toBe("Campaign Distributor");
|
||||
expect(definition?.metadata.color).toBe("yellow");
|
||||
expect(definition?.docs.markdownPath).toBe(
|
||||
"components/agents/campaign-distributor.md",
|
||||
);
|
||||
expect(definition?.acceptedSourceNodeTypes).toContain("text");
|
||||
expect(definition?.briefFieldOrder).toEqual([
|
||||
"briefing",
|
||||
"audience",
|
||||
"tone",
|
||||
"targetChannels",
|
||||
"hardConstraints",
|
||||
]);
|
||||
expect(definition?.channelCatalog).toContain("Instagram Feed");
|
||||
expect(definition?.operatorParameters).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ key: "targetChannels", type: "multi-select" }),
|
||||
expect.objectContaining({ key: "variantsPerChannel", type: "select" }),
|
||||
expect.objectContaining({ key: "toneOverride", type: "select" }),
|
||||
]),
|
||||
);
|
||||
expect(definition?.analysisRules.length).toBeGreaterThan(0);
|
||||
expect(definition?.executionRules.length).toBeGreaterThan(0);
|
||||
expect(definition?.defaultOutputBlueprints).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
artifactType: "social-caption-pack",
|
||||
requiredSections: expect.arrayContaining(["Hook", "Caption"]),
|
||||
requiredMetadataKeys: expect.arrayContaining([
|
||||
"objective",
|
||||
"targetAudience",
|
||||
]),
|
||||
qualityChecks: expect.arrayContaining([
|
||||
"matches_channel_constraints",
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps shared runtime fields accessible without template-specific branching", () => {
|
||||
const definition = getAgentDefinition("campaign-distributor");
|
||||
if (!definition) {
|
||||
throw new Error("Missing definition");
|
||||
}
|
||||
|
||||
const commonProjection = {
|
||||
id: definition.id,
|
||||
markdownPath: definition.docs.markdownPath,
|
||||
sourceTypeCount: definition.acceptedSourceNodeTypes.length,
|
||||
blueprintCount: definition.defaultOutputBlueprints.length,
|
||||
};
|
||||
|
||||
expect(commonProjection).toEqual({
|
||||
id: "campaign-distributor",
|
||||
markdownPath: "components/agents/campaign-distributor.md",
|
||||
sourceTypeCount: definition.acceptedSourceNodeTypes.length,
|
||||
blueprintCount: definition.defaultOutputBlueprints.length,
|
||||
});
|
||||
expect(commonProjection.sourceTypeCount).toBeGreaterThan(0);
|
||||
expect(commonProjection.blueprintCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
88
tests/lib/agent-doc-segments.test.ts
Normal file
88
tests/lib/agent-doc-segments.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
AGENT_PROMPT_SEGMENT_KEYS,
|
||||
compileAgentDocSegmentsFromMarkdown,
|
||||
type AgentPromptSegmentKey,
|
||||
} from "@/scripts/compile-agent-docs";
|
||||
import { AGENT_DOC_SEGMENTS } from "@/lib/generated/agent-doc-segments";
|
||||
|
||||
function markedSegment(key: AgentPromptSegmentKey, content: string): string {
|
||||
return [
|
||||
`<!-- AGENT_PROMPT_SEGMENT:${key}:start -->`,
|
||||
content,
|
||||
`<!-- AGENT_PROMPT_SEGMENT:${key}:end -->`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
describe("agent doc segment compiler", () => {
|
||||
it("extracts only explicitly marked sections in deterministic order", () => {
|
||||
const markdown = [
|
||||
"# Intro",
|
||||
"This prose should not be extracted.",
|
||||
markedSegment("role", "Role text"),
|
||||
"Unmarked detail should not leak.",
|
||||
markedSegment("style-rules", "Style text"),
|
||||
markedSegment("decision-framework", "Decision text"),
|
||||
markedSegment("channel-notes", "Channel text"),
|
||||
].join("\n\n");
|
||||
|
||||
const compiled = compileAgentDocSegmentsFromMarkdown(markdown, {
|
||||
sourcePath: "components/agents/test-agent.md",
|
||||
});
|
||||
|
||||
expect(Object.keys(compiled)).toEqual(AGENT_PROMPT_SEGMENT_KEYS);
|
||||
expect(compiled).toEqual({
|
||||
role: "Role text",
|
||||
"style-rules": "Style text",
|
||||
"decision-framework": "Decision text",
|
||||
"channel-notes": "Channel text",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not include unmarked prose in extracted segments", () => {
|
||||
const markdown = [
|
||||
"Top prose that must stay out.",
|
||||
markedSegment("role", "Keep me"),
|
||||
"Random prose should not appear.",
|
||||
markedSegment("style-rules", "Keep style"),
|
||||
markedSegment("decision-framework", "Keep framework"),
|
||||
markedSegment("channel-notes", "Keep channels"),
|
||||
"Bottom prose that must stay out.",
|
||||
].join("\n\n");
|
||||
|
||||
const compiled = compileAgentDocSegmentsFromMarkdown(markdown, {
|
||||
sourcePath: "components/agents/test-agent.md",
|
||||
});
|
||||
|
||||
const joined = Object.values(compiled).join("\n");
|
||||
expect(joined).toContain("Keep me");
|
||||
expect(joined).not.toContain("Top prose");
|
||||
expect(joined).not.toContain("Bottom prose");
|
||||
expect(joined).not.toContain("Random prose");
|
||||
});
|
||||
|
||||
it("fails loudly when a required section marker is missing", () => {
|
||||
const markdown = [
|
||||
markedSegment("role", "Role text"),
|
||||
markedSegment("style-rules", "Style text"),
|
||||
markedSegment("decision-framework", "Decision text"),
|
||||
].join("\n\n");
|
||||
|
||||
expect(() =>
|
||||
compileAgentDocSegmentsFromMarkdown(markdown, {
|
||||
sourcePath: "components/agents/test-agent.md",
|
||||
}),
|
||||
).toThrowError(/channel-notes/);
|
||||
});
|
||||
|
||||
it("ships generated campaign distributor segments with all required keys", () => {
|
||||
const campaignDistributor = AGENT_DOC_SEGMENTS["campaign-distributor"];
|
||||
expect(campaignDistributor).toBeDefined();
|
||||
expect(Object.keys(campaignDistributor)).toEqual(AGENT_PROMPT_SEGMENT_KEYS);
|
||||
expect(campaignDistributor.role.length).toBeGreaterThan(0);
|
||||
expect(campaignDistributor["style-rules"].length).toBeGreaterThan(0);
|
||||
expect(campaignDistributor["decision-framework"].length).toBeGreaterThan(0);
|
||||
expect(campaignDistributor["channel-notes"].length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
180
tests/lib/agent-prompting.test.ts
Normal file
180
tests/lib/agent-prompting.test.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getAgentDefinition } from "@/lib/agent-definitions";
|
||||
import {
|
||||
buildAnalyzeMessages,
|
||||
buildExecuteMessages,
|
||||
summarizeIncomingContext,
|
||||
type PromptContextNode,
|
||||
} from "@/lib/agent-prompting";
|
||||
import { normalizeAgentExecutionPlan } from "@/lib/agent-run-contract";
|
||||
import { AGENT_DOC_SEGMENTS } from "@/lib/generated/agent-doc-segments";
|
||||
|
||||
describe("agent prompting helpers", () => {
|
||||
const definition = getAgentDefinition("campaign-distributor");
|
||||
|
||||
it("summarizes incoming context by node with whitelisted fields", () => {
|
||||
const nodes: PromptContextNode[] = [
|
||||
{
|
||||
nodeId: "node-2",
|
||||
type: "image",
|
||||
status: "done",
|
||||
data: {
|
||||
url: "https://cdn.example.com/render.png",
|
||||
width: 1080,
|
||||
height: 1080,
|
||||
ignored: "must not be included",
|
||||
},
|
||||
},
|
||||
{
|
||||
nodeId: "node-1",
|
||||
type: "text",
|
||||
status: "idle",
|
||||
data: {
|
||||
content: " Product launch headline ",
|
||||
secret: "do not include",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const summary = summarizeIncomingContext(nodes);
|
||||
|
||||
expect(summary).toContain("Incoming context nodes: 2");
|
||||
expect(summary).toContain("1. nodeId=node-1, type=text, status=idle");
|
||||
expect(summary).toContain("2. nodeId=node-2, type=image, status=done");
|
||||
expect(summary).toContain("content: Product launch headline");
|
||||
expect(summary).toContain("url: https://cdn.example.com/render.png");
|
||||
expect(summary).toContain("width: 1080");
|
||||
expect(summary).not.toContain("secret");
|
||||
expect(summary).not.toContain("ignored");
|
||||
});
|
||||
|
||||
it("buildAnalyzeMessages includes definition metadata, prompt segments, rules, and constraints", () => {
|
||||
if (!definition) {
|
||||
throw new Error("campaign-distributor definition missing");
|
||||
}
|
||||
|
||||
const messages = buildAnalyzeMessages({
|
||||
definition,
|
||||
locale: "en",
|
||||
briefConstraints: {
|
||||
briefing: "Create launch copy",
|
||||
audience: "Design leads",
|
||||
tone: "bold",
|
||||
targetChannels: ["instagram", "linkedin"],
|
||||
hardConstraints: ["No emojis"],
|
||||
},
|
||||
clarificationAnswers: {
|
||||
budget: "organic",
|
||||
},
|
||||
incomingContextSummary: "Incoming context nodes: 1",
|
||||
incomingContextCount: 1,
|
||||
promptSegments: AGENT_DOC_SEGMENTS["campaign-distributor"],
|
||||
});
|
||||
|
||||
expect(messages).toHaveLength(2);
|
||||
expect(messages[0]?.role).toBe("system");
|
||||
expect(messages[1]?.role).toBe("user");
|
||||
|
||||
const system = messages[0]?.content ?? "";
|
||||
const user = messages[1]?.content ?? "";
|
||||
|
||||
expect(system).toContain("Campaign Distributor");
|
||||
expect(system).toContain("role");
|
||||
expect(system).toContain("style-rules");
|
||||
expect(system).toContain("decision-framework");
|
||||
expect(system).toContain("channel-notes");
|
||||
expect(system).toContain("analysis rules");
|
||||
expect(system).toContain("default output blueprints");
|
||||
expect(user).toContain("Brief + constraints");
|
||||
expect(user).toContain("Current clarification answers");
|
||||
expect(user).toContain("Incoming context node count: 1");
|
||||
expect(user).toContain("Incoming context nodes: 1");
|
||||
});
|
||||
|
||||
it("buildExecuteMessages includes execution rules, plan summary, per-step requirements, and checks", () => {
|
||||
if (!definition) {
|
||||
throw new Error("campaign-distributor definition missing");
|
||||
}
|
||||
|
||||
const executionPlan = normalizeAgentExecutionPlan({
|
||||
summary: "Ship launch content",
|
||||
steps: [
|
||||
{
|
||||
id: " ig-feed ",
|
||||
title: " Instagram Feed Pack ",
|
||||
channel: " Instagram Feed ",
|
||||
outputType: "caption-pack",
|
||||
artifactType: "social-caption-pack",
|
||||
goal: "Drive comments",
|
||||
requiredSections: ["Hook", "Caption", "CTA"],
|
||||
qualityChecks: ["matches_channel_constraints"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const messages = buildExecuteMessages({
|
||||
definition,
|
||||
locale: "de",
|
||||
briefConstraints: {
|
||||
briefing: "Post launch update",
|
||||
audience: "Founders",
|
||||
tone: "energetic",
|
||||
targetChannels: ["instagram"],
|
||||
hardConstraints: ["No discounts"],
|
||||
},
|
||||
clarificationAnswers: {
|
||||
length: "short",
|
||||
},
|
||||
incomingContextSummary: "Incoming context nodes: 1",
|
||||
executionPlan,
|
||||
promptSegments: AGENT_DOC_SEGMENTS["campaign-distributor"],
|
||||
});
|
||||
|
||||
expect(messages).toHaveLength(2);
|
||||
const system = messages[0]?.content ?? "";
|
||||
const user = messages[1]?.content ?? "";
|
||||
|
||||
expect(system).toContain("execution rules");
|
||||
expect(system).toContain("channel-notes");
|
||||
expect(system).toContain("German (de-DE)");
|
||||
expect(user).toContain("Execution plan summary: Ship launch content");
|
||||
expect(user).toContain("artifactType: social-caption-pack");
|
||||
expect(user).toContain("requiredSections: Hook, Caption, CTA");
|
||||
expect(user).toContain("qualityChecks: matches_channel_constraints");
|
||||
});
|
||||
|
||||
it("prompt builders stay definition-driven without hardcoded template branches", () => {
|
||||
if (!definition) {
|
||||
throw new Error("campaign-distributor definition missing");
|
||||
}
|
||||
|
||||
const variantDefinition = {
|
||||
...definition,
|
||||
metadata: {
|
||||
...definition.metadata,
|
||||
name: "Custom Runtime Agent",
|
||||
description: "Definition override for test.",
|
||||
},
|
||||
};
|
||||
|
||||
const messages = buildAnalyzeMessages({
|
||||
definition: variantDefinition,
|
||||
locale: "en",
|
||||
briefConstraints: {
|
||||
briefing: "Test",
|
||||
audience: "Test",
|
||||
tone: "Test",
|
||||
targetChannels: ["x"],
|
||||
hardConstraints: [],
|
||||
},
|
||||
clarificationAnswers: {},
|
||||
incomingContextSummary: "Incoming context nodes: 0",
|
||||
incomingContextCount: 0,
|
||||
promptSegments: AGENT_DOC_SEGMENTS["campaign-distributor"],
|
||||
});
|
||||
|
||||
expect(messages[0]?.content ?? "").toContain("Custom Runtime Agent");
|
||||
expect(messages[0]?.content ?? "").toContain("Definition override for test.");
|
||||
});
|
||||
});
|
||||
@@ -122,6 +122,10 @@ describe("agent run contract helpers", () => {
|
||||
title: "Instagram captions",
|
||||
channel: "Instagram",
|
||||
outputType: "caption-pack",
|
||||
artifactType: "caption-pack",
|
||||
goal: "Deliver channel-ready output.",
|
||||
requiredSections: [],
|
||||
qualityChecks: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -149,6 +153,10 @@ describe("agent run contract helpers", () => {
|
||||
title: "Untitled",
|
||||
channel: "general",
|
||||
outputType: "text",
|
||||
artifactType: "text",
|
||||
goal: "Deliver channel-ready output.",
|
||||
requiredSections: [],
|
||||
qualityChecks: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -181,6 +189,51 @@ describe("agent run contract helpers", () => {
|
||||
|
||||
expect(normalized.steps.map((step) => step.id)).toEqual(["step", "step-2", "step-3"]);
|
||||
});
|
||||
|
||||
it("normalizes enriched execution-step fields with deterministic array handling", () => {
|
||||
const normalized = normalizeAgentExecutionPlan({
|
||||
summary: "ready",
|
||||
steps: [
|
||||
{
|
||||
id: "main",
|
||||
title: "Deliver",
|
||||
channel: "linkedin",
|
||||
outputType: "post",
|
||||
artifactType: " social-caption-pack ",
|
||||
goal: " Explain launch value ",
|
||||
requiredSections: ["Hook", "CTA", "Hook", " "],
|
||||
qualityChecks: ["fits_tone", "fits_tone", "references_context", ""],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(normalized.steps[0]).toEqual({
|
||||
id: "main",
|
||||
title: "Deliver",
|
||||
channel: "linkedin",
|
||||
outputType: "post",
|
||||
artifactType: "social-caption-pack",
|
||||
goal: "Explain launch value",
|
||||
requiredSections: ["Hook", "CTA"],
|
||||
qualityChecks: ["fits_tone", "references_context"],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps compatibility by falling back artifactType to outputType", () => {
|
||||
const normalized = normalizeAgentExecutionPlan({
|
||||
summary: "ready",
|
||||
steps: [
|
||||
{
|
||||
id: "legacy",
|
||||
title: "Legacy step",
|
||||
channel: "email",
|
||||
outputType: "newsletter-copy",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(normalized.steps[0]?.artifactType).toBe("newsletter-copy");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeAgentBriefConstraints", () => {
|
||||
|
||||
148
tests/lib/agent-structured-output.test.ts
Normal file
148
tests/lib/agent-structured-output.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { normalizeAgentStructuredOutput } from "@/lib/agent-run-contract";
|
||||
|
||||
describe("normalizeAgentStructuredOutput", () => {
|
||||
it("preserves valid structured fields and compatibility body", () => {
|
||||
const normalized = normalizeAgentStructuredOutput(
|
||||
{
|
||||
title: " Launch Post ",
|
||||
channel: " linkedin ",
|
||||
artifactType: " social-post ",
|
||||
previewText: " Hook-first launch post. ",
|
||||
sections: [
|
||||
{
|
||||
id: " headline ",
|
||||
label: " Headline ",
|
||||
content: " Ship faster with LemonSpace. ",
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
language: " en ",
|
||||
tags: [" launch ", "saas", ""],
|
||||
},
|
||||
qualityChecks: [" concise ", "concise", "channel-fit"],
|
||||
body: " Legacy flat content ",
|
||||
},
|
||||
{
|
||||
title: "Fallback Title",
|
||||
channel: "fallback-channel",
|
||||
artifactType: "fallback-artifact",
|
||||
},
|
||||
);
|
||||
|
||||
expect(normalized).toEqual({
|
||||
title: "Launch Post",
|
||||
channel: "linkedin",
|
||||
artifactType: "social-post",
|
||||
previewText: "Hook-first launch post.",
|
||||
sections: [
|
||||
{
|
||||
id: "headline",
|
||||
label: "Headline",
|
||||
content: "Ship faster with LemonSpace.",
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
language: "en",
|
||||
tags: ["launch", "saas"],
|
||||
},
|
||||
qualityChecks: ["concise", "channel-fit"],
|
||||
body: "Legacy flat content",
|
||||
});
|
||||
});
|
||||
|
||||
it("removes blank or malformed section entries", () => {
|
||||
const normalized = normalizeAgentStructuredOutput(
|
||||
{
|
||||
sections: [
|
||||
{
|
||||
id: "intro",
|
||||
label: "Intro",
|
||||
content: "Keep this section.",
|
||||
},
|
||||
{
|
||||
id: "",
|
||||
label: "",
|
||||
content: "",
|
||||
},
|
||||
{
|
||||
id: "missing-content",
|
||||
label: "Missing Content",
|
||||
content: " ",
|
||||
},
|
||||
null,
|
||||
"bad-shape",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Fallback Title",
|
||||
channel: "fallback-channel",
|
||||
artifactType: "fallback-artifact",
|
||||
},
|
||||
);
|
||||
|
||||
expect(normalized.sections).toEqual([
|
||||
{
|
||||
id: "intro",
|
||||
label: "Intro",
|
||||
content: "Keep this section.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("derives previewText deterministically from first valid section when missing", () => {
|
||||
const normalized = normalizeAgentStructuredOutput(
|
||||
{
|
||||
sections: [
|
||||
{
|
||||
id: "hook",
|
||||
label: "Hook",
|
||||
content: "First section content.",
|
||||
},
|
||||
{
|
||||
id: "cta",
|
||||
label: "CTA",
|
||||
content: "Second section content.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Fallback Title",
|
||||
channel: "fallback-channel",
|
||||
artifactType: "fallback-artifact",
|
||||
},
|
||||
);
|
||||
|
||||
expect(normalized.previewText).toBe("First section content.");
|
||||
});
|
||||
|
||||
it("derives deterministic legacy body from sections when body is missing", () => {
|
||||
const normalized = normalizeAgentStructuredOutput(
|
||||
{
|
||||
previewText: "Preview should not override section flattening",
|
||||
sections: [
|
||||
{
|
||||
id: "hook",
|
||||
label: "Hook",
|
||||
content: "Lead with a bold claim.",
|
||||
},
|
||||
{
|
||||
id: "cta",
|
||||
label: "CTA",
|
||||
content: "Invite replies with a concrete question.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Fallback Title",
|
||||
channel: "fallback-channel",
|
||||
artifactType: "fallback-artifact",
|
||||
},
|
||||
);
|
||||
|
||||
expect(normalized.body).toBe(
|
||||
"Hook:\nLead with a bold claim.\n\nCTA:\nInvite replies with a concrete question.",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getAgentDefinition } from "@/lib/agent-definitions";
|
||||
import { AGENT_TEMPLATES, getAgentTemplate } from "@/lib/agent-templates";
|
||||
|
||||
describe("agent templates", () => {
|
||||
@@ -9,14 +10,25 @@ describe("agent templates", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("exposes normalized metadata needed by the canvas node", () => {
|
||||
it("projects runtime definition metadata for existing canvas callers", () => {
|
||||
const template = getAgentTemplate("campaign-distributor");
|
||||
const definition = getAgentDefinition("campaign-distributor");
|
||||
|
||||
expect(template?.name).toBe("Campaign Distributor");
|
||||
expect(template?.color).toBe("yellow");
|
||||
expect(template?.tools).toContain("WebFetch");
|
||||
expect(template?.channels).toContain("Instagram Feed");
|
||||
expect(template?.expectedInputs).toContain("Render-Node-Export");
|
||||
expect(template?.expectedOutputs).toContain("Caption-Pakete");
|
||||
expect(definition).toBeDefined();
|
||||
|
||||
expect(template?.name).toBe(definition?.metadata.name);
|
||||
expect(template?.description).toBe(definition?.metadata.description);
|
||||
expect(template?.emoji).toBe(definition?.metadata.emoji);
|
||||
expect(template?.color).toBe(definition?.metadata.color);
|
||||
expect(template?.vibe).toBe(definition?.metadata.vibe);
|
||||
expect(template?.tools).toEqual(definition?.uiReference.tools);
|
||||
expect(template?.channels).toEqual(definition?.channelCatalog);
|
||||
expect(template?.expectedInputs).toEqual(definition?.uiReference.expectedInputs);
|
||||
expect(template?.expectedOutputs).toEqual(definition?.uiReference.expectedOutputs);
|
||||
expect(template?.notes).toEqual(definition?.uiReference.notes);
|
||||
});
|
||||
|
||||
it("keeps unknown template lookup behavior unchanged", () => {
|
||||
expect(getAgentTemplate("unknown-template")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
clearDashboardSnapshotCache,
|
||||
emitDashboardSnapshotCacheInvalidationSignal,
|
||||
getDashboardSnapshotCacheInvalidationSignalKey,
|
||||
invalidateDashboardSnapshotForLastSignedInUser,
|
||||
readDashboardSnapshotCache,
|
||||
writeDashboardSnapshotCache,
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
|
||||
const USER_ID = "user-cache-test";
|
||||
const LAST_DASHBOARD_USER_KEY = "ls-last-dashboard-user";
|
||||
const INVALIDATION_SIGNAL_KEY = "lemonspace.dashboard:snapshot:invalidate:v1";
|
||||
const INVALIDATION_SIGNAL_KEY = getDashboardSnapshotCacheInvalidationSignalKey();
|
||||
|
||||
describe("dashboard snapshot cache", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user