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
|
@@ -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=""
|
|
@@ -1053,15 +982,6 @@ extract_recovery_worktree_from_publish_output() {
|
|
|
1053
982
|
awk -F= '/^RECOVERY_WORKTREE=/{print $2}' <<<"$publish_out" | tail -n 1
|
|
1054
983
|
}
|
|
1055
984
|
|
|
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
985
|
mark_reconciled() {
|
|
1066
986
|
local reconciled_at tmp_file
|
|
1067
987
|
if [[ -d "$run_dir" ]]; then
|
|
@@ -1141,6 +1061,40 @@ update_resident_issue_metadata() {
|
|
|
1141
1061
|
"LAST_WORKTREE_REUSED=${last_worktree_reused:-no}"
|
|
1142
1062
|
}
|
|
1143
1063
|
|
|
1064
|
+
cleanup_output_value() {
|
|
1065
|
+
local cleanup_output="${1:-}"
|
|
1066
|
+
local key="${2:?key required}"
|
|
1067
|
+
awk -F= -v target_key="${key}" '$1 == target_key { print substr($0, index($0, "=") + 1); exit }' <<<"${cleanup_output}"
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
warn_cleanup_issue_session() {
|
|
1071
|
+
local cleanup_output="${1:-}"
|
|
1072
|
+
local cleanup_exit="${2:-0}"
|
|
1073
|
+
local cleanup_status=""
|
|
1074
|
+
local cleanup_mode=""
|
|
1075
|
+
local cleanup_error=""
|
|
1076
|
+
|
|
1077
|
+
cleanup_status="$(cleanup_output_value "${cleanup_output}" "CLEANUP_STATUS")"
|
|
1078
|
+
if [[ -z "${cleanup_status}" ]]; then
|
|
1079
|
+
cleanup_status="${cleanup_exit}"
|
|
1080
|
+
fi
|
|
1081
|
+
[[ "${cleanup_status}" != "0" ]] || return 0
|
|
1082
|
+
|
|
1083
|
+
cleanup_mode="$(cleanup_output_value "${cleanup_output}" "CLEANUP_MODE")"
|
|
1084
|
+
cleanup_error="$(cleanup_output_value "${cleanup_output}" "CLEANUP_ERROR")"
|
|
1085
|
+
printf '[%s] issue cleanup warning session=%s status=%s mode=%s\n' \
|
|
1086
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
1087
|
+
"${session}" \
|
|
1088
|
+
"${cleanup_status}" \
|
|
1089
|
+
"${cleanup_mode:-unknown}" >&2
|
|
1090
|
+
if [[ -n "${cleanup_error}" ]]; then
|
|
1091
|
+
printf '[%s] issue cleanup detail session=%s %s\n' \
|
|
1092
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
1093
|
+
"${session}" \
|
|
1094
|
+
"${cleanup_error}" >&2
|
|
1095
|
+
fi
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1144
1098
|
cleanup_issue_session() {
|
|
1145
1099
|
local -a cleanup_args=(
|
|
1146
1100
|
--repo-root "$repo_root"
|
|
@@ -1156,7 +1110,14 @@ cleanup_issue_session() {
|
|
|
1156
1110
|
cleanup_args+=(--skip-worktree-cleanup)
|
|
1157
1111
|
fi
|
|
1158
1112
|
|
|
1159
|
-
|
|
1113
|
+
local cleanup_output=""
|
|
1114
|
+
local cleanup_exit="0"
|
|
1115
|
+
if cleanup_output="$("${shared_tools_dir}/agent-project-cleanup-session" "${cleanup_args[@]}" 2>&1)"; then
|
|
1116
|
+
cleanup_exit="0"
|
|
1117
|
+
else
|
|
1118
|
+
cleanup_exit="$?"
|
|
1119
|
+
fi
|
|
1120
|
+
warn_cleanup_issue_session "${cleanup_output}" "${cleanup_exit}"
|
|
1160
1121
|
}
|
|
1161
1122
|
|
|
1162
1123
|
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=""
|
|
@@ -257,6 +186,40 @@ if [[ "$status" == "RUNNING" && "$pr_state" != "MERGED" && "$pr_state" != "CLOSE
|
|
|
257
186
|
exit 0
|
|
258
187
|
fi
|
|
259
188
|
|
|
189
|
+
cleanup_output_value() {
|
|
190
|
+
local cleanup_output="${1:-}"
|
|
191
|
+
local key="${2:?key required}"
|
|
192
|
+
awk -F= -v target_key="${key}" '$1 == target_key { print substr($0, index($0, "=") + 1); exit }' <<<"${cleanup_output}"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
warn_cleanup_pr_session() {
|
|
196
|
+
local cleanup_output="${1:-}"
|
|
197
|
+
local cleanup_exit="${2:-0}"
|
|
198
|
+
local cleanup_status=""
|
|
199
|
+
local cleanup_mode=""
|
|
200
|
+
local cleanup_error=""
|
|
201
|
+
|
|
202
|
+
cleanup_status="$(cleanup_output_value "${cleanup_output}" "CLEANUP_STATUS")"
|
|
203
|
+
if [[ -z "${cleanup_status}" ]]; then
|
|
204
|
+
cleanup_status="${cleanup_exit}"
|
|
205
|
+
fi
|
|
206
|
+
[[ "${cleanup_status}" != "0" ]] || return 0
|
|
207
|
+
|
|
208
|
+
cleanup_mode="$(cleanup_output_value "${cleanup_output}" "CLEANUP_MODE")"
|
|
209
|
+
cleanup_error="$(cleanup_output_value "${cleanup_output}" "CLEANUP_ERROR")"
|
|
210
|
+
printf '[%s] pr cleanup warning session=%s status=%s mode=%s\n' \
|
|
211
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
212
|
+
"${session}" \
|
|
213
|
+
"${cleanup_status}" \
|
|
214
|
+
"${cleanup_mode:-unknown}" >&2
|
|
215
|
+
if [[ -n "${cleanup_error}" ]]; then
|
|
216
|
+
printf '[%s] pr cleanup detail session=%s %s\n' \
|
|
217
|
+
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
218
|
+
"${session}" \
|
|
219
|
+
"${cleanup_error}" >&2
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
260
223
|
review_pass_action_from_result_action() {
|
|
261
224
|
case "${1:-}" in
|
|
262
225
|
host-advance-double-check-2)
|
|
@@ -684,15 +647,6 @@ attempt_blocked_pr_host_verification_recovery() {
|
|
|
684
647
|
return 0
|
|
685
648
|
}
|
|
686
649
|
|
|
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
650
|
close_linked_issue_if_open() {
|
|
697
651
|
local issue_id="${1:-}"
|
|
698
652
|
[[ -n "$issue_id" ]] || return 0
|
|
@@ -957,13 +911,24 @@ approve_and_merge() {
|
|
|
957
911
|
}
|
|
958
912
|
|
|
959
913
|
cleanup_pr_session() {
|
|
960
|
-
"
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
914
|
+
local cleanup_output=""
|
|
915
|
+
local cleanup_exit="0"
|
|
916
|
+
|
|
917
|
+
if cleanup_output="$(
|
|
918
|
+
"${shared_tools_dir}/agent-project-cleanup-session" \
|
|
919
|
+
--repo-root "$repo_root" \
|
|
920
|
+
--runs-root "$runs_root" \
|
|
921
|
+
--history-root "$history_root" \
|
|
922
|
+
--session "$session" \
|
|
923
|
+
--worktree "$pr_worktree" \
|
|
924
|
+
--mode pr 2>&1
|
|
925
|
+
)"; then
|
|
926
|
+
cleanup_exit="0"
|
|
927
|
+
else
|
|
928
|
+
cleanup_exit="$?"
|
|
929
|
+
fi
|
|
930
|
+
|
|
931
|
+
warn_cleanup_pr_session "${cleanup_output}" "${cleanup_exit}"
|
|
967
932
|
}
|
|
968
933
|
|
|
969
934
|
notify_pr_reconciled() {
|
|
@@ -364,6 +364,16 @@ EOF
|
|
|
364
364
|
done
|
|
365
365
|
fi
|
|
366
366
|
|
|
367
|
+
# Always collect result.env from sandbox to artifact_dir
|
|
368
|
+
collect_copy_snippet+=$(
|
|
369
|
+
cat <<EOF
|
|
370
|
+
if [[ -f ${sandbox_run_dir_q}/result.env ]]; then
|
|
371
|
+
cp ${sandbox_run_dir_q}/result.env ${artifact_dir_q}/result.env
|
|
372
|
+
fi
|
|
373
|
+
EOF
|
|
374
|
+
)
|
|
375
|
+
collect_copy_snippet+=$'\n'
|
|
376
|
+
|
|
367
377
|
reconcile_snippet=""
|
|
368
378
|
if [[ -n "$reconcile_command" ]]; then
|
|
369
379
|
printf -v delayed_reconcile_q '%q' "export ACP_EXPECTED_RUN_STARTED_AT=${started_at_q}; export F_LOSNING_EXPECTED_RUN_STARTED_AT=${started_at_q}; while tmux has-session -t ${session_q} 2>/dev/null; do sleep 1; done; sleep 2; $reconcile_command"
|
|
@@ -38,6 +38,23 @@ auth_refresh_poll_seconds="${ACP_CODEX_AUTH_REFRESH_POLL_SECONDS:-${F_LOSNING_CO
|
|
|
38
38
|
max_quota_autoswitch_attempts="${ACP_CODEX_MAX_AUTOSWITCH_ATTEMPTS:-${F_LOSNING_CODEX_MAX_AUTOSWITCH_ATTEMPTS:-1}}"
|
|
39
39
|
codex_progress_heartbeat_seconds="${ACP_CODEX_PROGRESS_HEARTBEAT_SECONDS:-${F_LOSNING_CODEX_PROGRESS_HEARTBEAT_SECONDS:-30}}"
|
|
40
40
|
codex_stall_seconds="${ACP_CODEX_STALL_SECONDS:-${F_LOSNING_CODEX_STALL_SECONDS:-300}}"
|
|
41
|
+
python_bin=""
|
|
42
|
+
|
|
43
|
+
resolve_python_bin() {
|
|
44
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
45
|
+
command -v python3
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
if [[ -x /opt/homebrew/bin/python3 ]]; then
|
|
49
|
+
printf '%s\n' "/opt/homebrew/bin/python3"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
if command -v python >/dev/null 2>&1; then
|
|
53
|
+
command -v python
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
41
58
|
|
|
42
59
|
while [[ $# -gt 0 ]]; do
|
|
43
60
|
case "$1" in
|
|
@@ -92,6 +109,12 @@ case "$codex_stall_seconds" in
|
|
|
92
109
|
''|*[!0-9]*) echo "ACP_CODEX_STALL_SECONDS must be numeric" >&2; exit 1 ;;
|
|
93
110
|
esac
|
|
94
111
|
|
|
112
|
+
python_bin="$(resolve_python_bin || true)"
|
|
113
|
+
if [[ -z "$python_bin" || ! -x "$python_bin" ]]; then
|
|
114
|
+
echo "unable to resolve a runnable python interpreter for codex supervision" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
95
118
|
FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
|
|
96
119
|
state_file="${host_run_dir}/runner.env"
|
|
97
120
|
auth_file="${HOME}/.codex/auth.json"
|
|
@@ -177,7 +200,7 @@ run_with_timeout() {
|
|
|
177
200
|
local timeout_seconds="${1:?timeout seconds required}"
|
|
178
201
|
shift
|
|
179
202
|
|
|
180
|
-
|
|
203
|
+
"$python_bin" - "$timeout_seconds" "$@" <<'PY'
|
|
181
204
|
import os
|
|
182
205
|
import signal
|
|
183
206
|
import subprocess
|
|
@@ -220,6 +243,60 @@ sys.exit(proc.returncode)
|
|
|
220
243
|
PY
|
|
221
244
|
}
|
|
222
245
|
|
|
246
|
+
stat_file_size() {
|
|
247
|
+
local path="${1:?path required}"
|
|
248
|
+
local value=""
|
|
249
|
+
|
|
250
|
+
value="$(stat -f %z "$path" 2>/dev/null || true)"
|
|
251
|
+
if [[ "$value" =~ ^[0-9]+$ ]]; then
|
|
252
|
+
printf '%s\n' "$value"
|
|
253
|
+
return 0
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
value="$(stat -c %s "$path" 2>/dev/null || true)"
|
|
257
|
+
if [[ "$value" =~ ^[0-9]+$ ]]; then
|
|
258
|
+
printf '%s\n' "$value"
|
|
259
|
+
return 0
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
"$python_bin" - "$path" <<'PY'
|
|
263
|
+
import os
|
|
264
|
+
import sys
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
print(os.path.getsize(sys.argv[1]))
|
|
268
|
+
except OSError:
|
|
269
|
+
print("0")
|
|
270
|
+
PY
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
stat_file_mtime() {
|
|
274
|
+
local path="${1:?path required}"
|
|
275
|
+
local value=""
|
|
276
|
+
|
|
277
|
+
value="$(stat -f %m "$path" 2>/dev/null || true)"
|
|
278
|
+
if [[ "$value" =~ ^[0-9]+$ ]]; then
|
|
279
|
+
printf '%s\n' "$value"
|
|
280
|
+
return 0
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
value="$(stat -c %Y "$path" 2>/dev/null || true)"
|
|
284
|
+
if [[ "$value" =~ ^[0-9]+$ ]]; then
|
|
285
|
+
printf '%s\n' "$value"
|
|
286
|
+
return 0
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
"$python_bin" - "$path" <<'PY'
|
|
290
|
+
import os
|
|
291
|
+
import sys
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
print(int(os.path.getmtime(sys.argv[1])))
|
|
295
|
+
except OSError:
|
|
296
|
+
print("0")
|
|
297
|
+
PY
|
|
298
|
+
}
|
|
299
|
+
|
|
223
300
|
auth_fingerprint() {
|
|
224
301
|
if [[ ! -f "$auth_file" ]]; then
|
|
225
302
|
printf 'missing\n'
|
|
@@ -227,8 +304,8 @@ auth_fingerprint() {
|
|
|
227
304
|
fi
|
|
228
305
|
|
|
229
306
|
local mtime size sha
|
|
230
|
-
mtime="$(
|
|
231
|
-
size="$(
|
|
307
|
+
mtime="$(stat_file_mtime "$auth_file" 2>/dev/null || printf '0')"
|
|
308
|
+
size="$(stat_file_size "$auth_file" 2>/dev/null || printf '0')"
|
|
232
309
|
sha="$(shasum -a 256 "$auth_file" | awk '{print $1}')"
|
|
233
310
|
printf '%s:%s:%s\n' "$mtime" "$size" "$sha"
|
|
234
311
|
}
|
|
@@ -256,8 +333,8 @@ quota_switch_signature() {
|
|
|
256
333
|
fi
|
|
257
334
|
|
|
258
335
|
local mtime size sha
|
|
259
|
-
mtime="$(
|
|
260
|
-
size="$(
|
|
336
|
+
mtime="$(stat_file_mtime "$quota_switch_state_file" 2>/dev/null || printf '0')"
|
|
337
|
+
size="$(stat_file_size "$quota_switch_state_file" 2>/dev/null || printf '0')"
|
|
261
338
|
sha="$(shasum -a 256 "$quota_switch_state_file" | awk '{print $1}')"
|
|
262
339
|
printf '%s:%s:%s\n' "$mtime" "$size" "$sha"
|
|
263
340
|
}
|
|
@@ -390,7 +467,7 @@ run_quota_autoswitch() {
|
|
|
390
467
|
new_output_since() {
|
|
391
468
|
local start_size="${1:?start size required}"
|
|
392
469
|
local file_size
|
|
393
|
-
file_size="$(
|
|
470
|
+
file_size="$(stat_file_size "$output_file" 2>/dev/null || printf '0')"
|
|
394
471
|
if (( file_size <= start_size )); then
|
|
395
472
|
return 0
|
|
396
473
|
fi
|
|
@@ -456,7 +533,7 @@ stream_codex_exec() {
|
|
|
456
533
|
local progress_file=""
|
|
457
534
|
local line=""
|
|
458
535
|
|
|
459
|
-
last_attempt_start_size="$(
|
|
536
|
+
last_attempt_start_size="$(stat_file_size "$output_file" 2>/dev/null || printf '0')"
|
|
460
537
|
last_attempt_started_epoch="$(date +%s)"
|
|
461
538
|
progress_file="${host_run_dir}/.codex-progress.$$"
|
|
462
539
|
rm -f "$progress_file"
|
|
@@ -514,7 +591,7 @@ stream_codex_exec() {
|
|
|
514
591
|
break
|
|
515
592
|
fi
|
|
516
593
|
else
|
|
517
|
-
last_progress_epoch="$(
|
|
594
|
+
last_progress_epoch="$(stat_file_mtime "$progress_file" 2>/dev/null || printf '0')"
|
|
518
595
|
if [[ -n "$last_progress_epoch" && "$last_progress_epoch" != "0" ]]; then
|
|
519
596
|
idle_for=$((now - last_progress_epoch))
|
|
520
597
|
if (( idle_for >= codex_stall_seconds )); then
|
|
@@ -556,7 +633,7 @@ stream_codex_exec() {
|
|
|
556
633
|
}
|
|
557
634
|
|
|
558
635
|
extract_thread_id() {
|
|
559
|
-
|
|
636
|
+
"$python_bin" -c '
|
|
560
637
|
import json
|
|
561
638
|
import sys
|
|
562
639
|
|
|
@@ -39,10 +39,16 @@ declare -a context_items=()
|
|
|
39
39
|
declare -a collect_files=()
|
|
40
40
|
|
|
41
41
|
resolve_codex_bin() {
|
|
42
|
-
local configured_bin="${CODEX_BIN:-}"
|
|
42
|
+
local configured_bin="${CODEX_BIN:-${ACP_CODEX_BIN:-${F_LOSNING_CODEX_BIN:-}}}"
|
|
43
43
|
local best_version=""
|
|
44
44
|
local best_bin=""
|
|
45
45
|
local candidate version_line version
|
|
46
|
+
local -a fallback_paths=(
|
|
47
|
+
"${HOME}/.local/bin/codex"
|
|
48
|
+
"${HOME}/.codex/local/bin/codex"
|
|
49
|
+
"/usr/local/bin/codex"
|
|
50
|
+
"/opt/homebrew/bin/codex"
|
|
51
|
+
)
|
|
46
52
|
|
|
47
53
|
if [[ -n "$configured_bin" && -x "$configured_bin" ]]; then
|
|
48
54
|
printf '%s\n' "$configured_bin"
|
|
@@ -50,11 +56,16 @@ resolve_codex_bin() {
|
|
|
50
56
|
fi
|
|
51
57
|
|
|
52
58
|
if command -v codex >/dev/null 2>&1; then
|
|
53
|
-
|
|
59
|
+
command -v codex
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
for candidate in "${fallback_paths[@]}"; do
|
|
54
64
|
if [[ -x "$candidate" ]]; then
|
|
55
|
-
|
|
65
|
+
printf '%s\n' "$candidate"
|
|
66
|
+
return 0
|
|
56
67
|
fi
|
|
57
|
-
|
|
68
|
+
done
|
|
58
69
|
|
|
59
70
|
if [[ -d "${HOME:-}/.nvm/versions/node" ]]; then
|
|
60
71
|
while IFS= read -r candidate; do
|
|
@@ -347,7 +358,7 @@ find_logged_artifact_path() {
|
|
|
347
358
|
if [[ "\$(basename "\${candidate}")" == "\${artifact_name}" && -f "\${candidate}" ]]; then
|
|
348
359
|
printf '%s\n' "\${candidate}"
|
|
349
360
|
fi
|
|
350
|
-
done < <(grep -oE '/
|
|
361
|
+
done < <(grep -oE '/[^[:space:])"]+' ${output_q} 2>/dev/null || true)
|
|
351
362
|
}
|
|
352
363
|
recover_logged_artifact() {
|
|
353
364
|
local artifact_name="\${1:?artifact name required}"
|
|
@@ -233,6 +233,16 @@ EOF
|
|
|
233
233
|
done
|
|
234
234
|
fi
|
|
235
235
|
|
|
236
|
+
# Always collect result.env from sandbox to artifact_dir
|
|
237
|
+
collect_copy_snippet+=$(
|
|
238
|
+
cat <<EOF
|
|
239
|
+
if [[ -f ${sandbox_run_dir_q}/result.env ]]; then
|
|
240
|
+
cp ${sandbox_run_dir_q}/result.env ${artifact_dir_q}/result.env
|
|
241
|
+
fi
|
|
242
|
+
EOF
|
|
243
|
+
)
|
|
244
|
+
collect_copy_snippet+=$'\n'
|
|
245
|
+
|
|
236
246
|
reconcile_snippet=""
|
|
237
247
|
if [[ -n "$reconcile_command" ]]; then
|
|
238
248
|
printf -v delayed_reconcile_q '%q' "export ACP_EXPECTED_RUN_STARTED_AT=${started_at_q}; export F_LOSNING_EXPECTED_RUN_STARTED_AT=${started_at_q}; while tmux has-session -t ${session_q} 2>/dev/null; do sleep 1; done; sleep 2; $reconcile_command"
|