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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-control-plane",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Help a repo keep GitHub-driven coding agents running reliably without constant human babysitting",
|
|
5
5
|
"homepage": "https://github.com/ducminhnguyen0319/agent-control-plane",
|
|
6
6
|
"bugs": {
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"README.md",
|
|
22
|
-
"SKILL.md",
|
|
23
22
|
"assets/workflow-catalog.json",
|
|
24
23
|
"bin/agent-control-plane",
|
|
25
24
|
"bin/issue-resource-class.sh",
|
|
@@ -28,11 +27,12 @@
|
|
|
28
27
|
"bin/sync-pr-labels.sh",
|
|
29
28
|
"hooks",
|
|
30
29
|
"npm/bin",
|
|
31
|
-
"references",
|
|
32
30
|
"tools/bin",
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
"!tools/bin/audit-*.sh",
|
|
32
|
+
"!tools/bin/check-skill-contracts.sh",
|
|
33
|
+
"!tools/bin/split-retained-slice.sh",
|
|
34
|
+
"!tools/bin/render-dashboard-snapshot.py",
|
|
35
|
+
"!tools/bin/resident-issue-queue-status.py",
|
|
36
36
|
"tools/dashboard/app.js",
|
|
37
37
|
"tools/dashboard/dashboard_snapshot.py",
|
|
38
38
|
"tools/dashboard/index.html",
|
|
@@ -48,7 +48,11 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"doctor": "node ./npm/bin/agent-control-plane.js doctor",
|
|
50
50
|
"smoke": "node ./npm/bin/agent-control-plane.js smoke",
|
|
51
|
-
"test": "
|
|
51
|
+
"test": "node -e \"const { spawnSync } = require('node:child_process'); const result = spawnSync('bash', ['tools/tests/run-all.sh'], { stdio: 'inherit' }); if (result.error) throw result.error; process.exit(result.status ?? 1);\""
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"provenance": true
|
|
52
56
|
},
|
|
53
57
|
"keywords": [
|
|
54
58
|
"agents",
|
|
@@ -20,6 +20,7 @@ number=""
|
|
|
20
20
|
add_file="$(mktemp)"
|
|
21
21
|
remove_file="$(mktemp)"
|
|
22
22
|
trap 'rm -f "$add_file" "$remove_file"' EXIT
|
|
23
|
+
github_outbox_script="${SCRIPT_DIR}/github-write-outbox.sh"
|
|
23
24
|
|
|
24
25
|
while [[ $# -gt 0 ]]; do
|
|
25
26
|
case "$1" in
|
|
@@ -47,12 +48,40 @@ if [[ -z "$repo_slug" || -z "$number" ]]; then
|
|
|
47
48
|
exit 1
|
|
48
49
|
fi
|
|
49
50
|
|
|
51
|
+
enqueue_label_update() {
|
|
52
|
+
local -a args=()
|
|
53
|
+
local label=""
|
|
54
|
+
|
|
55
|
+
[[ "${ACP_GITHUB_OUTBOX_DISABLE_ENQUEUE:-0}" != "1" ]] || return 1
|
|
56
|
+
[[ -x "${github_outbox_script}" ]] || return 1
|
|
57
|
+
|
|
58
|
+
args=(enqueue-labels --repo-slug "${repo_slug}" --number "${number}")
|
|
59
|
+
while IFS= read -r label; do
|
|
60
|
+
[[ -n "${label}" ]] || continue
|
|
61
|
+
args+=(--add "${label}")
|
|
62
|
+
done <"${add_file}"
|
|
63
|
+
while IFS= read -r label; do
|
|
64
|
+
[[ -n "${label}" ]] || continue
|
|
65
|
+
args+=(--remove "${label}")
|
|
66
|
+
done <"${remove_file}"
|
|
67
|
+
|
|
68
|
+
"${github_outbox_script}" "${args[@]}" >/dev/null
|
|
69
|
+
}
|
|
70
|
+
|
|
50
71
|
resource="issues/${number}"
|
|
72
|
+
if flow_github_core_rate_limit_active; then
|
|
73
|
+
enqueue_label_update && exit 0
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
51
77
|
# Use caller-provided cached JSON if available to skip the GET call
|
|
52
78
|
if [[ -n "${ACP_CACHED_ISSUE_JSON:-}" ]]; then
|
|
53
79
|
current_json="${ACP_CACHED_ISSUE_JSON}"
|
|
54
80
|
else
|
|
55
|
-
current_json="$(flow_github_api_repo "${repo_slug}" "${resource}")"
|
|
81
|
+
if ! current_json="$(flow_github_api_repo "${repo_slug}" "${resource}")"; then
|
|
82
|
+
enqueue_label_update && exit 0
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
56
85
|
fi
|
|
57
86
|
add_json="$(jq -R . <"$add_file" | jq -s .)"
|
|
58
87
|
remove_json="$(jq -R . <"$remove_file" | jq -s .)"
|
|
@@ -68,4 +97,9 @@ process.stdout.write(JSON.stringify({ labels: Array.from(labels).sort() }));
|
|
|
68
97
|
EOF
|
|
69
98
|
)"
|
|
70
99
|
|
|
71
|
-
printf '%s' "$payload" | flow_github_api_repo "${repo_slug}" "${resource}" --method PATCH --input - >/dev/null
|
|
100
|
+
if printf '%s' "$payload" | flow_github_api_repo "${repo_slug}" "${resource}" --method PATCH --input - >/dev/null; then
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
enqueue_label_update && exit 0
|
|
105
|
+
exit 1
|
|
@@ -46,6 +46,7 @@ optional_hooks=(
|
|
|
46
46
|
pr_cleanup_merged_residue
|
|
47
47
|
pr_linked_issue_should_close
|
|
48
48
|
pr_after_merged
|
|
49
|
+
pr_after_closed
|
|
49
50
|
)
|
|
50
51
|
|
|
51
52
|
for hook_name in "${optional_hooks[@]}"; do
|
|
@@ -54,8 +55,9 @@ for hook_name in "${optional_hooks[@]}"; do
|
|
|
54
55
|
fi
|
|
55
56
|
done
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
forge_scope="$(printf '%s' "${ACP_FORGE_PROVIDER:-${F_LOSNING_FORGE_PROVIDER:-github}}" | tr -c '[:alnum:]._-' '-')"
|
|
59
|
+
merged_ledger_dir="${state_root}/merged-pr-catchup-${forge_scope}"
|
|
60
|
+
closed_ledger_dir="${state_root}/closed-pr-catchup-${forge_scope}"
|
|
59
61
|
mkdir -p "$merged_ledger_dir" "$closed_ledger_dir"
|
|
60
62
|
|
|
61
63
|
get_pr_risk_json() {
|
|
@@ -95,6 +95,7 @@ cleanup_error=""
|
|
|
95
95
|
cleanup_mode="noop"
|
|
96
96
|
orphan_fallback_used="false"
|
|
97
97
|
active_tmux_session="false"
|
|
98
|
+
archived_dir=""
|
|
98
99
|
|
|
99
100
|
if [[ -n "$session" ]]; then
|
|
100
101
|
meta_file="${runs_root}/${session}/run.env"
|
|
@@ -390,6 +391,47 @@ cleanup_orphan_worktree_dir() {
|
|
|
390
391
|
git -C "$repo_root" worktree prune >/dev/null 2>&1 || true
|
|
391
392
|
}
|
|
392
393
|
|
|
394
|
+
worktree_path_is_registered() {
|
|
395
|
+
local candidate_path="${1:-}"
|
|
396
|
+
[[ -n "${candidate_path}" ]] || return 1
|
|
397
|
+
|
|
398
|
+
git -C "$repo_root" worktree list --porcelain 2>/dev/null \
|
|
399
|
+
| grep -F -x -q -- "worktree ${candidate_path}"
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
write_cleanup_warning_artifact() {
|
|
403
|
+
local target_dir=""
|
|
404
|
+
local notice_file=""
|
|
405
|
+
local cleanup_error_line=""
|
|
406
|
+
local recorded_at=""
|
|
407
|
+
|
|
408
|
+
[[ "${cleanup_status}" != "0" ]] || return 0
|
|
409
|
+
|
|
410
|
+
if [[ -n "${archived_dir}" && -d "${archived_dir}" ]]; then
|
|
411
|
+
target_dir="${archived_dir}"
|
|
412
|
+
elif [[ -n "${session}" && -n "${runs_root}" && -d "${runs_root}/${session}" ]]; then
|
|
413
|
+
target_dir="${runs_root}/${session}"
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
[[ -n "${target_dir}" && -d "${target_dir}" ]] || return 0
|
|
417
|
+
|
|
418
|
+
cleanup_error_line="$(printf '%s' "${cleanup_error}" | tr '\n' ' ' | sed 's/ */ /g')"
|
|
419
|
+
recorded_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
420
|
+
notice_file="${target_dir}/cleanup-warning.txt"
|
|
421
|
+
{
|
|
422
|
+
printf 'recorded_at=%s\n' "${recorded_at}"
|
|
423
|
+
printf 'session=%s\n' "${session}"
|
|
424
|
+
printf 'mode=%s\n' "${mode}"
|
|
425
|
+
printf 'worktree=%s\n' "${worktree_path}"
|
|
426
|
+
printf 'branch=%s\n' "${branch_name}"
|
|
427
|
+
printf 'cleanup_mode=%s\n' "${cleanup_mode}"
|
|
428
|
+
printf 'cleanup_status=%s\n' "${cleanup_status}"
|
|
429
|
+
if [[ -n "${cleanup_error_line}" ]]; then
|
|
430
|
+
printf 'cleanup_error=%s\n' "${cleanup_error_line}"
|
|
431
|
+
fi
|
|
432
|
+
} >"${notice_file}"
|
|
433
|
+
}
|
|
434
|
+
|
|
393
435
|
if [[ "$active_tmux_session" == "true" ]]; then
|
|
394
436
|
cleanup_mode="deferred-active-session"
|
|
395
437
|
elif [[ "$skip_worktree_cleanup" != "true" && -n "${worktree_path}" ]] \
|
|
@@ -413,7 +455,7 @@ elif [[ "$skip_worktree_cleanup" != "true" && -n "$branch_name" ]]; then
|
|
|
413
455
|
fi
|
|
414
456
|
fi
|
|
415
457
|
fi
|
|
416
|
-
elif [[ "$skip_worktree_cleanup" != "true" && -n "$worktree_path" ]] &&
|
|
458
|
+
elif [[ "$skip_worktree_cleanup" != "true" && -n "$worktree_path" ]] && worktree_path_is_registered "$worktree_path"; then
|
|
417
459
|
git -C "$repo_root" worktree remove "$worktree_path" --force || true
|
|
418
460
|
git -C "$repo_root" worktree prune
|
|
419
461
|
cleanup_mode="worktree"
|
|
@@ -433,16 +475,14 @@ if [[ -n "$session" && "$active_tmux_session" != "true" ]]; then
|
|
|
433
475
|
--session "$session" \
|
|
434
476
|
--remove-file "${remove_file:-}"
|
|
435
477
|
)"
|
|
478
|
+
archived_dir="$(awk -F= '/^ARCHIVED_DIR=/{print substr($0, index($0, "=") + 1); exit}' <<<"${archive_output}")"
|
|
436
479
|
fi
|
|
437
480
|
|
|
438
481
|
if [[ "$skip_worktree_cleanup" != "true" && -n "$worktree_path" && ! -d "$worktree_path" ]]; then
|
|
439
482
|
clear_resident_worktree_realpath_references "$worktree_path"
|
|
440
483
|
fi
|
|
441
484
|
|
|
442
|
-
|
|
443
|
-
[[ -n "$cleanup_error" ]] && printf '%s\n' "$cleanup_error" >&2
|
|
444
|
-
exit "$cleanup_status"
|
|
445
|
-
fi
|
|
485
|
+
write_cleanup_warning_artifact
|
|
446
486
|
|
|
447
487
|
printf 'SESSION=%s\n' "$session"
|
|
448
488
|
printf 'MODE=%s\n' "$mode"
|
|
@@ -467,3 +507,7 @@ fi
|
|
|
467
507
|
if [[ "$cleanup_status" != "0" && -n "$cleanup_error" ]]; then
|
|
468
508
|
printf 'CLEANUP_ERROR=%s\n' "$(printf '%s' "$cleanup_error" | tr '\n' ' ' | sed 's/ */ /g')"
|
|
469
509
|
fi
|
|
510
|
+
|
|
511
|
+
if [[ "$cleanup_status" != "0" ]]; then
|
|
512
|
+
exit "$cleanup_status"
|
|
513
|
+
fi
|