relay-flow 0.0.1 → 0.2.0-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 +148 -143
- package/cmd/relay-flow/commands_test.go +464 -0
- package/cmd/relay-flow/main.go +670 -180
- package/cmd/relay-flow/scenario_test.go +1135 -0
- package/cmd/relay-flow/serve.go +609 -0
- package/go.mod +69 -2
- package/go.sum +185 -0
- package/internal/config/config.go +88 -0
- package/internal/config/machine.go +99 -48
- package/internal/config/machine_test.go +248 -0
- package/internal/config/merge_test.go +118 -0
- package/internal/config/writeatomic.go +36 -0
- package/internal/config/writeatomic_test.go +98 -0
- package/internal/execution/goworkflows/activities.go +490 -0
- package/internal/execution/goworkflows/engine.go +487 -0
- package/internal/execution/goworkflows/engine_test.go +600 -0
- package/internal/execution/goworkflows/fakes_test.go +517 -0
- package/internal/execution/goworkflows/interpreter.go +605 -0
- package/internal/execution/goworkflows/logging_test.go +154 -0
- package/internal/execution/goworkflows/mailbox_test.go +423 -0
- package/internal/execution/goworkflows/node_runtime_integration_test.go +127 -0
- package/internal/execution/goworkflows/node_runtime_test.go +486 -0
- package/internal/execution/goworkflows/projection.go +504 -0
- package/internal/execution/goworkflows/recovery_test.go +1092 -0
- package/internal/execution/goworkflows/retry_log_test.go +59 -0
- package/internal/execution/goworkflows/retry_projection_test.go +98 -0
- package/internal/harness/contract_test.go +169 -0
- package/internal/harness/factory.go +63 -0
- package/internal/harness/harness.go +41 -0
- package/internal/harness/opencode/opencode.go +166 -0
- package/internal/harness/opencode/opencode_test.go +50 -0
- package/internal/harness/plugin_selection_test.go +126 -0
- package/internal/identity/identity.go +37 -0
- package/internal/logging/logging.go +56 -0
- package/internal/logging/logging_test.go +116 -0
- package/internal/paths/paths.go +67 -0
- package/internal/recover/recover.go +115 -0
- package/internal/repo/poller.go +186 -0
- package/internal/repo/poller_test.go +327 -0
- package/internal/repo/repo.go +119 -0
- package/internal/repo/service.go +216 -0
- package/internal/repo/service_test.go +298 -0
- package/internal/retry/retry.go +118 -0
- package/internal/router/router.go +83 -0
- package/internal/router/router_test.go +144 -0
- package/internal/run/manager.go +108 -0
- package/internal/run/run.go +140 -0
- package/internal/run/run_identity_test.go +52 -0
- package/internal/run/run_manager_test.go +266 -0
- package/internal/runner/contract_test.go +221 -0
- package/internal/runner/factory.go +65 -0
- package/internal/runner/orca/orca.go +363 -170
- package/internal/runner/orca/orca_test.go +134 -160
- package/internal/runner/orca/orcacli/orcacli.go +215 -0
- package/internal/runner/orca/orcacli/orcacli_test.go +154 -0
- package/internal/runner/orca/orcacli/testdata/repo-list.json +18 -0
- package/internal/runner/orca/orcacli/testdata/strict-orca.sh +30 -0
- package/internal/runner/orca/orcacli/testdata/terminal-close.json +12 -0
- package/internal/runner/orca/orcacli/testdata/terminal-create.json +18 -0
- package/internal/runner/orca/orcacli/testdata/terminal-list.json +51 -0
- package/internal/runner/orca/orcacli/testdata/terminal-send.json +1 -0
- package/internal/runner/orca/orcacli/testdata/terminal-show.json +1 -0
- package/internal/runner/orca/orcacli/testdata/worktree-create.json +22 -0
- package/internal/runner/orca/orcacli/testdata/worktree-list.json +31 -0
- package/internal/runner/orca/orcacli/testdata/worktree-remove.json +6 -0
- package/internal/runner/runner.go +47 -64
- package/internal/server/api_test.go +300 -0
- package/internal/server/client.go +192 -74
- package/internal/server/fixture_test.go +248 -0
- package/internal/server/server.go +425 -248
- package/internal/server/shutdown_test.go +116 -0
- package/internal/task/contract_test.go +223 -0
- package/internal/task/factory.go +103 -0
- package/internal/task/jira/acli/acli.go +306 -0
- package/internal/task/jira/acli/acli_test.go +208 -0
- package/internal/task/jira/acli/testdata/acli_comments.json +55 -0
- package/internal/task/jira/acli/testdata/search_invalid_assignee.txt +1 -0
- package/internal/task/jira/acli/testdata/search_invalid_status.txt +1 -0
- package/internal/task/jira/acli/testdata/search_success.json +1 -0
- package/internal/task/jira/filters_test.go +234 -0
- package/internal/task/jira/helpers_test.go +60 -0
- package/internal/task/jira/jira.go +507 -0
- package/internal/task/jira/normalize.go +101 -0
- package/internal/task/jira/testdata/acli_search.json +120 -0
- package/internal/task/jira/transition_defaults_test.go +156 -0
- package/internal/task/jira/validation_test.go +94 -0
- package/internal/task/task.go +84 -0
- package/internal/workflow/report.go +85 -0
- package/internal/workflow/report_test.go +259 -0
- package/internal/workflow/service.go +142 -0
- package/internal/workflow/store.go +136 -0
- package/internal/workflow/store_test.go +282 -0
- package/internal/workflow/workflow.go +342 -0
- package/internal/workflow/workflow_test.go +410 -0
- package/package.json +1 -1
- package/internal/acli/acli.go +0 -229
- package/internal/config/demo_test.go +0 -17
- package/internal/config/schema.go +0 -193
- package/internal/config/schema_test.go +0 -162
- package/internal/daemon/daemon.go +0 -218
- package/internal/daemon/daemon_test.go +0 -204
- package/internal/discovery/discovery.go +0 -122
- package/internal/discovery/discovery_test.go +0 -62
- package/internal/opencode/opencode.go +0 -26
- package/internal/orcacli/orcacli.go +0 -264
- package/internal/runner/orca/README.md +0 -64
- package/internal/runner/runner_test.go +0 -64
- package/internal/server/server_test.go +0 -195
- package/internal/tasks/jira/README.md +0 -69
- package/internal/tasks/jira/component_test.go +0 -16
- package/internal/tasks/jira/decode.go +0 -24
- package/internal/tasks/jira/jira.go +0 -231
- package/internal/tasks/jira/jira_test.go +0 -259
- package/internal/tasks/jira/jql_test.go +0 -16
- package/internal/tasks/tasks.go +0 -90
- package/internal/tasks/tasks_test.go +0 -91
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
package orcacli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"os"
|
|
6
|
+
"os/exec"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func TestCLIContractsAgainstCapturedRealOutput(t *testing.T) {
|
|
13
|
+
installStrictFakeOrca(t)
|
|
14
|
+
ctx := context.Background()
|
|
15
|
+
cli := New()
|
|
16
|
+
|
|
17
|
+
repos, err := cli.ListRepos(ctx)
|
|
18
|
+
if err != nil {
|
|
19
|
+
t.Fatal(err)
|
|
20
|
+
}
|
|
21
|
+
if len(repos) != 1 || repos[0].ID != "repo-1" || repos[0].DisplayName != "app" || repos[0].Path != "/srv/app" {
|
|
22
|
+
t.Fatalf("ListRepos = %+v", repos)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
worktrees, err := cli.ListWorktrees(ctx)
|
|
26
|
+
if err != nil {
|
|
27
|
+
t.Fatal(err)
|
|
28
|
+
}
|
|
29
|
+
if len(worktrees) != 1 || worktrees[0].ID != "wt-main" || worktrees[0].Branch != "refs/heads/master" || !worktrees[0].IsMainWorktree {
|
|
30
|
+
t.Fatalf("ListWorktrees = %+v", worktrees)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if err := cli.CreateWorktree(ctx, "PAY-101", "repo-1", "wt-main", "origin/alice/PAY-101"); err != nil {
|
|
34
|
+
t.Fatal(err)
|
|
35
|
+
}
|
|
36
|
+
if err := cli.DeleteWorktree(ctx, "wt-PAY-101"); err != nil {
|
|
37
|
+
t.Fatal(err)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
terminals, err := cli.ListTerminals(ctx, "id:wt-PAY-101")
|
|
41
|
+
if err != nil {
|
|
42
|
+
t.Fatal(err)
|
|
43
|
+
}
|
|
44
|
+
if len(terminals) != 1 {
|
|
45
|
+
t.Fatalf("ListTerminals = %+v", terminals)
|
|
46
|
+
}
|
|
47
|
+
if got := terminals[0]; got.Handle != "term-1" || got.Title != "PAY-101:implement" || !got.Connected {
|
|
48
|
+
t.Fatalf("ListTerminals[0] = %+v", got)
|
|
49
|
+
}
|
|
50
|
+
if terminals[0].Title == "opencode" {
|
|
51
|
+
t.Fatal("ListTerminals used mutable pane title instead of stable visualLayouts tab title")
|
|
52
|
+
}
|
|
53
|
+
direct, err := cli.ShowTerminal(ctx, "term-1")
|
|
54
|
+
if err != nil || direct.Handle != "term-1" || !direct.Connected {
|
|
55
|
+
t.Fatalf("ShowTerminal = %+v, %v", direct, err)
|
|
56
|
+
}
|
|
57
|
+
if err := cli.SendTerminal(ctx, "term-1", "hello"); err != nil {
|
|
58
|
+
t.Fatal(err)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
handle, err := cli.CreateTerminal(ctx, "PAY-101", "PAY-101:implement", "echo hello")
|
|
62
|
+
if err != nil {
|
|
63
|
+
t.Fatal(err)
|
|
64
|
+
}
|
|
65
|
+
if handle != "term-1" {
|
|
66
|
+
t.Fatalf("CreateTerminal handle = %q", handle)
|
|
67
|
+
}
|
|
68
|
+
if err := cli.CloseTerminal(ctx, handle); err != nil {
|
|
69
|
+
t.Fatal(err)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func TestStrictFakeOrcaRejectsMissingFlags(t *testing.T) {
|
|
74
|
+
fake := installStrictFakeOrca(t)
|
|
75
|
+
if err := exec.Command(fake, "terminal", "list", "--worktree", "id:wt-PAY-101", "--json").Run(); err == nil {
|
|
76
|
+
t.Fatal("strict fake accepted terminal list without --include-visual-layouts")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func TestFindExistingBranchLocalOnly(t *testing.T) {
|
|
81
|
+
repo := newGitRepo(t)
|
|
82
|
+
runGit(t, repo, "branch", "alice/PAY-101")
|
|
83
|
+
|
|
84
|
+
got, ok, err := FindExistingBranch(repo, "PAY-101")
|
|
85
|
+
if err != nil {
|
|
86
|
+
t.Fatal(err)
|
|
87
|
+
}
|
|
88
|
+
if !ok || got != "alice/PAY-101" {
|
|
89
|
+
t.Fatalf("FindExistingBranch = %q, %v; want alice/PAY-101, true", got, ok)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func TestFindExistingBranchRemoteOnly(t *testing.T) {
|
|
94
|
+
repo := newGitRepo(t)
|
|
95
|
+
runGit(t, repo, "update-ref", "refs/remotes/origin/alice/PAY-102", "HEAD")
|
|
96
|
+
|
|
97
|
+
got, ok, err := FindExistingBranch(repo, "PAY-102")
|
|
98
|
+
if err != nil {
|
|
99
|
+
t.Fatal(err)
|
|
100
|
+
}
|
|
101
|
+
if !ok || got != "origin/alice/PAY-102" {
|
|
102
|
+
t.Fatalf("FindExistingBranch = %q, %v; want origin/alice/PAY-102, true", got, ok)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func TestFindExistingBranchMissing(t *testing.T) {
|
|
107
|
+
repo := newGitRepo(t)
|
|
108
|
+
runGit(t, repo, "branch", "alice/OTHER-1")
|
|
109
|
+
|
|
110
|
+
got, ok, err := FindExistingBranch(repo, "PAY-103")
|
|
111
|
+
if err != nil {
|
|
112
|
+
t.Fatal(err)
|
|
113
|
+
}
|
|
114
|
+
if ok || got != "" {
|
|
115
|
+
t.Fatalf("FindExistingBranch = %q, %v; want empty, false", got, ok)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func newGitRepo(t *testing.T) string {
|
|
120
|
+
t.Helper()
|
|
121
|
+
repo := t.TempDir()
|
|
122
|
+
runGit(t, repo, "init", "--quiet")
|
|
123
|
+
runGit(t, repo, "-c", "user.name=Relay Flow", "-c", "user.email=relay-flow@example.invalid", "commit", "--allow-empty", "--quiet", "-m", "initial")
|
|
124
|
+
return repo
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func runGit(t *testing.T, repo string, args ...string) {
|
|
128
|
+
t.Helper()
|
|
129
|
+
cmdArgs := append([]string{"-C", repo}, args...)
|
|
130
|
+
out, err := exec.Command("git", cmdArgs...).CombinedOutput()
|
|
131
|
+
if err != nil {
|
|
132
|
+
t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, out)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func installStrictFakeOrca(t *testing.T) string {
|
|
137
|
+
t.Helper()
|
|
138
|
+
binDir := t.TempDir()
|
|
139
|
+
fake := filepath.Join(binDir, "orca")
|
|
140
|
+
script, err := os.ReadFile(filepath.Join("testdata", "strict-orca.sh"))
|
|
141
|
+
if err != nil {
|
|
142
|
+
t.Fatal(err)
|
|
143
|
+
}
|
|
144
|
+
if err := os.WriteFile(fake, script, 0o755); err != nil {
|
|
145
|
+
t.Fatal(err)
|
|
146
|
+
}
|
|
147
|
+
fixtureDir, err := filepath.Abs("testdata")
|
|
148
|
+
if err != nil {
|
|
149
|
+
t.Fatal(err)
|
|
150
|
+
}
|
|
151
|
+
t.Setenv("ORCA_FIXTURE_DIR", fixtureDir)
|
|
152
|
+
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
153
|
+
return fake
|
|
154
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "captured-repo-list",
|
|
3
|
+
"ok": true,
|
|
4
|
+
"result": {
|
|
5
|
+
"repos": [
|
|
6
|
+
{
|
|
7
|
+
"id": "repo-1",
|
|
8
|
+
"path": "/srv/app",
|
|
9
|
+
"displayName": "app",
|
|
10
|
+
"kind": "git",
|
|
11
|
+
"externalWorktreeVisibility": "hide",
|
|
12
|
+
"upstream": null,
|
|
13
|
+
"gitRemoteIdentity": null
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"_meta": {"runtimeId": "runtime-1"}
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
fixture=
|
|
5
|
+
if [ "$#" -eq 3 ] && [ "$1" = repo ] && [ "$2" = list ] && [ "$3" = --json ]; then
|
|
6
|
+
fixture=repo-list.json
|
|
7
|
+
elif [ "$#" -eq 3 ] && [ "$1" = worktree ] && [ "$2" = list ] && [ "$3" = --json ]; then
|
|
8
|
+
fixture=worktree-list.json
|
|
9
|
+
elif [ "$#" -eq 11 ] && [ "$1" = worktree ] && [ "$2" = create ] && [ "$3" = --name ] && [ "$4" = PAY-101 ] && [ "$5" = --repo ] && [ "$6" = id:repo-1 ] && [ "$7" = --parent-worktree ] && [ "$8" = worktree:wt-main ] && [ "$9" = --base-branch ] && [ "${10}" = origin/alice/PAY-101 ] && [ "${11}" = --json ]; then
|
|
10
|
+
fixture=worktree-create.json
|
|
11
|
+
elif [ "$#" -eq 5 ] && [ "$1" = worktree ] && [ "$2" = rm ] && [ "$3" = --worktree ] && [ "$4" = id:wt-PAY-101 ] && [ "$5" = --json ]; then
|
|
12
|
+
fixture=worktree-remove.json
|
|
13
|
+
elif [ "$#" -eq 6 ] && [ "$1" = terminal ] && [ "$2" = list ] && [ "$3" = --worktree ] && [ "$4" = id:wt-PAY-101 ] && [ "$5" = --include-visual-layouts ] && [ "$6" = --json ]; then
|
|
14
|
+
fixture=terminal-list.json
|
|
15
|
+
elif [ "$#" -eq 5 ] && [ "$1" = terminal ] && [ "$2" = show ] && [ "$3" = --terminal ] && [ "$4" = term-1 ] && [ "$5" = --json ]; then
|
|
16
|
+
fixture=terminal-show.json
|
|
17
|
+
elif [ "$#" -eq 8 ] && [ "$1" = terminal ] && [ "$2" = send ] && [ "$3" = --terminal ] && [ "$4" = term-1 ] && [ "$5" = --text ] && [ "$6" = 'hello' ] && [ "$7" = --enter ] && [ "$8" = --json ]; then
|
|
18
|
+
fixture=terminal-send.json
|
|
19
|
+
elif [ "$#" -eq 9 ] && [ "$1" = terminal ] && [ "$2" = create ] && [ "$3" = --worktree ] && [ "$4" = name:PAY-101 ] && [ "$5" = --title ] && [ "$6" = PAY-101:implement ] && [ "$7" = --command ] && [ "$8" = 'echo hello' ] && [ "$9" = --json ]; then
|
|
20
|
+
fixture=terminal-create.json
|
|
21
|
+
elif [ "$#" -eq 5 ] && [ "$1" = terminal ] && [ "$2" = close ] && [ "$3" = --terminal ] && [ "$4" = term-1 ] && [ "$5" = --json ]; then
|
|
22
|
+
fixture=terminal-close.json
|
|
23
|
+
else
|
|
24
|
+
printf 'unsupported or malformed orca invocation:' >&2
|
|
25
|
+
printf ' <%s>' "$@" >&2
|
|
26
|
+
printf '\n' >&2
|
|
27
|
+
exit 64
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
cat "$ORCA_FIXTURE_DIR/$fixture"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "captured-terminal-create",
|
|
3
|
+
"ok": true,
|
|
4
|
+
"result": {
|
|
5
|
+
"terminal": {
|
|
6
|
+
"handle": "term-1",
|
|
7
|
+
"tabId": "tab-1",
|
|
8
|
+
"paneKey": "tab-1:leaf-1",
|
|
9
|
+
"ptyId": "wt-PAY-101@@pty-1",
|
|
10
|
+
"worktreeId": "wt-PAY-101",
|
|
11
|
+
"title": "PAY-101:implement",
|
|
12
|
+
"executionHostId": "local",
|
|
13
|
+
"hostPlatform": "linux",
|
|
14
|
+
"surface": "visible"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"_meta": {"runtimeId": "runtime-1"}
|
|
18
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "captured-terminal-list",
|
|
3
|
+
"ok": true,
|
|
4
|
+
"result": {
|
|
5
|
+
"terminals": [
|
|
6
|
+
{
|
|
7
|
+
"handle": "term-1",
|
|
8
|
+
"ptyId": "wt-PAY-101@@pty-1",
|
|
9
|
+
"worktreeId": "wt-PAY-101",
|
|
10
|
+
"worktreePath": "/srv/worktrees/PAY-101",
|
|
11
|
+
"branch": "refs/heads/alice/PAY-101",
|
|
12
|
+
"tabId": "tab-1",
|
|
13
|
+
"leafId": "leaf-1",
|
|
14
|
+
"title": "opencode",
|
|
15
|
+
"connected": true,
|
|
16
|
+
"writable": true,
|
|
17
|
+
"preview": ""
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"visualLayouts": [
|
|
21
|
+
{
|
|
22
|
+
"worktreeId": "wt-PAY-101",
|
|
23
|
+
"worktreePath": "/srv/worktrees/PAY-101",
|
|
24
|
+
"root": {
|
|
25
|
+
"type": "group",
|
|
26
|
+
"groupId": "group-1",
|
|
27
|
+
"activeTabId": "tab-1",
|
|
28
|
+
"tabs": [
|
|
29
|
+
{
|
|
30
|
+
"tabId": "tab-1",
|
|
31
|
+
"title": "PAY-101:implement",
|
|
32
|
+
"activeLeafId": "leaf-1",
|
|
33
|
+
"panes": {
|
|
34
|
+
"type": "terminal",
|
|
35
|
+
"handle": "term-1",
|
|
36
|
+
"tabId": "tab-1",
|
|
37
|
+
"leafId": "leaf-1",
|
|
38
|
+
"title": "opencode",
|
|
39
|
+
"connected": true,
|
|
40
|
+
"active": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"totalCount": 1,
|
|
48
|
+
"truncated": false
|
|
49
|
+
},
|
|
50
|
+
"_meta": {"runtimeId": "runtime-1"}
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"ok":true,"result":{}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"ok":true,"result":{"terminal":{"handle":"term-1","title":"PAY-101:implement","connected":true,"writable":true}}}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "captured-worktree-create",
|
|
3
|
+
"ok": true,
|
|
4
|
+
"result": {
|
|
5
|
+
"worktree": {
|
|
6
|
+
"id": "wt-PAY-101",
|
|
7
|
+
"instanceId": "instance-PAY-101",
|
|
8
|
+
"repoId": "repo-1",
|
|
9
|
+
"path": "/srv/worktrees/PAY-101",
|
|
10
|
+
"head": "0123456789abcdef",
|
|
11
|
+
"branch": "refs/heads/alice/PAY-101",
|
|
12
|
+
"isBare": false,
|
|
13
|
+
"isMainWorktree": false,
|
|
14
|
+
"displayName": "PAY-101",
|
|
15
|
+
"baseRef": "origin/alice/PAY-101",
|
|
16
|
+
"parentWorktreeId": "wt-main",
|
|
17
|
+
"childWorktreeIds": []
|
|
18
|
+
},
|
|
19
|
+
"warnings": []
|
|
20
|
+
},
|
|
21
|
+
"_meta": {"runtimeId": "runtime-1"}
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "captured-worktree-list",
|
|
3
|
+
"ok": true,
|
|
4
|
+
"result": {
|
|
5
|
+
"worktrees": [
|
|
6
|
+
{
|
|
7
|
+
"id": "wt-main",
|
|
8
|
+
"instanceId": "instance-main",
|
|
9
|
+
"repoId": "repo-1",
|
|
10
|
+
"path": "/srv/app",
|
|
11
|
+
"head": "0123456789abcdef",
|
|
12
|
+
"branch": "refs/heads/master",
|
|
13
|
+
"isBare": false,
|
|
14
|
+
"isMainWorktree": true,
|
|
15
|
+
"displayName": "master",
|
|
16
|
+
"parentWorktreeId": null,
|
|
17
|
+
"childWorktreeIds": [],
|
|
18
|
+
"git": {
|
|
19
|
+
"path": "/srv/app",
|
|
20
|
+
"head": "0123456789abcdef",
|
|
21
|
+
"branch": "refs/heads/master",
|
|
22
|
+
"isBare": false,
|
|
23
|
+
"isMainWorktree": true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"totalCount": 1,
|
|
28
|
+
"truncated": false
|
|
29
|
+
},
|
|
30
|
+
"_meta": {"runtimeId": "runtime-1"}
|
|
31
|
+
}
|
|
@@ -1,81 +1,64 @@
|
|
|
1
|
-
// Package runner defines the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Package runner defines the runner contract: workspaces/environments,
|
|
2
|
+
// terminals, liveness, and process handles. The runner does not know
|
|
3
|
+
// task-system fields, workflow routes, report contents, or agent command
|
|
4
|
+
// syntax.
|
|
5
5
|
package runner
|
|
6
6
|
|
|
7
7
|
import (
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"sync"
|
|
8
|
+
"context"
|
|
9
|
+
"errors"
|
|
11
10
|
|
|
12
|
-
"github.com/rajpopat27/relay-flow/internal/
|
|
11
|
+
"github.com/rajpopat27/relay-flow/internal/identity"
|
|
13
12
|
)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
type Session struct {
|
|
17
|
-
ID string
|
|
18
|
-
Title string
|
|
19
|
-
}
|
|
14
|
+
var ErrSessionUnavailable = errors.New("stored session unavailable")
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// "<key>:<agent>:<node>", injects env, and sends the initial prompt.
|
|
25
|
-
Spawn(t tasks.Ticket, node, agent, prompt string, env map[string]string) error
|
|
26
|
-
// Find locates the existing session for this ticket+node (bounce).
|
|
27
|
-
Find(t tasks.Ticket, node string) (Session, bool, error)
|
|
28
|
-
// Nudge sends a prompt into an existing session.
|
|
29
|
-
Nudge(s Session, prompt string) error
|
|
30
|
-
// Close tears down all of the ticket's sessions (terminal node).
|
|
31
|
-
Close(t tasks.Ticket) error
|
|
16
|
+
type RepoCandidate struct {
|
|
17
|
+
Name string `json:"name"`
|
|
18
|
+
Path string `json:"path"`
|
|
32
19
|
}
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
UnmarshalConfig func(map[string]any) (any, error)
|
|
38
|
-
New func(cfg any) (Runner, error)
|
|
21
|
+
type Environment struct {
|
|
22
|
+
ID string `json:"id"`
|
|
23
|
+
Path string `json:"path"`
|
|
39
24
|
}
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
type Terminal struct {
|
|
27
|
+
ID string `json:"id"`
|
|
28
|
+
Title string `json:"title"`
|
|
29
|
+
}
|
|
45
30
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
defer mu.Unlock()
|
|
51
|
-
if _, dup := factories[name]; dup {
|
|
52
|
-
panic("runner: duplicate registration " + name)
|
|
53
|
-
}
|
|
54
|
-
factories[name] = f
|
|
31
|
+
type Command struct {
|
|
32
|
+
Executable string `json:"executable"`
|
|
33
|
+
Args []string `json:"args,omitempty"`
|
|
34
|
+
Env map[string]string `json:"env,omitempty"`
|
|
55
35
|
}
|
|
56
36
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if !ok {
|
|
63
|
-
return nil, fmt.Errorf("unknown runner type %q (registered: %v)", typeName, registered())
|
|
64
|
-
}
|
|
65
|
-
cfg, err := f.UnmarshalConfig(rawCfg)
|
|
66
|
-
if err != nil {
|
|
67
|
-
return nil, fmt.Errorf("runner type %q config: %w", typeName, err)
|
|
68
|
-
}
|
|
69
|
-
return f.New(cfg)
|
|
37
|
+
type RunSpec struct {
|
|
38
|
+
RunID identity.RunID
|
|
39
|
+
RepoName string
|
|
40
|
+
RepoPath string
|
|
41
|
+
TicketKey string
|
|
70
42
|
}
|
|
71
43
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
44
|
+
// Runner owns ticket-scoped environments, terminals, and liveness.
|
|
45
|
+
// FindTerminal returns only a live usable terminal. CloseTerminals closes
|
|
46
|
+
// agent terminals while preserving the environment/workspace; CleanupRun
|
|
47
|
+
// removes all runner-owned run resources at end. Both reconstruct identity
|
|
48
|
+
// from RunSpec without SQLite IDs.
|
|
49
|
+
//
|
|
50
|
+
// Terminal titles are stable and minimal: only "<ticket>:<node>" — never
|
|
51
|
+
// nodeVisitID, workflow name, agent name, or other changing metadata.
|
|
52
|
+
type Runner interface {
|
|
53
|
+
DiscoverRepos(ctx context.Context) ([]RepoCandidate, error)
|
|
54
|
+
ValidateRepo(ctx context.Context, name, path string) error
|
|
55
|
+
EnsureEnvironment(ctx context.Context, spec RunSpec) (Environment, error)
|
|
56
|
+
InspectTerminal(ctx context.Context, terminal Terminal) (Terminal, bool, error)
|
|
57
|
+
SendTerminal(ctx context.Context, terminal Terminal, text string) error
|
|
58
|
+
CreateTerminal(ctx context.Context, env Environment, title string, command Command) (Terminal, error)
|
|
59
|
+
FindTerminal(ctx context.Context, env Environment, title string) (Terminal, bool, error)
|
|
60
|
+
CloseTerminal(ctx context.Context, terminal Terminal) error
|
|
61
|
+
EnsureTerminal(ctx context.Context, env Environment, title string, command Command) (Terminal, error)
|
|
62
|
+
CloseTerminals(ctx context.Context, spec RunSpec) error
|
|
63
|
+
CleanupRun(ctx context.Context, spec RunSpec) error
|
|
81
64
|
}
|