feat: migrate examples to status-based routing + fix mustache HTML escape
- Migrate solve-issue.yaml, analyze-topic.yaml, debate.yaml to new format
- Add status enum field to all role frontmatter schemas
- Use {{{ }}} (triple mustache) for prompt templates with user content
- Disable mustache HTML escaping globally (prompts are plain text, not HTML)
- Add 2 new tests for HTML escape behavior
- 9 moderator tests pass
Phase 2 of #490 (closes #492)
This commit is contained in:
@@ -22,6 +22,8 @@ roles:
|
|||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
status:
|
||||||
|
enum: ["_"]
|
||||||
thesis:
|
thesis:
|
||||||
type: string
|
type: string
|
||||||
keyPoints:
|
keyPoints:
|
||||||
@@ -30,14 +32,9 @@ roles:
|
|||||||
type: string
|
type: string
|
||||||
caveats:
|
caveats:
|
||||||
type: string
|
type: string
|
||||||
required: [thesis, keyPoints]
|
required: [status, thesis, keyPoints]
|
||||||
conditions: {}
|
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
- role: "analyst"
|
_: { role: "analyst", prompt: "Analyze the topic in the task and produce a structured summary with key points." }
|
||||||
condition: null
|
|
||||||
prompt: "Analyze the topic in the task and produce a structured summary with key points."
|
|
||||||
analyst:
|
analyst:
|
||||||
- role: "$END"
|
_: { role: "$END", prompt: "Analysis complete. Finish the workflow." }
|
||||||
condition: null
|
|
||||||
prompt: "Analysis complete. Finish the workflow."
|
|
||||||
|
|||||||
+15
-30
@@ -16,15 +16,16 @@ roles:
|
|||||||
3. If you find yourself genuinely convinced by the other side, you may concede.
|
3. If you find yourself genuinely convinced by the other side, you may concede.
|
||||||
output: |
|
output: |
|
||||||
Provide your argument in the frontmatter.
|
Provide your argument in the frontmatter.
|
||||||
Set conceded to true ONLY if you are genuinely convinced and wish to stop debating.
|
Set status to "conceded" ONLY if you are genuinely convinced and wish to stop debating.
|
||||||
|
Otherwise set status to "continue".
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
status:
|
||||||
|
enum: ["continue", "conceded"]
|
||||||
argument:
|
argument:
|
||||||
type: string
|
type: string
|
||||||
conceded:
|
required: [status, argument]
|
||||||
type: boolean
|
|
||||||
required: [argument, conceded]
|
|
||||||
for:
|
for:
|
||||||
description: "Argues for the proposition"
|
description: "Argues for the proposition"
|
||||||
goal: |
|
goal: |
|
||||||
@@ -40,38 +41,22 @@ roles:
|
|||||||
3. If you find yourself genuinely convinced by the other side, you may concede.
|
3. If you find yourself genuinely convinced by the other side, you may concede.
|
||||||
output: |
|
output: |
|
||||||
Provide your argument in the frontmatter.
|
Provide your argument in the frontmatter.
|
||||||
Set conceded to true ONLY if you are genuinely convinced and wish to stop debating.
|
Set status to "conceded" ONLY if you are genuinely convinced and wish to stop debating.
|
||||||
|
Otherwise set status to "continue".
|
||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
status:
|
||||||
|
enum: ["continue", "conceded"]
|
||||||
argument:
|
argument:
|
||||||
type: string
|
type: string
|
||||||
conceded:
|
required: [status, argument]
|
||||||
type: boolean
|
|
||||||
required: [argument, conceded]
|
|
||||||
conditions:
|
|
||||||
againstConceded:
|
|
||||||
description: "The against side conceded"
|
|
||||||
expression: "$last('against').conceded = true"
|
|
||||||
forConceded:
|
|
||||||
description: "The for side conceded"
|
|
||||||
expression: "$last('for').conceded = true"
|
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
- role: "against"
|
_: { role: "against", prompt: "Present your opening argument against the proposition." }
|
||||||
condition: null
|
|
||||||
prompt: "Present your opening argument against the proposition."
|
|
||||||
against:
|
against:
|
||||||
- role: "$END"
|
conceded: { role: "$END", prompt: "The against side conceded. Debate over." }
|
||||||
condition: "againstConceded"
|
continue: { role: "for", prompt: "Counter the opposing argument: {{{argument}}}" }
|
||||||
prompt: "The against side conceded. Debate over."
|
|
||||||
- role: "for"
|
|
||||||
condition: null
|
|
||||||
prompt: "Counter the opposing argument. Address their points directly."
|
|
||||||
for:
|
for:
|
||||||
- role: "$END"
|
conceded: { role: "$END", prompt: "The for side conceded. Debate over." }
|
||||||
condition: "forConceded"
|
continue: { role: "against", prompt: "Counter the opposing argument: {{{argument}}}" }
|
||||||
prompt: "The for side conceded. Debate over."
|
|
||||||
- role: "against"
|
|
||||||
condition: null
|
|
||||||
prompt: "Counter the opposing argument. Address their points directly."
|
|
||||||
|
|||||||
+14
-24
@@ -27,11 +27,13 @@ roles:
|
|||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
status:
|
||||||
|
enum: ["_"]
|
||||||
repoPath:
|
repoPath:
|
||||||
type: string
|
type: string
|
||||||
plan:
|
plan:
|
||||||
type: string
|
type: string
|
||||||
required: [repoPath, plan]
|
required: [status, repoPath, plan]
|
||||||
developer:
|
developer:
|
||||||
description: "Implements code changes"
|
description: "Implements code changes"
|
||||||
goal: "You are a developer agent. You implement code changes according to plans."
|
goal: "You are a developer agent. You implement code changes according to plans."
|
||||||
@@ -50,13 +52,15 @@ roles:
|
|||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
status:
|
||||||
|
enum: ["_"]
|
||||||
filesChanged:
|
filesChanged:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
summary:
|
summary:
|
||||||
type: string
|
type: string
|
||||||
required: [filesChanged, summary]
|
required: [status, filesChanged, summary]
|
||||||
reviewer:
|
reviewer:
|
||||||
description: "Reviews code changes"
|
description: "Reviews code changes"
|
||||||
goal: "You are a code reviewer. You review implementations for correctness and quality."
|
goal: "You are a code reviewer. You review implementations for correctness and quality."
|
||||||
@@ -71,32 +75,18 @@ roles:
|
|||||||
frontmatter:
|
frontmatter:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
approved:
|
status:
|
||||||
type: boolean
|
enum: ["approved", "rejected"]
|
||||||
comments:
|
comments:
|
||||||
type: string
|
type: string
|
||||||
required: [approved, comments]
|
required: [status, comments]
|
||||||
conditions:
|
|
||||||
notApproved:
|
|
||||||
description: "Reviewer rejected the implementation"
|
|
||||||
expression: "$last('reviewer').approved = false"
|
|
||||||
graph:
|
graph:
|
||||||
$START:
|
$START:
|
||||||
- role: "planner"
|
_: { role: "planner", prompt: "Analyze the issue described in the task and produce a detailed implementation plan." }
|
||||||
condition: null
|
|
||||||
prompt: "Analyze the issue described in the task and produce a detailed implementation plan."
|
|
||||||
planner:
|
planner:
|
||||||
- role: "developer"
|
_: { role: "developer", prompt: "Implement the plan from the planner. Write code, tests, and ensure existing tests pass." }
|
||||||
condition: null
|
|
||||||
prompt: "Implement the plan from the planner. Write code, tests, and ensure existing tests pass."
|
|
||||||
developer:
|
developer:
|
||||||
- role: "reviewer"
|
_: { role: "reviewer", prompt: "Review the developer's implementation against the plan for correctness and quality." }
|
||||||
condition: null
|
|
||||||
prompt: "Review the developer's implementation against the plan for correctness and quality."
|
|
||||||
reviewer:
|
reviewer:
|
||||||
- role: "developer"
|
approved: { role: "$END", prompt: "The review passed. Complete the workflow." }
|
||||||
condition: "notApproved"
|
rejected: { role: "developer", prompt: "The reviewer rejected your implementation. Read their feedback and fix the issues: {{{comments}}}" }
|
||||||
prompt: "The reviewer rejected your implementation. Read their feedback and fix the issues."
|
|
||||||
- role: "$END"
|
|
||||||
condition: null
|
|
||||||
prompt: "The review passed. Complete the workflow."
|
|
||||||
|
|||||||
@@ -74,6 +74,33 @@ describe("evaluate", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mustache does not HTML-escape prompt content", () => {
|
||||||
|
const result = evaluate(solveIssueGraph, "reviewer", {
|
||||||
|
status: "rejected",
|
||||||
|
comments: 'use <T> & "Result<T, E>" types',
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
ok: true,
|
||||||
|
value: { role: "developer", prompt: 'Fix: use <T> & "Result<T, E>" types' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("triple mustache also works for unescaped output", () => {
|
||||||
|
const graph: Record<string, Record<string, Target>> = {
|
||||||
|
reviewer: {
|
||||||
|
_: { role: "developer", prompt: "Fix: {{{comments}}}" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = evaluate(graph, "reviewer", {
|
||||||
|
status: "_",
|
||||||
|
comments: "<script>alert(1)</script>",
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
ok: true,
|
||||||
|
value: { role: "developer", prompt: "Fix: <script>alert(1)</script>" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("mustache template with nested object paths", () => {
|
test("mustache template with nested object paths", () => {
|
||||||
const graph: Record<string, Record<string, Target>> = {
|
const graph: Record<string, Record<string, Target>> = {
|
||||||
reviewer: {
|
reviewer: {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import mustache from "mustache";
|
|||||||
|
|
||||||
import type { EvaluateResult, Result } from "./types.js";
|
import type { EvaluateResult, Result } from "./types.js";
|
||||||
|
|
||||||
|
// Disable HTML escaping — prompts are plain text, not HTML.
|
||||||
|
mustache.escape = (text: string) => text;
|
||||||
|
|
||||||
const START_ROLE = "$START";
|
const START_ROLE = "$START";
|
||||||
const UNIT_STATUS = "_";
|
const UNIT_STATUS = "_";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user