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.
Files changed (59) hide show
  1. package/README.md +69 -19
  2. package/assets/workflow-catalog.json +1 -1
  3. package/bin/pr-risk.sh +22 -7
  4. package/bin/sync-pr-labels.sh +1 -1
  5. package/hooks/heartbeat-hooks.sh +125 -12
  6. package/hooks/issue-reconcile-hooks.sh +1 -1
  7. package/hooks/pr-reconcile-hooks.sh +1 -1
  8. package/npm/bin/agent-control-plane.js +296 -61
  9. package/package.json +11 -7
  10. package/tools/bin/agent-github-update-labels +36 -2
  11. package/tools/bin/agent-project-catch-up-merged-prs +4 -2
  12. package/tools/bin/agent-project-cleanup-session +49 -5
  13. package/tools/bin/agent-project-heartbeat-loop +119 -1471
  14. package/tools/bin/agent-project-publish-issue-pr +6 -3
  15. package/tools/bin/agent-project-reconcile-issue-session +78 -106
  16. package/tools/bin/agent-project-reconcile-pr-session +166 -143
  17. package/tools/bin/agent-project-retry-state +18 -7
  18. package/tools/bin/agent-project-run-claude-session +10 -0
  19. package/tools/bin/agent-project-run-codex-resilient +99 -14
  20. package/tools/bin/agent-project-run-codex-session +16 -5
  21. package/tools/bin/agent-project-run-kilo-session +10 -0
  22. package/tools/bin/agent-project-run-openclaw-session +10 -0
  23. package/tools/bin/agent-project-run-opencode-session +10 -0
  24. package/tools/bin/agent-project-sync-source-repo-main +163 -0
  25. package/tools/bin/agent-project-worker-status +10 -7
  26. package/tools/bin/cleanup-worktree.sh +6 -1
  27. package/tools/bin/flow-config-lib.sh +1257 -34
  28. package/tools/bin/flow-resident-worker-lib.sh +119 -1
  29. package/tools/bin/flow-shell-lib.sh +56 -0
  30. package/tools/bin/github-core-rate-limit-state.sh +77 -0
  31. package/tools/bin/github-write-outbox.sh +470 -0
  32. package/tools/bin/heartbeat-loop-cache-lib.sh +164 -0
  33. package/tools/bin/heartbeat-loop-counting-lib.sh +306 -0
  34. package/tools/bin/heartbeat-loop-pr-strategy-lib.sh +199 -0
  35. package/tools/bin/heartbeat-loop-scheduling-lib.sh +506 -0
  36. package/tools/bin/heartbeat-loop-worker-lib.sh +319 -0
  37. package/tools/bin/heartbeat-recovery-preflight.sh +12 -1
  38. package/tools/bin/heartbeat-safe-auto.sh +56 -3
  39. package/tools/bin/install-project-launchd.sh +17 -2
  40. package/tools/bin/project-init.sh +21 -1
  41. package/tools/bin/project-launchd-bootstrap.sh +16 -9
  42. package/tools/bin/project-runtimectl.sh +46 -2
  43. package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
  44. package/tools/bin/resident-issue-controller-lib.sh +448 -0
  45. package/tools/bin/scaffold-profile.sh +61 -3
  46. package/tools/bin/start-pr-fix-worker.sh +47 -10
  47. package/tools/bin/start-resident-issue-loop.sh +28 -439
  48. package/tools/dashboard/app.js +37 -1
  49. package/tools/dashboard/dashboard_snapshot.py +65 -26
  50. package/tools/templates/pr-fix-template.md +3 -1
  51. package/tools/templates/pr-merge-repair-template.md +2 -1
  52. package/SKILL.md +0 -149
  53. package/references/architecture.md +0 -217
  54. package/references/commands.md +0 -128
  55. package/references/control-plane-map.md +0 -124
  56. package/references/docs-map.md +0 -73
  57. package/references/release-checklist.md +0 -65
  58. package/references/repo-map.md +0 -36
  59. 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.2.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
- "!tools/bin/audit-*.sh",
34
- "!tools/bin/check-skill-contracts.sh",
35
- "!tools/bin/render-dashboard-snapshot.py",
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": "bash tools/tests/test-agent-control-plane-npm-cli.sh && bash tools/tests/test-agent-project-detached-launch-stable-cwd.sh && bash tools/tests/test-agent-project-claude-session-wrapper-reaps-child-on-term.sh && bash tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh && bash tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh && bash tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh && bash tools/tests/test-issue-reconcile-hooks-kick-scheduler-uses-profile.sh && bash tools/tests/test-profile-adopt-skip-anchor-sync-creates-agent-repo-root.sh && bash tools/tests/test-vendored-codex-quota-claude-oauth-only.sh && bash tools/tests/test-package-smoke-command.sh"
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
- merged_ledger_dir="${state_root}/merged-pr-catchup"
58
- closed_ledger_dir="${state_root}/closed-pr-catchup"
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" ]] && git -C "$repo_root" worktree list --porcelain | rg -F -q "worktree $worktree_path"; then
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
- if [[ "$cleanup_status" != "0" && -z "$session" ]]; then
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