agent-control-plane 0.2.0 → 0.3.0
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/npm/bin/agent-control-plane.js +39 -2
- package/package.json +6 -3
- package/tools/bin/agent-project-catch-up-merged-prs +1 -0
- package/tools/bin/agent-project-cleanup-session +49 -5
- package/tools/bin/agent-project-heartbeat-loop +119 -1471
- package/tools/bin/agent-project-reconcile-issue-session +66 -105
- package/tools/bin/agent-project-reconcile-pr-session +76 -111
- package/tools/bin/agent-project-run-claude-session +10 -0
- package/tools/bin/agent-project-run-codex-resilient +86 -9
- package/tools/bin/agent-project-run-codex-session +16 -5
- package/tools/bin/agent-project-run-kilo-session +10 -0
- package/tools/bin/agent-project-run-openclaw-session +10 -0
- package/tools/bin/agent-project-run-opencode-session +10 -0
- package/tools/bin/agent-project-worker-status +10 -7
- package/tools/bin/cleanup-worktree.sh +6 -1
- package/tools/bin/flow-config-lib.sh +80 -0
- package/tools/bin/flow-resident-worker-lib.sh +119 -1
- package/tools/bin/flow-shell-lib.sh +24 -0
- package/tools/bin/heartbeat-loop-cache-lib.sh +164 -0
- package/tools/bin/heartbeat-loop-counting-lib.sh +306 -0
- package/tools/bin/heartbeat-loop-pr-strategy-lib.sh +199 -0
- package/tools/bin/heartbeat-loop-scheduling-lib.sh +506 -0
- package/tools/bin/heartbeat-loop-worker-lib.sh +319 -0
- package/tools/bin/heartbeat-recovery-preflight.sh +12 -1
- package/tools/bin/heartbeat-safe-auto.sh +14 -3
- package/tools/bin/project-launchd-bootstrap.sh +11 -8
- package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
- package/tools/bin/resident-issue-controller-lib.sh +448 -0
- package/tools/bin/resident-issue-queue-status.py +35 -0
- package/tools/bin/start-resident-issue-loop.sh +26 -437
- package/tools/dashboard/app.js +7 -0
- package/tools/dashboard/dashboard_snapshot.py +13 -29
- package/SKILL.md +0 -149
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# heartbeat-loop-counting-lib.sh — worker counting, pending launch counts, capacity queries
|
|
3
|
+
|
|
4
|
+
running_heavy_issue_workers() {
|
|
5
|
+
local session issue_id is_heavy count=0
|
|
6
|
+
ensure_running_issue_workers_cache
|
|
7
|
+
while IFS= read -r session; do
|
|
8
|
+
[[ -n "$session" ]] || continue
|
|
9
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
10
|
+
[[ -n "$issue_id" ]] || continue
|
|
11
|
+
is_heavy="$(cached_issue_attr heavy "$issue_id")"
|
|
12
|
+
if [[ "$is_heavy" == "yes" ]]; then
|
|
13
|
+
count=$((count + 1))
|
|
14
|
+
fi
|
|
15
|
+
done <<<"$running_issue_workers_cache"
|
|
16
|
+
printf '%s\n' "$count"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pending_issue_launch_count() {
|
|
20
|
+
local pending_file issue_id count=0
|
|
21
|
+
for pending_file in "${pending_launch_dir}"/issue-*.pid; do
|
|
22
|
+
[[ -f "$pending_file" ]] || continue
|
|
23
|
+
issue_id="${pending_file##*/issue-}"
|
|
24
|
+
issue_id="${issue_id%.pid}"
|
|
25
|
+
[[ -n "$issue_id" ]] || continue
|
|
26
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
27
|
+
continue
|
|
28
|
+
fi
|
|
29
|
+
if pending_issue_launch_counts_toward_capacity "$issue_id"; then
|
|
30
|
+
count=$((count + 1))
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
printf '%s\n' "$count"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pending_pr_launch_count() {
|
|
37
|
+
local pending_file pr_id count=0
|
|
38
|
+
for pending_file in "${pending_launch_dir}"/pr-*.pid; do
|
|
39
|
+
[[ -f "$pending_file" ]] || continue
|
|
40
|
+
pr_id="${pending_file##*/pr-}"
|
|
41
|
+
pr_id="${pr_id%.pid}"
|
|
42
|
+
[[ -n "$pr_id" ]] || continue
|
|
43
|
+
if tmux has-session -t "${pr_prefix}${pr_id}" 2>/dev/null; then
|
|
44
|
+
continue
|
|
45
|
+
fi
|
|
46
|
+
if pending_pr_launch_active "$pr_id"; then
|
|
47
|
+
count=$((count + 1))
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
printf '%s\n' "$count"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pending_heavy_issue_launch_count() {
|
|
54
|
+
local pending_file issue_id count=0
|
|
55
|
+
for pending_file in "${pending_launch_dir}"/issue-*.pid; do
|
|
56
|
+
[[ -f "$pending_file" ]] || continue
|
|
57
|
+
issue_id="${pending_file##*/issue-}"
|
|
58
|
+
issue_id="${issue_id%.pid}"
|
|
59
|
+
[[ -n "$issue_id" ]] || continue
|
|
60
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
61
|
+
continue
|
|
62
|
+
fi
|
|
63
|
+
if pending_issue_launch_counts_toward_capacity "$issue_id" && [[ "$(cached_issue_attr heavy "$issue_id")" == "yes" ]]; then
|
|
64
|
+
count=$((count + 1))
|
|
65
|
+
fi
|
|
66
|
+
done
|
|
67
|
+
printf '%s\n' "$count"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pending_scheduled_issue_launch_count() {
|
|
71
|
+
local pending_file issue_id count=0
|
|
72
|
+
for pending_file in "${pending_launch_dir}"/issue-*.pid; do
|
|
73
|
+
[[ -f "$pending_file" ]] || continue
|
|
74
|
+
issue_id="${pending_file##*/issue-}"
|
|
75
|
+
issue_id="${issue_id%.pid}"
|
|
76
|
+
[[ -n "$issue_id" ]] || continue
|
|
77
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
78
|
+
continue
|
|
79
|
+
fi
|
|
80
|
+
if pending_issue_launch_counts_toward_capacity "$issue_id" && [[ "$(cached_issue_attr scheduled "$issue_id")" == "yes" ]]; then
|
|
81
|
+
count=$((count + 1))
|
|
82
|
+
fi
|
|
83
|
+
done
|
|
84
|
+
printf '%s\n' "$count"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pending_scheduled_heavy_issue_launch_count() {
|
|
88
|
+
local pending_file issue_id count=0
|
|
89
|
+
for pending_file in "${pending_launch_dir}"/issue-*.pid; do
|
|
90
|
+
[[ -f "$pending_file" ]] || continue
|
|
91
|
+
issue_id="${pending_file##*/issue-}"
|
|
92
|
+
issue_id="${issue_id%.pid}"
|
|
93
|
+
[[ -n "$issue_id" ]] || continue
|
|
94
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
95
|
+
continue
|
|
96
|
+
fi
|
|
97
|
+
if pending_issue_launch_counts_toward_capacity "$issue_id" \
|
|
98
|
+
&& [[ "$(cached_issue_attr scheduled "$issue_id")" == "yes" ]] \
|
|
99
|
+
&& [[ "$(cached_issue_attr heavy "$issue_id")" == "yes" ]]; then
|
|
100
|
+
count=$((count + 1))
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
printf '%s\n' "$count"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pending_recurring_issue_launch_count() {
|
|
107
|
+
local pending_file issue_id count=0
|
|
108
|
+
for pending_file in "${pending_launch_dir}"/issue-*.pid; do
|
|
109
|
+
[[ -f "$pending_file" ]] || continue
|
|
110
|
+
issue_id="${pending_file##*/issue-}"
|
|
111
|
+
issue_id="${issue_id%.pid}"
|
|
112
|
+
[[ -n "$issue_id" ]] || continue
|
|
113
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
114
|
+
continue
|
|
115
|
+
fi
|
|
116
|
+
if pending_issue_launch_counts_toward_capacity "$issue_id" \
|
|
117
|
+
&& [[ "$(cached_issue_attr scheduled "$issue_id")" != "yes" ]] \
|
|
118
|
+
&& [[ "$(cached_issue_attr recurring "$issue_id")" == "yes" ]]; then
|
|
119
|
+
count=$((count + 1))
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
printf '%s\n' "$count"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pending_blocked_recovery_issue_launch_count() {
|
|
126
|
+
local pending_file issue_id count=0
|
|
127
|
+
for pending_file in "${pending_launch_dir}"/issue-*.pid; do
|
|
128
|
+
[[ -f "$pending_file" ]] || continue
|
|
129
|
+
issue_id="${pending_file##*/issue-}"
|
|
130
|
+
issue_id="${issue_id%.pid}"
|
|
131
|
+
[[ -n "$issue_id" ]] || continue
|
|
132
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
133
|
+
continue
|
|
134
|
+
fi
|
|
135
|
+
if pending_issue_launch_counts_toward_capacity "$issue_id" && blocked_recovery_issue_has_state "$issue_id"; then
|
|
136
|
+
count=$((count + 1))
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
printf '%s\n' "$count"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pending_exclusive_issue_launch_count() {
|
|
143
|
+
local pending_file issue_id count=0
|
|
144
|
+
for pending_file in "${pending_launch_dir}"/issue-*.pid; do
|
|
145
|
+
[[ -f "$pending_file" ]] || continue
|
|
146
|
+
issue_id="${pending_file##*/issue-}"
|
|
147
|
+
issue_id="${issue_id%.pid}"
|
|
148
|
+
[[ -n "$issue_id" ]] || continue
|
|
149
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
150
|
+
continue
|
|
151
|
+
fi
|
|
152
|
+
if pending_issue_launch_counts_toward_capacity "$issue_id" && [[ "$(cached_issue_attr exclusive "$issue_id")" == "yes" ]]; then
|
|
153
|
+
count=$((count + 1))
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
printf '%s\n' "$count"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pending_exclusive_pr_launch_count() {
|
|
160
|
+
local pending_file pr_id count=0
|
|
161
|
+
for pending_file in "${pending_launch_dir}"/pr-*.pid; do
|
|
162
|
+
[[ -f "$pending_file" ]] || continue
|
|
163
|
+
pr_id="${pending_file##*/pr-}"
|
|
164
|
+
pr_id="${pr_id%.pid}"
|
|
165
|
+
[[ -n "$pr_id" ]] || continue
|
|
166
|
+
if tmux has-session -t "${pr_prefix}${pr_id}" 2>/dev/null; then
|
|
167
|
+
continue
|
|
168
|
+
fi
|
|
169
|
+
if pending_pr_launch_active "$pr_id" && [[ "$(cached_pr_is_exclusive "$pr_id")" == "yes" ]]; then
|
|
170
|
+
count=$((count + 1))
|
|
171
|
+
fi
|
|
172
|
+
done
|
|
173
|
+
printf '%s\n' "$count"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
running_non_recurring_issue_workers() {
|
|
177
|
+
local session issue_id is_recurring is_scheduled count=0
|
|
178
|
+
ensure_running_issue_workers_cache
|
|
179
|
+
while IFS= read -r session; do
|
|
180
|
+
[[ -n "$session" ]] || continue
|
|
181
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
182
|
+
[[ -n "$issue_id" ]] || continue
|
|
183
|
+
is_scheduled="$(cached_issue_attr scheduled "$issue_id")"
|
|
184
|
+
if [[ "$is_scheduled" == "yes" ]]; then
|
|
185
|
+
continue
|
|
186
|
+
fi
|
|
187
|
+
is_recurring="$(cached_issue_attr recurring "$issue_id")"
|
|
188
|
+
if [[ "$is_recurring" != "yes" ]]; then
|
|
189
|
+
count=$((count + 1))
|
|
190
|
+
fi
|
|
191
|
+
done <<<"$running_issue_workers_cache"
|
|
192
|
+
printf '%s\n' "$count"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
running_recurring_issue_workers() {
|
|
196
|
+
local session issue_id is_recurring is_scheduled count=0
|
|
197
|
+
ensure_running_issue_workers_cache
|
|
198
|
+
while IFS= read -r session; do
|
|
199
|
+
[[ -n "$session" ]] || continue
|
|
200
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
201
|
+
[[ -n "$issue_id" ]] || continue
|
|
202
|
+
is_scheduled="$(cached_issue_attr scheduled "$issue_id")"
|
|
203
|
+
if [[ "$is_scheduled" == "yes" ]]; then
|
|
204
|
+
continue
|
|
205
|
+
fi
|
|
206
|
+
is_recurring="$(cached_issue_attr recurring "$issue_id")"
|
|
207
|
+
if [[ "$is_recurring" == "yes" ]]; then
|
|
208
|
+
count=$((count + 1))
|
|
209
|
+
fi
|
|
210
|
+
done <<<"$running_issue_workers_cache"
|
|
211
|
+
# Also count pending recurring launches that are still in progress
|
|
212
|
+
# (prevents infinite respawning when workers die before creating tmux sessions)
|
|
213
|
+
count=$((count + $(pending_recurring_issue_launch_count)))
|
|
214
|
+
printf '%s\n' "$count"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
running_blocked_recovery_issue_workers() {
|
|
218
|
+
local session issue_id count=0
|
|
219
|
+
ensure_running_issue_workers_cache
|
|
220
|
+
while IFS= read -r session; do
|
|
221
|
+
[[ -n "$session" ]] || continue
|
|
222
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
223
|
+
[[ -n "$issue_id" ]] || continue
|
|
224
|
+
if blocked_recovery_issue_has_state "$issue_id"; then
|
|
225
|
+
count=$((count + 1))
|
|
226
|
+
fi
|
|
227
|
+
done <<<"$running_issue_workers_cache"
|
|
228
|
+
printf '%s\n' "$count"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
running_exclusive_issue_workers() {
|
|
232
|
+
local session issue_id is_exclusive count=0
|
|
233
|
+
ensure_running_issue_workers_cache
|
|
234
|
+
while IFS= read -r session; do
|
|
235
|
+
[[ -n "$session" ]] || continue
|
|
236
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
237
|
+
[[ -n "$issue_id" ]] || continue
|
|
238
|
+
is_exclusive="$(cached_issue_attr exclusive "$issue_id")"
|
|
239
|
+
if [[ "$is_exclusive" == "yes" ]]; then
|
|
240
|
+
count=$((count + 1))
|
|
241
|
+
fi
|
|
242
|
+
done <<<"$running_issue_workers_cache"
|
|
243
|
+
printf '%s\n' "$count"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
running_exclusive_pr_workers() {
|
|
247
|
+
local session pr_id is_exclusive count=0
|
|
248
|
+
ensure_running_pr_workers_cache
|
|
249
|
+
while IFS= read -r session; do
|
|
250
|
+
[[ -n "$session" ]] || continue
|
|
251
|
+
pr_id="$(pr_id_from_session "$session" || true)"
|
|
252
|
+
[[ -n "$pr_id" ]] || continue
|
|
253
|
+
is_exclusive="$(cached_pr_is_exclusive "$pr_id")"
|
|
254
|
+
if [[ "$is_exclusive" == "yes" ]]; then
|
|
255
|
+
count=$((count + 1))
|
|
256
|
+
fi
|
|
257
|
+
done <<<"$running_pr_workers_cache"
|
|
258
|
+
printf '%s\n' "$count"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
running_scheduled_issue_workers() {
|
|
262
|
+
local session issue_id is_scheduled count=0
|
|
263
|
+
ensure_running_issue_workers_cache
|
|
264
|
+
while IFS= read -r session; do
|
|
265
|
+
[[ -n "$session" ]] || continue
|
|
266
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
267
|
+
[[ -n "$issue_id" ]] || continue
|
|
268
|
+
is_scheduled="$(cached_issue_attr scheduled "$issue_id")"
|
|
269
|
+
if [[ "$is_scheduled" == "yes" ]]; then
|
|
270
|
+
count=$((count + 1))
|
|
271
|
+
fi
|
|
272
|
+
done <<<"$running_issue_workers_cache"
|
|
273
|
+
printf '%s\n' "$count"
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
running_scheduled_heavy_issue_workers() {
|
|
277
|
+
local session issue_id is_scheduled is_heavy count=0
|
|
278
|
+
ensure_running_issue_workers_cache
|
|
279
|
+
while IFS= read -r session; do
|
|
280
|
+
[[ -n "$session" ]] || continue
|
|
281
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
282
|
+
[[ -n "$issue_id" ]] || continue
|
|
283
|
+
is_scheduled="$(cached_issue_attr scheduled "$issue_id")"
|
|
284
|
+
is_heavy="$(cached_issue_attr heavy "$issue_id")"
|
|
285
|
+
if [[ "$is_scheduled" == "yes" && "$is_heavy" == "yes" ]]; then
|
|
286
|
+
count=$((count + 1))
|
|
287
|
+
fi
|
|
288
|
+
done <<<"$running_issue_workers_cache"
|
|
289
|
+
printf '%s\n' "$count"
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
ready_non_recurring_issue_count() {
|
|
293
|
+
local issue_id is_recurring count=0
|
|
294
|
+
ensure_ready_issue_ids_cache
|
|
295
|
+
while IFS= read -r issue_id; do
|
|
296
|
+
[[ -n "$issue_id" ]] || continue
|
|
297
|
+
if [[ "$(cached_issue_attr scheduled "$issue_id")" == "yes" ]]; then
|
|
298
|
+
continue
|
|
299
|
+
fi
|
|
300
|
+
is_recurring="$(cached_issue_attr recurring "$issue_id")"
|
|
301
|
+
if [[ "$is_recurring" != "yes" ]]; then
|
|
302
|
+
count=$((count + 1))
|
|
303
|
+
fi
|
|
304
|
+
done <<<"$ready_issue_ids_cache"
|
|
305
|
+
printf '%s\n' "$count"
|
|
306
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# heartbeat-loop-pr-strategy-lib.sh — PR candidate selection and strategy.
|
|
3
|
+
#
|
|
4
|
+
# Implements the priority-ordered lane dispatch for PR workers:
|
|
5
|
+
# double-check-2 > double-check-1 > automerge > merge-repair > fix > ci-refresh.
|
|
6
|
+
#
|
|
7
|
+
# Depends on: heartbeat-loop-worker-lib.sh, heartbeat-loop-cache-lib.sh,
|
|
8
|
+
# heartbeat-loop-scheduling-lib.sh
|
|
9
|
+
|
|
10
|
+
next_pr_candidate_json() {
|
|
11
|
+
local target_lane pr_number risk_json lane
|
|
12
|
+
ensure_open_agent_pr_ids_cache
|
|
13
|
+
for target_lane in double-check-2 double-check-1 automerge merge-repair fix ci-refresh; do
|
|
14
|
+
while IFS= read -r pr_number; do
|
|
15
|
+
[[ -n "$pr_number" ]] || continue
|
|
16
|
+
if tmux has-session -t "${pr_prefix}${pr_number}" 2>/dev/null; then
|
|
17
|
+
continue
|
|
18
|
+
fi
|
|
19
|
+
if pr_launch_reserved "$pr_number"; then
|
|
20
|
+
continue
|
|
21
|
+
fi
|
|
22
|
+
if pending_pr_launch_active "$pr_number"; then
|
|
23
|
+
continue
|
|
24
|
+
fi
|
|
25
|
+
if ! retry_ready pr "$pr_number"; then
|
|
26
|
+
continue
|
|
27
|
+
fi
|
|
28
|
+
risk_json="$(cached_pr_risk_json "$pr_number")"
|
|
29
|
+
lane="$(jq -r '.agentLane' <<<"$risk_json")"
|
|
30
|
+
if [[ "$lane" == "$target_lane" ]]; then
|
|
31
|
+
printf '%s\n' "$risk_json"
|
|
32
|
+
return 0
|
|
33
|
+
fi
|
|
34
|
+
done <<<"$open_agent_pr_ids_cache"
|
|
35
|
+
done
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
next_priority_review_pr_candidate_json() {
|
|
39
|
+
local target_lane pr_number risk_json lane
|
|
40
|
+
ensure_open_agent_pr_ids_cache
|
|
41
|
+
for target_lane in double-check-2 double-check-1; do
|
|
42
|
+
while IFS= read -r pr_number; do
|
|
43
|
+
[[ -n "$pr_number" ]] || continue
|
|
44
|
+
if tmux has-session -t "${pr_prefix}${pr_number}" 2>/dev/null; then
|
|
45
|
+
continue
|
|
46
|
+
fi
|
|
47
|
+
if pr_launch_reserved "$pr_number"; then
|
|
48
|
+
continue
|
|
49
|
+
fi
|
|
50
|
+
if pending_pr_launch_active "$pr_number"; then
|
|
51
|
+
continue
|
|
52
|
+
fi
|
|
53
|
+
if ! retry_ready pr "$pr_number"; then
|
|
54
|
+
continue
|
|
55
|
+
fi
|
|
56
|
+
risk_json="$(cached_pr_risk_json "$pr_number")"
|
|
57
|
+
lane="$(jq -r '.agentLane' <<<"$risk_json")"
|
|
58
|
+
if [[ "$lane" == "$target_lane" ]]; then
|
|
59
|
+
printf '%s\n' "$risk_json"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
done <<<"$open_agent_pr_ids_cache"
|
|
63
|
+
done
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
eligible_pr_backlog_count() {
|
|
67
|
+
local pr_number risk_json lane count=0
|
|
68
|
+
ensure_open_agent_pr_ids_cache
|
|
69
|
+
while IFS= read -r pr_number; do
|
|
70
|
+
[[ -n "$pr_number" ]] || continue
|
|
71
|
+
if tmux has-session -t "${pr_prefix}${pr_number}" 2>/dev/null; then
|
|
72
|
+
continue
|
|
73
|
+
fi
|
|
74
|
+
if pr_launch_reserved "$pr_number"; then
|
|
75
|
+
continue
|
|
76
|
+
fi
|
|
77
|
+
if pending_pr_launch_active "$pr_number"; then
|
|
78
|
+
continue
|
|
79
|
+
fi
|
|
80
|
+
if ! retry_ready pr "$pr_number"; then
|
|
81
|
+
continue
|
|
82
|
+
fi
|
|
83
|
+
risk_json="$(cached_pr_risk_json "$pr_number")"
|
|
84
|
+
lane="$(jq -r '.agentLane' <<<"$risk_json")"
|
|
85
|
+
case "$lane" in
|
|
86
|
+
double-check-1|double-check-2|automerge|merge-repair|fix)
|
|
87
|
+
count=$((count + 1))
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
done <<<"$open_agent_pr_ids_cache"
|
|
91
|
+
printf '%s\n' "$count"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
priority_review_backlog_count() {
|
|
95
|
+
local pr_number risk_json lane count=0
|
|
96
|
+
ensure_open_agent_pr_ids_cache
|
|
97
|
+
while IFS= read -r pr_number; do
|
|
98
|
+
[[ -n "$pr_number" ]] || continue
|
|
99
|
+
if tmux has-session -t "${pr_prefix}${pr_number}" 2>/dev/null; then
|
|
100
|
+
continue
|
|
101
|
+
fi
|
|
102
|
+
if pr_launch_reserved "$pr_number"; then
|
|
103
|
+
continue
|
|
104
|
+
fi
|
|
105
|
+
if pending_pr_launch_active "$pr_number"; then
|
|
106
|
+
continue
|
|
107
|
+
fi
|
|
108
|
+
if ! retry_ready pr "$pr_number"; then
|
|
109
|
+
continue
|
|
110
|
+
fi
|
|
111
|
+
risk_json="$(cached_pr_risk_json "$pr_number")"
|
|
112
|
+
lane="$(jq -r '.agentLane' <<<"$risk_json")"
|
|
113
|
+
case "$lane" in
|
|
114
|
+
double-check-1|double-check-2)
|
|
115
|
+
count=$((count + 1))
|
|
116
|
+
;;
|
|
117
|
+
esac
|
|
118
|
+
done <<<"$open_agent_pr_ids_cache"
|
|
119
|
+
printf '%s\n' "$count"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
next_exclusive_pr_candidate_json() {
|
|
123
|
+
local target_lane pr_number risk_json lane
|
|
124
|
+
ensure_exclusive_pr_ids_cache
|
|
125
|
+
for target_lane in double-check-2 double-check-1 automerge merge-repair fix ci-refresh; do
|
|
126
|
+
while IFS= read -r pr_number; do
|
|
127
|
+
[[ -n "$pr_number" ]] || continue
|
|
128
|
+
if tmux has-session -t "${pr_prefix}${pr_number}" 2>/dev/null; then
|
|
129
|
+
continue
|
|
130
|
+
fi
|
|
131
|
+
if pr_launch_reserved "$pr_number"; then
|
|
132
|
+
continue
|
|
133
|
+
fi
|
|
134
|
+
if pending_pr_launch_active "$pr_number"; then
|
|
135
|
+
continue
|
|
136
|
+
fi
|
|
137
|
+
if ! retry_ready pr "$pr_number"; then
|
|
138
|
+
continue
|
|
139
|
+
fi
|
|
140
|
+
risk_json="$(cached_pr_risk_json "$pr_number")"
|
|
141
|
+
lane="$(jq -r '.agentLane' <<<"$risk_json")"
|
|
142
|
+
# Skip PRs requiring human review; they should not hold exclusive lock
|
|
143
|
+
if [[ "$lane" == "human-review" ]]; then
|
|
144
|
+
continue
|
|
145
|
+
fi
|
|
146
|
+
if [[ "$lane" == "$target_lane" ]]; then
|
|
147
|
+
printf '%s\n' "$risk_json"
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
done <<<"$exclusive_pr_ids_cache"
|
|
151
|
+
done
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
next_exclusive_issue_id() {
|
|
155
|
+
local issue_id
|
|
156
|
+
ensure_exclusive_issue_ids_cache
|
|
157
|
+
while IFS= read -r issue_id; do
|
|
158
|
+
[[ -n "$issue_id" ]] || continue
|
|
159
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
160
|
+
continue
|
|
161
|
+
fi
|
|
162
|
+
if pending_issue_launch_active "$issue_id"; then
|
|
163
|
+
continue
|
|
164
|
+
fi
|
|
165
|
+
if ! retry_ready issue "$issue_id"; then
|
|
166
|
+
continue
|
|
167
|
+
fi
|
|
168
|
+
printf '%s\n' "$issue_id"
|
|
169
|
+
return 0
|
|
170
|
+
done <<<"$exclusive_issue_ids_cache"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
count_pr_lane() {
|
|
174
|
+
local target_lane="${1:?target lane required}"
|
|
175
|
+
local pr_number risk_json lane count=0
|
|
176
|
+
ensure_open_agent_pr_ids_cache
|
|
177
|
+
while IFS= read -r pr_number; do
|
|
178
|
+
[[ -n "$pr_number" ]] || continue
|
|
179
|
+
risk_json="$(cached_pr_risk_json "$pr_number")"
|
|
180
|
+
lane="$(jq -r '.agentLane' <<<"$risk_json")"
|
|
181
|
+
if [[ "$lane" == "$target_lane" ]]; then
|
|
182
|
+
count=$((count + 1))
|
|
183
|
+
fi
|
|
184
|
+
done <<<"$open_agent_pr_ids_cache"
|
|
185
|
+
printf '%s\n' "$count"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
human_review_pr_ids() {
|
|
189
|
+
local pr_number risk_json lane
|
|
190
|
+
ensure_open_agent_pr_ids_cache
|
|
191
|
+
while IFS= read -r pr_number; do
|
|
192
|
+
[[ -n "$pr_number" ]] || continue
|
|
193
|
+
risk_json="$(cached_pr_risk_json "$pr_number")"
|
|
194
|
+
lane="$(jq -r '.agentLane' <<<"$risk_json")"
|
|
195
|
+
if [[ "$lane" == "human-review" ]]; then
|
|
196
|
+
printf '%s\n' "$pr_number"
|
|
197
|
+
fi
|
|
198
|
+
done <<<"$open_agent_pr_ids_cache"
|
|
199
|
+
}
|