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.
Files changed (57) hide show
  1. package/README.md +30 -34
  2. package/cmd/relay-flow/commands_test.go +449 -23
  3. package/cmd/relay-flow/main.go +207 -37
  4. package/cmd/relay-flow/observability_test.go +11 -0
  5. package/cmd/relay-flow/onboarding.go +1 -1
  6. package/cmd/relay-flow/onboarding_test.go +1 -1
  7. package/cmd/relay-flow/scenario_test.go +26 -10
  8. package/cmd/relay-flow/serve.go +27 -1
  9. package/examples/config-reference.yaml +23 -19
  10. package/examples/workflow-reference.yaml +3 -0
  11. package/internal/execution/goworkflows/activities.go +13 -22
  12. package/internal/execution/goworkflows/engine_test.go +17 -3
  13. package/internal/execution/goworkflows/fakes_test.go +20 -0
  14. package/internal/execution/goworkflows/interpreter.go +25 -11
  15. package/internal/execution/goworkflows/mailbox_test.go +28 -1
  16. package/internal/execution/goworkflows/ownership_test.go +259 -0
  17. package/internal/execution/goworkflows/recovery_test.go +81 -1
  18. package/internal/execution/temporal/activities.go +13 -22
  19. package/internal/execution/temporal/compact_mailbox_test.go +56 -0
  20. package/internal/execution/temporal/interpreter.go +16 -3
  21. package/internal/execution/temporal/recovery.go +68 -36
  22. package/internal/harness/harness.go +23 -10
  23. package/internal/harness/opencode/opencode.go +22 -4
  24. package/internal/harness/opencode/opencode_test.go +41 -1
  25. package/internal/harness/opencode/repo_setup.go +1 -1
  26. package/internal/harness/opencode/task_env_test.go +2 -2
  27. package/internal/harness/pi/pi.go +21 -2
  28. package/internal/harness/pi/pi_test.go +7 -4
  29. package/internal/harness/pi/prompt_test.go +13 -5
  30. package/internal/harness/pi/task_env_test.go +18 -2
  31. package/internal/recover/recover.go +30 -1
  32. package/internal/repo/binding_test.go +63 -0
  33. package/internal/repo/repo.go +47 -6
  34. package/internal/router/router.go +39 -5
  35. package/internal/router/router_test.go +67 -1
  36. package/internal/run/manager.go +90 -3
  37. package/internal/run/run_manager_test.go +74 -0
  38. package/internal/server/api_test.go +13 -0
  39. package/internal/server/client.go +7 -0
  40. package/internal/server/fixture_test.go +5 -0
  41. package/internal/server/server.go +32 -3
  42. package/internal/task/beads/beads.go +201 -3
  43. package/internal/task/beads/beads_test.go +172 -0
  44. package/internal/task/jira/effects_test.go +86 -0
  45. package/internal/task/jira/filters_test.go +103 -0
  46. package/internal/task/jira/helpers_test.go +11 -2
  47. package/internal/task/jira/jira.go +244 -15
  48. package/internal/task/jira/normalize.go +30 -15
  49. package/internal/task/jira/rest/client.go +17 -2
  50. package/internal/task/jira/templates_test.go +38 -2
  51. package/internal/task/jira/transition_defaults_test.go +3 -1
  52. package/internal/task/task.go +67 -0
  53. package/internal/workflow/report.go +10 -0
  54. package/internal/workflow/report_test.go +16 -0
  55. package/internal/workflow/workflow.go +24 -0
  56. package/internal/workflow/workflow_test.go +58 -1
  57. 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: end\n when: give up", 1)
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-flow",
3
- "version": "0.3.9-alpha",
3
+ "version": "0.3.11-alpha",
4
4
  "description": "Graph-based agent workflow engine — tracker-agnostic, pluggable runners",
5
5
  "bin": {
6
6
  "relay-flow": "./bin/relay-flow.js",