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.
Files changed (33) hide show
  1. package/npm/bin/agent-control-plane.js +39 -2
  2. package/package.json +6 -3
  3. package/tools/bin/agent-project-catch-up-merged-prs +1 -0
  4. package/tools/bin/agent-project-cleanup-session +49 -5
  5. package/tools/bin/agent-project-heartbeat-loop +119 -1471
  6. package/tools/bin/agent-project-reconcile-issue-session +66 -105
  7. package/tools/bin/agent-project-reconcile-pr-session +76 -111
  8. package/tools/bin/agent-project-run-claude-session +10 -0
  9. package/tools/bin/agent-project-run-codex-resilient +86 -9
  10. package/tools/bin/agent-project-run-codex-session +16 -5
  11. package/tools/bin/agent-project-run-kilo-session +10 -0
  12. package/tools/bin/agent-project-run-openclaw-session +10 -0
  13. package/tools/bin/agent-project-run-opencode-session +10 -0
  14. package/tools/bin/agent-project-worker-status +10 -7
  15. package/tools/bin/cleanup-worktree.sh +6 -1
  16. package/tools/bin/flow-config-lib.sh +80 -0
  17. package/tools/bin/flow-resident-worker-lib.sh +119 -1
  18. package/tools/bin/flow-shell-lib.sh +24 -0
  19. package/tools/bin/heartbeat-loop-cache-lib.sh +164 -0
  20. package/tools/bin/heartbeat-loop-counting-lib.sh +306 -0
  21. package/tools/bin/heartbeat-loop-pr-strategy-lib.sh +199 -0
  22. package/tools/bin/heartbeat-loop-scheduling-lib.sh +506 -0
  23. package/tools/bin/heartbeat-loop-worker-lib.sh +319 -0
  24. package/tools/bin/heartbeat-recovery-preflight.sh +12 -1
  25. package/tools/bin/heartbeat-safe-auto.sh +14 -3
  26. package/tools/bin/project-launchd-bootstrap.sh +11 -8
  27. package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
  28. package/tools/bin/resident-issue-controller-lib.sh +448 -0
  29. package/tools/bin/resident-issue-queue-status.py +35 -0
  30. package/tools/bin/start-resident-issue-loop.sh +26 -437
  31. package/tools/dashboard/app.js +7 -0
  32. package/tools/dashboard/dashboard_snapshot.py +13 -29
  33. package/SKILL.md +0 -149
@@ -774,6 +774,9 @@ function detectPackageManager() {
774
774
  if (commandExists("brew")) {
775
775
  return { name: "brew" };
776
776
  }
777
+ if (commandExists("apt")) {
778
+ return { name: "apt" };
779
+ }
777
780
  if (commandExists("apt-get")) {
778
781
  return { name: "apt-get" };
779
782
  }
@@ -789,6 +792,9 @@ function detectPackageManager() {
789
792
  if (commandExists("zypper")) {
790
793
  return { name: "zypper" };
791
794
  }
795
+ if (commandExists("apk")) {
796
+ return { name: "apk" };
797
+ }
792
798
  return null;
793
799
  }
794
800
 
@@ -812,6 +818,24 @@ function dependencyPackageMap(managerName) {
812
818
  python3: "python3",
813
819
  tmux: "tmux"
814
820
  };
821
+ case "apt":
822
+ return {
823
+ bash: "bash",
824
+ git: "git",
825
+ gh: "gh",
826
+ jq: "jq",
827
+ python3: "python3",
828
+ tmux: "tmux"
829
+ };
830
+ case "apk":
831
+ return {
832
+ bash: "bash",
833
+ git: "git",
834
+ gh: "gh",
835
+ jq: "jq",
836
+ python3: "python3",
837
+ tmux: "tmux"
838
+ };
815
839
  case "dnf":
816
840
  case "yum":
817
841
  return {
@@ -870,6 +894,10 @@ function buildDependencyInstallPlan(missingTools) {
870
894
  commands.push([...prefix, "apt-get", "update"]);
871
895
  commands.push([...prefix, "apt-get", "install", "-y", ...packages]);
872
896
  break;
897
+ case "apt":
898
+ commands.push([...prefix, "apt", "update"]);
899
+ commands.push([...prefix, "apt", "install", "-y", ...packages]);
900
+ break;
873
901
  case "dnf":
874
902
  commands.push([...prefix, "dnf", "install", "-y", ...packages]);
875
903
  break;
@@ -882,6 +910,9 @@ function buildDependencyInstallPlan(missingTools) {
882
910
  case "zypper":
883
911
  commands.push([...prefix, "zypper", "install", "-y", ...packages]);
884
912
  break;
913
+ case "apk":
914
+ commands.push([...prefix, "apk", "add", "--no-cache", ...packages]);
915
+ break;
885
916
  default:
886
917
  return null;
887
918
  }
@@ -923,7 +954,7 @@ async function maybeInstallMissingDependencies(options, prereq) {
923
954
  if (!plan) {
924
955
  console.log("\nACP found missing core dependencies but cannot install them automatically on this machine.");
925
956
  console.log(`- missing tools: ${prereq.missingRequired.join(", ")}`);
926
- console.log("- supported auto-install package managers today: brew, apt-get, dnf, yum, pacman, zypper");
957
+ console.log("- supported auto-install package managers today: brew, apt, apt-get, dnf, yum, pacman, zypper, apk");
927
958
  return {
928
959
  status: "unavailable",
929
960
  reason: "no-supported-package-manager",
@@ -1827,10 +1858,16 @@ async function collectSetupConfig(options, context) {
1827
1858
  let profileId = suggestedProfileId;
1828
1859
  let codingWorker = suggestedWorker;
1829
1860
 
1830
- if (!fs.existsSync(detectedRepoRoot)) {
1861
+ const detectedRepoRootExists = fs.existsSync(detectedRepoRoot);
1862
+ if (!detectedRepoRootExists && !options.allowMissingRepo) {
1831
1863
  throw new Error(`setup repo root does not exist: ${detectedRepoRoot}`);
1832
1864
  }
1833
1865
 
1866
+ if (options.allowMissingRepo && !detectedRepoRootExists) {
1867
+ console.log(`\nSource repo root not found yet: ${detectedRepoRoot}`);
1868
+ console.log("- continuing because --allow-missing-repo was set; profile adopt will run in missing-repo mode.");
1869
+ }
1870
+
1834
1871
  if (!options.interactive) {
1835
1872
  if (!repoSlug) {
1836
1873
  throw new Error("setup could not detect --repo-slug automatically; pass --repo-slug <owner/repo> or run interactively inside a git checkout with origin set");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-control-plane",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",
@@ -48,7 +47,11 @@
48
47
  "scripts": {
49
48
  "doctor": "node ./npm/bin/agent-control-plane.js doctor",
50
49
  "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"
50
+ "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-run-codex-resilient-uses-path-python-and-gnu-stat.sh && bash tools/tests/test-heartbeat-safe-auto-uses-path-python.sh && bash tools/tests/test-heartbeat-safe-auto-skips-self-sync.sh && bash tools/tests/test-agent-project-catch-up-terminal-prs-defaults-closed-hook.sh && bash tools/tests/test-agent-project-codex-session-wrapper-prefers-path-codex.sh && bash tools/tests/test-agent-project-codex-session-wrapper-recovers-var-tmp-logged-artifacts.sh && bash tools/tests/test-agent-project-cleanup-session-removes-registered-worktree-without-rg.sh && bash tools/tests/test-agent-project-cleanup-session-propagates-failure-with-session.sh && bash tools/tests/test-cleanup-worktree-syncs-workspace-after-cleanup-failure.sh && bash tools/tests/test-resident-issue-queue-status-contract.sh && bash tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh && bash tools/tests/test-agent-project-reconcile-issue-session-warns-on-cleanup-failure.sh && bash tools/tests/test-agent-project-reconcile-pr-session-warns-on-cleanup-failure.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
+ },
52
+ "publishConfig": {
53
+ "access": "public",
54
+ "provenance": true
52
55
  },
53
56
  "keywords": [
54
57
  "agents",
@@ -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
@@ -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