Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, expect, test } from "bun:test";
import { renderPromptTemplate } from "../core/prompts.ts";

describe("literal template values", () => {
const values = {
goal: "Build a feature",
agentId: "agent-123",
dispatcherInstruction: "",
slackChannelInstruction: "",
loopHygiene: "",
};

test("preserves JavaScript replacement tokens in a task goal", () => {
const goal = "Implement replacements $& and $` and $' literally";
expect(renderPromptTemplate("root", { ...values, goal })).toContain(goal);
});

test("preserves code template braces in a task goal", () => {
const goal = "Render the user template {{customer}} literally";
expect(renderPromptTemplate("root", { ...values, goal })).toContain(goal);
});

test("does not replace inserted text with other template variable values", () => {
const goal = "Test literal {{agentId}} syntax";
expect(renderPromptTemplate("root", { ...values, goal })).toContain(goal);
});

test("ordinary template variables still render", () => {
const rendered = renderPromptTemplate("root", values);
expect(rendered).toContain(values.goal);
expect(rendered).toContain(values.agentId);
});

test("missing template variables still fail with an actionable error", () => {
expect(() => renderPromptTemplate("root", { goal: "test" })).toThrow("unrendered placeholders");
});
});
16 changes: 8 additions & 8 deletions orchestrate/skills/orchestrate/scripts/core/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export function renderPromptTemplate(
templateCache.set(name, template);
}

let rendered = template;
for (const [key, value] of Object.entries(vars)) {
rendered = rendered.replaceAll(`{{${key}}}`, value);
}

const unrendered = [...rendered.matchAll(/{{([^{}]+)}}/g)].map(m =>
m[1].trim()
);
const unrendered: string[] = [];
// Match placeholders in the template once. Values are task content, so they
// must not be interpreted as replacement tokens or additional placeholders.
const rendered = template.replace(/{{([^{}]+)}}/g, (placeholder, key: string) => {
if (Object.hasOwn(vars, key)) return vars[key];
unrendered.push(key.trim());
return placeholder;
});
if (unrendered.length > 0) {
const placeholders = [...new Set(unrendered)].join(", ");
throw new PlanValidationError(
Expand Down