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.
- package/npm/bin/agent-control-plane.js +39 -2
- package/package.json +6 -3
- package/tools/bin/agent-project-catch-up-merged-prs +1 -0
- package/tools/bin/agent-project-cleanup-session +49 -5
- package/tools/bin/agent-project-heartbeat-loop +119 -1471
- package/tools/bin/agent-project-reconcile-issue-session +66 -105
- package/tools/bin/agent-project-reconcile-pr-session +76 -111
- package/tools/bin/agent-project-run-claude-session +10 -0
- package/tools/bin/agent-project-run-codex-resilient +86 -9
- 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-worker-status +10 -7
- package/tools/bin/cleanup-worktree.sh +6 -1
- package/tools/bin/flow-config-lib.sh +80 -0
- package/tools/bin/flow-resident-worker-lib.sh +119 -1
- package/tools/bin/flow-shell-lib.sh +24 -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 +14 -3
- package/tools/bin/project-launchd-bootstrap.sh +11 -8
- package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
- package/tools/bin/resident-issue-controller-lib.sh +448 -0
- package/tools/bin/resident-issue-queue-status.py +35 -0
- package/tools/bin/start-resident-issue-loop.sh +26 -437
- package/tools/dashboard/app.js +7 -0
- package/tools/dashboard/dashboard_snapshot.py +13 -29
- 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
|
-
|
|
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.
|
|
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",
|
|
@@ -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
|