agent-control-plane 0.1.9 → 0.1.12
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/hooks/heartbeat-hooks.sh +97 -8
- package/package.json +8 -2
- package/references/commands.md +1 -0
- package/tools/bin/agent-project-cleanup-session +133 -0
- package/tools/bin/agent-project-publish-issue-pr +178 -62
- package/tools/bin/agent-project-reconcile-issue-session +171 -3
- package/tools/bin/agent-project-run-codex-resilient +121 -16
- package/tools/bin/agent-project-run-codex-session +60 -10
- package/tools/bin/agent-project-run-openclaw-session +82 -8
- package/tools/bin/cleanup-worktree.sh +4 -1
- package/tools/bin/dashboard-launchd-bootstrap.sh +16 -4
- package/tools/bin/ensure-runtime-sync.sh +182 -0
- package/tools/bin/flow-config-lib.sh +76 -30
- package/tools/bin/flow-resident-worker-lib.sh +28 -2
- package/tools/bin/flow-shell-lib.sh +15 -1
- package/tools/bin/heartbeat-safe-auto.sh +32 -0
- package/tools/bin/issue-publish-localization-guard.sh +142 -0
- package/tools/bin/project-launchd-bootstrap.sh +17 -4
- package/tools/bin/project-runtime-supervisor.sh +7 -1
- package/tools/bin/project-runtimectl.sh +78 -15
- package/tools/bin/reuse-issue-worktree.sh +46 -0
- package/tools/bin/start-issue-worker.sh +76 -6
- package/tools/bin/start-resident-issue-loop.sh +1 -0
- package/tools/bin/sync-shared-agent-home.sh +26 -0
- package/tools/bin/test-smoke.sh +6 -1
- package/tools/dashboard/app.js +71 -1
- package/tools/dashboard/dashboard_snapshot.py +74 -0
- package/tools/dashboard/styles.css +43 -0
- package/tools/templates/issue-prompt-template.md +18 -66
- package/tools/templates/legacy/issue-prompt-template-pre-slim.md +109 -0
- package/bin/audit-issue-routing.sh +0 -74
- package/tools/bin/audit-agent-worktrees.sh +0 -310
- package/tools/bin/audit-issue-routing.sh +0 -11
- package/tools/bin/audit-retained-layout.sh +0 -58
- package/tools/bin/audit-retained-overlap.sh +0 -135
- package/tools/bin/audit-retained-worktrees.sh +0 -228
- package/tools/bin/check-skill-contracts.sh +0 -324
package/hooks/heartbeat-hooks.sh
CHANGED
|
@@ -15,15 +15,40 @@ FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
|
|
|
15
15
|
DETACHED_LAUNCH_BIN="${FLOW_TOOLS_DIR}/agent-project-detached-launch"
|
|
16
16
|
RESIDENT_ISSUE_LOOP_BIN="${FLOW_TOOLS_DIR}/start-resident-issue-loop.sh"
|
|
17
17
|
REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
|
|
18
|
+
STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
19
|
+
PENDING_LAUNCH_DIR="${ACP_PENDING_LAUNCH_DIR:-${F_LOSNING_PENDING_LAUNCH_DIR:-${STATE_ROOT}/pending-launches}}"
|
|
18
20
|
AGENT_PR_PREFIXES_JSON="$(flow_managed_pr_prefixes_json "${CONFIG_YAML}")"
|
|
19
21
|
AGENT_PR_ISSUE_CAPTURE_REGEX="$(flow_managed_issue_branch_regex "${CONFIG_YAML}")"
|
|
20
22
|
AGENT_PR_HANDOFF_LABEL="${AGENT_PR_HANDOFF_LABEL:-agent-handoff}"
|
|
21
23
|
AGENT_EXCLUSIVE_LABEL="${AGENT_EXCLUSIVE_LABEL:-agent-exclusive}"
|
|
22
24
|
CODING_WORKER="${ACP_CODING_WORKER:-${F_LOSNING_CODING_WORKER:-codex}}"
|
|
25
|
+
HEARTBEAT_ISSUE_JSON_CACHE_DIR="${TMPDIR:-/tmp}/heartbeat-issue-json.$$"
|
|
26
|
+
|
|
27
|
+
heartbeat_issue_json_cached() {
|
|
28
|
+
local issue_id="${1:?issue id required}"
|
|
29
|
+
local cache_file=""
|
|
30
|
+
local issue_json=""
|
|
31
|
+
|
|
32
|
+
if [[ ! -d "${HEARTBEAT_ISSUE_JSON_CACHE_DIR}" ]]; then
|
|
33
|
+
mkdir -p "${HEARTBEAT_ISSUE_JSON_CACHE_DIR}"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
cache_file="${HEARTBEAT_ISSUE_JSON_CACHE_DIR}/${issue_id}.json"
|
|
37
|
+
if [[ -f "${cache_file}" ]]; then
|
|
38
|
+
cat "${cache_file}"
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
issue_json="$(flow_github_issue_view_json "$REPO_SLUG" "$issue_id" 2>/dev/null || true)"
|
|
43
|
+
printf '%s' "${issue_json}" >"${cache_file}"
|
|
44
|
+
printf '%s\n' "${issue_json}"
|
|
45
|
+
}
|
|
23
46
|
|
|
24
47
|
heartbeat_open_agent_pr_issue_ids() {
|
|
25
|
-
|
|
26
|
-
|
|
48
|
+
local pr_issue_ids_json=""
|
|
49
|
+
pr_issue_ids_json="$(
|
|
50
|
+
flow_github_pr_list_json "$REPO_SLUG" open 100 \
|
|
51
|
+
| jq --argjson agentPrPrefixes "${AGENT_PR_PREFIXES_JSON}" --arg handoffLabel "${AGENT_PR_HANDOFF_LABEL}" --arg branchIssueRegex "${AGENT_PR_ISSUE_CAPTURE_REGEX}" '
|
|
27
52
|
map(
|
|
28
53
|
. as $pr
|
|
29
54
|
| select(
|
|
@@ -48,6 +73,13 @@ heartbeat_open_agent_pr_issue_ids() {
|
|
|
48
73
|
)
|
|
49
74
|
| unique
|
|
50
75
|
'
|
|
76
|
+
)"
|
|
77
|
+
|
|
78
|
+
if [[ -z "${pr_issue_ids_json:-}" ]]; then
|
|
79
|
+
printf '[]\n'
|
|
80
|
+
else
|
|
81
|
+
printf '%s\n' "${pr_issue_ids_json}"
|
|
82
|
+
fi
|
|
51
83
|
}
|
|
52
84
|
|
|
53
85
|
heartbeat_list_ready_issue_ids() {
|
|
@@ -124,7 +156,7 @@ heartbeat_issue_blocked_recovery_reason() {
|
|
|
124
156
|
return 0
|
|
125
157
|
fi
|
|
126
158
|
|
|
127
|
-
issue_json="$(
|
|
159
|
+
issue_json="$(heartbeat_issue_json_cached "$issue_id")"
|
|
128
160
|
if [[ -z "${issue_json:-}" ]]; then
|
|
129
161
|
return 0
|
|
130
162
|
fi
|
|
@@ -163,6 +195,8 @@ if (explicitFailureReason) {
|
|
|
163
195
|
reason = 'scope-guard-blocked';
|
|
164
196
|
} else if (/verification guard/i.test(body)) {
|
|
165
197
|
reason = 'verification-guard-blocked';
|
|
198
|
+
} else if (/localization guard/i.test(body) || /^# Blocker: Localization requirements were not satisfied$/im.test(body)) {
|
|
199
|
+
reason = 'localization-guard-blocked';
|
|
166
200
|
} else if (/missing referenced OpenSpec paths/i.test(body)) {
|
|
167
201
|
reason = 'missing-openspec-paths';
|
|
168
202
|
} else if (/superseded by focused follow-up issues/i.test(body)) {
|
|
@@ -248,7 +282,7 @@ heartbeat_issue_is_heavy() {
|
|
|
248
282
|
heartbeat_issue_is_recurring() {
|
|
249
283
|
local issue_id="${1:?issue id required}"
|
|
250
284
|
local issue_json
|
|
251
|
-
issue_json="$(
|
|
285
|
+
issue_json="$(heartbeat_issue_json_cached "$issue_id")"
|
|
252
286
|
if [[ -n "$issue_json" ]] && jq -e 'any(.labels[]?; .name == "agent-keep-open")' >/dev/null <<<"$issue_json"; then
|
|
253
287
|
printf 'yes\n'
|
|
254
288
|
else
|
|
@@ -259,7 +293,7 @@ heartbeat_issue_is_recurring() {
|
|
|
259
293
|
heartbeat_issue_schedule_interval_seconds() {
|
|
260
294
|
local issue_id="${1:?issue id required}"
|
|
261
295
|
local issue_json issue_body
|
|
262
|
-
issue_json="$(
|
|
296
|
+
issue_json="$(heartbeat_issue_json_cached "$issue_id")"
|
|
263
297
|
if [[ -z "$issue_json" ]]; then
|
|
264
298
|
issue_json='{}'
|
|
265
299
|
fi
|
|
@@ -282,7 +316,7 @@ EOF
|
|
|
282
316
|
heartbeat_issue_schedule_token() {
|
|
283
317
|
local issue_id="${1:?issue id required}"
|
|
284
318
|
local issue_json issue_body
|
|
285
|
-
issue_json="$(
|
|
319
|
+
issue_json="$(heartbeat_issue_json_cached "$issue_id")"
|
|
286
320
|
if [[ -z "$issue_json" ]]; then
|
|
287
321
|
issue_json='{}'
|
|
288
322
|
fi
|
|
@@ -321,7 +355,7 @@ heartbeat_issue_is_scheduled() {
|
|
|
321
355
|
heartbeat_issue_is_exclusive() {
|
|
322
356
|
local issue_id="${1:?issue id required}"
|
|
323
357
|
local issue_json
|
|
324
|
-
issue_json="$(
|
|
358
|
+
issue_json="$(heartbeat_issue_json_cached "$issue_id")"
|
|
325
359
|
if [[ -n "$issue_json" ]] && jq -e --arg exclusiveLabel "${AGENT_EXCLUSIVE_LABEL}" 'any(.labels[]?; .name == $exclusiveLabel)' >/dev/null <<<"$issue_json"; then
|
|
326
360
|
printf 'yes\n'
|
|
327
361
|
else
|
|
@@ -379,7 +413,7 @@ heartbeat_sync_issue_labels() {
|
|
|
379
413
|
local -a add_args=()
|
|
380
414
|
local -a update_args=()
|
|
381
415
|
|
|
382
|
-
issue_json="$(
|
|
416
|
+
issue_json="$(heartbeat_issue_json_cached "$issue_id")"
|
|
383
417
|
if [[ -z "$issue_json" ]]; then
|
|
384
418
|
return 0
|
|
385
419
|
fi
|
|
@@ -501,6 +535,52 @@ heartbeat_issue_resident_worker_key() {
|
|
|
501
535
|
flow_resident_issue_lane_key "${CODING_WORKER}" "safe" "${lane_kind}" "${lane_value}"
|
|
502
536
|
}
|
|
503
537
|
|
|
538
|
+
heartbeat_pending_issue_launch_pid() {
|
|
539
|
+
local issue_id="${1:?issue id required}"
|
|
540
|
+
local pending_file pid=""
|
|
541
|
+
|
|
542
|
+
pending_file="${PENDING_LAUNCH_DIR}/issue-${issue_id}.pid"
|
|
543
|
+
[[ -f "${pending_file}" ]] || return 1
|
|
544
|
+
|
|
545
|
+
pid="$(tr -d '[:space:]' <"${pending_file}" 2>/dev/null || true)"
|
|
546
|
+
[[ "${pid}" =~ ^[0-9]+$ ]] || return 1
|
|
547
|
+
kill -0 "${pid}" 2>/dev/null || return 1
|
|
548
|
+
|
|
549
|
+
printf '%s\n' "${pid}"
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
heartbeat_pending_resident_lane_launch_issue_id() {
|
|
553
|
+
local issue_id="${1:?issue id required}"
|
|
554
|
+
local worker_key=""
|
|
555
|
+
local pending_file=""
|
|
556
|
+
local candidate_issue_id=""
|
|
557
|
+
local candidate_worker_key=""
|
|
558
|
+
|
|
559
|
+
worker_key="$(heartbeat_issue_resident_worker_key "${issue_id}")"
|
|
560
|
+
[[ -n "${worker_key}" ]] || return 1
|
|
561
|
+
[[ -d "${PENDING_LAUNCH_DIR}" ]] || return 1
|
|
562
|
+
|
|
563
|
+
for pending_file in "${PENDING_LAUNCH_DIR}"/issue-*.pid; do
|
|
564
|
+
[[ -f "${pending_file}" ]] || continue
|
|
565
|
+
candidate_issue_id="${pending_file##*/issue-}"
|
|
566
|
+
candidate_issue_id="${candidate_issue_id%.pid}"
|
|
567
|
+
[[ -n "${candidate_issue_id}" ]] || continue
|
|
568
|
+
if ! heartbeat_pending_issue_launch_pid "${candidate_issue_id}" >/dev/null 2>&1; then
|
|
569
|
+
rm -f "${pending_file}" 2>/dev/null || true
|
|
570
|
+
continue
|
|
571
|
+
fi
|
|
572
|
+
if [[ "$(heartbeat_issue_uses_resident_loop "${candidate_issue_id}")" != "yes" ]]; then
|
|
573
|
+
continue
|
|
574
|
+
fi
|
|
575
|
+
candidate_worker_key="$(heartbeat_issue_resident_worker_key "${candidate_issue_id}")"
|
|
576
|
+
[[ -n "${candidate_worker_key}" && "${candidate_worker_key}" == "${worker_key}" ]] || continue
|
|
577
|
+
printf '%s\n' "${candidate_issue_id}"
|
|
578
|
+
return 0
|
|
579
|
+
done
|
|
580
|
+
|
|
581
|
+
return 1
|
|
582
|
+
}
|
|
583
|
+
|
|
504
584
|
heartbeat_live_issue_controller_for_lane() {
|
|
505
585
|
local issue_id="${1:?issue id required}"
|
|
506
586
|
local worker_key=""
|
|
@@ -548,11 +628,20 @@ heartbeat_enqueue_issue_for_resident_controller() {
|
|
|
548
628
|
|
|
549
629
|
heartbeat_start_issue_worker() {
|
|
550
630
|
local issue_id="${1:?issue id required}"
|
|
631
|
+
local pending_lane_issue_id=""
|
|
551
632
|
if [[ "$(heartbeat_issue_uses_resident_loop "${issue_id}")" == "yes" ]]; then
|
|
552
633
|
if heartbeat_enqueue_issue_for_live_resident_lane "${issue_id}"; then
|
|
553
634
|
printf 'LAUNCH_MODE=resident-lease\n'
|
|
554
635
|
return 0
|
|
555
636
|
fi
|
|
637
|
+
pending_lane_issue_id="$(heartbeat_pending_resident_lane_launch_issue_id "${issue_id}" || true)"
|
|
638
|
+
if [[ -n "${pending_lane_issue_id}" ]]; then
|
|
639
|
+
if [[ "${pending_lane_issue_id}" != "${issue_id}" ]]; then
|
|
640
|
+
flow_resident_issue_enqueue "${CONFIG_YAML}" "${issue_id}" "heartbeat-pending-lane" >/dev/null
|
|
641
|
+
fi
|
|
642
|
+
printf 'LAUNCH_MODE=resident-pending-lane\n'
|
|
643
|
+
return 0
|
|
644
|
+
fi
|
|
556
645
|
if heartbeat_enqueue_issue_for_resident_controller "${issue_id}"; then
|
|
557
646
|
printf 'LAUNCH_MODE=resident-lease\n'
|
|
558
647
|
return 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-control-plane",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
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": {
|
|
@@ -21,11 +21,17 @@
|
|
|
21
21
|
"README.md",
|
|
22
22
|
"SKILL.md",
|
|
23
23
|
"assets/workflow-catalog.json",
|
|
24
|
-
"bin",
|
|
24
|
+
"bin/agent-control-plane",
|
|
25
|
+
"bin/issue-resource-class.sh",
|
|
26
|
+
"bin/label-follow-up-issues.sh",
|
|
27
|
+
"bin/pr-risk.sh",
|
|
28
|
+
"bin/sync-pr-labels.sh",
|
|
25
29
|
"hooks",
|
|
26
30
|
"npm/bin",
|
|
27
31
|
"references",
|
|
28
32
|
"tools/bin",
|
|
33
|
+
"!tools/bin/audit-*.sh",
|
|
34
|
+
"!tools/bin/check-skill-contracts.sh",
|
|
29
35
|
"tools/dashboard/app.js",
|
|
30
36
|
"tools/dashboard/dashboard_snapshot.py",
|
|
31
37
|
"tools/dashboard/index.html",
|
package/references/commands.md
CHANGED
|
@@ -76,6 +76,7 @@ tools/bin/profile-smoke.sh
|
|
|
76
76
|
tools/bin/test-smoke.sh
|
|
77
77
|
tools/bin/profile-adopt.sh --profile-id <id>
|
|
78
78
|
tools/bin/project-runtimectl.sh status --profile-id <id>
|
|
79
|
+
tools/bin/project-runtimectl.sh sync --profile-id <id>
|
|
79
80
|
tools/bin/project-runtimectl.sh stop --profile-id <id>
|
|
80
81
|
tools/bin/project-runtimectl.sh start --profile-id <id>
|
|
81
82
|
tools/bin/project-runtimectl.sh restart --profile-id <id>
|
|
@@ -15,6 +15,7 @@ shared_agent_home="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
|
15
15
|
repo_root="${AGENT_PROJECT_REPO_ROOT:-}"
|
|
16
16
|
runs_root="${AGENT_PROJECT_RUNS_ROOT:-}"
|
|
17
17
|
history_root="${AGENT_PROJECT_HISTORY_ROOT:-}"
|
|
18
|
+
state_root="${AGENT_PROJECT_STATE_ROOT:-${F_LOSNING_STATE_ROOT:-}}"
|
|
18
19
|
session=""
|
|
19
20
|
worktree_path=""
|
|
20
21
|
mode="generic"
|
|
@@ -30,6 +31,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
30
31
|
--repo-root) repo_root="${2:-}"; shift 2 ;;
|
|
31
32
|
--runs-root) runs_root="${2:-}"; shift 2 ;;
|
|
32
33
|
--history-root) history_root="${2:-}"; shift 2 ;;
|
|
34
|
+
--state-root) state_root="${2:-}"; shift 2 ;;
|
|
33
35
|
--session) session="${2:-}"; shift 2 ;;
|
|
34
36
|
--worktree) worktree_path="${2:-}"; shift 2 ;;
|
|
35
37
|
--mode) mode="${2:-}"; shift 2 ;;
|
|
@@ -141,6 +143,131 @@ path_is_within_root() {
|
|
|
141
143
|
[[ "$target_canonical" == "$root_canonical" || "$target_canonical" == "$root_canonical"/* ]]
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
derive_state_root() {
|
|
147
|
+
if [[ -n "${state_root}" && -d "${state_root}" ]]; then
|
|
148
|
+
printf '%s\n' "${state_root}"
|
|
149
|
+
return 0
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if [[ -n "${runs_root}" && -d "${runs_root}" ]]; then
|
|
153
|
+
local candidate_root=""
|
|
154
|
+
candidate_root="$(cd "${runs_root}/.." 2>/dev/null && pwd -P || true)"
|
|
155
|
+
if [[ -n "${candidate_root}" && -d "${candidate_root}/state" ]]; then
|
|
156
|
+
printf '%s/state\n' "${candidate_root}"
|
|
157
|
+
return 0
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
resident_worktree_protected() {
|
|
165
|
+
local candidate_path="${1:-}"
|
|
166
|
+
local resolved_candidate=""
|
|
167
|
+
local resolved_state_root=""
|
|
168
|
+
local metadata_file=""
|
|
169
|
+
local resident_worktree=""
|
|
170
|
+
local resident_realpath=""
|
|
171
|
+
local resolved_resident=""
|
|
172
|
+
|
|
173
|
+
[[ -n "${candidate_path}" && -d "${candidate_path}" ]] || return 1
|
|
174
|
+
resolved_candidate="$(canonicalize_existing_dir "${candidate_path}" || true)"
|
|
175
|
+
[[ -n "${resolved_candidate}" ]] || return 1
|
|
176
|
+
|
|
177
|
+
resolved_state_root="$(derive_state_root || true)"
|
|
178
|
+
[[ -n "${resolved_state_root}" && -d "${resolved_state_root}/resident-workers/issues" ]] || return 1
|
|
179
|
+
|
|
180
|
+
for metadata_file in "${resolved_state_root}"/resident-workers/issues/*/metadata.env; do
|
|
181
|
+
[[ -f "${metadata_file}" ]] || continue
|
|
182
|
+
resident_worktree=""
|
|
183
|
+
resident_realpath=""
|
|
184
|
+
set +u
|
|
185
|
+
set -a
|
|
186
|
+
# shellcheck source=/dev/null
|
|
187
|
+
source "${metadata_file}"
|
|
188
|
+
set +a
|
|
189
|
+
set -u
|
|
190
|
+
resident_worktree="${WORKTREE_REALPATH:-${WORKTREE:-}}"
|
|
191
|
+
[[ -n "${resident_worktree}" && -d "${resident_worktree}" ]] || continue
|
|
192
|
+
resolved_resident="$(canonicalize_existing_dir "${resident_worktree}" || true)"
|
|
193
|
+
[[ -n "${resolved_resident}" ]] || continue
|
|
194
|
+
if [[ "${resolved_candidate}" == "${resolved_resident}" ]]; then
|
|
195
|
+
return 0
|
|
196
|
+
fi
|
|
197
|
+
done
|
|
198
|
+
|
|
199
|
+
return 1
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
active_resident_run_worktree_protected() {
|
|
203
|
+
local candidate_path="${1:-}"
|
|
204
|
+
local resolved_candidate=""
|
|
205
|
+
local run_meta=""
|
|
206
|
+
local run_dir=""
|
|
207
|
+
local session_name=""
|
|
208
|
+
local resident_enabled=""
|
|
209
|
+
local resident_worktree=""
|
|
210
|
+
local runner_state_file=""
|
|
211
|
+
local runner_state=""
|
|
212
|
+
local active_session="false"
|
|
213
|
+
local resolved_resident=""
|
|
214
|
+
|
|
215
|
+
[[ -n "${candidate_path}" && -d "${candidate_path}" ]] || return 1
|
|
216
|
+
[[ -n "${runs_root}" && -d "${runs_root}" ]] || return 1
|
|
217
|
+
|
|
218
|
+
resolved_candidate="$(canonicalize_existing_dir "${candidate_path}" || true)"
|
|
219
|
+
[[ -n "${resolved_candidate}" ]] || return 1
|
|
220
|
+
|
|
221
|
+
for run_meta in "${runs_root}"/*/run.env; do
|
|
222
|
+
[[ -f "${run_meta}" ]] || continue
|
|
223
|
+
|
|
224
|
+
session_name=""
|
|
225
|
+
resident_enabled=""
|
|
226
|
+
resident_worktree=""
|
|
227
|
+
runner_state=""
|
|
228
|
+
active_session="false"
|
|
229
|
+
|
|
230
|
+
set +u
|
|
231
|
+
set -a
|
|
232
|
+
# shellcheck source=/dev/null
|
|
233
|
+
source "${run_meta}"
|
|
234
|
+
set +a
|
|
235
|
+
set -u
|
|
236
|
+
|
|
237
|
+
resident_enabled="${RESIDENT_WORKER_ENABLED:-no}"
|
|
238
|
+
[[ "${resident_enabled}" == "yes" ]] || continue
|
|
239
|
+
|
|
240
|
+
resident_worktree="${WORKTREE_REALPATH:-${WORKTREE:-}}"
|
|
241
|
+
[[ -n "${resident_worktree}" && -d "${resident_worktree}" ]] || continue
|
|
242
|
+
resolved_resident="$(canonicalize_existing_dir "${resident_worktree}" || true)"
|
|
243
|
+
[[ -n "${resolved_resident}" ]] || continue
|
|
244
|
+
[[ "${resolved_candidate}" == "${resolved_resident}" ]] || continue
|
|
245
|
+
|
|
246
|
+
run_dir="$(dirname "${run_meta}")"
|
|
247
|
+
runner_state_file="${run_dir}/runner.env"
|
|
248
|
+
if [[ -f "${runner_state_file}" ]]; then
|
|
249
|
+
set +u
|
|
250
|
+
set -a
|
|
251
|
+
# shellcheck source=/dev/null
|
|
252
|
+
source "${runner_state_file}"
|
|
253
|
+
set +a
|
|
254
|
+
set -u
|
|
255
|
+
runner_state="${RUNNER_STATE:-}"
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
session_name="${SESSION:-}"
|
|
259
|
+
if [[ -n "${session_name}" ]] && tmux has-session -t "${session_name}" 2>/dev/null; then
|
|
260
|
+
active_session="true"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
if [[ "${runner_state}" == "running" || "${active_session}" == "true" ]]; then
|
|
264
|
+
return 0
|
|
265
|
+
fi
|
|
266
|
+
done
|
|
267
|
+
|
|
268
|
+
return 1
|
|
269
|
+
}
|
|
270
|
+
|
|
144
271
|
cleanup_with_branch_tool() {
|
|
145
272
|
local include_path="${1:-yes}"
|
|
146
273
|
local -a cleanup_args
|
|
@@ -185,6 +312,9 @@ cleanup_orphan_worktree_dir() {
|
|
|
185
312
|
|
|
186
313
|
if [[ "$active_tmux_session" == "true" ]]; then
|
|
187
314
|
cleanup_mode="deferred-active-session"
|
|
315
|
+
elif [[ "$skip_worktree_cleanup" != "true" && -n "${worktree_path}" ]] \
|
|
316
|
+
&& { resident_worktree_protected "${worktree_path}" || active_resident_run_worktree_protected "${worktree_path}"; }; then
|
|
317
|
+
cleanup_mode="protected-resident-worktree"
|
|
188
318
|
elif [[ "$skip_worktree_cleanup" != "true" && -n "$branch_name" ]]; then
|
|
189
319
|
if cleanup_output="$(cleanup_with_branch_tool yes 2>&1)"; then
|
|
190
320
|
cleanup_mode="branch"
|
|
@@ -207,6 +337,9 @@ elif [[ "$skip_worktree_cleanup" != "true" && -n "$worktree_path" ]] && git -C "
|
|
|
207
337
|
git -C "$repo_root" worktree remove "$worktree_path" --force || true
|
|
208
338
|
git -C "$repo_root" worktree prune
|
|
209
339
|
cleanup_mode="worktree"
|
|
340
|
+
elif [[ "$skip_worktree_cleanup" != "true" ]] && cleanup_orphan_worktree_dir; then
|
|
341
|
+
orphan_fallback_used="true"
|
|
342
|
+
cleanup_mode="orphan-worktree"
|
|
210
343
|
elif [[ "$skip_worktree_cleanup" == "true" ]]; then
|
|
211
344
|
cleanup_mode="archived-only"
|
|
212
345
|
fi
|