agent-control-plane 0.4.9 → 0.6.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.
Files changed (80) hide show
  1. package/README.md +72 -9
  2. package/npm/bin/agent-control-plane.js +1 -1
  3. package/package.json +39 -33
  4. package/tools/bin/debug-session.sh +106 -0
  5. package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
  6. package/tools/bin/flow-runtime-doctor.sh +5 -1
  7. package/tools/bin/install-project-systemd.sh +255 -0
  8. package/tools/bin/project-runtimectl.sh +45 -0
  9. package/tools/bin/project-systemd-bootstrap.sh +74 -0
  10. package/tools/bin/uninstall-project-systemd.sh +87 -0
  11. package/tools/dashboard/app.js +198 -5
  12. package/tools/dashboard/issue_queue_state.py +101 -0
  13. package/tools/dashboard/server.py +123 -1
  14. package/tools/dashboard/styles.css +526 -455
  15. package/tools/bin/agent-cleanup-worktree +0 -247
  16. package/tools/bin/agent-github-update-labels +0 -105
  17. package/tools/bin/agent-init-worktree +0 -216
  18. package/tools/bin/agent-project-archive-run +0 -52
  19. package/tools/bin/agent-project-capture-worker +0 -46
  20. package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
  21. package/tools/bin/agent-project-catch-up-merged-prs +0 -195
  22. package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
  23. package/tools/bin/agent-project-cleanup-session +0 -513
  24. package/tools/bin/agent-project-detached-launch +0 -127
  25. package/tools/bin/agent-project-heartbeat-loop +0 -1029
  26. package/tools/bin/agent-project-open-issue-worktree +0 -89
  27. package/tools/bin/agent-project-open-pr-worktree +0 -80
  28. package/tools/bin/agent-project-publish-issue-pr +0 -468
  29. package/tools/bin/agent-project-reconcile-issue-session +0 -1409
  30. package/tools/bin/agent-project-reconcile-pr-session +0 -1288
  31. package/tools/bin/agent-project-retry-state +0 -158
  32. package/tools/bin/agent-project-run-claude-session +0 -805
  33. package/tools/bin/agent-project-run-codex-resilient +0 -963
  34. package/tools/bin/agent-project-run-codex-session +0 -435
  35. package/tools/bin/agent-project-run-kilo-session +0 -369
  36. package/tools/bin/agent-project-run-ollama-session +0 -658
  37. package/tools/bin/agent-project-run-openclaw-session +0 -1309
  38. package/tools/bin/agent-project-run-opencode-session +0 -377
  39. package/tools/bin/agent-project-run-pi-session +0 -479
  40. package/tools/bin/agent-project-sync-anchor-repo +0 -139
  41. package/tools/bin/agent-project-sync-source-repo-main +0 -163
  42. package/tools/bin/agent-project-worker-status +0 -188
  43. package/tools/bin/branch-verification-guard.sh +0 -364
  44. package/tools/bin/capture-worker.sh +0 -18
  45. package/tools/bin/cleanup-worktree.sh +0 -52
  46. package/tools/bin/codex-quota +0 -31
  47. package/tools/bin/create-follow-up-issue.sh +0 -114
  48. package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
  49. package/tools/bin/issue-publish-localization-guard.sh +0 -142
  50. package/tools/bin/issue-publish-scope-guard.sh +0 -242
  51. package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
  52. package/tools/bin/issue-resource-class.sh +0 -12
  53. package/tools/bin/kick-scheduler.sh +0 -75
  54. package/tools/bin/label-follow-up-issues.sh +0 -14
  55. package/tools/bin/new-pr-worktree.sh +0 -50
  56. package/tools/bin/new-worktree.sh +0 -49
  57. package/tools/bin/pr-risk.sh +0 -12
  58. package/tools/bin/prepare-worktree.sh +0 -142
  59. package/tools/bin/provider-cooldown-state.sh +0 -204
  60. package/tools/bin/publish-issue-worker.sh +0 -31
  61. package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
  62. package/tools/bin/reconcile-issue-worker.sh +0 -34
  63. package/tools/bin/reconcile-pr-worker.sh +0 -34
  64. package/tools/bin/record-verification.sh +0 -71
  65. package/tools/bin/render-flow-config.sh +0 -98
  66. package/tools/bin/resident-issue-controller-lib.sh +0 -448
  67. package/tools/bin/retry-state.sh +0 -31
  68. package/tools/bin/reuse-issue-worktree.sh +0 -121
  69. package/tools/bin/run-codex-bypass.sh +0 -3
  70. package/tools/bin/run-codex-safe.sh +0 -3
  71. package/tools/bin/run-codex-task.sh +0 -280
  72. package/tools/bin/serve-dashboard.sh +0 -5
  73. package/tools/bin/start-issue-worker.sh +0 -943
  74. package/tools/bin/start-pr-fix-worker.sh +0 -528
  75. package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
  76. package/tools/bin/start-pr-review-worker.sh +0 -261
  77. package/tools/bin/start-resident-issue-loop.sh +0 -499
  78. package/tools/bin/update-github-labels.sh +0 -14
  79. package/tools/bin/worker-status.sh +0 -19
  80. package/tools/bin/workflow-catalog.sh +0 -77
@@ -1,499 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
- # shellcheck source=/dev/null
8
- source "${SCRIPT_DIR}/flow-resident-worker-lib.sh"
9
-
10
- ISSUE_ID="${1:?usage: start-resident-issue-loop.sh ISSUE_ID [safe|bypass]}"
11
- MODE="${2:-safe}"
12
-
13
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
14
- if ! flow_require_explicit_profile_selection "${FLOW_SKILL_DIR}" "start-resident-issue-loop.sh"; then
15
- exit 64
16
- fi
17
-
18
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
19
- flow_export_execution_env "${CONFIG_YAML}"
20
- flow_export_project_env_aliases
21
-
22
- FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
23
- HOOK_FILE="${FLOW_SKILL_DIR}/hooks/heartbeat-hooks.sh"
24
- REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
25
- ISSUE_SESSION_PREFIX="$(flow_resolve_issue_session_prefix "${CONFIG_YAML}")"
26
- SESSION="${ISSUE_SESSION_PREFIX}${ISSUE_ID}"
27
- STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
28
- PENDING_LAUNCH_DIR="${ACP_PENDING_LAUNCH_DIR:-${F_LOSNING_PENDING_LAUNCH_DIR:-${STATE_ROOT}/pending-launches}}"
29
- SCHEDULED_STATE_DIR="${STATE_ROOT}/scheduled-issues"
30
- CONTROLLER_FILE="$(flow_resident_issue_controller_file "${CONFIG_YAML}" "${ISSUE_ID}")"
31
- RESIDENT_META_FILE="$(flow_resident_issue_meta_file "${CONFIG_YAML}" "${ISSUE_ID}")"
32
- CODING_WORKER="${ACP_CODING_WORKER:-codex}"
33
- MAX_IMMEDIATE_CYCLES="$(flow_resident_issue_controller_max_immediate_cycles "${CONFIG_YAML}")"
34
- POLL_SECONDS="$(flow_resident_issue_controller_poll_seconds "${CONFIG_YAML}")"
35
- IDLE_TIMEOUT_SECONDS="$(flow_resident_issue_controller_idle_timeout_seconds "${CONFIG_YAML}")"
36
- CONTROLLER_LOOP_COUNT="0"
37
- CONTROLLER_STATE="starting"
38
- CONTROLLER_REASON=""
39
- NEXT_WAKE_EPOCH=""
40
- NEXT_WAKE_AT=""
41
- IDLE_WAIT_STARTED_EPOCH=""
42
- PROVIDER_WAITED="no"
43
- ACTIVE_RESIDENT_WORKER_KEY=""
44
- ACTIVE_RESIDENT_META_FILE=""
45
- ACTIVE_RESIDENT_LANE_KIND=""
46
- ACTIVE_RESIDENT_LANE_VALUE=""
47
- ACTIVE_PROVIDER_POOL_NAME=""
48
- ACTIVE_PROVIDER_BACKEND=""
49
- ACTIVE_PROVIDER_MODEL=""
50
- ACTIVE_PROVIDER_KEY=""
51
- ACTIVE_PROVIDER_SELECTION_REASON=""
52
- ACTIVE_PROVIDER_NEXT_ATTEMPT_EPOCH=""
53
- ACTIVE_PROVIDER_NEXT_ATTEMPT_AT=""
54
- ACTIVE_PROVIDER_LAST_REASON=""
55
- LAST_RECORDED_PROVIDER_POOL_NAME=""
56
- LAST_RECORDED_PROVIDER_BACKEND=""
57
- LAST_RECORDED_PROVIDER_MODEL=""
58
- LAST_RECORDED_PROVIDER_KEY=""
59
- LAST_LAUNCHED_PROVIDER_POOL_NAME=""
60
- LAST_LAUNCHED_PROVIDER_BACKEND=""
61
- LAST_LAUNCHED_PROVIDER_MODEL=""
62
- LAST_LAUNCHED_PROVIDER_KEY=""
63
- LAST_PROVIDER_SWITCH_AT=""
64
- LAST_PROVIDER_SWITCH_REASON=""
65
- LAST_PROVIDER_FROM_POOL_NAME=""
66
- LAST_PROVIDER_FROM_BACKEND=""
67
- LAST_PROVIDER_FROM_MODEL=""
68
- LAST_PROVIDER_FROM_KEY=""
69
- LAST_PROVIDER_TO_POOL_NAME=""
70
- LAST_PROVIDER_TO_BACKEND=""
71
- LAST_PROVIDER_TO_MODEL=""
72
- LAST_PROVIDER_TO_KEY=""
73
- LAST_PROVIDER_FAILOVER_AT=""
74
- PROVIDER_SWITCH_COUNT="0"
75
- PROVIDER_FAILOVER_COUNT="0"
76
- PROVIDER_WAIT_COUNT="0"
77
- PROVIDER_WAIT_TOTAL_SECONDS="0"
78
- PROVIDER_LAST_WAIT_SECONDS="0"
79
- PROVIDER_LAST_WAIT_STARTED_AT=""
80
- PROVIDER_LAST_WAIT_COMPLETED_AT=""
81
-
82
- mkdir -p "${SCHEDULED_STATE_DIR}" "${PENDING_LAUNCH_DIR}"
83
-
84
- if [[ -f "${HOOK_FILE}" ]]; then
85
- # shellcheck source=/dev/null
86
- source "${HOOK_FILE}"
87
- fi
88
-
89
- issue_json_for() {
90
- local issue_id="${1:?issue id required}"
91
- flow_github_issue_view_json "${REPO_SLUG}" "${issue_id}"
92
- }
93
-
94
- issue_json() {
95
- issue_json_for "${ISSUE_ID}"
96
- }
97
-
98
- issue_json_is_open() {
99
- local issue_payload="${1-}"
100
- if [[ -z "${issue_payload}" ]]; then
101
- issue_payload='{}'
102
- fi
103
- jq -e '(.state // "") == "OPEN"' >/dev/null <<<"${issue_payload}"
104
- }
105
-
106
- issue_json_is_keep_open() {
107
- local issue_payload="${1-}"
108
- if [[ -z "${issue_payload}" ]]; then
109
- issue_payload='{}'
110
- fi
111
- jq -e 'any(.labels[]?; .name == "agent-keep-open")' >/dev/null <<<"${issue_payload}"
112
- }
113
-
114
- issue_schedule_interval_seconds_from_json() {
115
- local issue_payload="${1-}"
116
- if [[ -z "${issue_payload}" ]]; then
117
- issue_payload='{}'
118
- fi
119
- ISSUE_JSON="${issue_payload}" node <<'EOF'
120
- const issue = JSON.parse(process.env.ISSUE_JSON || '{}');
121
- const body = String(issue.body || '');
122
- const match = body.match(/^\s*(?:Agent schedule|Schedule|Cadence)\s*:\s*(?:every\s+)?(\d+)\s*([mhd])\s*$/im);
123
- if (!match) {
124
- process.stdout.write('0\n');
125
- process.exit(0);
126
- }
127
- const value = Number(match[1]);
128
- const unit = String(match[2] || '').toLowerCase();
129
- const multiplier = { m: 60, h: 3600, d: 86400 }[unit] || 0;
130
- const seconds = Number.isFinite(value) && value > 0 ? value * multiplier : 0;
131
- process.stdout.write(`${seconds}\n`);
132
- EOF
133
- }
134
-
135
- issue_json_is_scheduled() {
136
- local interval_seconds=""
137
- interval_seconds="$(issue_schedule_interval_seconds_from_json "${1-}")"
138
- [[ "${interval_seconds}" =~ ^[1-9][0-9]*$ ]]
139
- }
140
-
141
- issue_has_open_agent_pr() {
142
- issue_id_has_open_agent_pr "${ISSUE_ID}"
143
- }
144
-
145
- issue_id_has_open_agent_pr() {
146
- local issue_id="${1:?issue id required}"
147
- local open_ids_json=""
148
-
149
- if ! declare -F heartbeat_open_agent_pr_issue_ids >/dev/null 2>&1; then
150
- return 1
151
- fi
152
-
153
- open_ids_json="$(heartbeat_open_agent_pr_issue_ids 2>/dev/null || printf '[]\n')"
154
- jq -e --arg issueId "${issue_id}" 'index($issueId) != null' >/dev/null <<<"${open_ids_json}"
155
- }
156
-
157
- issue_pending_file() {
158
- local issue_id="${1:?issue id required}"
159
- printf '%s/issue-%s.pid\n' "${PENDING_LAUNCH_DIR}" "${issue_id}"
160
- }
161
-
162
- RESIDENT_CONTROLLER_LIB=""
163
- for _rcl_candidate in \
164
- "${SCRIPT_DIR}/resident-issue-controller-lib.sh" \
165
- "${AGENT_CONTROL_PLANE_ROOT:-}/tools/bin/resident-issue-controller-lib.sh" \
166
- "${ACP_ROOT:-}/tools/bin/resident-issue-controller-lib.sh" \
167
- "${SHARED_AGENT_HOME:-}/tools/bin/resident-issue-controller-lib.sh"; do
168
- if [[ -n "${_rcl_candidate}" && -f "${_rcl_candidate}" ]]; then
169
- RESIDENT_CONTROLLER_LIB="${_rcl_candidate}"
170
- break
171
- fi
172
- done
173
- if [[ -n "${SHARED_AGENT_HOME:-}" && -z "${RESIDENT_CONTROLLER_LIB}" ]]; then
174
- for _rcl_skill in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
175
- [[ -n "${_rcl_skill}" ]] || continue
176
- _rcl_candidate="${SHARED_AGENT_HOME}/skills/openclaw/${_rcl_skill}/tools/bin/resident-issue-controller-lib.sh"
177
- if [[ -f "${_rcl_candidate}" ]]; then
178
- RESIDENT_CONTROLLER_LIB="${_rcl_candidate}"
179
- break
180
- fi
181
- done
182
- fi
183
- if [[ -z "${RESIDENT_CONTROLLER_LIB}" ]]; then
184
- echo "unable to locate resident-issue-controller-lib.sh" >&2
185
- exit 1
186
- fi
187
- source "${RESIDENT_CONTROLLER_LIB}"
188
-
189
- issue_id_is_recurring() {
190
- local issue_id="${1:?issue id required}"
191
- if declare -F heartbeat_issue_is_recurring >/dev/null 2>&1; then
192
- [[ "$(heartbeat_issue_is_recurring "${issue_id}" 2>/dev/null || printf 'no\n')" == "yes" ]]
193
- return $?
194
- fi
195
-
196
- issue_json_is_keep_open "$(issue_json_for "${issue_id}" 2>/dev/null || printf '{}\n')"
197
- }
198
-
199
- issue_id_is_scheduled() {
200
- local issue_id="${1:?issue id required}"
201
- if declare -F heartbeat_issue_is_scheduled >/dev/null 2>&1; then
202
- [[ "$(heartbeat_issue_is_scheduled "${issue_id}" 2>/dev/null || printf 'no\n')" == "yes" ]]
203
- return $?
204
- fi
205
-
206
- issue_json_is_scheduled "$(issue_json_for "${issue_id}" 2>/dev/null || printf '{}\n')"
207
- }
208
-
209
- select_next_recurring_issue_id() {
210
- local candidate_id=""
211
-
212
- if ! declare -F heartbeat_list_ready_issue_ids >/dev/null 2>&1; then
213
- return 1
214
- fi
215
-
216
- while IFS= read -r candidate_id; do
217
- [[ -n "${candidate_id}" ]] || continue
218
- [[ "${candidate_id}" != "${ISSUE_ID}" ]] || continue
219
- if issue_id_has_open_agent_pr "${candidate_id}"; then
220
- continue
221
- fi
222
- if ! issue_id_is_recurring "${candidate_id}"; then
223
- continue
224
- fi
225
- if issue_id_is_scheduled "${candidate_id}"; then
226
- continue
227
- fi
228
- printf '%s\n' "${candidate_id}"
229
- return 0
230
- done < <(heartbeat_list_ready_issue_ids 2>/dev/null || true)
231
-
232
- return 1
233
- }
234
-
235
- record_scheduled_next_due() {
236
- local interval_seconds="${1:-0}"
237
- local state_file now_epoch next_due_epoch
238
-
239
- if ! [[ "${interval_seconds}" =~ ^[1-9][0-9]*$ ]]; then
240
- return 0
241
- fi
242
-
243
- now_epoch="$(date +%s)"
244
- next_due_epoch=$((now_epoch + interval_seconds))
245
- NEXT_WAKE_EPOCH="${next_due_epoch}"
246
- NEXT_WAKE_AT="$(flow_format_epoch_utc "${next_due_epoch}")"
247
- state_file="${SCHEDULED_STATE_DIR}/${ISSUE_ID}.env"
248
- cat >"${state_file}" <<EOF
249
- INTERVAL_SECONDS=${interval_seconds}
250
- LAST_STARTED_EPOCH=${now_epoch}
251
- LAST_STARTED_AT=$(flow_format_epoch_utc "${now_epoch}")
252
- NEXT_DUE_EPOCH=${next_due_epoch}
253
- NEXT_DUE_AT=${NEXT_WAKE_AT}
254
- UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
255
- EOF
256
- }
257
-
258
- trap 'CONTROLLER_REASON="${CONTROLLER_REASON:-terminated}"; controller_cleanup' EXIT
259
- trap 'CONTROLLER_REASON="interrupted"; exit 0' INT TERM
260
-
261
- controller_refresh_execution_context
262
- if ! flow_resident_issue_backend_supported "${CODING_WORKER}" || ! flow_is_truthy "$(flow_resident_issue_workers_enabled "${CONFIG_YAML}")"; then
263
- exec "${FLOW_TOOLS_DIR}/start-issue-worker.sh" "${ISSUE_ID}" "${MODE}"
264
- fi
265
-
266
- wait_for_worker_cycle() {
267
- local appear_attempts attempt=0 saw_session="no"
268
- local reconcile_out=""
269
- local reconcile_status=""
270
- local reconcile_attempts=0
271
- local reconcile_max_attempts=20
272
-
273
- # Poll quickly during launch so short-lived test shims and fast workers do not
274
- # get misclassified as launch-no-session before the controller ever sees them.
275
- appear_attempts=100
276
- while (( attempt < appear_attempts )); do
277
- if tmux has-session -t "${SESSION}" 2>/dev/null; then
278
- saw_session="yes"
279
- break
280
- fi
281
- sleep 0.1
282
- attempt=$((attempt + 1))
283
- done
284
-
285
- if [[ "${saw_session}" != "yes" ]]; then
286
- return 1
287
- fi
288
-
289
- controller_write_state "waiting-worker" ""
290
- while tmux has-session -t "${SESSION}" 2>/dev/null; do
291
- sleep 2
292
- done
293
-
294
- controller_write_state "reconciling" ""
295
- while (( reconcile_attempts < reconcile_max_attempts )); do
296
- if ! reconcile_out="$(bash "${FLOW_TOOLS_DIR}/reconcile-issue-worker.sh" "${SESSION}" 2>&1)"; then
297
- printf '%s\n' "${reconcile_out}" >&2
298
- CONTROLLER_REASON="reconcile-failed"
299
- return 1
300
- fi
301
-
302
- reconcile_status="$(awk -F= '/^STATUS=/{print $2; exit}' <<<"${reconcile_out}")"
303
- case "${reconcile_status}" in
304
- SUCCEEDED|FAILED)
305
- return 0
306
- ;;
307
- "")
308
- # Older test shims may not print STATUS. The real reconcile wrapper always
309
- # does, so treat blank STATUS as successful test completion.
310
- return 0
311
- ;;
312
- RUNNING)
313
- controller_write_state "reconciling" "worker-still-finalizing"
314
- sleep 1
315
- ;;
316
- *)
317
- printf '%s\n' "${reconcile_out}" >&2
318
- CONTROLLER_REASON="reconcile-non-terminal-${reconcile_status}"
319
- return 1
320
- ;;
321
- esac
322
-
323
- reconcile_attempts=$((reconcile_attempts + 1))
324
- done
325
-
326
- CONTROLLER_REASON="reconcile-timeout"
327
- return 1
328
- }
329
-
330
- sleep_until_next_due() {
331
- local target_epoch="${1:-0}"
332
- local now_epoch remaining sleep_seconds
333
-
334
- while true; do
335
- now_epoch="$(date +%s)"
336
- if ! [[ "${target_epoch}" =~ ^[0-9]+$ ]] || (( target_epoch <= now_epoch )); then
337
- return 0
338
- fi
339
- remaining=$((target_epoch - now_epoch))
340
- sleep_seconds="${POLL_SECONDS}"
341
- if ! [[ "${sleep_seconds}" =~ ^[1-9][0-9]*$ ]]; then
342
- sleep_seconds="60"
343
- fi
344
- if (( remaining < sleep_seconds )); then
345
- sleep_seconds="${remaining}"
346
- fi
347
- controller_write_state "waiting-due" ""
348
- sleep "${sleep_seconds}"
349
- done
350
- }
351
-
352
- while true; do
353
- issue_payload="$(issue_json 2>/dev/null || printf '{}\n')"
354
- if ! issue_json_is_open "${issue_payload}"; then
355
- if controller_adopt_next_recurring_issue; then
356
- continue
357
- fi
358
- CONTROLLER_REASON="issue-closed"
359
- if controller_wait_for_leased_issue; then
360
- continue
361
- fi
362
- break
363
- fi
364
-
365
- is_keep_open="no"
366
- if issue_json_is_keep_open "${issue_payload}"; then
367
- is_keep_open="yes"
368
- fi
369
-
370
- schedule_interval_seconds="$(issue_schedule_interval_seconds_from_json "${issue_payload}")"
371
- is_scheduled="no"
372
- if [[ "${schedule_interval_seconds}" =~ ^[1-9][0-9]*$ ]]; then
373
- is_scheduled="yes"
374
- fi
375
- controller_refresh_execution_context
376
- controller_refresh_issue_lane_context "${is_scheduled}" "${schedule_interval_seconds}"
377
- controller_track_provider_selection "provider-selection"
378
- controller_write_state "starting" ""
379
-
380
- if controller_yield_to_live_lane_peer; then
381
- break
382
- fi
383
-
384
- if [[ "${is_keep_open}" != "yes" && "${is_scheduled}" != "yes" ]]; then
385
- if controller_adopt_next_recurring_issue; then
386
- continue
387
- fi
388
- CONTROLLER_REASON="resident-ineligible"
389
- if controller_wait_for_leased_issue; then
390
- continue
391
- fi
392
- break
393
- fi
394
-
395
- if issue_has_open_agent_pr; then
396
- if controller_adopt_next_recurring_issue; then
397
- continue
398
- fi
399
- CONTROLLER_REASON="open-agent-pr"
400
- if controller_wait_for_leased_issue; then
401
- continue
402
- fi
403
- controller_write_state "waiting-open-pr" ""
404
- break
405
- fi
406
-
407
- if [[ "${is_scheduled}" == "yes" && -n "${NEXT_WAKE_EPOCH}" ]]; then
408
- sleep_until_next_due "${NEXT_WAKE_EPOCH}"
409
- fi
410
-
411
- NEXT_WAKE_EPOCH=""
412
- NEXT_WAKE_AT=""
413
- if ! controller_wait_for_provider_capacity; then
414
- CONTROLLER_REASON="provider-unavailable"
415
- break
416
- fi
417
- if [[ "${PROVIDER_WAITED}" == "yes" ]]; then
418
- CONTROLLER_REASON="provider-ready"
419
- continue
420
- fi
421
- controller_write_state "launching" ""
422
- controller_mark_issue_running
423
- if ! bash "${FLOW_TOOLS_DIR}/start-issue-worker.sh" "${ISSUE_ID}" "${MODE}" >/dev/null; then
424
- controller_rollback_issue_launch
425
- CONTROLLER_REASON="launch-failed"
426
- break
427
- fi
428
- controller_mark_provider_launched
429
-
430
- if ! wait_for_worker_cycle; then
431
- CONTROLLER_REASON="launch-no-session"
432
- break
433
- fi
434
-
435
- CONTROLLER_LOOP_COUNT=$((CONTROLLER_LOOP_COUNT + 1))
436
-
437
- if [[ "$(controller_last_failure_reason || true)" == "provider-quota-limit" ]]; then
438
- controller_refresh_execution_context
439
- controller_refresh_issue_lane_context "${is_scheduled}" "${schedule_interval_seconds}"
440
- controller_track_provider_selection "provider-failover"
441
- if ! controller_wait_for_provider_capacity; then
442
- CONTROLLER_REASON="provider-unavailable"
443
- break
444
- fi
445
- CONTROLLER_REASON="provider-failover"
446
- continue
447
- fi
448
-
449
- issue_payload="$(issue_json 2>/dev/null || printf '{}\n')"
450
- if ! issue_json_is_open "${issue_payload}"; then
451
- if controller_adopt_next_recurring_issue; then
452
- continue
453
- fi
454
- CONTROLLER_REASON="issue-closed"
455
- if controller_wait_for_leased_issue; then
456
- continue
457
- fi
458
- break
459
- fi
460
- if jq -e 'any(.labels[]?; .name == "agent-blocked")' >/dev/null <<<"${issue_payload}"; then
461
- if controller_adopt_next_recurring_issue; then
462
- continue
463
- fi
464
- CONTROLLER_REASON="issue-blocked"
465
- if controller_wait_for_leased_issue; then
466
- continue
467
- fi
468
- break
469
- fi
470
- if issue_has_open_agent_pr; then
471
- if controller_adopt_next_recurring_issue; then
472
- continue
473
- fi
474
- CONTROLLER_REASON="open-agent-pr"
475
- if controller_wait_for_leased_issue; then
476
- continue
477
- fi
478
- break
479
- fi
480
-
481
- if [[ "${is_scheduled}" == "yes" ]]; then
482
- record_scheduled_next_due "${schedule_interval_seconds}"
483
- controller_write_state "sleeping" ""
484
- continue
485
- fi
486
-
487
- if [[ "${MAX_IMMEDIATE_CYCLES}" =~ ^[1-9][0-9]*$ ]] && (( CONTROLLER_LOOP_COUNT >= MAX_IMMEDIATE_CYCLES )); then
488
- if controller_adopt_next_recurring_issue; then
489
- continue
490
- fi
491
- CONTROLLER_REASON="max-immediate-cycles"
492
- if controller_wait_for_leased_issue; then
493
- continue
494
- fi
495
- break
496
- fi
497
-
498
- controller_write_state "idle" ""
499
- done
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
-
8
- NUMBER="${1:?usage: update-github-labels.sh NUMBER [--add LABEL]... [--remove LABEL]...}"
9
- shift
10
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
11
- REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
12
- UPDATE_LABELS_BIN="${SCRIPT_DIR}/agent-github-update-labels"
13
-
14
- bash "${UPDATE_LABELS_BIN}" --repo-slug "$REPO_SLUG" --number "$NUMBER" "$@"
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
-
8
- SESSION="${1:?usage: worker-status.sh SESSION}"
9
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
10
- if ! flow_require_explicit_profile_selection "${FLOW_SKILL_DIR}" "worker-status.sh"; then
11
- exit 64
12
- fi
13
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
14
- RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
15
- FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
16
-
17
- exec bash "${FLOW_TOOLS_DIR}/agent-project-worker-status" \
18
- --runs-root "$RUNS_ROOT" \
19
- --session "$SESSION"
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
-
8
- FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
9
- CATALOG_FILE="${FLOW_SKILL_DIR}/assets/workflow-catalog.json"
10
- COMMAND="${1:-list}"
11
- WORKFLOW_ID="${2:-}"
12
- AVAILABLE_PROFILES="$(flow_list_profile_ids "${FLOW_SKILL_DIR}" | paste -sd, -)"
13
- ACTIVE_PROFILE="$(flow_resolve_adapter_id "$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")")"
14
- PROFILE_SELECTION_MODE="$(flow_profile_selection_mode "${FLOW_SKILL_DIR}")"
15
- PROFILE_NOTES="$(flow_resolve_profile_notes_file "$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")")"
16
-
17
- python3 - "$CATALOG_FILE" "$COMMAND" "$WORKFLOW_ID" "$AVAILABLE_PROFILES" "$ACTIVE_PROFILE" "$PROFILE_SELECTION_MODE" "$PROFILE_NOTES" <<'PY'
18
- import json
19
- import sys
20
-
21
- catalog_file, command, workflow_id, available_profiles, active_profile, profile_selection_mode, profile_notes = sys.argv[1:8]
22
- with open(catalog_file, "r", encoding="utf-8") as fh:
23
- payload = json.load(fh)
24
-
25
- workflows = payload.get("workflows", [])
26
- profile_ids = [item for item in available_profiles.split(",") if item]
27
-
28
- if profile_ids:
29
- payload["available_profiles"] = profile_ids
30
-
31
- payload["active_profile"] = active_profile
32
- payload["profile_selection_mode"] = profile_selection_mode
33
- payload["profile_notes"] = profile_notes
34
-
35
- if command == "json":
36
- json.dump(payload, sys.stdout, indent=2)
37
- sys.stdout.write("\n")
38
- raise SystemExit(0)
39
-
40
- if command == "profiles":
41
- for profile_id in profile_ids:
42
- sys.stdout.write(f"{profile_id}\n")
43
- raise SystemExit(0)
44
-
45
- if command == "ids":
46
- for workflow in workflows:
47
- sys.stdout.write(f"{workflow['id']}\n")
48
- raise SystemExit(0)
49
-
50
- if command == "context":
51
- sys.stdout.write(f"ACTIVE_PROFILE={active_profile}\n")
52
- sys.stdout.write(f"PROFILE_SELECTION_MODE={profile_selection_mode}\n")
53
- sys.stdout.write(f"PROFILE_NOTES={profile_notes}\n")
54
- raise SystemExit(0)
55
-
56
- if command == "show":
57
- match = next((wf for wf in workflows if wf["id"] == workflow_id), None)
58
- if match is None:
59
- raise SystemExit(f"unknown workflow id: {workflow_id}")
60
- sys.stdout.write(f"ACTIVE_PROFILE={active_profile}\n")
61
- sys.stdout.write(f"PROFILE_SELECTION_MODE={profile_selection_mode}\n")
62
- for key in ("id", "kind", "trigger", "entrypoint", "summary"):
63
- sys.stdout.write(f"{key.upper()}={match.get(key, '')}\n")
64
- raise SystemExit(0)
65
-
66
- if command != "list":
67
- raise SystemExit(f"unknown command: {command}")
68
-
69
- for workflow in workflows:
70
- row = [
71
- workflow.get("id", ""),
72
- workflow.get("kind", ""),
73
- workflow.get("entrypoint", ""),
74
- workflow.get("trigger", ""),
75
- ]
76
- sys.stdout.write("\t".join(row) + "\n")
77
- PY