agent-control-plane 0.2.0 → 0.4.9
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 +69 -19
- package/assets/workflow-catalog.json +1 -1
- package/bin/pr-risk.sh +22 -7
- package/bin/sync-pr-labels.sh +1 -1
- package/hooks/heartbeat-hooks.sh +125 -12
- package/hooks/issue-reconcile-hooks.sh +1 -1
- package/hooks/pr-reconcile-hooks.sh +1 -1
- package/npm/bin/agent-control-plane.js +296 -61
- package/package.json +11 -7
- package/tools/bin/agent-github-update-labels +36 -2
- package/tools/bin/agent-project-catch-up-merged-prs +4 -2
- package/tools/bin/agent-project-cleanup-session +49 -5
- package/tools/bin/agent-project-heartbeat-loop +119 -1471
- package/tools/bin/agent-project-publish-issue-pr +6 -3
- package/tools/bin/agent-project-reconcile-issue-session +78 -106
- package/tools/bin/agent-project-reconcile-pr-session +166 -143
- package/tools/bin/agent-project-retry-state +18 -7
- package/tools/bin/agent-project-run-claude-session +10 -0
- package/tools/bin/agent-project-run-codex-resilient +99 -14
- 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-sync-source-repo-main +163 -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 +1257 -34
- package/tools/bin/flow-resident-worker-lib.sh +119 -1
- package/tools/bin/flow-shell-lib.sh +56 -0
- package/tools/bin/github-core-rate-limit-state.sh +77 -0
- package/tools/bin/github-write-outbox.sh +470 -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 +56 -3
- package/tools/bin/install-project-launchd.sh +17 -2
- package/tools/bin/project-init.sh +21 -1
- package/tools/bin/project-launchd-bootstrap.sh +16 -9
- package/tools/bin/project-runtimectl.sh +46 -2
- package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
- package/tools/bin/resident-issue-controller-lib.sh +448 -0
- package/tools/bin/scaffold-profile.sh +61 -3
- package/tools/bin/start-pr-fix-worker.sh +47 -10
- package/tools/bin/start-resident-issue-loop.sh +28 -439
- package/tools/dashboard/app.js +37 -1
- package/tools/dashboard/dashboard_snapshot.py +65 -26
- package/tools/templates/pr-fix-template.md +3 -1
- package/tools/templates/pr-merge-repair-template.md +2 -1
- package/SKILL.md +0 -149
- package/references/architecture.md +0 -217
- package/references/commands.md +0 -128
- package/references/control-plane-map.md +0 -124
- package/references/docs-map.md +0 -73
- package/references/release-checklist.md +0 -65
- package/references/repo-map.md +0 -36
- package/tools/bin/split-retained-slice.sh +0 -124
|
@@ -285,6 +285,22 @@ flow_resident_issue_queue_file() {
|
|
|
285
285
|
printf '%s/issue-%s.env\n' "$(flow_resident_issue_queue_pending_dir "${config_file}")" "${issue_id}"
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
flow_resident_issue_claim_file() {
|
|
289
|
+
local config_file="${1:-}"
|
|
290
|
+
local issue_id="${2:?issue id required}"
|
|
291
|
+
local claimer_key="${3:?claimer key required}"
|
|
292
|
+
|
|
293
|
+
if [[ -z "${config_file}" ]]; then
|
|
294
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
printf '%s/issue-%s.%s.%s.env\n' \
|
|
298
|
+
"$(flow_resident_issue_queue_claims_dir "${config_file}")" \
|
|
299
|
+
"${issue_id}" \
|
|
300
|
+
"${claimer_key}" \
|
|
301
|
+
"$$"
|
|
302
|
+
}
|
|
303
|
+
|
|
288
304
|
flow_resident_issue_controller_file() {
|
|
289
305
|
local config_file="${1:-}"
|
|
290
306
|
local issue_id="${2:?issue id required}"
|
|
@@ -342,8 +358,11 @@ flow_resident_issue_enqueue() {
|
|
|
342
358
|
|
|
343
359
|
tmp_file="${queue_file}.tmp.$$"
|
|
344
360
|
flow_resident_write_metadata "${tmp_file}" \
|
|
361
|
+
"STATE_FORMAT_VERSION=1" \
|
|
362
|
+
"STATE_KIND=pending" \
|
|
345
363
|
"ISSUE_ID=${issue_id}" \
|
|
346
364
|
"QUEUED_BY=${queued_by}" \
|
|
365
|
+
"UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
347
366
|
"QUEUED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
348
367
|
mv "${tmp_file}" "${queue_file}"
|
|
349
368
|
|
|
@@ -361,6 +380,9 @@ flow_resident_issue_claim_next() {
|
|
|
361
380
|
local issue_id=""
|
|
362
381
|
local claim_file=""
|
|
363
382
|
local claim_key=""
|
|
383
|
+
local queued_by=""
|
|
384
|
+
local queued_at=""
|
|
385
|
+
local claimed_at=""
|
|
364
386
|
|
|
365
387
|
if [[ -z "${config_file}" ]]; then
|
|
366
388
|
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
@@ -378,8 +400,22 @@ flow_resident_issue_claim_next() {
|
|
|
378
400
|
[[ -n "${issue_id}" ]] || continue
|
|
379
401
|
[[ "${issue_id}" != "${skip_issue_id}" ]] || continue
|
|
380
402
|
|
|
381
|
-
|
|
403
|
+
queued_by="$(flow_resident_metadata_value "${queue_file}" "QUEUED_BY" || true)"
|
|
404
|
+
queued_at="$(flow_resident_metadata_value "${queue_file}" "QUEUED_AT" || true)"
|
|
405
|
+
claimed_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
406
|
+
claim_file="$(flow_resident_issue_claim_file "${config_file}" "${issue_id}" "${claim_key}")"
|
|
382
407
|
if mv "${queue_file}" "${claim_file}" 2>/dev/null; then
|
|
408
|
+
flow_resident_write_metadata "${claim_file}" \
|
|
409
|
+
"STATE_FORMAT_VERSION=1" \
|
|
410
|
+
"STATE_KIND=claim" \
|
|
411
|
+
"ISSUE_ID=${issue_id}" \
|
|
412
|
+
"QUEUED_BY=${queued_by}" \
|
|
413
|
+
"QUEUED_AT=${queued_at}" \
|
|
414
|
+
"SESSION=${claimer_key}" \
|
|
415
|
+
"CLAIMED_BY=${claim_key}" \
|
|
416
|
+
"CLAIMED_AT=${claimed_at}" \
|
|
417
|
+
"UPDATED_AT=${claimed_at}" \
|
|
418
|
+
"CLAIM_FILE=${claim_file}"
|
|
383
419
|
printf 'ISSUE_ID=%s\n' "${issue_id}"
|
|
384
420
|
printf 'CLAIM_FILE=%s\n' "${claim_file}"
|
|
385
421
|
return 0
|
|
@@ -441,6 +477,87 @@ flow_resident_issue_controller_reap_file() {
|
|
|
441
477
|
return 0
|
|
442
478
|
}
|
|
443
479
|
|
|
480
|
+
flow_resident_issue_reap_stale_claims() {
|
|
481
|
+
local config_file="${1:-}"
|
|
482
|
+
local claims_dir=""
|
|
483
|
+
local claim_file=""
|
|
484
|
+
local claim_token=""
|
|
485
|
+
local claim_pid=""
|
|
486
|
+
local issue_id=""
|
|
487
|
+
local queued_by=""
|
|
488
|
+
local queued_at=""
|
|
489
|
+
local claimed_at=""
|
|
490
|
+
local existing_pending_file=""
|
|
491
|
+
local other_claim=""
|
|
492
|
+
local other_token=""
|
|
493
|
+
local other_pid=""
|
|
494
|
+
|
|
495
|
+
if [[ -z "${config_file}" ]]; then
|
|
496
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
497
|
+
fi
|
|
498
|
+
|
|
499
|
+
claims_dir="$(flow_resident_issue_queue_claims_dir "${config_file}")"
|
|
500
|
+
mkdir -p "${claims_dir}"
|
|
501
|
+
|
|
502
|
+
for claim_file in "${claims_dir}"/issue-*.env; do
|
|
503
|
+
[[ -f "${claim_file}" ]] || continue
|
|
504
|
+
|
|
505
|
+
claim_token="${claim_file##*/}"
|
|
506
|
+
claim_token="${claim_token%.env}"
|
|
507
|
+
claim_pid="${claim_token##*.}"
|
|
508
|
+
[[ "${claim_pid}" =~ ^[0-9]+$ ]] || continue
|
|
509
|
+
|
|
510
|
+
if flow_resident_controller_pid_live "${claim_pid}" "start-resident-issue-loop.sh"; then
|
|
511
|
+
continue
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
issue_id="$(flow_resident_metadata_value "${claim_file}" "ISSUE_ID" || true)"
|
|
515
|
+
[[ -n "${issue_id}" ]] || issue_id="${claim_token#issue-}"
|
|
516
|
+
issue_id="${issue_id%%.*}"
|
|
517
|
+
[[ -n "${issue_id}" ]] || continue
|
|
518
|
+
|
|
519
|
+
# If another live claim exists for the same issue, do not re-queue this one.
|
|
520
|
+
for other_claim in "${claims_dir}/issue-${issue_id}."*; do
|
|
521
|
+
[[ -f "${other_claim}" ]] || continue
|
|
522
|
+
other_token="${other_claim##*/}"
|
|
523
|
+
other_token="${other_token%.env}"
|
|
524
|
+
other_pid="${other_token##*.}"
|
|
525
|
+
[[ "${other_pid}" =~ ^[0-9]+$ ]] || continue
|
|
526
|
+
if [[ "${other_pid}" == "${claim_pid}" ]]; then
|
|
527
|
+
continue
|
|
528
|
+
fi
|
|
529
|
+
if flow_resident_controller_pid_live "${other_pid}" "start-resident-issue-loop.sh"; then
|
|
530
|
+
issue_id=""
|
|
531
|
+
break
|
|
532
|
+
fi
|
|
533
|
+
done
|
|
534
|
+
[[ -n "${issue_id}" ]] || continue
|
|
535
|
+
|
|
536
|
+
existing_pending_file="$(flow_resident_issue_queue_file "${config_file}" "${issue_id}")"
|
|
537
|
+
if [[ -f "${existing_pending_file}" ]]; then
|
|
538
|
+
rm -f "${claim_file}"
|
|
539
|
+
continue
|
|
540
|
+
fi
|
|
541
|
+
|
|
542
|
+
queued_by="$(flow_resident_metadata_value "${claim_file}" "QUEUED_BY" || true)"
|
|
543
|
+
queued_at="$(flow_resident_metadata_value "${claim_file}" "QUEUED_AT" || true)"
|
|
544
|
+
claimed_at="$(flow_resident_metadata_value "${claim_file}" "CLAIMED_AT" || true)"
|
|
545
|
+
|
|
546
|
+
[[ -n "${queued_by}" ]] || queued_by="heartbeat"
|
|
547
|
+
[[ -n "${queued_at}" ]] || queued_at="${claimed_at:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")}"
|
|
548
|
+
|
|
549
|
+
flow_resident_write_metadata "${existing_pending_file}" \
|
|
550
|
+
"STATE_FORMAT_VERSION=1" \
|
|
551
|
+
"STATE_KIND=pending" \
|
|
552
|
+
"ISSUE_ID=${issue_id}" \
|
|
553
|
+
"QUEUED_BY=${queued_by}" \
|
|
554
|
+
"QUEUED_AT=${queued_at}" \
|
|
555
|
+
"UPDATED_AT=${claimed_at:-${queued_at}}"
|
|
556
|
+
rm -f "${claim_file}"
|
|
557
|
+
|
|
558
|
+
done
|
|
559
|
+
}
|
|
560
|
+
|
|
444
561
|
flow_resident_issue_reap_stale_state() {
|
|
445
562
|
local config_file="${1:-}"
|
|
446
563
|
local resident_root=""
|
|
@@ -458,6 +575,7 @@ flow_resident_issue_reap_stale_state() {
|
|
|
458
575
|
reaped=$((reaped + 1))
|
|
459
576
|
fi
|
|
460
577
|
done
|
|
578
|
+
flow_resident_issue_reap_stale_claims "${config_file}" || true
|
|
461
579
|
|
|
462
580
|
printf '%s\n' "${reaped}"
|
|
463
581
|
}
|
|
@@ -5,6 +5,62 @@ flow_canonical_skill_name() {
|
|
|
5
5
|
printf '%s\n' "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}"
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
flow_resolve_python_bin() {
|
|
9
|
+
if [[ -n "${PYTHON_BIN:-}" && -x "${PYTHON_BIN:-}" ]]; then
|
|
10
|
+
printf '%s\n' "${PYTHON_BIN}"
|
|
11
|
+
return 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
15
|
+
command -v python3
|
|
16
|
+
return 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [[ -x /opt/homebrew/bin/python3 ]]; then
|
|
20
|
+
printf '%s\n' "/opt/homebrew/bin/python3"
|
|
21
|
+
return 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if command -v python >/dev/null 2>&1; then
|
|
25
|
+
command -v python
|
|
26
|
+
return 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
return 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
flow_format_epoch_utc() {
|
|
33
|
+
local epoch="${1:-}"
|
|
34
|
+
local python_bin=""
|
|
35
|
+
|
|
36
|
+
if ! [[ "${epoch}" =~ ^[0-9]+$ ]] || [[ "${epoch}" == "0" ]]; then
|
|
37
|
+
return 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if date -u -r "${epoch}" +"%Y-%m-%dT%H:%M:%SZ" >/dev/null 2>&1; then
|
|
41
|
+
date -u -r "${epoch}" +"%Y-%m-%dT%H:%M:%SZ"
|
|
42
|
+
return 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if date -u -d "@${epoch}" +"%Y-%m-%dT%H:%M:%SZ" >/dev/null 2>&1; then
|
|
46
|
+
date -u -d "@${epoch}" +"%Y-%m-%dT%H:%M:%SZ"
|
|
47
|
+
return 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
python_bin="$(flow_resolve_python_bin 2>/dev/null || true)"
|
|
51
|
+
if [[ -n "${python_bin}" ]]; then
|
|
52
|
+
"${python_bin}" - "${epoch}" <<'PY'
|
|
53
|
+
import datetime
|
|
54
|
+
import sys
|
|
55
|
+
|
|
56
|
+
print(datetime.datetime.fromtimestamp(int(sys.argv[1]), datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
57
|
+
PY
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
63
|
+
|
|
8
64
|
flow_compat_skill_alias() {
|
|
9
65
|
printf '%s\n' "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"
|
|
10
66
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
usage() {
|
|
9
|
+
cat <<'EOF'
|
|
10
|
+
Usage:
|
|
11
|
+
github-core-rate-limit-state.sh get
|
|
12
|
+
github-core-rate-limit-state.sh schedule [reason] [--next-at-epoch <unix-seconds>]
|
|
13
|
+
github-core-rate-limit-state.sh clear
|
|
14
|
+
EOF
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
action="${1:-}"
|
|
18
|
+
reason="${2:-github-api-rate-limit}"
|
|
19
|
+
next_at_epoch=""
|
|
20
|
+
|
|
21
|
+
if [[ $# -lt 1 ]]; then
|
|
22
|
+
usage >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
shift || true
|
|
27
|
+
if [[ $# -gt 0 ]]; then
|
|
28
|
+
reason="${1:-github-api-rate-limit}"
|
|
29
|
+
shift || true
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
while [[ $# -gt 0 ]]; do
|
|
33
|
+
case "$1" in
|
|
34
|
+
--next-at-epoch)
|
|
35
|
+
next_at_epoch="${2:-}"
|
|
36
|
+
shift 2
|
|
37
|
+
;;
|
|
38
|
+
--help|-h)
|
|
39
|
+
usage
|
|
40
|
+
exit 0
|
|
41
|
+
;;
|
|
42
|
+
*)
|
|
43
|
+
echo "unknown argument: $1" >&2
|
|
44
|
+
usage >&2
|
|
45
|
+
exit 1
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
case "${action}" in
|
|
51
|
+
get|schedule|clear) ;;
|
|
52
|
+
*)
|
|
53
|
+
usage >&2
|
|
54
|
+
exit 1
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
59
|
+
STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
60
|
+
COOLDOWNS="$(flow_resolve_retry_cooldowns "${CONFIG_YAML}")"
|
|
61
|
+
|
|
62
|
+
exec_args=(
|
|
63
|
+
--state-root "${STATE_ROOT}"
|
|
64
|
+
--kind github
|
|
65
|
+
--item-id core-api
|
|
66
|
+
--action "${action}"
|
|
67
|
+
--reason "${reason}"
|
|
68
|
+
--cooldowns "${COOLDOWNS}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if [[ "${action}" == "schedule" && "${next_at_epoch}" =~ ^[0-9]+$ ]]; then
|
|
72
|
+
exec_args+=(--next-at-epoch "${next_at_epoch}")
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
ACP_STATE_ROOT="${STATE_ROOT}" \
|
|
76
|
+
ACP_RETRY_COOLDOWNS="${COOLDOWNS}" \
|
|
77
|
+
exec bash "${SCRIPT_DIR}/agent-project-retry-state" "${exec_args[@]}"
|