relay-flow 0.3.12-alpha → 0.3.13-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 +5 -5
- package/cmd/relay-flow/commands_test.go +39 -0
- package/cmd/relay-flow/main.go +1 -1
- package/internal/execution/goworkflows/engine_test.go +1 -1
- package/internal/execution/temporal/operations_test.go +1 -1
- package/internal/harness/opencode/opencode_test.go +1 -1
- package/internal/harness/opencode/repo_setup.go +1 -1
- package/internal/workflow/report.go +4 -4
- package/internal/workflow/report_test.go +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,13 +28,13 @@ same `relay-flow-plugin` package with host-specific entrypoints.
|
|
|
28
28
|
**OpenCode**
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
opencode plugin relay-flow-plugin@0.3.
|
|
31
|
+
opencode plugin relay-flow-plugin@0.3.13-alpha
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
**Pi**
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
pi install npm:relay-flow-plugin@0.3.
|
|
37
|
+
pi install npm:relay-flow-plugin@0.3.13-alpha
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Pi loads the package's `pi.ts` extension from its manifest. Do not add
|
|
@@ -164,7 +164,7 @@ OpenCode plugin configuration uses both entrypoints. The server entrypoint is li
|
|
|
164
164
|
```json
|
|
165
165
|
{
|
|
166
166
|
"$schema": "https://opencode.ai/config.json",
|
|
167
|
-
"plugin": ["relay-flow-plugin@0.3.
|
|
167
|
+
"plugin": ["relay-flow-plugin@0.3.13-alpha"]
|
|
168
168
|
}
|
|
169
169
|
```
|
|
170
170
|
|
|
@@ -173,7 +173,7 @@ The native HITL approval entrypoint is listed in `.opencode/tui.json`:
|
|
|
173
173
|
```json
|
|
174
174
|
{
|
|
175
175
|
"$schema": "https://opencode.ai/tui.json",
|
|
176
|
-
"plugin": ["relay-flow-plugin@0.3.
|
|
176
|
+
"plugin": ["relay-flow-plugin@0.3.13-alpha"]
|
|
177
177
|
}
|
|
178
178
|
```
|
|
179
179
|
|
|
@@ -189,7 +189,7 @@ Pi plugin: install the same published package manually in Pi's global package
|
|
|
189
189
|
settings before starting a Pi harness session:
|
|
190
190
|
|
|
191
191
|
```sh
|
|
192
|
-
pi install npm:relay-flow-plugin@0.3.
|
|
192
|
+
pi install npm:relay-flow-plugin@0.3.13-alpha
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
Relay-flow does not install or configure the package automatically. Pi resolves
|
|
@@ -308,6 +308,45 @@ func TestReportValidationErrorIsStructuredOnStderr(t *testing.T) {
|
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
func TestReportOtherAPIErrorsPreserveCodeForRetryClassification(t *testing.T) {
|
|
312
|
+
const report = `{"runId":"run-1","node":"coding","reportId":"s:m","report":{"status":"success","nextStep":"end"}}`
|
|
313
|
+
for _, tc := range []struct {
|
|
314
|
+
name, code string
|
|
315
|
+
err error
|
|
316
|
+
}{
|
|
317
|
+
{"invalid request", "invalid", fmt.Errorf("%w: malformed report", server.ErrInvalid)},
|
|
318
|
+
{"server failure", "internalError", errors.New("temporary backend failure")},
|
|
319
|
+
} {
|
|
320
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
321
|
+
home := t.TempDir()
|
|
322
|
+
serveAck(t, home, runsvc.ReportAck{}, tc.err)
|
|
323
|
+
reader, writer, err := os.Pipe()
|
|
324
|
+
if err != nil {
|
|
325
|
+
t.Fatal(err)
|
|
326
|
+
}
|
|
327
|
+
original := os.Stderr
|
|
328
|
+
os.Stderr = writer
|
|
329
|
+
code := cli(t, home, report, "report")
|
|
330
|
+
os.Stderr = original
|
|
331
|
+
_ = writer.Close()
|
|
332
|
+
defer reader.Close()
|
|
333
|
+
output, err := io.ReadAll(reader)
|
|
334
|
+
if err != nil {
|
|
335
|
+
t.Fatal(err)
|
|
336
|
+
}
|
|
337
|
+
var response struct {
|
|
338
|
+
Error struct {
|
|
339
|
+
Code string `json:"code"`
|
|
340
|
+
Message string `json:"message"`
|
|
341
|
+
} `json:"error"`
|
|
342
|
+
}
|
|
343
|
+
if err := json.Unmarshal(output, &response); err != nil || code != exitFail || response.Error.Code != tc.code || response.Error.Message != tc.err.Error() {
|
|
344
|
+
t.Fatalf("CLI response: exit=%d stderr=%q parsed=%+v err=%v", code, output, response, err)
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
311
350
|
func TestReportUnreachableServerExits1(t *testing.T) {
|
|
312
351
|
valid := `{"runId":"payments/basicFlow/PAY-101","node":"coding","reportId":"s:m","report":{"status":"success","nextStep":"end","summary":{"completed":"x","commits":"abc123","notCompleted":"None","issuesDiscovered":"None","verification":"x","notes":"None"},"feedback":{"reasonForNextStep":"None","requiredActions":"None","relevantContext":"None","expectedResult":"None"}}}`
|
|
313
352
|
if code := cli(t, t.TempDir(), valid, "report"); code != 1 {
|
package/cmd/relay-flow/main.go
CHANGED
|
@@ -1148,7 +1148,7 @@ func cmdReport(c *server.Client, stdin io.Reader) int {
|
|
|
1148
1148
|
defer cancel()
|
|
1149
1149
|
if _, err := c.SubmitReport(ctx, req); err != nil {
|
|
1150
1150
|
var apiErr *server.APIError
|
|
1151
|
-
if errors.As(err, &apiErr)
|
|
1151
|
+
if errors.As(err, &apiErr) {
|
|
1152
1152
|
_ = json.NewEncoder(os.Stderr).Encode(map[string]any{
|
|
1153
1153
|
"error": map[string]string{"code": apiErr.Code, "message": apiErr.Message},
|
|
1154
1154
|
})
|
|
@@ -316,7 +316,7 @@ func TestInvalidReportIsPermanentAndDoesNotSignal(t *testing.T) {
|
|
|
316
316
|
if !errors.Is(err, run.ErrInvalidReport) || ack.Accepted {
|
|
317
317
|
t.Fatalf("invalid report: ack=%+v err=%v", ack, err)
|
|
318
318
|
}
|
|
319
|
-
if want := `
|
|
319
|
+
if want := `NEXT STEP is end, so FEEDBACK must be exactly None.`; err.Error() != want {
|
|
320
320
|
t.Fatalf("validation message = %q, want %q", err, want)
|
|
321
321
|
}
|
|
322
322
|
after, err := engine.GetRun(context.Background(), rid)
|
|
@@ -85,7 +85,7 @@ func TestTemporalSubmitReportRejectsInvalidReportBeforeSignal(t *testing.T) {
|
|
|
85
85
|
if !errors.Is(err, run.ErrInvalidReport) || ack.Accepted || client.signaled {
|
|
86
86
|
t.Fatalf("invalid Temporal report: ack=%+v err=%v signaled=%v", ack, err, client.signaled)
|
|
87
87
|
}
|
|
88
|
-
if want := `
|
|
88
|
+
if want := `NEXT STEP is end, so FEEDBACK must be exactly None.`; err.Error() != want {
|
|
89
89
|
t.Fatalf("validation message = %q, want %q", err, want)
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -14,7 +14,7 @@ import (
|
|
|
14
14
|
"github.com/rajpopat27/relay-flow/internal/workflow"
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
const configuredPlugin = "relay-flow-plugin@0.3.
|
|
17
|
+
const configuredPlugin = "relay-flow-plugin@0.3.13-alpha"
|
|
18
18
|
|
|
19
19
|
func TestBuildCommandArgv(t *testing.T) {
|
|
20
20
|
t.Setenv("RELAY_FLOW_HOME", "/var/lib/relay-flow-test")
|
|
@@ -50,9 +50,6 @@ func (w *Workflow) ValidateReport(node string, report Report) error {
|
|
|
50
50
|
if report.Status != OutcomeSuccess && report.Status != OutcomeFailure {
|
|
51
51
|
return fmt.Errorf("report status %q must be %q or %q", report.Status, OutcomeSuccess, OutcomeFailure)
|
|
52
52
|
}
|
|
53
|
-
if report.Status == OutcomeFailure && report.NextStep == EndNode {
|
|
54
|
-
return fmt.Errorf("failure reports cannot select %q", EndNode)
|
|
55
|
-
}
|
|
56
53
|
for _, field := range []struct{ name, value string }{
|
|
57
54
|
{"summary.completed", report.Summary.Completed},
|
|
58
55
|
{"summary.commits", report.Summary.Commits},
|
|
@@ -74,6 +71,9 @@ func (w *Workflow) ValidateReport(node string, report Report) error {
|
|
|
74
71
|
if err != nil {
|
|
75
72
|
return err
|
|
76
73
|
}
|
|
74
|
+
if report.Status == OutcomeFailure && report.NextStep == EndNode {
|
|
75
|
+
return fmt.Errorf("STATUS is failure, so NEXT STEP cannot be end; valid targets: %s", strings.Join(sortedTargets(routes), ", "))
|
|
76
|
+
}
|
|
77
77
|
legal := false
|
|
78
78
|
for _, r := range routes {
|
|
79
79
|
if r.Target == report.NextStep {
|
|
@@ -88,7 +88,7 @@ func (w *Workflow) ValidateReport(node string, report Report) error {
|
|
|
88
88
|
if report.NextStep == EndNode {
|
|
89
89
|
f := report.Feedback
|
|
90
90
|
if f.ReasonForNextStep != None || f.RequiredActions != None || f.RelevantContext != None || f.ExpectedResult != None {
|
|
91
|
-
return fmt.Errorf("
|
|
91
|
+
return fmt.Errorf("NEXT STEP is end, so FEEDBACK must be exactly None.")
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
return nil
|
|
@@ -208,8 +208,8 @@ func TestValidateReportNextStepMustMatchStatusRoute(t *testing.T) {
|
|
|
208
208
|
Summary: fullSummary(),
|
|
209
209
|
Feedback: fullFeedback(),
|
|
210
210
|
}
|
|
211
|
-
if err := wf.ValidateReport("coding", r); err == nil {
|
|
212
|
-
t.
|
|
211
|
+
if err := wf.ValidateReport("coding", r); err == nil || !strings.Contains(err.Error(), "valid targets: coding") {
|
|
212
|
+
t.Fatalf("failure report selecting a success-only target: %v, want configured failure route", err)
|
|
213
213
|
}
|
|
214
214
|
})
|
|
215
215
|
|
|
@@ -265,8 +265,8 @@ func TestValidateReportEndRequiresNoneFeedback(t *testing.T) {
|
|
|
265
265
|
Summary: fullSummary(),
|
|
266
266
|
Feedback: fullFeedback(),
|
|
267
267
|
}
|
|
268
|
-
if err := wf.ValidateReport("coding", r); err == nil {
|
|
269
|
-
t.
|
|
268
|
+
if err := wf.ValidateReport("coding", r); err == nil || err.Error() != "NEXT STEP is end, so FEEDBACK must be exactly None." {
|
|
269
|
+
t.Fatalf("end report error = %v, want actionable four-field correction", err)
|
|
270
270
|
}
|
|
271
271
|
})
|
|
272
272
|
|