Files
Agent-System-Backend/internal/workflow/render.go
weichenandCursor 63ea1a262c Register plugin workflows and drop them on uninstall
Why: Intent routing needs a registry of workflow definitions, and uninstall was leaving intents and workflows in place.
Evidence: go test ./internal/workflow/ ./internal/plugins/ ./internal/app/ passed. Validation rejects duplicate ids, unknown kinds, denied tools, and both step caps. Unregister clears the registry and the intent gate.
Impact: Script steps must stay inside the plugin directory. The server holds the registry for later routing.
Refs: feature/agent-harness-p4
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 15:59:54 +00:00

90 lines
2.0 KiB
Go

package workflow
import (
"encoding/json"
"fmt"
"regexp"
core "agent-system-core"
)
// Vars 是一次执行里可以填进模板的值。
type Vars struct {
Input string
Slots map[string]string
Steps map[string]string
}
var placeholder = regexp.MustCompile(`\{\{(input|slots\.[A-Za-z_][A-Za-z0-9_]*|steps\.[A-Za-z_][A-Za-z0-9_]*\.output)\}\}`)
// Render 替换 {{input}}、{{slots.<key>}}、{{steps.<id>.output}}。缺值替换成空串。
func Render(s string, v Vars) string {
return placeholder.ReplaceAllStringFunc(s, func(token string) string {
body := token[2 : len(token)-2]
switch {
case body == "input":
return v.Input
case len(body) > 6 && body[:6] == "slots.":
return v.Slots[body[6:]]
case len(body) > 6 && body[:6] == "steps.":
id := body[6 : len(body)-len(".output")]
return v.Steps[id]
default:
return token
}
})
}
// RenderRaw 在 JSON 字符串内部替换占位符,结果仍是合法 JSON。
func RenderRaw(raw json.RawMessage, v Vars) (json.RawMessage, error) {
if len(raw) == 0 {
return nil, nil
}
var node any
if err := json.Unmarshal(raw, &node); err != nil {
return nil, fmt.Errorf("workflow: 参数不是合法 JSON")
}
out, err := json.Marshal(walk(node, v))
if err != nil {
return nil, err
}
return out, nil
}
func walk(node any, v Vars) any {
switch x := node.(type) {
case string:
return Render(x, v)
case []any:
for i := range x {
x[i] = walk(x[i], v)
}
return x
case map[string]any:
for k, val := range x {
x[k] = walk(val, v)
}
return x
default:
return node
}
}
// RenderStep 渲染这一步会交给执行器的文本和参数。
func RenderStep(step core.WorkflowStep, v Vars) (core.WorkflowStep, error) {
step.Prompt = Render(step.Prompt, v)
step.Artifact = Render(step.Artifact, v)
if step.Hitl != nil {
step.Hitl.Question = Render(step.Hitl.Question, v)
}
if step.Card != nil {
step.Card.Title = Render(step.Card.Title, v)
}
args, err := RenderRaw(step.Args, v)
if err != nil {
return step, err
}
step.Args = args
return step, nil
}