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
|
@@ -186,9 +186,12 @@ fi
|
|
|
186
186
|
resolve_actor_login() {
|
|
187
187
|
local login=""
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
login
|
|
191
|
-
|
|
189
|
+
login="$(flow_github_current_login 2>/dev/null || true)"
|
|
190
|
+
if [[ -z "${login}" && ! "$(flow_forge_provider)" =~ ^gitea$ ]]; then
|
|
191
|
+
flow_export_github_cli_auth_env "${repo_slug}"
|
|
192
|
+
login="$(gh api user --jq .login 2>/dev/null || true)"
|
|
193
|
+
fi
|
|
194
|
+
if [[ -z "${login}" && ! "$(flow_forge_provider)" =~ ^gitea$ ]]; then
|
|
192
195
|
login="$(
|
|
193
196
|
gh auth status 2>/dev/null \
|
|
194
197
|
| sed -n 's/^ ✓ Logged in to github.com account \([^ ]*\) (.*/\1/p' \
|
|
@@ -2,82 +2,33 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"${
|
|
13
|
-
|
|
14
|
-
"${F_LOSNING_FLOW_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
15
|
-
"${AGENT_FLOW_SKILL_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
16
|
-
"${SHARED_AGENT_HOME:-}/tools/bin/flow-shell-lib.sh" \
|
|
17
|
-
"$(pwd)/tools/bin/flow-shell-lib.sh"; do
|
|
18
|
-
if [[ -n "${candidate}" && -f "${candidate}" ]]; then
|
|
19
|
-
printf '%s\n' "${candidate}"
|
|
20
|
-
return 0
|
|
21
|
-
fi
|
|
22
|
-
done
|
|
23
|
-
|
|
24
|
-
if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
|
|
25
|
-
for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
|
|
26
|
-
[[ -n "${skill_name}" ]] || continue
|
|
27
|
-
candidate="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}/tools/bin/flow-shell-lib.sh"
|
|
28
|
-
if [[ -f "${candidate}" ]]; then
|
|
29
|
-
printf '%s\n' "${candidate}"
|
|
30
|
-
return 0
|
|
31
|
-
fi
|
|
32
|
-
done
|
|
5
|
+
RECONCILE_BOOTSTRAP_LIB=""
|
|
6
|
+
for _rbl_candidate in \
|
|
7
|
+
"${SCRIPT_DIR}/reconcile-bootstrap-lib.sh" \
|
|
8
|
+
"${AGENT_CONTROL_PLANE_ROOT:-}/tools/bin/reconcile-bootstrap-lib.sh" \
|
|
9
|
+
"${ACP_ROOT:-}/tools/bin/reconcile-bootstrap-lib.sh" \
|
|
10
|
+
"${SHARED_AGENT_HOME:-}/tools/bin/reconcile-bootstrap-lib.sh"; do
|
|
11
|
+
if [[ -n "${_rbl_candidate}" && -f "${_rbl_candidate}" ]]; then
|
|
12
|
+
RECONCILE_BOOTSTRAP_LIB="${_rbl_candidate}"
|
|
13
|
+
break
|
|
33
14
|
fi
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
source "${FLOW_SHELL_LIB_PATH}"
|
|
43
|
-
|
|
44
|
-
resolve_reconcile_tools_dir() {
|
|
45
|
-
local candidate_root=""
|
|
46
|
-
local skill_name=""
|
|
47
|
-
|
|
48
|
-
for candidate_root in \
|
|
49
|
-
"${AGENT_CONTROL_PLANE_ROOT:-}" \
|
|
50
|
-
"${ACP_ROOT:-}" \
|
|
51
|
-
"${F_LOSNING_FLOW_ROOT:-}" \
|
|
52
|
-
"${AGENT_FLOW_SKILL_ROOT:-}"; do
|
|
53
|
-
if [[ -n "${candidate_root}" && -d "${candidate_root}/tools/bin" ]]; then
|
|
54
|
-
printf '%s/tools/bin\n' "${candidate_root}"
|
|
55
|
-
return 0
|
|
15
|
+
done
|
|
16
|
+
if [[ -n "${SHARED_AGENT_HOME:-}" && -z "${RECONCILE_BOOTSTRAP_LIB}" ]]; then
|
|
17
|
+
for _rbl_skill in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
|
|
18
|
+
[[ -n "${_rbl_skill}" ]] || continue
|
|
19
|
+
_rbl_candidate="${SHARED_AGENT_HOME}/skills/openclaw/${_rbl_skill}/tools/bin/reconcile-bootstrap-lib.sh"
|
|
20
|
+
if [[ -f "${_rbl_candidate}" ]]; then
|
|
21
|
+
RECONCILE_BOOTSTRAP_LIB="${_rbl_candidate}"
|
|
22
|
+
break
|
|
56
23
|
fi
|
|
57
24
|
done
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
[[ -n "${skill_name}" ]] || continue
|
|
66
|
-
candidate_root="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}"
|
|
67
|
-
if [[ -d "${candidate_root}/tools/bin" ]]; then
|
|
68
|
-
printf '%s/tools/bin\n' "${candidate_root}"
|
|
69
|
-
return 0
|
|
70
|
-
fi
|
|
71
|
-
done
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
if [[ -d "${SCRIPT_DIR}" ]]; then
|
|
75
|
-
printf '%s\n' "${SCRIPT_DIR}"
|
|
76
|
-
return 0
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
printf '%s\n' "${BOOTSTRAP_TOOLS_DIR}"
|
|
80
|
-
}
|
|
25
|
+
fi
|
|
26
|
+
if [[ -z "${RECONCILE_BOOTSTRAP_LIB}" ]]; then
|
|
27
|
+
echo "unable to locate reconcile-bootstrap-lib.sh" >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
# shellcheck source=/dev/null
|
|
31
|
+
source "${RECONCILE_BOOTSTRAP_LIB}"
|
|
81
32
|
|
|
82
33
|
usage() {
|
|
83
34
|
cat <<'EOF'
|
|
@@ -89,30 +40,8 @@ allowing project adapters to inject policy hooks.
|
|
|
89
40
|
EOF
|
|
90
41
|
}
|
|
91
42
|
|
|
92
|
-
shared_tools_dir="$(resolve_reconcile_tools_dir)"
|
|
93
|
-
resolve_reconcile_helper_path() {
|
|
94
|
-
local helper_name="${1:?helper name required}"
|
|
95
|
-
local candidate=""
|
|
96
|
-
|
|
97
|
-
for candidate in \
|
|
98
|
-
"${SCRIPT_DIR}/${helper_name}" \
|
|
99
|
-
"${BOOTSTRAP_TOOLS_DIR}/${helper_name}" \
|
|
100
|
-
"${shared_tools_dir}/${helper_name}"; do
|
|
101
|
-
if [[ -n "${candidate}" && -f "${candidate}" ]]; then
|
|
102
|
-
printf '%s\n' "${candidate}"
|
|
103
|
-
return 0
|
|
104
|
-
fi
|
|
105
|
-
done
|
|
106
|
-
|
|
107
|
-
echo "unable to locate ${helper_name} for reconcile bootstrap" >&2
|
|
108
|
-
return 1
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
FLOW_CONFIG_LIB_PATH="$(resolve_reconcile_helper_path "flow-config-lib.sh")"
|
|
112
43
|
FLOW_RESIDENT_WORKER_LIB_PATH="$(resolve_reconcile_helper_path "flow-resident-worker-lib.sh")"
|
|
113
44
|
# shellcheck source=/dev/null
|
|
114
|
-
source "${FLOW_CONFIG_LIB_PATH}"
|
|
115
|
-
# shellcheck source=/dev/null
|
|
116
45
|
source "${FLOW_RESIDENT_WORKER_LIB_PATH}"
|
|
117
46
|
session=""
|
|
118
47
|
repo_slug=""
|
|
@@ -302,6 +231,7 @@ if [[ -n "$hook_file" && -f "$hook_file" ]]; then
|
|
|
302
231
|
fi
|
|
303
232
|
|
|
304
233
|
provider_cooldown_script="${shared_tools_dir}/provider-cooldown-state.sh"
|
|
234
|
+
github_write_outbox_script="${shared_tools_dir}/github-write-outbox.sh"
|
|
305
235
|
|
|
306
236
|
schedule_provider_quota_cooldown() {
|
|
307
237
|
local reason="${1:-provider-quota-limit}"
|
|
@@ -406,7 +336,17 @@ post_issue_comment_if_present() {
|
|
|
406
336
|
if issue_latest_comment_matches_artifact; then
|
|
407
337
|
return 0
|
|
408
338
|
fi
|
|
409
|
-
flow_github_api_repo "${repo_slug}" "issues/${issue_id}/comments" --method POST -f body="$(cat "$comment_file")" >/dev/null
|
|
339
|
+
if flow_github_api_repo "${repo_slug}" "issues/${issue_id}/comments" --method POST -f body="$(cat "$comment_file")" >/dev/null 2>&1; then
|
|
340
|
+
return 0
|
|
341
|
+
fi
|
|
342
|
+
if [[ -x "${github_write_outbox_script}" ]]; then
|
|
343
|
+
"${github_write_outbox_script}" enqueue-comment \
|
|
344
|
+
--repo-slug "${repo_slug}" \
|
|
345
|
+
--number "${issue_id}" \
|
|
346
|
+
--kind issue \
|
|
347
|
+
--body-file "${comment_file}" >/dev/null 2>&1 || true
|
|
348
|
+
fi
|
|
349
|
+
return 0
|
|
410
350
|
}
|
|
411
351
|
|
|
412
352
|
issue_latest_comment_matches_artifact() {
|
|
@@ -1053,15 +993,6 @@ extract_recovery_worktree_from_publish_output() {
|
|
|
1053
993
|
awk -F= '/^RECOVERY_WORKTREE=/{print $2}' <<<"$publish_out" | tail -n 1
|
|
1054
994
|
}
|
|
1055
995
|
|
|
1056
|
-
require_transition() {
|
|
1057
|
-
local step="${1:?step required}"
|
|
1058
|
-
shift
|
|
1059
|
-
if ! "$@"; then
|
|
1060
|
-
echo "reconcile transition failed: ${step}" >&2
|
|
1061
|
-
exit 1
|
|
1062
|
-
fi
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
996
|
mark_reconciled() {
|
|
1066
997
|
local reconciled_at tmp_file
|
|
1067
998
|
if [[ -d "$run_dir" ]]; then
|
|
@@ -1141,6 +1072,40 @@ update_resident_issue_metadata() {
|
|
|
1141
1072
|
"LAST_WORKTREE_REUSED=${last_worktree_reused:-no}"
|
|
1142
1073
|
}
|
|
1143
1074
|
|
|
1075
|
+
cleanup_output_value() {
|
|
1076
|
+
local cleanup_output="${1:-}"
|
|
1077
|
+
local key="${2:?key required}"
|
|
1078
|
+
awk -F= -v target_key="${key}" '$1 == target_key { print substr($0, index($0, "=") + 1); exit }' <<<"${cleanup_output}"
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
warn_cleanup_issue_session() {
|
|
1082
|
+
local cleanup_output="${1:-}"
|
|
1083
|
+
local cleanup_exit="${2:-0}"
|
|
1084
|
+
local cleanup_status=""
|
|
1085
|
+
local cleanup_mode=""
|
|
1086
|
+
local cleanup_error=""
|
|
1087
|
+
|
|
1088
|
+
cleanup_status="$(cleanup_output_value "${cleanup_output}" "CLEANUP_STATUS")"
|
|
1089
|
+
if [[ -z "${cleanup_status}" ]]; then
|
|
1090
|
+
cleanup_status="${cleanup_exit}"
|
|
1091
|
+
fi
|
|
1092
|
+
[[ "${cleanup_status}" != "0" ]] || return 0
|
|
1093
|
+
|
|
1094
|
+
cleanup_mode="$(cleanup_output_value "${cleanup_output}" "CLEANUP_MODE")"
|
|
1095
|
+
cleanup_error="$(cleanup_output_value "${cleanup_output}" "CLEANUP_ERROR")"
|
|
1096
|
+
printf '[%s] issue cleanup warning session=%s status=%s mode=%s\n' \
|
|
1097
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
1098
|
+
"${session}" \
|
|
1099
|
+
"${cleanup_status}" \
|
|
1100
|
+
"${cleanup_mode:-unknown}" >&2
|
|
1101
|
+
if [[ -n "${cleanup_error}" ]]; then
|
|
1102
|
+
printf '[%s] issue cleanup detail session=%s %s\n' \
|
|
1103
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
1104
|
+
"${session}" \
|
|
1105
|
+
"${cleanup_error}" >&2
|
|
1106
|
+
fi
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1144
1109
|
cleanup_issue_session() {
|
|
1145
1110
|
local -a cleanup_args=(
|
|
1146
1111
|
--repo-root "$repo_root"
|
|
@@ -1156,7 +1121,14 @@ cleanup_issue_session() {
|
|
|
1156
1121
|
cleanup_args+=(--skip-worktree-cleanup)
|
|
1157
1122
|
fi
|
|
1158
1123
|
|
|
1159
|
-
|
|
1124
|
+
local cleanup_output=""
|
|
1125
|
+
local cleanup_exit="0"
|
|
1126
|
+
if cleanup_output="$("${shared_tools_dir}/agent-project-cleanup-session" "${cleanup_args[@]}" 2>&1)"; then
|
|
1127
|
+
cleanup_exit="0"
|
|
1128
|
+
else
|
|
1129
|
+
cleanup_exit="$?"
|
|
1130
|
+
fi
|
|
1131
|
+
warn_cleanup_issue_session "${cleanup_output}" "${cleanup_exit}"
|
|
1160
1132
|
}
|
|
1161
1133
|
|
|
1162
1134
|
notify_issue_reconciled() {
|
|
@@ -2,82 +2,33 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"${
|
|
13
|
-
|
|
14
|
-
"${F_LOSNING_FLOW_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
15
|
-
"${AGENT_FLOW_SKILL_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
16
|
-
"${SHARED_AGENT_HOME:-}/tools/bin/flow-shell-lib.sh" \
|
|
17
|
-
"$(pwd)/tools/bin/flow-shell-lib.sh"; do
|
|
18
|
-
if [[ -n "${candidate}" && -f "${candidate}" ]]; then
|
|
19
|
-
printf '%s\n' "${candidate}"
|
|
20
|
-
return 0
|
|
21
|
-
fi
|
|
22
|
-
done
|
|
23
|
-
|
|
24
|
-
if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
|
|
25
|
-
for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
|
|
26
|
-
[[ -n "${skill_name}" ]] || continue
|
|
27
|
-
candidate="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}/tools/bin/flow-shell-lib.sh"
|
|
28
|
-
if [[ -f "${candidate}" ]]; then
|
|
29
|
-
printf '%s\n' "${candidate}"
|
|
30
|
-
return 0
|
|
31
|
-
fi
|
|
32
|
-
done
|
|
5
|
+
RECONCILE_BOOTSTRAP_LIB=""
|
|
6
|
+
for _rbl_candidate in \
|
|
7
|
+
"${SCRIPT_DIR}/reconcile-bootstrap-lib.sh" \
|
|
8
|
+
"${AGENT_CONTROL_PLANE_ROOT:-}/tools/bin/reconcile-bootstrap-lib.sh" \
|
|
9
|
+
"${ACP_ROOT:-}/tools/bin/reconcile-bootstrap-lib.sh" \
|
|
10
|
+
"${SHARED_AGENT_HOME:-}/tools/bin/reconcile-bootstrap-lib.sh"; do
|
|
11
|
+
if [[ -n "${_rbl_candidate}" && -f "${_rbl_candidate}" ]]; then
|
|
12
|
+
RECONCILE_BOOTSTRAP_LIB="${_rbl_candidate}"
|
|
13
|
+
break
|
|
33
14
|
fi
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
source "${FLOW_SHELL_LIB_PATH}"
|
|
43
|
-
|
|
44
|
-
resolve_reconcile_tools_dir() {
|
|
45
|
-
local candidate_root=""
|
|
46
|
-
local skill_name=""
|
|
47
|
-
|
|
48
|
-
for candidate_root in \
|
|
49
|
-
"${AGENT_CONTROL_PLANE_ROOT:-}" \
|
|
50
|
-
"${ACP_ROOT:-}" \
|
|
51
|
-
"${F_LOSNING_FLOW_ROOT:-}" \
|
|
52
|
-
"${AGENT_FLOW_SKILL_ROOT:-}"; do
|
|
53
|
-
if [[ -n "${candidate_root}" && -d "${candidate_root}/tools/bin" ]]; then
|
|
54
|
-
printf '%s/tools/bin\n' "${candidate_root}"
|
|
55
|
-
return 0
|
|
15
|
+
done
|
|
16
|
+
if [[ -n "${SHARED_AGENT_HOME:-}" && -z "${RECONCILE_BOOTSTRAP_LIB}" ]]; then
|
|
17
|
+
for _rbl_skill in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
|
|
18
|
+
[[ -n "${_rbl_skill}" ]] || continue
|
|
19
|
+
_rbl_candidate="${SHARED_AGENT_HOME}/skills/openclaw/${_rbl_skill}/tools/bin/reconcile-bootstrap-lib.sh"
|
|
20
|
+
if [[ -f "${_rbl_candidate}" ]]; then
|
|
21
|
+
RECONCILE_BOOTSTRAP_LIB="${_rbl_candidate}"
|
|
22
|
+
break
|
|
56
23
|
fi
|
|
57
24
|
done
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
[[ -n "${skill_name}" ]] || continue
|
|
66
|
-
candidate_root="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}"
|
|
67
|
-
if [[ -d "${candidate_root}/tools/bin" ]]; then
|
|
68
|
-
printf '%s/tools/bin\n' "${candidate_root}"
|
|
69
|
-
return 0
|
|
70
|
-
fi
|
|
71
|
-
done
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
if [[ -d "${SCRIPT_DIR}" ]]; then
|
|
75
|
-
printf '%s\n' "${SCRIPT_DIR}"
|
|
76
|
-
return 0
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
printf '%s\n' "${BOOTSTRAP_TOOLS_DIR}"
|
|
80
|
-
}
|
|
25
|
+
fi
|
|
26
|
+
if [[ -z "${RECONCILE_BOOTSTRAP_LIB}" ]]; then
|
|
27
|
+
echo "unable to locate reconcile-bootstrap-lib.sh" >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
# shellcheck source=/dev/null
|
|
31
|
+
source "${RECONCILE_BOOTSTRAP_LIB}"
|
|
81
32
|
|
|
82
33
|
usage() {
|
|
83
34
|
cat <<'EOF'
|
|
@@ -89,28 +40,6 @@ allowing project adapters to inject policy hooks.
|
|
|
89
40
|
EOF
|
|
90
41
|
}
|
|
91
42
|
|
|
92
|
-
shared_tools_dir="$(resolve_reconcile_tools_dir)"
|
|
93
|
-
resolve_reconcile_helper_path() {
|
|
94
|
-
local helper_name="${1:?helper name required}"
|
|
95
|
-
local candidate=""
|
|
96
|
-
|
|
97
|
-
for candidate in \
|
|
98
|
-
"${SCRIPT_DIR}/${helper_name}" \
|
|
99
|
-
"${BOOTSTRAP_TOOLS_DIR}/${helper_name}" \
|
|
100
|
-
"${shared_tools_dir}/${helper_name}"; do
|
|
101
|
-
if [[ -n "${candidate}" && -f "${candidate}" ]]; then
|
|
102
|
-
printf '%s\n' "${candidate}"
|
|
103
|
-
return 0
|
|
104
|
-
fi
|
|
105
|
-
done
|
|
106
|
-
|
|
107
|
-
echo "unable to locate ${helper_name} for reconcile bootstrap" >&2
|
|
108
|
-
return 1
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
FLOW_CONFIG_LIB_PATH="$(resolve_reconcile_helper_path "flow-config-lib.sh")"
|
|
112
|
-
# shellcheck source=/dev/null
|
|
113
|
-
source "${FLOW_CONFIG_LIB_PATH}"
|
|
114
43
|
verification_guard_script="${shared_tools_dir}/branch-verification-guard.sh"
|
|
115
44
|
session=""
|
|
116
45
|
repo_slug=""
|
|
@@ -222,6 +151,9 @@ if [[ -n "$hook_file" && -f "$hook_file" ]]; then
|
|
|
222
151
|
fi
|
|
223
152
|
|
|
224
153
|
provider_cooldown_script="${shared_tools_dir}/provider-cooldown-state.sh"
|
|
154
|
+
github_core_rate_limit_script="${shared_tools_dir}/github-core-rate-limit-state.sh"
|
|
155
|
+
github_write_outbox_script="${shared_tools_dir}/github-write-outbox.sh"
|
|
156
|
+
automated_pr_approval_body="Automated final review passed. Safe low-risk scope, green checks, and host-side merge approved."
|
|
225
157
|
|
|
226
158
|
schedule_provider_quota_cooldown() {
|
|
227
159
|
local reason="${1:-provider-quota-limit}"
|
|
@@ -243,6 +175,26 @@ blocked_runtime_reason=""
|
|
|
243
175
|
host_github_rate_limited="no"
|
|
244
176
|
host_github_rate_limit_detail=""
|
|
245
177
|
|
|
178
|
+
host_github_rate_limit_state_active() {
|
|
179
|
+
local state_out=""
|
|
180
|
+
local ready=""
|
|
181
|
+
local next_attempt_at=""
|
|
182
|
+
|
|
183
|
+
[[ -x "${github_core_rate_limit_script}" ]] || return 1
|
|
184
|
+
state_out="$("${github_core_rate_limit_script}" get 2>/dev/null || true)"
|
|
185
|
+
ready="$(awk -F= '/^READY=/{print $2; exit}' <<<"${state_out}")"
|
|
186
|
+
[[ "${ready}" == "no" ]] || return 1
|
|
187
|
+
|
|
188
|
+
next_attempt_at="$(awk -F= '/^NEXT_ATTEMPT_AT=/{print $2; exit}' <<<"${state_out}")"
|
|
189
|
+
host_github_rate_limited="yes"
|
|
190
|
+
if [[ -n "${next_attempt_at}" ]]; then
|
|
191
|
+
host_github_rate_limit_detail="GitHub core API rate limit cooldown is active and resets at ${next_attempt_at}."
|
|
192
|
+
else
|
|
193
|
+
host_github_rate_limit_detail="GitHub core API rate limit cooldown is active."
|
|
194
|
+
fi
|
|
195
|
+
return 0
|
|
196
|
+
}
|
|
197
|
+
|
|
246
198
|
owner="${repo_slug%%/*}"
|
|
247
199
|
repo="${repo_slug#*/}"
|
|
248
200
|
pr_view_json="$(flow_github_pr_view_json "$repo_slug" "$pr_number")"
|
|
@@ -257,6 +209,40 @@ if [[ "$status" == "RUNNING" && "$pr_state" != "MERGED" && "$pr_state" != "CLOSE
|
|
|
257
209
|
exit 0
|
|
258
210
|
fi
|
|
259
211
|
|
|
212
|
+
cleanup_output_value() {
|
|
213
|
+
local cleanup_output="${1:-}"
|
|
214
|
+
local key="${2:?key required}"
|
|
215
|
+
awk -F= -v target_key="${key}" '$1 == target_key { print substr($0, index($0, "=") + 1); exit }' <<<"${cleanup_output}"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
warn_cleanup_pr_session() {
|
|
219
|
+
local cleanup_output="${1:-}"
|
|
220
|
+
local cleanup_exit="${2:-0}"
|
|
221
|
+
local cleanup_status=""
|
|
222
|
+
local cleanup_mode=""
|
|
223
|
+
local cleanup_error=""
|
|
224
|
+
|
|
225
|
+
cleanup_status="$(cleanup_output_value "${cleanup_output}" "CLEANUP_STATUS")"
|
|
226
|
+
if [[ -z "${cleanup_status}" ]]; then
|
|
227
|
+
cleanup_status="${cleanup_exit}"
|
|
228
|
+
fi
|
|
229
|
+
[[ "${cleanup_status}" != "0" ]] || return 0
|
|
230
|
+
|
|
231
|
+
cleanup_mode="$(cleanup_output_value "${cleanup_output}" "CLEANUP_MODE")"
|
|
232
|
+
cleanup_error="$(cleanup_output_value "${cleanup_output}" "CLEANUP_ERROR")"
|
|
233
|
+
printf '[%s] pr cleanup warning session=%s status=%s mode=%s\n' \
|
|
234
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
235
|
+
"${session}" \
|
|
236
|
+
"${cleanup_status}" \
|
|
237
|
+
"${cleanup_mode:-unknown}" >&2
|
|
238
|
+
if [[ -n "${cleanup_error}" ]]; then
|
|
239
|
+
printf '[%s] pr cleanup detail session=%s %s\n' \
|
|
240
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
241
|
+
"${session}" \
|
|
242
|
+
"${cleanup_error}" >&2
|
|
243
|
+
fi
|
|
244
|
+
}
|
|
245
|
+
|
|
260
246
|
review_pass_action_from_result_action() {
|
|
261
247
|
case "${1:-}" in
|
|
262
248
|
host-advance-double-check-2)
|
|
@@ -402,9 +388,27 @@ post_pr_comment_if_present() {
|
|
|
402
388
|
if pr_comment_already_posted; then
|
|
403
389
|
return 0
|
|
404
390
|
fi
|
|
405
|
-
if
|
|
406
|
-
return
|
|
391
|
+
if host_github_post_issue_comment "${pr_number}" "$(cat "$comment_file")"; then
|
|
392
|
+
return 0
|
|
407
393
|
fi
|
|
394
|
+
if [[ -x "${github_write_outbox_script}" ]]; then
|
|
395
|
+
if "${github_write_outbox_script}" enqueue-comment \
|
|
396
|
+
--repo-slug "${repo_slug}" \
|
|
397
|
+
--number "${pr_number}" \
|
|
398
|
+
--kind pr \
|
|
399
|
+
--body-file "${comment_file}" >/dev/null 2>&1; then
|
|
400
|
+
return 0
|
|
401
|
+
fi
|
|
402
|
+
fi
|
|
403
|
+
return 1
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
enqueue_pr_approval_intent() {
|
|
407
|
+
[[ -x "${github_write_outbox_script}" ]] || return 1
|
|
408
|
+
"${github_write_outbox_script}" enqueue-approval \
|
|
409
|
+
--repo-slug "${repo_slug}" \
|
|
410
|
+
--number "${pr_number}" \
|
|
411
|
+
--body "${automated_pr_approval_body}" >/dev/null 2>&1
|
|
408
412
|
}
|
|
409
413
|
|
|
410
414
|
pr_comment_already_posted() {
|
|
@@ -427,6 +431,9 @@ record_host_github_rate_limit() {
|
|
|
427
431
|
host_github_rate_limited="yes"
|
|
428
432
|
host_github_rate_limit_detail="${output}"
|
|
429
433
|
printf '%s\n' "${output}" >"${detail_file}"
|
|
434
|
+
if [[ -x "${github_core_rate_limit_script}" ]]; then
|
|
435
|
+
"${github_core_rate_limit_script}" schedule "github-api-rate-limit" >/dev/null 2>&1 || true
|
|
436
|
+
fi
|
|
430
437
|
}
|
|
431
438
|
|
|
432
439
|
host_github_post_issue_comment() {
|
|
@@ -434,9 +441,13 @@ host_github_post_issue_comment() {
|
|
|
434
441
|
local body="${2:-}"
|
|
435
442
|
local output=""
|
|
436
443
|
|
|
444
|
+
if host_github_rate_limit_state_active; then
|
|
445
|
+
return 1
|
|
446
|
+
fi
|
|
447
|
+
|
|
437
448
|
flow_export_github_cli_auth_env "${repo_slug}"
|
|
438
449
|
if output="$(
|
|
439
|
-
|
|
450
|
+
flow_github_api_repo "${repo_slug}" "issues/${issue_number}/comments" \
|
|
440
451
|
--method POST \
|
|
441
452
|
-f body="${body}" 2>&1
|
|
442
453
|
)"; then
|
|
@@ -455,23 +466,25 @@ host_github_post_issue_comment() {
|
|
|
455
466
|
host_github_submit_pr_approval() {
|
|
456
467
|
local output=""
|
|
457
468
|
|
|
469
|
+
if host_github_rate_limit_state_active; then
|
|
470
|
+
enqueue_pr_approval_intent || true
|
|
471
|
+
return 1
|
|
472
|
+
fi
|
|
473
|
+
|
|
458
474
|
flow_export_github_cli_auth_env "${repo_slug}"
|
|
459
475
|
if output="$(
|
|
460
|
-
|
|
461
|
-
--method POST \
|
|
462
|
-
-f event=APPROVE \
|
|
463
|
-
-f body="Automated final review passed. Safe low-risk scope, green checks, and host-side merge approved." \
|
|
464
|
-
2>&1
|
|
476
|
+
flow_github_pr_review_approve "${repo_slug}" "${pr_number}" "${automated_pr_approval_body}" 2>&1
|
|
465
477
|
)"; then
|
|
466
478
|
return 0
|
|
467
479
|
fi
|
|
468
480
|
|
|
469
|
-
if grep -q "Can not approve your own pull request" <<<"${output}"; then
|
|
481
|
+
if grep -q "Can not approve your own pull request" <<<"${output}" || grep -q "approve your own pull is not allowed" <<<"${output}"; then
|
|
470
482
|
return 0
|
|
471
483
|
fi
|
|
472
484
|
|
|
473
485
|
if host_github_output_indicates_rate_limit "${output}"; then
|
|
474
486
|
record_host_github_rate_limit "${output}"
|
|
487
|
+
enqueue_pr_approval_intent || true
|
|
475
488
|
return 1
|
|
476
489
|
fi
|
|
477
490
|
|
|
@@ -684,15 +697,6 @@ attempt_blocked_pr_host_verification_recovery() {
|
|
|
684
697
|
return 0
|
|
685
698
|
}
|
|
686
699
|
|
|
687
|
-
require_transition() {
|
|
688
|
-
local step="${1:?step required}"
|
|
689
|
-
shift
|
|
690
|
-
if ! "$@"; then
|
|
691
|
-
echo "reconcile transition failed: ${step}" >&2
|
|
692
|
-
exit 1
|
|
693
|
-
fi
|
|
694
|
-
}
|
|
695
|
-
|
|
696
700
|
close_linked_issue_if_open() {
|
|
697
701
|
local issue_id="${1:-}"
|
|
698
702
|
[[ -n "$issue_id" ]] || return 0
|
|
@@ -889,13 +893,21 @@ merge_state_prepared() {
|
|
|
889
893
|
}
|
|
890
894
|
|
|
891
895
|
current_github_login() {
|
|
896
|
+
if host_github_rate_limit_state_active; then
|
|
897
|
+
printf '\n'
|
|
898
|
+
return 0
|
|
899
|
+
fi
|
|
892
900
|
flow_export_github_cli_auth_env "${repo_slug}"
|
|
893
|
-
|
|
901
|
+
flow_github_current_login
|
|
894
902
|
}
|
|
895
903
|
|
|
896
904
|
pr_author_login() {
|
|
905
|
+
if host_github_rate_limit_state_active; then
|
|
906
|
+
printf '\n'
|
|
907
|
+
return 0
|
|
908
|
+
fi
|
|
897
909
|
flow_export_github_cli_auth_env "${repo_slug}"
|
|
898
|
-
|
|
910
|
+
flow_github_pr_author_login "${repo_slug}" "${pr_number}"
|
|
899
911
|
}
|
|
900
912
|
|
|
901
913
|
pr_is_self_authored_for_current_actor() {
|
|
@@ -908,8 +920,12 @@ pr_is_self_authored_for_current_actor() {
|
|
|
908
920
|
}
|
|
909
921
|
|
|
910
922
|
pr_remote_head_oid() {
|
|
923
|
+
if host_github_rate_limit_state_active; then
|
|
924
|
+
printf '\n'
|
|
925
|
+
return 0
|
|
926
|
+
fi
|
|
911
927
|
flow_export_github_cli_auth_env "${repo_slug}"
|
|
912
|
-
|
|
928
|
+
flow_github_pr_head_oid "${repo_slug}" "${pr_number}"
|
|
913
929
|
}
|
|
914
930
|
|
|
915
931
|
pr_remote_already_has_final_head() {
|
|
@@ -931,39 +947,46 @@ approve_and_merge() {
|
|
|
931
947
|
fi
|
|
932
948
|
fi
|
|
933
949
|
|
|
950
|
+
if host_github_rate_limit_state_active; then
|
|
951
|
+
return 2
|
|
952
|
+
fi
|
|
953
|
+
|
|
934
954
|
flow_export_github_cli_auth_env "${repo_slug}"
|
|
935
|
-
if
|
|
936
|
-
|
|
937
|
-
merge_output="$(cat "${run_dir}/host-github-merge.err" 2>/dev/null || true)"
|
|
938
|
-
if host_github_output_indicates_rate_limit "${merge_output}"; then
|
|
939
|
-
record_host_github_rate_limit "${merge_output}"
|
|
940
|
-
return 2
|
|
941
|
-
fi
|
|
942
|
-
if flow_github_pr_merge "$repo_slug" "$pr_number" "squash" "yes" 2>"${run_dir}/host-github-merge.err"; then
|
|
943
|
-
return 0
|
|
944
|
-
fi
|
|
945
|
-
merge_output="$(cat "${run_dir}/host-github-merge.err" 2>/dev/null || true)"
|
|
946
|
-
if host_github_output_indicates_rate_limit "${merge_output}"; then
|
|
947
|
-
record_host_github_rate_limit "${merge_output}"
|
|
948
|
-
return 2
|
|
949
|
-
fi
|
|
950
|
-
if [[ -n "${merge_output}" ]]; then
|
|
951
|
-
printf '%s\n' "${merge_output}" >&2
|
|
952
|
-
fi
|
|
953
|
-
return 1
|
|
955
|
+
if flow_github_pr_merge "$repo_slug" "$pr_number" "squash" "yes" >"${run_dir}/host-github-merge.out" 2>"${run_dir}/host-github-merge.err"; then
|
|
956
|
+
return 0
|
|
954
957
|
fi
|
|
955
958
|
|
|
956
|
-
|
|
959
|
+
local merge_output=""
|
|
960
|
+
merge_output="$(cat "${run_dir}/host-github-merge.err" 2>/dev/null || true)"
|
|
961
|
+
if host_github_output_indicates_rate_limit "${merge_output}"; then
|
|
962
|
+
record_host_github_rate_limit "${merge_output}"
|
|
963
|
+
return 2
|
|
964
|
+
fi
|
|
965
|
+
if [[ -n "${merge_output}" ]]; then
|
|
966
|
+
printf '%s\n' "${merge_output}" >&2
|
|
967
|
+
fi
|
|
968
|
+
return 1
|
|
957
969
|
}
|
|
958
970
|
|
|
959
971
|
cleanup_pr_session() {
|
|
960
|
-
"
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
972
|
+
local cleanup_output=""
|
|
973
|
+
local cleanup_exit="0"
|
|
974
|
+
|
|
975
|
+
if cleanup_output="$(
|
|
976
|
+
"${shared_tools_dir}/agent-project-cleanup-session" \
|
|
977
|
+
--repo-root "$repo_root" \
|
|
978
|
+
--runs-root "$runs_root" \
|
|
979
|
+
--history-root "$history_root" \
|
|
980
|
+
--session "$session" \
|
|
981
|
+
--worktree "$pr_worktree" \
|
|
982
|
+
--mode pr 2>&1
|
|
983
|
+
)"; then
|
|
984
|
+
cleanup_exit="0"
|
|
985
|
+
else
|
|
986
|
+
cleanup_exit="$?"
|
|
987
|
+
fi
|
|
988
|
+
|
|
989
|
+
warn_cleanup_pr_session "${cleanup_output}" "${cleanup_exit}"
|
|
967
990
|
}
|
|
968
991
|
|
|
969
992
|
notify_pr_reconciled() {
|