relay-flow 0.3.10-alpha → 0.3.12-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 (34) hide show
  1. package/README.md +10 -22
  2. package/cmd/relay-flow/commands_test.go +40 -2
  3. package/cmd/relay-flow/main.go +13 -1
  4. package/examples/config-reference.yaml +23 -19
  5. package/examples/workflow-reference.yaml +3 -0
  6. package/internal/execution/goworkflows/activities.go +13 -22
  7. package/internal/execution/goworkflows/engine.go +1 -1
  8. package/internal/execution/goworkflows/engine_test.go +52 -3
  9. package/internal/execution/goworkflows/interpreter.go +24 -11
  10. package/internal/execution/goworkflows/mailbox_test.go +28 -1
  11. package/internal/execution/temporal/activities.go +13 -22
  12. package/internal/execution/temporal/compact_mailbox_test.go +56 -0
  13. package/internal/execution/temporal/interpreter.go +14 -2
  14. package/internal/execution/temporal/operations.go +1 -1
  15. package/internal/execution/temporal/operations_test.go +75 -0
  16. package/internal/harness/harness.go +22 -10
  17. package/internal/harness/opencode/opencode.go +17 -4
  18. package/internal/harness/opencode/opencode_test.go +25 -1
  19. package/internal/harness/opencode/repo_setup.go +1 -1
  20. package/internal/harness/opencode/task_env_test.go +2 -2
  21. package/internal/harness/pi/pi.go +15 -2
  22. package/internal/harness/pi/pi_test.go +7 -4
  23. package/internal/harness/pi/prompt_test.go +13 -5
  24. package/internal/harness/pi/task_env_test.go +2 -2
  25. package/internal/run/run.go +10 -0
  26. package/internal/server/api_test.go +31 -0
  27. package/internal/server/client.go +11 -1
  28. package/internal/server/fixture_test.go +4 -0
  29. package/internal/server/server.go +2 -0
  30. package/internal/task/jira/jira.go +16 -11
  31. package/internal/task/jira/templates_test.go +38 -2
  32. package/internal/task/task.go +1 -0
  33. package/internal/workflow/report.go +7 -0
  34. package/package.json +1 -1
@@ -66,17 +66,16 @@ const (
66
66
  defaultWorkTaskStatus = "In Progress"
67
67
  defaultEndParentStatus = "Done"
68
68
  currentUserAssigneeFilter = "currentUser()"
69
- defaultMailboxDescription = `Parent ticket: {{ticket}}
70
- Workflow: {{workflow}}
71
- Node: {{node}}
72
- Node type: {{nodeType}}
73
- Agent: {{agent}}
74
- Mailbox: {{mailbox}}
75
-
76
- Node work:
69
+ defaultMailboxDescription = `{{ticket}} / {{node}} — {{nodeType}} — {{agent}}
77
70
  {{nodeDescription}}
78
71
 
79
- Read this mailbox's comments for feedback from previous nodes.`
72
+ Success routes:
73
+ {{successRoutes}}
74
+
75
+ Failure routes:
76
+ {{failureRoutes}}
77
+
78
+ {{report}}`
80
79
  defaultSummaryComment = `Summary for {{node}}
81
80
 
82
81
  {{summaryReport}}`
@@ -93,6 +92,7 @@ var knownTextVars = map[string]bool{
93
92
  "nodeDescription": true, "nextSteps": true, "successRoutes": true,
94
93
  "failureRoutes": true, "mailbox": true, "sourceNode": true,
95
94
  "targetNode": true, "summaryReport": true, "feedbackReport": true,
95
+ "report": true,
96
96
  }
97
97
 
98
98
  // DefaultConfig supplies Jira task-system text defaults for relay-flow init.
@@ -509,6 +509,11 @@ func validateTemplates(templates Templates) error {
509
509
  }
510
510
  }
511
511
  }
512
+ for _, key := range []string{"report", "ticket", "node", "nodeType", "agent", "nodeDescription", "successRoutes", "failureRoutes"} {
513
+ if !strings.Contains(templates.MailboxDescription, "{{"+key+"}}") {
514
+ return fmt.Errorf("mailboxDescription must contain {{%s}}", key)
515
+ }
516
+ }
512
517
  if !strings.Contains(templates.SummaryComment, "{{summaryReport}}") {
513
518
  return fmt.Errorf("summaryComment must contain {{summaryReport}}")
514
519
  }
@@ -535,11 +540,11 @@ func (s *system) RenderText(kind task.TextKind, data task.TextData) (string, err
535
540
  "runID": data.RunID, "ticket": data.Ticket,
536
541
  "workflow": data.Workflow, "repo": data.Repo, "node": data.Node,
537
542
  "nodeType": data.NodeType, "agent": data.Agent,
538
- "nodeDescription": data.NodeDescription, "nextSteps": data.NextSteps,
543
+ "nodeDescription": strings.ReplaceAll(data.NodeDescription, "{{ticket}}", data.Ticket), "nextSteps": data.NextSteps,
539
544
  "successRoutes": data.SuccessRoutes, "failureRoutes": data.FailureRoutes,
540
545
  "mailbox": data.Mailbox, "sourceNode": data.SourceNode,
541
546
  "targetNode": data.TargetNode, "summaryReport": data.SummaryReport,
542
- "feedbackReport": data.FeedbackReport,
547
+ "feedbackReport": data.FeedbackReport, "report": data.Report,
543
548
  }
544
549
  return textVarPattern.ReplaceAllStringFunc(tmpl, func(match string) string {
545
550
  return values[textVarPattern.FindStringSubmatch(match)[1]]
@@ -10,6 +10,7 @@ import (
10
10
  "github.com/rajpopat27/relay-flow/internal/config"
11
11
  "github.com/rajpopat27/relay-flow/internal/run"
12
12
  "github.com/rajpopat27/relay-flow/internal/task"
13
+ "github.com/rajpopat27/relay-flow/internal/workflow"
13
14
  )
14
15
 
15
16
  func TestTaskTextTemplatesExposeValues(t *testing.T) {
@@ -17,7 +18,7 @@ func TestTaskTextTemplatesExposeValues(t *testing.T) {
17
18
  sys, err := newSystem(context.Background(), &fakeClient{fake: fake}, task.RepoSpec{
18
19
  Name: "payments",
19
20
  RootConfig: config.RawValues{"templates": map[string]any{
20
- "mailboxDescription": "mailbox {{runID}}|{{ticket}}|{{workflow}}|{{repo}}|{{node}}|{{nodeType}}|{{agent}}|{{nodeDescription}}|{{nextSteps}}|{{successRoutes}}|{{failureRoutes}}|{{mailbox}}",
21
+ "mailboxDescription": "mailbox {{runID}}|{{ticket}}|{{workflow}}|{{repo}}|{{node}}|{{nodeType}}|{{agent}}|{{nodeDescription}}|{{nextSteps}}|{{successRoutes}}|{{failureRoutes}}|{{mailbox}}|{{report}}",
21
22
  "summaryComment": "summary {{sourceNode}}|{{targetNode}}|{{mailbox}}\n{{summaryReport}}",
22
23
  "feedbackComment": "feedback {{sourceNode}}|{{targetNode}}|{{mailbox}}\n{{feedbackReport}}",
23
24
  }},
@@ -31,7 +32,7 @@ func TestTaskTextTemplatesExposeValues(t *testing.T) {
31
32
  Node: "review", NodeType: "hitl", Agent: "reviewer", NodeDescription: "Review changes.",
32
33
  NextSteps: "implement; end", SuccessRoutes: "end", FailureRoutes: "implement",
33
34
  Mailbox: "PAY-3", SourceNode: "review", TargetNode: "implement",
34
- SummaryReport: "COMPLETED:\nreviewed", FeedbackReport: "REQUIRED ACTIONS:\nfix it",
35
+ SummaryReport: "COMPLETED:\nreviewed", FeedbackReport: "REQUIRED ACTIONS:\nfix it", Report: "STATUS: success | failure",
35
36
  }
36
37
  description, err := sys.RenderText(task.TextMailboxDescription, data)
37
38
  if err != nil {
@@ -52,12 +53,47 @@ func TestTaskTextTemplatesExposeValues(t *testing.T) {
52
53
  }
53
54
  }
54
55
 
56
+ func TestJiraDefaultMailboxDescriptionIsCompact(t *testing.T) {
57
+ sys, err := newSystem(context.Background(), &fakeClient{fake: &fakeJira{}}, task.RepoSpec{
58
+ Name: "payments", RepoConfig: testRepoConfig(nil),
59
+ })
60
+ if err != nil {
61
+ t.Fatal(err)
62
+ }
63
+ data := task.TextData{
64
+ Ticket: "PAY-101", Node: "coder", NodeType: "agent", Agent: "coder",
65
+ NodeDescription: "Implement the change.", SuccessRoutes: "reviewer — when: ready",
66
+ FailureRoutes: "coder — when: retry", Report: workflow.ReportFormat,
67
+ }
68
+ description, err := sys.RenderText(task.TextMailboxDescription, data)
69
+ if err != nil {
70
+ t.Fatal(err)
71
+ }
72
+ want := "PAY-101 / coder — agent — coder\nImplement the change.\n\nSuccess routes:\nreviewer — when: ready\n\nFailure routes:\ncoder — when: retry\n\n" + workflow.ReportFormat
73
+ if description != want {
74
+ t.Fatalf("description = %q, want %q", description, want)
75
+ }
76
+ if strings.Contains(description, "comment history") {
77
+ t.Fatal("Jira description requested comment history")
78
+ }
79
+ data.NodeDescription = "Read the parent Jira ticket for this step:\nacli jira workitem view \"{{ticket}}\" --fields \"summary,description\" --json"
80
+ withParent, err := sys.RenderText(task.TextMailboxDescription, data)
81
+ if err != nil || !strings.Contains(withParent, `acli jira workitem view "PAY-101" --fields "summary,description" --json`) {
82
+ t.Fatalf("node-specific parent instruction = %q, %v", withParent, err)
83
+ }
84
+ }
85
+
55
86
  func TestCommentTemplatesRequireReportValues(t *testing.T) {
56
87
  for _, tc := range []struct {
57
88
  name string
58
89
  override map[string]any
59
90
  wantError string
60
91
  }{
92
+ {name: "description", override: map[string]any{"mailboxDescription": "missing"}, wantError: "mailboxDescription must contain {{report}}"},
93
+ {name: "report only", override: map[string]any{"mailboxDescription": "{{report}}"}, wantError: "mailboxDescription must contain {{ticket}}"},
94
+ {name: "no node work", override: map[string]any{"mailboxDescription": strings.Replace(defaultMailboxDescription, "{{nodeDescription}}", "No work provided", 1)}, wantError: "mailboxDescription must contain {{nodeDescription}}"},
95
+ {name: "no success routes", override: map[string]any{"mailboxDescription": strings.Replace(defaultMailboxDescription, "{{successRoutes}}", "None", 1)}, wantError: "mailboxDescription must contain {{successRoutes}}"},
96
+ {name: "no failure routes", override: map[string]any{"mailboxDescription": strings.Replace(defaultMailboxDescription, "{{failureRoutes}}", "None", 1)}, wantError: "mailboxDescription must contain {{failureRoutes}}"},
61
97
  {name: "summary", override: map[string]any{"summaryComment": "missing"}, wantError: "summaryComment must contain {{summaryReport}}"},
62
98
  {name: "feedback", override: map[string]any{"feedbackComment": "missing"}, wantError: "feedbackComment must contain {{feedbackReport}}"},
63
99
  {name: "unknown", override: map[string]any{"mailboxDescription": "{{mailboxInstructions}}"}, wantError: "unknown template variable {{mailboxInstructions}}"},
@@ -70,6 +70,7 @@ type TextData struct {
70
70
  TargetNode string
71
71
  SummaryReport string
72
72
  FeedbackReport string
73
+ Report string
73
74
  }
74
75
 
75
76
  type Target struct {
@@ -9,6 +9,13 @@ import (
9
9
  // represents an intentionally empty section.
10
10
  const None = "None"
11
11
 
12
+ // ReportFormat is the canonical agent-facing contract. Runtime plugins parse
13
+ // these four fields into the existing structured Report wire shape.
14
+ const ReportFormat = `STATUS: success | failure
15
+ NEXT STEP: <one valid route>
16
+ SUMMARY: <concise result>
17
+ FEEDBACK: <concise handoff, or None when NEXT STEP is end>`
18
+
12
19
  type Summary struct {
13
20
  Completed string `json:"completed"`
14
21
  Commits string `json:"commits"`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-flow",
3
- "version": "0.3.10-alpha",
3
+ "version": "0.3.12-alpha",
4
4
  "description": "Graph-based agent workflow engine — tracker-agnostic, pluggable runners",
5
5
  "bin": {
6
6
  "relay-flow": "./bin/relay-flow.js",