relay-flow 0.2.3-alpha → 0.2.5-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 (85) hide show
  1. package/README.md +121 -10
  2. package/cmd/relay-flow/backend_selection_test.go +149 -0
  3. package/cmd/relay-flow/main.go +96 -13
  4. package/cmd/relay-flow/pi_wiring_test.go +64 -0
  5. package/cmd/relay-flow/scenario_test.go +19 -2
  6. package/cmd/relay-flow/serve.go +100 -19
  7. package/cmd/relay-flow/serve_recovery_test.go +100 -0
  8. package/cmd/relay-flow/temporal_init.go +170 -0
  9. package/cmd/relay-flow/temporal_init_test.go +217 -0
  10. package/cmd/relay-flow/temporal_report_test.go +733 -0
  11. package/examples/beads-workflow.yaml +3 -3
  12. package/examples/config-reference.yaml +144 -0
  13. package/examples/minimal-beads-task-workflow.yaml +35 -0
  14. package/examples/minimal-jira-task-workflow.yaml +68 -0
  15. package/examples/workflow-reference.yaml +112 -0
  16. package/go.mod +37 -16
  17. package/go.sum +129 -61
  18. package/internal/config/machine.go +33 -1
  19. package/internal/config/machine_test.go +76 -0
  20. package/internal/execution/goworkflows/engine.go +13 -38
  21. package/internal/execution/goworkflows/projection.go +47 -464
  22. package/internal/execution/projection/projection.go +867 -0
  23. package/internal/execution/projection/projection_test.go +347 -0
  24. package/internal/execution/temporal/activities.go +567 -0
  25. package/internal/execution/temporal/engine.go +384 -0
  26. package/internal/execution/temporal/engine_test.go +277 -0
  27. package/internal/execution/temporal/interpreter.go +736 -0
  28. package/internal/execution/temporal/operations.go +455 -0
  29. package/internal/execution/temporal/operations_test.go +101 -0
  30. package/internal/execution/temporal/recovery.go +194 -0
  31. package/internal/execution/temporal/recovery_runtime.go +41 -0
  32. package/internal/execution/temporal/recovery_test.go +102 -0
  33. package/internal/execution/temporal/snapshot_restart_test.go +72 -0
  34. package/internal/execution/temporal/spike_test.go +934 -0
  35. package/internal/execution/temporal/visibility_lag_test.go +415 -0
  36. package/internal/harness/opencode/opencode.go +3 -1
  37. package/internal/harness/opencode/opencode_test.go +1 -1
  38. package/internal/harness/opencode/repo_setup.go +1 -1
  39. package/internal/harness/pi/config_test.go +46 -0
  40. package/internal/harness/pi/lifecycle_test.go +69 -0
  41. package/internal/harness/pi/pi.go +264 -0
  42. package/internal/harness/pi/pi_test.go +338 -0
  43. package/internal/harness/pi/prompt_test.go +150 -0
  44. package/internal/harness/pi/testdata/pi-0.84.1/capture.json +126 -0
  45. package/internal/harness/pi/testdata/pi-0.84.1/noninteractive-output.txt +11 -0
  46. package/internal/harness/pi/testdata/pi-0.84.1/tui-output-sanitized.txt +17 -0
  47. package/internal/harness/pi/validation_test.go +144 -0
  48. package/internal/runner/herdr/baseref.go +60 -0
  49. package/internal/runner/herdr/herdr.go +592 -0
  50. package/internal/runner/herdr/herdr_test.go +708 -0
  51. package/internal/runner/herdr/herdrcli/contract.go +128 -0
  52. package/internal/runner/herdr/herdrcli/exec.go +68 -0
  53. package/internal/runner/herdr/herdrcli/herdrcli_test.go +274 -0
  54. package/internal/runner/herdr/herdrcli/live_test.go +132 -0
  55. package/internal/runner/herdr/herdrcli/operations.go +281 -0
  56. package/internal/runner/herdr/herdrcli/response.go +116 -0
  57. package/internal/runner/herdr/herdrcli/testdata/empty-panes.json +1 -0
  58. package/internal/runner/herdr/herdrcli/testdata/empty-tabs.json +1 -0
  59. package/internal/runner/herdr/herdrcli/testdata/error-not-git-worktree.json +1 -0
  60. package/internal/runner/herdr/herdrcli/testdata/error-pane-not-found.json +1 -0
  61. package/internal/runner/herdr/herdrcli/testdata/error-workspace-not-found.json +1 -0
  62. package/internal/runner/herdr/herdrcli/testdata/error-worktree-not-found.json +1 -0
  63. package/internal/runner/herdr/herdrcli/testdata/malformed.json +1 -0
  64. package/internal/runner/herdr/herdrcli/testdata/pane-close.json +6 -0
  65. package/internal/runner/herdr/herdrcli/testdata/pane-get.json +25 -0
  66. package/internal/runner/herdr/herdrcli/testdata/pane-list.json +45 -0
  67. package/internal/runner/herdr/herdrcli/testdata/pane-process-info-shell.json +22 -0
  68. package/internal/runner/herdr/herdrcli/testdata/pane-process-info.json +23 -0
  69. package/internal/runner/herdr/herdrcli/testdata/pane-rename.json +23 -0
  70. package/internal/runner/herdr/herdrcli/testdata/snapshot.json +213 -0
  71. package/internal/runner/herdr/herdrcli/testdata/strict-herdr.sh +175 -0
  72. package/internal/runner/herdr/herdrcli/testdata/tab-create.json +31 -0
  73. package/internal/runner/herdr/herdrcli/testdata/tab-list.json +26 -0
  74. package/internal/runner/herdr/herdrcli/testdata/workspace-close.json +6 -0
  75. package/internal/runner/herdr/herdrcli/testdata/worktree-create.json +58 -0
  76. package/internal/runner/herdr/herdrcli/testdata/worktree-list.json +35 -0
  77. package/internal/runner/herdr/herdrcli/testdata/worktree-open.json +59 -0
  78. package/internal/runner/orca/orca.go +33 -0
  79. package/internal/runner/orca/orca_test.go +33 -4
  80. package/internal/runner/runner.go +8 -0
  81. package/internal/task/beads/beads.go +0 -16
  82. package/internal/task/beads/config_compatibility_test.go +7 -8
  83. package/internal/task/jira/filters_test.go +32 -6
  84. package/internal/task/jira/jira.go +27 -17
  85. package/package.json +1 -1
@@ -0,0 +1,213 @@
1
+ {
2
+ "id": "cli:api:snapshot",
3
+ "result": {
4
+ "snapshot": {
5
+ "agents": [],
6
+ "focused_pane_id": "w1:p1",
7
+ "focused_tab_id": "w1:t1",
8
+ "focused_workspace_id": "w1",
9
+ "layouts": [
10
+ {
11
+ "area": {
12
+ "height": 39,
13
+ "width": 94,
14
+ "x": 26,
15
+ "y": 1
16
+ },
17
+ "focused_pane_id": "w1:p1",
18
+ "panes": [
19
+ {
20
+ "focused": true,
21
+ "pane_id": "w1:p1",
22
+ "rect": {
23
+ "height": 39,
24
+ "width": 94,
25
+ "x": 26,
26
+ "y": 1
27
+ }
28
+ }
29
+ ],
30
+ "splits": [],
31
+ "tab_id": "w1:t1",
32
+ "workspace_id": "w1",
33
+ "zoomed": false
34
+ },
35
+ {
36
+ "area": {
37
+ "height": 39,
38
+ "width": 94,
39
+ "x": 26,
40
+ "y": 1
41
+ },
42
+ "focused_pane_id": "w2:p1",
43
+ "panes": [
44
+ {
45
+ "focused": true,
46
+ "pane_id": "w2:p1",
47
+ "rect": {
48
+ "height": 39,
49
+ "width": 94,
50
+ "x": 26,
51
+ "y": 1
52
+ }
53
+ }
54
+ ],
55
+ "splits": [],
56
+ "tab_id": "w2:t1",
57
+ "workspace_id": "w2",
58
+ "zoomed": false
59
+ },
60
+ {
61
+ "area": {
62
+ "height": 39,
63
+ "width": 94,
64
+ "x": 26,
65
+ "y": 1
66
+ },
67
+ "focused_pane_id": "w2:p2",
68
+ "panes": [
69
+ {
70
+ "focused": true,
71
+ "pane_id": "w2:p2",
72
+ "rect": {
73
+ "height": 39,
74
+ "width": 94,
75
+ "x": 26,
76
+ "y": 1
77
+ }
78
+ }
79
+ ],
80
+ "splits": [],
81
+ "tab_id": "w2:t2",
82
+ "workspace_id": "w2",
83
+ "zoomed": false
84
+ }
85
+ ],
86
+ "panes": [
87
+ {
88
+ "agent_status": "unknown",
89
+ "cwd": "/work/payments",
90
+ "focused": true,
91
+ "foreground_cwd": "/work/payments",
92
+ "pane_id": "w1:p1",
93
+ "revision": 1,
94
+ "scroll": {
95
+ "max_offset_from_bottom": 0,
96
+ "offset_from_bottom": 0,
97
+ "viewport_rows": 39
98
+ },
99
+ "tab_id": "w1:t1",
100
+ "terminal_id": "term_65a5cfcc8c6361",
101
+ "terminal_title": "relay@relay-host:/work/payments",
102
+ "terminal_title_stripped": "relay@relay-host:/work/payments",
103
+ "workspace_id": "w1"
104
+ },
105
+ {
106
+ "agent_status": "unknown",
107
+ "cwd": "/work/worktrees/payments/pay-101",
108
+ "focused": false,
109
+ "foreground_cwd": "/work/worktrees/payments/pay-101",
110
+ "pane_id": "w2:p1",
111
+ "revision": 1,
112
+ "scroll": {
113
+ "max_offset_from_bottom": 0,
114
+ "offset_from_bottom": 0,
115
+ "viewport_rows": 39
116
+ },
117
+ "tab_id": "w2:t1",
118
+ "terminal_id": "term_65a5cfcc8cd9e2",
119
+ "terminal_title": "relay@relay-host:~/.herdr/worktrees/repo/pay-101",
120
+ "terminal_title_stripped": "relay@relay-host:~/.herdr/worktrees/repo/pay-101",
121
+ "workspace_id": "w2"
122
+ },
123
+ {
124
+ "agent_status": "unknown",
125
+ "cwd": "/work/worktrees/payments/pay-101",
126
+ "focused": false,
127
+ "foreground_cwd": "/work/worktrees/payments/pay-101",
128
+ "label": "PAY-101:coding",
129
+ "pane_id": "w2:p2",
130
+ "revision": 2,
131
+ "scroll": {
132
+ "max_offset_from_bottom": 0,
133
+ "offset_from_bottom": 0,
134
+ "viewport_rows": 39
135
+ },
136
+ "tab_id": "w2:t2",
137
+ "terminal_id": "term_65a5cfcc913e03",
138
+ "terminal_title": "sleep 300",
139
+ "terminal_title_stripped": "sleep 300",
140
+ "workspace_id": "w2"
141
+ }
142
+ ],
143
+ "protocol": 20,
144
+ "tabs": [
145
+ {
146
+ "agent_status": "unknown",
147
+ "focused": true,
148
+ "label": "1",
149
+ "number": 1,
150
+ "pane_count": 1,
151
+ "tab_id": "w1:t1",
152
+ "workspace_id": "w1"
153
+ },
154
+ {
155
+ "agent_status": "unknown",
156
+ "focused": false,
157
+ "label": "1",
158
+ "number": 1,
159
+ "pane_count": 1,
160
+ "tab_id": "w2:t1",
161
+ "workspace_id": "w2"
162
+ },
163
+ {
164
+ "agent_status": "unknown",
165
+ "focused": false,
166
+ "label": "PAY-101:coding",
167
+ "number": 2,
168
+ "pane_count": 1,
169
+ "tab_id": "w2:t2",
170
+ "workspace_id": "w2"
171
+ }
172
+ ],
173
+ "version": "0.8.2",
174
+ "workspaces": [
175
+ {
176
+ "active_tab_id": "w1:t1",
177
+ "agent_status": "unknown",
178
+ "focused": true,
179
+ "label": "repo",
180
+ "number": 1,
181
+ "pane_count": 1,
182
+ "tab_count": 1,
183
+ "workspace_id": "w1",
184
+ "worktree": {
185
+ "checkout_path": "/work/payments",
186
+ "is_linked_worktree": false,
187
+ "repo_key": "/work/payments/.git",
188
+ "repo_name": "repo",
189
+ "repo_root": "/work/payments"
190
+ }
191
+ },
192
+ {
193
+ "active_tab_id": "w2:t1",
194
+ "agent_status": "unknown",
195
+ "focused": false,
196
+ "label": "PAY-101",
197
+ "number": 2,
198
+ "pane_count": 2,
199
+ "tab_count": 2,
200
+ "workspace_id": "w2",
201
+ "worktree": {
202
+ "checkout_path": "/work/worktrees/payments/pay-101",
203
+ "is_linked_worktree": true,
204
+ "repo_key": "/work/payments/.git",
205
+ "repo_name": "repo",
206
+ "repo_root": "/work/payments"
207
+ }
208
+ }
209
+ ]
210
+ },
211
+ "type": "session_snapshot"
212
+ }
213
+ }
@@ -0,0 +1,175 @@
1
+ #!/bin/sh
2
+ # Strict fake `herdr`. It accepts only the exact production command shapes and
3
+ # reproduces the observed transport contract of herdr 0.8.2:
4
+ # success -> result envelope on stdout, exit 0 (pane run prints nothing)
5
+ # failure -> error envelope on stderr, exit 1, empty stdout
6
+ set -eu
7
+
8
+ fail() {
9
+ printf 'unsupported or malformed herdr invocation:' >&2
10
+ for arg in "$@"; do
11
+ printf ' <%s>' "$arg" >&2
12
+ done
13
+ printf '\n' >&2
14
+ exit 64
15
+ }
16
+
17
+ # The wrapper supplies selector configuration through the environment rather
18
+ # than adding session/socket flags to every Herdr command.
19
+ check_selector() {
20
+ actual_name=$1
21
+ expected_name=$2
22
+ eval "expected_set=\${${expected_name}+x}"
23
+ if [ "$expected_set" = x ]; then
24
+ eval "expected=\${${expected_name}-}"
25
+ eval "actual=\${${actual_name}-}"
26
+ [ "$actual" = "$expected" ] || fail "wrong $actual_name: $actual"
27
+ else
28
+ eval "actual_set=\${${actual_name}+x}"
29
+ [ "$actual_set" != x ] || fail "ambient $actual_name leaked"
30
+ fi
31
+ }
32
+
33
+ check_selector HERDR_SESSION HERDR_EXPECT_SESSION
34
+ check_selector HERDR_SOCKET_PATH HERDR_EXPECT_SOCKET_PATH
35
+
36
+ fixture_path() {
37
+ fixture_dir=${HERDR_FIXTURE_DIR-}
38
+ [ -n "$fixture_dir" ] || fail "HERDR_FIXTURE_DIR is unset"
39
+ [ -f "$fixture_dir/$1" ] || fail "missing fixture: $1"
40
+ printf '%s' "$fixture_dir/$1"
41
+ }
42
+
43
+ emit_result() { cat "$(fixture_path "$1")"; exit 0; }
44
+
45
+ # Herdr writes error envelopes to stderr and exits 1.
46
+ emit_error() { cat "$(fixture_path "$1")" >&2; exit 1; }
47
+
48
+ check_absolute_cwd() {
49
+ case "$1" in
50
+ /*) ;;
51
+ *) fail "--cwd must be absolute: $1" ;;
52
+ esac
53
+ }
54
+
55
+ check_value() {
56
+ [ -n "$1" ] || fail "empty positional value"
57
+ case "$1" in
58
+ -*) fail "unexpected flag in positional value: $1" ;;
59
+ esac
60
+ }
61
+
62
+ case "$#:${1-}:${2-}" in
63
+ 2:api:snapshot)
64
+ emit_result snapshot.json
65
+ ;;
66
+ 4:worktree:list)
67
+ [ "$3" = --cwd ] || fail "$@"
68
+ check_absolute_cwd "$4"
69
+ case "$4" in
70
+ *not-a-repo) emit_error error-not-git-worktree.json ;;
71
+ *) emit_result worktree-list.json ;;
72
+ esac
73
+ ;;
74
+ 11:worktree:create)
75
+ [ "$3" = --cwd ] || fail "$@"
76
+ check_absolute_cwd "$4"
77
+ [ "$5" = --branch ] || fail "$@"
78
+ check_value "$6"
79
+ [ "$7" = --base ] || fail "$@"
80
+ check_value "$8"
81
+ [ "$9" = --label ] || fail "$@"
82
+ check_value "${10}"
83
+ [ "${11}" = --no-focus ] || fail "$@"
84
+ emit_result worktree-create.json
85
+ ;;
86
+ 9:worktree:open)
87
+ [ "$3" = --cwd ] || fail "$@"
88
+ check_absolute_cwd "$4"
89
+ [ "$5" = --branch ] || fail "$@"
90
+ check_value "$6"
91
+ [ "$7" = --label ] || fail "$@"
92
+ check_value "$8"
93
+ [ "$9" = --no-focus ] || fail "$@"
94
+ case "$6" in
95
+ MISSING-*) emit_error error-worktree-not-found.json ;;
96
+ esac
97
+ emit_result worktree-open.json
98
+ ;;
99
+ 9:tab:create)
100
+ [ "$3" = --workspace ] || fail "$@"
101
+ check_value "$4"
102
+ [ "$5" = --cwd ] || fail "$@"
103
+ check_absolute_cwd "$6"
104
+ [ "$7" = --label ] || fail "$@"
105
+ check_value "$8"
106
+ [ "$9" = --no-focus ] || fail "$@"
107
+ emit_result tab-create.json
108
+ ;;
109
+ 4:tab:list)
110
+ [ "$3" = --workspace ] || fail "$@"
111
+ check_value "$4"
112
+ case "$4" in
113
+ empty) emit_result empty-tabs.json ;;
114
+ missing) emit_error error-workspace-not-found.json ;;
115
+ *) emit_result tab-list.json ;;
116
+ esac
117
+ ;;
118
+ 4:pane:list)
119
+ [ "$3" = --workspace ] || fail "$@"
120
+ check_value "$4"
121
+ case "$4" in
122
+ empty) emit_result empty-panes.json ;;
123
+ missing) emit_error error-workspace-not-found.json ;;
124
+ *) emit_result pane-list.json ;;
125
+ esac
126
+ ;;
127
+ 3:pane:get)
128
+ check_value "$3"
129
+ case "$3" in
130
+ malformed) emit_result malformed.json ;;
131
+ warning) printf 'captured warning\n' >&2; emit_result pane-get.json ;;
132
+ missing) emit_error error-pane-not-found.json ;;
133
+ *) emit_result pane-get.json ;;
134
+ esac
135
+ ;;
136
+ 4:pane:process-info)
137
+ [ "$3" = --pane ] || fail "$@"
138
+ check_value "$4"
139
+ case "$4" in
140
+ shell) emit_result pane-process-info-shell.json ;;
141
+ missing) emit_error error-pane-not-found.json ;;
142
+ *) emit_result pane-process-info.json ;;
143
+ esac
144
+ ;;
145
+ 4:pane:rename)
146
+ check_value "$3"
147
+ check_value "$4"
148
+ emit_result pane-rename.json
149
+ ;;
150
+ 4:pane:run)
151
+ # COMMAND is deliberately one logical argument: a command containing
152
+ # spaces or newlines must arrive as the same argument the wrapper passed
153
+ # to exec.CommandContext. Herdr prints nothing on success.
154
+ check_value "$3"
155
+ [ -n "$4" ] || fail "empty command"
156
+ exit 0
157
+ ;;
158
+ 3:pane:close)
159
+ check_value "$3"
160
+ case "$3" in
161
+ missing) emit_error error-pane-not-found.json ;;
162
+ *) emit_result pane-close.json ;;
163
+ esac
164
+ ;;
165
+ 3:workspace:close)
166
+ check_value "$3"
167
+ case "$3" in
168
+ missing) emit_error error-workspace-not-found.json ;;
169
+ *) emit_result workspace-close.json ;;
170
+ esac
171
+ ;;
172
+ *)
173
+ fail "$@"
174
+ ;;
175
+ esac
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "cli:tab:create",
3
+ "result": {
4
+ "root_pane": {
5
+ "agent_status": "unknown",
6
+ "cwd": "/work/worktrees/payments/pay-101",
7
+ "focused": false,
8
+ "foreground_cwd": "/work/worktrees/payments/pay-101",
9
+ "pane_id": "w2:p2",
10
+ "revision": 0,
11
+ "scroll": {
12
+ "max_offset_from_bottom": 0,
13
+ "offset_from_bottom": 0,
14
+ "viewport_rows": 39
15
+ },
16
+ "tab_id": "w2:t2",
17
+ "terminal_id": "term_65a5cfcc913e03",
18
+ "workspace_id": "w2"
19
+ },
20
+ "tab": {
21
+ "agent_status": "unknown",
22
+ "focused": false,
23
+ "label": "PAY-101:coding",
24
+ "number": 2,
25
+ "pane_count": 1,
26
+ "tab_id": "w2:t2",
27
+ "workspace_id": "w2"
28
+ },
29
+ "type": "tab_created"
30
+ }
31
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "id": "cli:tab:list",
3
+ "result": {
4
+ "tabs": [
5
+ {
6
+ "agent_status": "unknown",
7
+ "focused": false,
8
+ "label": "1",
9
+ "number": 1,
10
+ "pane_count": 1,
11
+ "tab_id": "w2:t1",
12
+ "workspace_id": "w2"
13
+ },
14
+ {
15
+ "agent_status": "unknown",
16
+ "focused": false,
17
+ "label": "PAY-101:coding",
18
+ "number": 2,
19
+ "pane_count": 1,
20
+ "tab_id": "w2:t2",
21
+ "workspace_id": "w2"
22
+ }
23
+ ],
24
+ "type": "tab_list"
25
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "cli:workspace:close",
3
+ "result": {
4
+ "type": "ok"
5
+ }
6
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "id": "cli:worktree:create",
3
+ "result": {
4
+ "root_pane": {
5
+ "agent_status": "unknown",
6
+ "cwd": "/work/worktrees/payments/pay-101",
7
+ "focused": false,
8
+ "foreground_cwd": "/work/worktrees/payments/pay-101",
9
+ "pane_id": "w2:p1",
10
+ "revision": 0,
11
+ "scroll": {
12
+ "max_offset_from_bottom": 0,
13
+ "offset_from_bottom": 0,
14
+ "viewport_rows": 40
15
+ },
16
+ "tab_id": "w2:t1",
17
+ "terminal_id": "term_65a5cfcc8cd9e2",
18
+ "workspace_id": "w2"
19
+ },
20
+ "tab": {
21
+ "agent_status": "unknown",
22
+ "focused": false,
23
+ "label": "1",
24
+ "number": 1,
25
+ "pane_count": 1,
26
+ "tab_id": "w2:t1",
27
+ "workspace_id": "w2"
28
+ },
29
+ "type": "worktree_created",
30
+ "workspace": {
31
+ "active_tab_id": "w2:t1",
32
+ "agent_status": "unknown",
33
+ "focused": false,
34
+ "label": "PAY-101",
35
+ "number": 2,
36
+ "pane_count": 1,
37
+ "tab_count": 1,
38
+ "workspace_id": "w2",
39
+ "worktree": {
40
+ "checkout_path": "/work/worktrees/payments/pay-101",
41
+ "is_linked_worktree": true,
42
+ "repo_key": "/work/payments/.git",
43
+ "repo_name": "repo",
44
+ "repo_root": "/work/payments"
45
+ }
46
+ },
47
+ "worktree": {
48
+ "branch": "PAY-101",
49
+ "is_bare": false,
50
+ "is_detached": false,
51
+ "is_linked_worktree": true,
52
+ "is_prunable": false,
53
+ "label": "repo",
54
+ "open_workspace_id": "w2",
55
+ "path": "/work/worktrees/payments/pay-101"
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "id": "cli:worktree:list",
3
+ "result": {
4
+ "source": {
5
+ "repo_key": "/work/payments/.git",
6
+ "repo_name": "repo",
7
+ "repo_root": "/work/payments",
8
+ "source_checkout_path": "/work/payments",
9
+ "source_workspace_id": "w1"
10
+ },
11
+ "type": "worktree_list",
12
+ "worktrees": [
13
+ {
14
+ "branch": "main",
15
+ "is_bare": false,
16
+ "is_detached": false,
17
+ "is_linked_worktree": false,
18
+ "is_prunable": false,
19
+ "label": "repo",
20
+ "open_workspace_id": "w1",
21
+ "path": "/work/payments"
22
+ },
23
+ {
24
+ "branch": "PAY-101",
25
+ "is_bare": false,
26
+ "is_detached": false,
27
+ "is_linked_worktree": true,
28
+ "is_prunable": false,
29
+ "label": "repo",
30
+ "open_workspace_id": "w2",
31
+ "path": "/work/worktrees/payments/pay-101"
32
+ }
33
+ ]
34
+ }
35
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "id": "cli:worktree:open",
3
+ "result": {
4
+ "already_open": true,
5
+ "root_pane": {
6
+ "agent_status": "unknown",
7
+ "cwd": "/work/worktrees/payments/pay-101",
8
+ "focused": false,
9
+ "foreground_cwd": "/work/worktrees/payments/pay-101",
10
+ "pane_id": "w2:p1",
11
+ "revision": 0,
12
+ "scroll": {
13
+ "max_offset_from_bottom": 0,
14
+ "offset_from_bottom": 0,
15
+ "viewport_rows": 39
16
+ },
17
+ "tab_id": "w2:t1",
18
+ "terminal_id": "term_65a5cfcc8cd9e2",
19
+ "workspace_id": "w2"
20
+ },
21
+ "tab": {
22
+ "agent_status": "unknown",
23
+ "focused": false,
24
+ "label": "1",
25
+ "number": 1,
26
+ "pane_count": 1,
27
+ "tab_id": "w2:t1",
28
+ "workspace_id": "w2"
29
+ },
30
+ "type": "worktree_opened",
31
+ "workspace": {
32
+ "active_tab_id": "w2:t1",
33
+ "agent_status": "unknown",
34
+ "focused": false,
35
+ "label": "PAY-101",
36
+ "number": 2,
37
+ "pane_count": 1,
38
+ "tab_count": 1,
39
+ "workspace_id": "w2",
40
+ "worktree": {
41
+ "checkout_path": "/work/worktrees/payments/pay-101",
42
+ "is_linked_worktree": true,
43
+ "repo_key": "/work/payments/.git",
44
+ "repo_name": "repo",
45
+ "repo_root": "/work/payments"
46
+ }
47
+ },
48
+ "worktree": {
49
+ "branch": "PAY-101",
50
+ "is_bare": false,
51
+ "is_detached": false,
52
+ "is_linked_worktree": true,
53
+ "is_prunable": false,
54
+ "label": "repo",
55
+ "open_workspace_id": "w2",
56
+ "path": "/work/worktrees/payments/pay-101"
57
+ }
58
+ }
59
+ }
@@ -206,6 +206,39 @@ func (a *adapter) CloseTerminal(ctx context.Context, terminal runner.Terminal) e
206
206
  return err
207
207
  }
208
208
 
209
+ // DiscoverTerminal finds a live terminal by stable title during explicit
210
+ // projection recovery. It never creates an environment or terminal.
211
+ func (a *adapter) DiscoverTerminal(ctx context.Context, spec runner.RunSpec, title string) (runner.Terminal, bool, error) {
212
+ repoID, err := a.repoID(ctx, spec.RepoName, spec.RepoPath)
213
+ if err != nil {
214
+ return runner.Terminal{}, false, err
215
+ }
216
+ worktrees, err := a.cli.ListWorktrees(ctx)
217
+ if err != nil {
218
+ return runner.Terminal{}, false, err
219
+ }
220
+ var worktreeID string
221
+ for _, worktree := range worktrees {
222
+ if worktree.RepoID == repoID && worktree.DisplayName == spec.TicketKey {
223
+ worktreeID = worktree.ID
224
+ break
225
+ }
226
+ }
227
+ if worktreeID == "" {
228
+ return runner.Terminal{}, false, nil
229
+ }
230
+ terminals, err := a.cli.ListTerminals(ctx, "id:"+worktreeID)
231
+ if err != nil {
232
+ return runner.Terminal{}, false, err
233
+ }
234
+ for _, terminal := range terminals {
235
+ if terminal.Title == title && terminal.Connected {
236
+ return runner.Terminal{ID: terminal.Handle, Title: title}, true, nil
237
+ }
238
+ }
239
+ return runner.Terminal{}, false, nil
240
+ }
241
+
209
242
  // FindTerminal addresses a persisted handle directly. Normal execution never
210
243
  // lists terminals or rediscovers by title.
211
244
  func (a *adapter) FindTerminal(ctx context.Context, terminal runner.Terminal) (runner.Terminal, bool, error) {