relay-flow 0.3.9-alpha → 0.3.11-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -34
- package/cmd/relay-flow/commands_test.go +449 -23
- package/cmd/relay-flow/main.go +207 -37
- package/cmd/relay-flow/observability_test.go +11 -0
- package/cmd/relay-flow/onboarding.go +1 -1
- package/cmd/relay-flow/onboarding_test.go +1 -1
- package/cmd/relay-flow/scenario_test.go +26 -10
- package/cmd/relay-flow/serve.go +27 -1
- package/examples/config-reference.yaml +23 -19
- package/examples/workflow-reference.yaml +3 -0
- package/internal/execution/goworkflows/activities.go +13 -22
- package/internal/execution/goworkflows/engine_test.go +17 -3
- package/internal/execution/goworkflows/fakes_test.go +20 -0
- package/internal/execution/goworkflows/interpreter.go +25 -11
- package/internal/execution/goworkflows/mailbox_test.go +28 -1
- package/internal/execution/goworkflows/ownership_test.go +259 -0
- package/internal/execution/goworkflows/recovery_test.go +81 -1
- package/internal/execution/temporal/activities.go +13 -22
- package/internal/execution/temporal/compact_mailbox_test.go +56 -0
- package/internal/execution/temporal/interpreter.go +16 -3
- package/internal/execution/temporal/recovery.go +68 -36
- package/internal/harness/harness.go +23 -10
- package/internal/harness/opencode/opencode.go +22 -4
- package/internal/harness/opencode/opencode_test.go +41 -1
- package/internal/harness/opencode/repo_setup.go +1 -1
- package/internal/harness/opencode/task_env_test.go +2 -2
- package/internal/harness/pi/pi.go +21 -2
- package/internal/harness/pi/pi_test.go +7 -4
- package/internal/harness/pi/prompt_test.go +13 -5
- package/internal/harness/pi/task_env_test.go +18 -2
- package/internal/recover/recover.go +30 -1
- package/internal/repo/binding_test.go +63 -0
- package/internal/repo/repo.go +47 -6
- package/internal/router/router.go +39 -5
- package/internal/router/router_test.go +67 -1
- package/internal/run/manager.go +90 -3
- package/internal/run/run_manager_test.go +74 -0
- package/internal/server/api_test.go +13 -0
- package/internal/server/client.go +7 -0
- package/internal/server/fixture_test.go +5 -0
- package/internal/server/server.go +32 -3
- package/internal/task/beads/beads.go +201 -3
- package/internal/task/beads/beads_test.go +172 -0
- package/internal/task/jira/effects_test.go +86 -0
- package/internal/task/jira/filters_test.go +103 -0
- package/internal/task/jira/helpers_test.go +11 -2
- package/internal/task/jira/jira.go +244 -15
- package/internal/task/jira/normalize.go +30 -15
- package/internal/task/jira/rest/client.go +17 -2
- package/internal/task/jira/templates_test.go +38 -2
- package/internal/task/jira/transition_defaults_test.go +3 -1
- package/internal/task/task.go +67 -0
- package/internal/workflow/report.go +10 -0
- package/internal/workflow/report_test.go +16 -0
- package/internal/workflow/workflow.go +24 -0
- package/internal/workflow/workflow_test.go +58 -1
- package/package.json +1 -1
|
@@ -65,9 +65,15 @@ type Node struct {
|
|
|
65
65
|
Agent string `yaml:"agent,omitempty" json:"agent,omitempty"`
|
|
66
66
|
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
|
67
67
|
NudgePrompt string `yaml:"nudgePrompt,omitempty" json:"nudgePrompt,omitempty"`
|
|
68
|
+
AutoReject bool `yaml:"autoReject,omitempty" json:"autoReject,omitempty"`
|
|
68
69
|
TaskConfig config.RawValues `yaml:"taskConfig,omitempty" json:"taskConfig,omitempty"`
|
|
69
70
|
OnSuccess []Route `yaml:"onSuccess,omitempty" json:"onSuccess,omitempty"`
|
|
70
71
|
OnFailure []Route `yaml:"onFailure,omitempty" json:"onFailure,omitempty"`
|
|
72
|
+
|
|
73
|
+
// autoRejectSet lets validation distinguish an explicitly configured false
|
|
74
|
+
// from an omitted field. The marker is parser metadata and is not part of
|
|
75
|
+
// the workflow snapshot or JSON/YAML representation.
|
|
76
|
+
autoRejectSet bool
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
type Route struct {
|
|
@@ -119,6 +125,10 @@ func Parse(name string, yamlBytes []byte) (*Workflow, error) {
|
|
|
119
125
|
rawNodes, _ := raw["nodes"].(map[string]any)
|
|
120
126
|
for nodeName, n := range wf.Nodes {
|
|
121
127
|
if rn, ok := rawNodes[nodeName].(map[string]any); ok {
|
|
128
|
+
if _, present := rn["autoReject"]; present {
|
|
129
|
+
n.autoRejectSet = true
|
|
130
|
+
wf.Nodes[nodeName] = n
|
|
131
|
+
}
|
|
122
132
|
if v, present := rn["taskConfig"]; present && v == nil {
|
|
123
133
|
return nil, fmt.Errorf("parse workflow %q node %q: taskConfig: explicit null is not allowed", name, nodeName)
|
|
124
134
|
}
|
|
@@ -216,6 +226,9 @@ func (w *Workflow) Validate() error {
|
|
|
216
226
|
}
|
|
217
227
|
|
|
218
228
|
func validateStart(n Node) error {
|
|
229
|
+
if n.AutoReject || n.autoRejectSet {
|
|
230
|
+
return fmt.Errorf("autoReject is valid only on hitl nodes")
|
|
231
|
+
}
|
|
219
232
|
if n.Type != "" || n.Agent != "" || n.Description != "" || n.NudgePrompt != "" {
|
|
220
233
|
return fmt.Errorf("start must not declare type, agent, description, or nudgePrompt")
|
|
221
234
|
}
|
|
@@ -229,6 +242,9 @@ func validateStart(n Node) error {
|
|
|
229
242
|
}
|
|
230
243
|
|
|
231
244
|
func validateEnd(n Node) error {
|
|
245
|
+
if n.AutoReject || n.autoRejectSet {
|
|
246
|
+
return fmt.Errorf("autoReject is valid only on hitl nodes")
|
|
247
|
+
}
|
|
232
248
|
if n.Type != "" || n.Agent != "" || n.Description != "" || n.NudgePrompt != "" {
|
|
233
249
|
return fmt.Errorf("end must not declare type, agent, description, or nudgePrompt")
|
|
234
250
|
}
|
|
@@ -242,6 +258,9 @@ func validateWorkNode(wfName, name string, n Node) error {
|
|
|
242
258
|
if n.Type != NodeAgent && n.Type != NodeHITL {
|
|
243
259
|
return fmt.Errorf("workflow %q node %q: type must be %q or %q", wfName, name, NodeAgent, NodeHITL)
|
|
244
260
|
}
|
|
261
|
+
if n.Type != NodeHITL && (n.AutoReject || n.autoRejectSet) {
|
|
262
|
+
return fmt.Errorf("workflow %q node %q: autoReject is valid only on hitl nodes", wfName, name)
|
|
263
|
+
}
|
|
245
264
|
if n.Agent == "" {
|
|
246
265
|
return fmt.Errorf("workflow %q node %q: agent is required", wfName, name)
|
|
247
266
|
}
|
|
@@ -254,6 +273,11 @@ func validateWorkNode(wfName, name string, n Node) error {
|
|
|
254
273
|
if len(n.OnFailure) == 0 {
|
|
255
274
|
return fmt.Errorf("workflow %q node %q: at least one failure route is required", wfName, name)
|
|
256
275
|
}
|
|
276
|
+
for _, route := range n.OnFailure {
|
|
277
|
+
if route.Target == EndNode {
|
|
278
|
+
return fmt.Errorf("workflow %q node %q: failure routes cannot target %q", wfName, name, EndNode)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
257
281
|
for _, m := range nudgeVarPattern.FindAllStringSubmatch(n.NudgePrompt, -1) {
|
|
258
282
|
if !knownNudgeVars[m[1]] {
|
|
259
283
|
return fmt.Errorf("workflow %q node %q: unknown nudge template variable {{%s}}", wfName, name, m[1])
|
|
@@ -221,6 +221,63 @@ func TestValidateEndNode(t *testing.T) {
|
|
|
221
221
|
})
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
func TestAutoRejectPolicy(t *testing.T) {
|
|
225
|
+
t.Run("defaults false on HITL", func(t *testing.T) {
|
|
226
|
+
yaml := strings.Replace(minimalValid, " type: agent", " type: hitl", 1)
|
|
227
|
+
wf := parse(t, "basicFlow", yaml)
|
|
228
|
+
if err := wf.Validate(); err != nil {
|
|
229
|
+
t.Fatalf("HITL workflow rejected: %v", err)
|
|
230
|
+
}
|
|
231
|
+
if wf.Nodes["coding"].AutoReject {
|
|
232
|
+
t.Fatal("omitted autoReject defaulted to true")
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
t.Run("accepts true on HITL", func(t *testing.T) {
|
|
237
|
+
yaml := strings.Replace(minimalValid, " type: agent", " type: hitl\n autoReject: true", 1)
|
|
238
|
+
wf := parse(t, "basicFlow", yaml)
|
|
239
|
+
if err := wf.Validate(); err != nil {
|
|
240
|
+
t.Fatalf("HITL autoReject workflow rejected: %v", err)
|
|
241
|
+
}
|
|
242
|
+
if !wf.Nodes["coding"].AutoReject {
|
|
243
|
+
t.Fatal("autoReject true was not parsed")
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
for _, tc := range []struct {
|
|
248
|
+
name string
|
|
249
|
+
yaml string
|
|
250
|
+
}{
|
|
251
|
+
{
|
|
252
|
+
name: "agent",
|
|
253
|
+
yaml: strings.Replace(minimalValid, " type: agent", " type: agent\n autoReject: false", 1),
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "start",
|
|
257
|
+
yaml: strings.Replace(minimalValid, " start:\n onSuccess:", " start:\n autoReject: false\n onSuccess:", 1),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "end",
|
|
261
|
+
yaml: strings.Replace(minimalValid, " end: {}", " end:\n autoReject: false", 1),
|
|
262
|
+
},
|
|
263
|
+
} {
|
|
264
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
265
|
+
wf := parse(t, "basicFlow", tc.yaml)
|
|
266
|
+
if err := wf.Validate(); err == nil {
|
|
267
|
+
t.Fatal("autoReject accepted outside a HITL node")
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
t.Run("rejects failure route to end", func(t *testing.T) {
|
|
273
|
+
yaml := strings.Replace(minimalValid, " onFailure:\n - target: coding", " onFailure:\n - target: end", 1)
|
|
274
|
+
wf := parse(t, "basicFlow", yaml)
|
|
275
|
+
if err := wf.Validate(); err == nil {
|
|
276
|
+
t.Fatal("failure route to end accepted")
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
224
281
|
func TestValidateWorkNodes(t *testing.T) {
|
|
225
282
|
replaceCoding := func(node string) string {
|
|
226
283
|
return strings.Replace(minimalValid, " coding:\n type: agent\n agent: build\n description: Do the coding work.\n onSuccess:\n - target: end\n onFailure:\n - target: coding", node, 1)
|
|
@@ -303,7 +360,7 @@ func TestValidateRoutes(t *testing.T) {
|
|
|
303
360
|
}
|
|
304
361
|
})
|
|
305
362
|
t.Run("several routes for one outcome", func(t *testing.T) {
|
|
306
|
-
yaml := strings.Replace(minimalValid, " onFailure:\n - target: coding", " onFailure:\n - target: coding\n when: retry\n - target:
|
|
363
|
+
yaml := strings.Replace(minimalValid, " onFailure:\n - target: coding", " onFailure:\n - target: coding\n when: retry\n - target: coding\n when: try again", 1)
|
|
307
364
|
wf := parse(t, "basicFlow", yaml)
|
|
308
365
|
if err := wf.Validate(); err != nil {
|
|
309
366
|
t.Fatalf("two failure routes rejected: %v", err)
|