agent-control-plane 0.1.16 → 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 (63) hide show
  1. package/README.md +93 -14
  2. package/bin/pr-risk.sh +28 -6
  3. package/hooks/heartbeat-hooks.sh +62 -22
  4. package/npm/bin/agent-control-plane.js +360 -10
  5. package/package.json +6 -3
  6. package/references/architecture.md +8 -0
  7. package/references/control-plane-map.md +6 -2
  8. package/references/release-checklist.md +0 -2
  9. package/tools/bin/agent-github-update-labels +6 -1
  10. package/tools/bin/agent-project-catch-up-issue-pr-links +118 -0
  11. package/tools/bin/agent-project-catch-up-merged-prs +78 -21
  12. package/tools/bin/agent-project-catch-up-scheduled-issue-retries +123 -0
  13. package/tools/bin/agent-project-cleanup-session +132 -4
  14. package/tools/bin/agent-project-heartbeat-loop +116 -1461
  15. package/tools/bin/agent-project-reconcile-issue-session +90 -117
  16. package/tools/bin/agent-project-reconcile-pr-session +76 -111
  17. package/tools/bin/agent-project-run-claude-session +12 -2
  18. package/tools/bin/agent-project-run-codex-resilient +86 -9
  19. package/tools/bin/agent-project-run-codex-session +16 -5
  20. package/tools/bin/agent-project-run-kilo-session +356 -14
  21. package/tools/bin/agent-project-run-ollama-session +658 -0
  22. package/tools/bin/agent-project-run-openclaw-session +37 -25
  23. package/tools/bin/agent-project-run-opencode-session +364 -14
  24. package/tools/bin/agent-project-run-pi-session +479 -0
  25. package/tools/bin/agent-project-worker-status +11 -8
  26. package/tools/bin/cleanup-worktree.sh +6 -1
  27. package/tools/bin/flow-config-lib.sh +196 -3
  28. package/tools/bin/flow-resident-worker-lib.sh +120 -2
  29. package/tools/bin/flow-shell-lib.sh +29 -2
  30. package/tools/bin/heartbeat-loop-cache-lib.sh +164 -0
  31. package/tools/bin/heartbeat-loop-counting-lib.sh +306 -0
  32. package/tools/bin/heartbeat-loop-pr-strategy-lib.sh +199 -0
  33. package/tools/bin/heartbeat-loop-scheduling-lib.sh +506 -0
  34. package/tools/bin/heartbeat-loop-worker-lib.sh +319 -0
  35. package/tools/bin/heartbeat-recovery-preflight.sh +13 -1
  36. package/tools/bin/heartbeat-safe-auto.sh +119 -20
  37. package/tools/bin/install-project-launchd.sh +19 -2
  38. package/tools/bin/prepare-worktree.sh +4 -4
  39. package/tools/bin/profile-activate.sh +2 -2
  40. package/tools/bin/profile-adopt.sh +2 -2
  41. package/tools/bin/project-init.sh +1 -1
  42. package/tools/bin/project-launchd-bootstrap.sh +11 -8
  43. package/tools/bin/project-runtimectl.sh +90 -7
  44. package/tools/bin/provider-cooldown-state.sh +14 -14
  45. package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
  46. package/tools/bin/render-flow-config.sh +30 -33
  47. package/tools/bin/resident-issue-controller-lib.sh +448 -0
  48. package/tools/bin/resident-issue-queue-status.py +35 -0
  49. package/tools/bin/run-codex-task.sh +53 -4
  50. package/tools/bin/scaffold-profile.sh +18 -3
  51. package/tools/bin/start-issue-worker.sh +1 -1
  52. package/tools/bin/start-pr-fix-worker.sh +30 -0
  53. package/tools/bin/start-pr-review-worker.sh +31 -0
  54. package/tools/bin/start-resident-issue-loop.sh +27 -438
  55. package/tools/bin/sync-agent-repo.sh +2 -2
  56. package/tools/bin/sync-dependency-baseline.sh +3 -3
  57. package/tools/bin/sync-shared-agent-home.sh +4 -1
  58. package/tools/dashboard/app.js +7 -0
  59. package/tools/dashboard/dashboard_snapshot.py +13 -29
  60. package/tools/templates/pr-fix-template.md +3 -7
  61. package/tools/templates/pr-merge-repair-template.md +3 -7
  62. package/tools/templates/pr-review-template.md +2 -1
  63. 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
- bootstrap_flow_shell_lib() {
7
- local candidate=""
8
- local skill_name=""
9
-
10
- for candidate in \
11
- "${SCRIPT_DIR}/flow-shell-lib.sh" \
12
- "${AGENT_CONTROL_PLANE_ROOT:-}/tools/bin/flow-shell-lib.sh" \
13
- "${ACP_ROOT:-}/tools/bin/flow-shell-lib.sh" \
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
- echo "unable to locate flow-shell-lib.sh for reconcile bootstrap" >&2
36
- return 1
37
- }
38
-
39
- FLOW_SHELL_LIB_PATH="$(bootstrap_flow_shell_lib)"
40
- BOOTSTRAP_TOOLS_DIR="$(cd "$(dirname "${FLOW_SHELL_LIB_PATH}")" && pwd)"
41
- # shellcheck source=/dev/null
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
- if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
60
- if [[ -d "${SHARED_AGENT_HOME}/tools/bin" ]]; then
61
- printf '%s/tools/bin\n' "${SHARED_AGENT_HOME}"
62
- return 0
63
- fi
64
- for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
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=""
@@ -640,6 +569,9 @@ try {
640
569
 
641
570
  const changedFilesLower = gitChangedFiles.map((file) => file.toLowerCase());
642
571
  const repoHasScript = (scriptName) => Boolean(packageJson?.scripts && Object.prototype.hasOwnProperty.call(packageJson.scripts, scriptName));
572
+ const rootTestScript = String(packageJson?.scripts?.test || '').trim();
573
+ const rootTestScriptUsesNodeTest = /^node\s+--test(?:\s|$)/.test(rootTestScript);
574
+ const rootTestScriptLooksWatchMode = /\B--watch(?:All)?(?:[=\s]|$)|(?:^|\s)vitest\s+watch(?:\s|$)/.test(rootTestScript);
643
575
  const commandLooksRunnable = (command) => {
644
576
  if (/^npm test(?:\s|$)?/.test(command)) return repoHasScript('test');
645
577
  if (/^pnpm test(?:\s|$)?/.test(command)) return repoHasScript('test');
@@ -648,6 +580,14 @@ const commandLooksRunnable = (command) => {
648
580
  if (/^node\s+--test(?:\s|$)/.test(command)) return true;
649
581
  return true;
650
582
  };
583
+ const rootTestFallbackCommand = () => {
584
+ if (!rootTestScript) return '';
585
+ if (rootTestScriptUsesNodeTest) return 'npm test';
586
+ if (!rootTestScriptLooksWatchMode) return 'npm test';
587
+ if (/\bjest\b/i.test(rootTestScript)) return 'npx jest --runInBand --watchAll=false';
588
+ if (/\bvitest\b/i.test(rootTestScript)) return 'npx vitest run';
589
+ return '';
590
+ };
651
591
 
652
592
  if (promptFile && fs.existsSync(promptFile)) {
653
593
  const lines = fs.readFileSync(promptFile, 'utf8').split(/\r?\n/).slice(0, 40);
@@ -678,15 +618,17 @@ if (promptFile && fs.existsSync(promptFile)) {
678
618
  }
679
619
 
680
620
  const changedTestFiles = [...new Set(gitChangedFiles.filter((file) => /\.(?:spec|test)\.[cm]?[jt]sx?$/.test(file)))];
681
- const rootTestScript = String(packageJson?.scripts?.test || '').trim();
682
- if (/^node\s+--test(?:\s|$)/.test(rootTestScript)) {
621
+ if (rootTestScriptUsesNodeTest) {
683
622
  for (const file of changedTestFiles) {
684
623
  addCommand(`node --test ${file}`);
685
624
  }
686
625
  }
687
626
 
688
- if (commands.length === 0 && rootTestScript) {
689
- addCommand('npm test');
627
+ if (commands.length === 0) {
628
+ const fallbackCommand = rootTestFallbackCommand();
629
+ if (fallbackCommand) {
630
+ addCommand(fallbackCommand);
631
+ }
690
632
  }
691
633
 
692
634
  const filtered = commands.filter((command) => !recordedPassCommands.has(command));
@@ -1040,15 +982,6 @@ extract_recovery_worktree_from_publish_output() {
1040
982
  awk -F= '/^RECOVERY_WORKTREE=/{print $2}' <<<"$publish_out" | tail -n 1
1041
983
  }
1042
984
 
1043
- require_transition() {
1044
- local step="${1:?step required}"
1045
- shift
1046
- if ! "$@"; then
1047
- echo "reconcile transition failed: ${step}" >&2
1048
- exit 1
1049
- fi
1050
- }
1051
-
1052
985
  mark_reconciled() {
1053
986
  local reconciled_at tmp_file
1054
987
  if [[ -d "$run_dir" ]]; then
@@ -1128,6 +1061,40 @@ update_resident_issue_metadata() {
1128
1061
  "LAST_WORKTREE_REUSED=${last_worktree_reused:-no}"
1129
1062
  }
1130
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
+
1131
1098
  cleanup_issue_session() {
1132
1099
  local -a cleanup_args=(
1133
1100
  --repo-root "$repo_root"
@@ -1143,7 +1110,14 @@ cleanup_issue_session() {
1143
1110
  cleanup_args+=(--skip-worktree-cleanup)
1144
1111
  fi
1145
1112
 
1146
- "${shared_tools_dir}/agent-project-cleanup-session" "${cleanup_args[@]}" >/dev/null
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}"
1147
1121
  }
1148
1122
 
1149
1123
  notify_issue_reconciled() {
@@ -1382,19 +1356,18 @@ case "$status" in
1382
1356
  failure_reason="$(normalize_issue_failure_reason "${failure_reason:-worker-exit-failed}")"
1383
1357
  schedule_provider_quota_cooldown "${failure_reason}"
1384
1358
  normalize_issue_runner_state "failed" "${LAST_EXIT_CODE:-}" "${failure_reason}"
1385
- if [[ "${result_outcome:-}" == "blocked" && "${result_action:-}" == "host-comment-blocker" ]] \
1386
- || [[ "${failure_reason}" == "provider-quota-limit" ]]; then
1387
- if [[ -z "${result_outcome:-}" ]]; then
1388
- result_outcome="blocked"
1389
- fi
1390
- if [[ -z "${result_action:-}" ]]; then
1391
- result_action="host-comment-blocker"
1392
- fi
1359
+ if [[ "${result_outcome:-}" == "blocked" && "${result_action:-}" == "host-comment-blocker" ]]; then
1393
1360
  if [[ ! -s "${run_dir}/issue-comment.md" ]]; then
1394
1361
  write_issue_comment_artifact "$(build_issue_runtime_blocker_comment "${failure_reason}")" || true
1395
1362
  fi
1396
1363
  post_issue_comment_if_present
1397
1364
  issue_set_reconcile_summary "$status" "$result_outcome" "$result_action" "$failure_reason"
1365
+ elif [[ "${failure_reason}" == "provider-quota-limit" ]]; then
1366
+ if [[ ! -s "${run_dir}/issue-comment.md" ]]; then
1367
+ write_issue_comment_artifact "$(build_issue_runtime_blocker_comment "${failure_reason}")" || true
1368
+ fi
1369
+ post_issue_comment_if_present
1370
+ issue_set_reconcile_summary "$status" "" "" "$failure_reason"
1398
1371
  else
1399
1372
  issue_set_reconcile_summary "$status" "" "" "$failure_reason"
1400
1373
  fi
@@ -2,82 +2,33 @@
2
2
  set -euo pipefail
3
3
 
4
4
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
-
6
- bootstrap_flow_shell_lib() {
7
- local candidate=""
8
- local skill_name=""
9
-
10
- for candidate in \
11
- "${SCRIPT_DIR}/flow-shell-lib.sh" \
12
- "${AGENT_CONTROL_PLANE_ROOT:-}/tools/bin/flow-shell-lib.sh" \
13
- "${ACP_ROOT:-}/tools/bin/flow-shell-lib.sh" \
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
- echo "unable to locate flow-shell-lib.sh for reconcile bootstrap" >&2
36
- return 1
37
- }
38
-
39
- FLOW_SHELL_LIB_PATH="$(bootstrap_flow_shell_lib)"
40
- BOOTSTRAP_TOOLS_DIR="$(cd "$(dirname "${FLOW_SHELL_LIB_PATH}")" && pwd)"
41
- # shellcheck source=/dev/null
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
- if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
60
- if [[ -d "${SHARED_AGENT_HOME}/tools/bin" ]]; then
61
- printf '%s/tools/bin\n' "${SHARED_AGENT_HOME}"
62
- return 0
63
- fi
64
- for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
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
- "${shared_tools_dir}/agent-project-cleanup-session" \
961
- --repo-root "$repo_root" \
962
- --runs-root "$runs_root" \
963
- --history-root "$history_root" \
964
- --session "$session" \
965
- --worktree "$pr_worktree" \
966
- --mode pr >/dev/null || true
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"
@@ -774,12 +784,12 @@ fi
774
784
 
775
785
  ${collect_copy_snippet}
776
786
  if [[ "\${status}" -eq 0 ]]; then
777
- write_state completed "\${status}" '' "\${attempt}" "\$((attempt - 1))"
787
+ write_state succeeded "\${status}" '' "\${attempt}" "\$((attempt - 1))"
778
788
  else
779
789
  write_state failed "\${status}" "\${failure_reason}" "\${attempt}" "\$((attempt - 1))"
780
790
  fi
781
791
  ${reconcile_snippet}
782
- printf '\n__CLAUDE_EXIT__:%s\n' "\${status}" | tee -a "\${output_file}"
792
+ printf '\n__CODEX_EXIT__:%s\n' "\${status}" | tee -a "\${output_file}"
783
793
  exit "\${status}"
784
794
  EOF
785
795