agent-control-plane 0.4.9 → 0.6.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 (80) hide show
  1. package/README.md +72 -9
  2. package/npm/bin/agent-control-plane.js +1 -1
  3. package/package.json +39 -33
  4. package/tools/bin/debug-session.sh +106 -0
  5. package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
  6. package/tools/bin/flow-runtime-doctor.sh +5 -1
  7. package/tools/bin/install-project-systemd.sh +255 -0
  8. package/tools/bin/project-runtimectl.sh +45 -0
  9. package/tools/bin/project-systemd-bootstrap.sh +74 -0
  10. package/tools/bin/uninstall-project-systemd.sh +87 -0
  11. package/tools/dashboard/app.js +198 -5
  12. package/tools/dashboard/issue_queue_state.py +101 -0
  13. package/tools/dashboard/server.py +123 -1
  14. package/tools/dashboard/styles.css +526 -455
  15. package/tools/bin/agent-cleanup-worktree +0 -247
  16. package/tools/bin/agent-github-update-labels +0 -105
  17. package/tools/bin/agent-init-worktree +0 -216
  18. package/tools/bin/agent-project-archive-run +0 -52
  19. package/tools/bin/agent-project-capture-worker +0 -46
  20. package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
  21. package/tools/bin/agent-project-catch-up-merged-prs +0 -195
  22. package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
  23. package/tools/bin/agent-project-cleanup-session +0 -513
  24. package/tools/bin/agent-project-detached-launch +0 -127
  25. package/tools/bin/agent-project-heartbeat-loop +0 -1029
  26. package/tools/bin/agent-project-open-issue-worktree +0 -89
  27. package/tools/bin/agent-project-open-pr-worktree +0 -80
  28. package/tools/bin/agent-project-publish-issue-pr +0 -468
  29. package/tools/bin/agent-project-reconcile-issue-session +0 -1409
  30. package/tools/bin/agent-project-reconcile-pr-session +0 -1288
  31. package/tools/bin/agent-project-retry-state +0 -158
  32. package/tools/bin/agent-project-run-claude-session +0 -805
  33. package/tools/bin/agent-project-run-codex-resilient +0 -963
  34. package/tools/bin/agent-project-run-codex-session +0 -435
  35. package/tools/bin/agent-project-run-kilo-session +0 -369
  36. package/tools/bin/agent-project-run-ollama-session +0 -658
  37. package/tools/bin/agent-project-run-openclaw-session +0 -1309
  38. package/tools/bin/agent-project-run-opencode-session +0 -377
  39. package/tools/bin/agent-project-run-pi-session +0 -479
  40. package/tools/bin/agent-project-sync-anchor-repo +0 -139
  41. package/tools/bin/agent-project-sync-source-repo-main +0 -163
  42. package/tools/bin/agent-project-worker-status +0 -188
  43. package/tools/bin/branch-verification-guard.sh +0 -364
  44. package/tools/bin/capture-worker.sh +0 -18
  45. package/tools/bin/cleanup-worktree.sh +0 -52
  46. package/tools/bin/codex-quota +0 -31
  47. package/tools/bin/create-follow-up-issue.sh +0 -114
  48. package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
  49. package/tools/bin/issue-publish-localization-guard.sh +0 -142
  50. package/tools/bin/issue-publish-scope-guard.sh +0 -242
  51. package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
  52. package/tools/bin/issue-resource-class.sh +0 -12
  53. package/tools/bin/kick-scheduler.sh +0 -75
  54. package/tools/bin/label-follow-up-issues.sh +0 -14
  55. package/tools/bin/new-pr-worktree.sh +0 -50
  56. package/tools/bin/new-worktree.sh +0 -49
  57. package/tools/bin/pr-risk.sh +0 -12
  58. package/tools/bin/prepare-worktree.sh +0 -142
  59. package/tools/bin/provider-cooldown-state.sh +0 -204
  60. package/tools/bin/publish-issue-worker.sh +0 -31
  61. package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
  62. package/tools/bin/reconcile-issue-worker.sh +0 -34
  63. package/tools/bin/reconcile-pr-worker.sh +0 -34
  64. package/tools/bin/record-verification.sh +0 -71
  65. package/tools/bin/render-flow-config.sh +0 -98
  66. package/tools/bin/resident-issue-controller-lib.sh +0 -448
  67. package/tools/bin/retry-state.sh +0 -31
  68. package/tools/bin/reuse-issue-worktree.sh +0 -121
  69. package/tools/bin/run-codex-bypass.sh +0 -3
  70. package/tools/bin/run-codex-safe.sh +0 -3
  71. package/tools/bin/run-codex-task.sh +0 -280
  72. package/tools/bin/serve-dashboard.sh +0 -5
  73. package/tools/bin/start-issue-worker.sh +0 -943
  74. package/tools/bin/start-pr-fix-worker.sh +0 -528
  75. package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
  76. package/tools/bin/start-pr-review-worker.sh +0 -261
  77. package/tools/bin/start-resident-issue-loop.sh +0 -499
  78. package/tools/bin/update-github-labels.sh +0 -14
  79. package/tools/bin/worker-status.sh +0 -19
  80. package/tools/bin/workflow-catalog.sh +0 -77
@@ -1,513 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- usage() {
5
- cat <<'EOF'
6
- Usage:
7
- agent-project-cleanup-session --repo-root <path> [--runs-root <path> --history-root <path> --session <id>] [--worktree <path>] [--mode generic|issue|pr] [--remote <name>] [--remove-file <path>] [--keep-remote] [--allow-unmerged] [--skip-worktree-cleanup]
8
-
9
- Cleanup a project-lane worktree/branch using shared agent helpers, then archive
10
- the session run directory when one is provided.
11
- EOF
12
- }
13
-
14
- shared_agent_home="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
15
- repo_root="${AGENT_PROJECT_REPO_ROOT:-}"
16
- runs_root="${AGENT_PROJECT_RUNS_ROOT:-}"
17
- history_root="${AGENT_PROJECT_HISTORY_ROOT:-}"
18
- state_root="${AGENT_PROJECT_STATE_ROOT:-${F_LOSNING_STATE_ROOT:-}}"
19
- session=""
20
- worktree_path=""
21
- mode="generic"
22
- remote_name="origin"
23
- remove_file=""
24
- keep_remote="auto"
25
- allow_unmerged="auto"
26
- merged_base=""
27
- skip_worktree_cleanup="false"
28
-
29
- while [[ $# -gt 0 ]]; do
30
- case "$1" in
31
- --repo-root) repo_root="${2:-}"; shift 2 ;;
32
- --runs-root) runs_root="${2:-}"; shift 2 ;;
33
- --history-root) history_root="${2:-}"; shift 2 ;;
34
- --state-root) state_root="${2:-}"; shift 2 ;;
35
- --session) session="${2:-}"; shift 2 ;;
36
- --worktree) worktree_path="${2:-}"; shift 2 ;;
37
- --mode) mode="${2:-}"; shift 2 ;;
38
- --remote) remote_name="${2:-}"; shift 2 ;;
39
- --remove-file) remove_file="${2:-}"; shift 2 ;;
40
- --merged-base) merged_base="${2:-}"; shift 2 ;;
41
- --keep-remote) keep_remote="true"; shift ;;
42
- --allow-unmerged) allow_unmerged="true"; shift ;;
43
- --skip-worktree-cleanup) skip_worktree_cleanup="true"; shift ;;
44
- --help|-h) usage; exit 0 ;;
45
- *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
46
- esac
47
- done
48
-
49
- if [[ -z "$repo_root" ]]; then
50
- echo "--repo-root is required" >&2
51
- usage >&2
52
- exit 1
53
- fi
54
-
55
- case "$mode" in
56
- generic|issue|pr) ;;
57
- *)
58
- echo "--mode must be generic, issue, or pr" >&2
59
- exit 1
60
- ;;
61
- esac
62
-
63
- if [[ -z "$worktree_path" && -z "$session" ]]; then
64
- echo "Provide at least one of --worktree or --session" >&2
65
- usage >&2
66
- exit 1
67
- fi
68
-
69
- if [[ -n "$session" && ( -z "$runs_root" || -z "$history_root" ) ]]; then
70
- echo "--runs-root and --history-root are required when --session is set" >&2
71
- usage >&2
72
- exit 1
73
- fi
74
-
75
- if [[ "$keep_remote" == "auto" ]]; then
76
- case "$mode" in
77
- issue) keep_remote="true" ;;
78
- *) keep_remote="false" ;;
79
- esac
80
- fi
81
-
82
- if [[ "$allow_unmerged" == "auto" ]]; then
83
- case "$mode" in
84
- issue|pr) allow_unmerged="true" ;;
85
- *) allow_unmerged="false" ;;
86
- esac
87
- fi
88
-
89
- meta_file=""
90
- branch_name=""
91
- result_file=""
92
- cleanup_warning=""
93
- cleanup_status="0"
94
- cleanup_error=""
95
- cleanup_mode="noop"
96
- orphan_fallback_used="false"
97
- active_tmux_session="false"
98
- archived_dir=""
99
-
100
- if [[ -n "$session" ]]; then
101
- meta_file="${runs_root}/${session}/run.env"
102
- if [[ -f "$meta_file" ]]; then
103
- set -a
104
- # shellcheck source=/dev/null
105
- source "$meta_file"
106
- set +a
107
- branch_name="${BRANCH:-}"
108
- result_file="${RESULT_FILE:-}"
109
- if [[ -z "$worktree_path" ]]; then
110
- worktree_path="${WORKTREE:-}"
111
- fi
112
- fi
113
- fi
114
-
115
- if [[ -z "$remove_file" ]]; then
116
- remove_file="$result_file"
117
- fi
118
-
119
- if [[ -n "$session" ]] && tmux has-session -t "$session" 2>/dev/null; then
120
- active_tmux_session="true"
121
- fi
122
-
123
- cleanup_tool="${shared_agent_home}/tools/bin/agent-cleanup-worktree"
124
- archive_tool="${shared_agent_home}/tools/bin/agent-project-archive-run"
125
-
126
- canonicalize_existing_dir() {
127
- local target="${1:-}"
128
- [[ -n "$target" && -d "$target" ]] || return 1
129
- (
130
- cd "$target"
131
- pwd -P
132
- )
133
- }
134
-
135
- canonicalize_dir_or_parent_join() {
136
- local target="${1:-}"
137
- local resolved=""
138
-
139
- [[ -n "${target}" ]] || return 1
140
-
141
- resolved="$(canonicalize_existing_dir "${target}" || true)"
142
- if [[ -n "${resolved}" ]]; then
143
- printf '%s\n' "${resolved}"
144
- return 0
145
- fi
146
-
147
- (
148
- cd "$(dirname "${target}")" 2>/dev/null
149
- printf '%s/%s\n' "$(pwd -P)" "$(basename "${target}")"
150
- ) || return 1
151
- }
152
-
153
- path_is_within_root() {
154
- local target="${1:-}"
155
- local root="${2:-}"
156
- local target_canonical=""
157
- local root_canonical=""
158
-
159
- target_canonical="$(canonicalize_existing_dir "$target" || true)"
160
- root_canonical="$(canonicalize_existing_dir "$root" || true)"
161
- [[ -n "$target_canonical" && -n "$root_canonical" ]] || return 1
162
- [[ "$target_canonical" == "$root_canonical" || "$target_canonical" == "$root_canonical"/* ]]
163
- }
164
-
165
- derive_state_root() {
166
- if [[ -n "${state_root}" && -d "${state_root}" ]]; then
167
- printf '%s\n' "${state_root}"
168
- return 0
169
- fi
170
-
171
- if [[ -n "${runs_root}" && -d "${runs_root}" ]]; then
172
- local candidate_root=""
173
- candidate_root="$(cd "${runs_root}/.." 2>/dev/null && pwd -P || true)"
174
- if [[ -n "${candidate_root}" && -d "${candidate_root}/state" ]]; then
175
- printf '%s/state\n' "${candidate_root}"
176
- return 0
177
- fi
178
- fi
179
-
180
- return 1
181
- }
182
-
183
- resident_worktree_protected() {
184
- local candidate_path="${1:-}"
185
- local resolved_candidate=""
186
- local resolved_state_root=""
187
- local metadata_file=""
188
- local resident_worktree=""
189
- local resident_realpath=""
190
- local resolved_resident=""
191
- local resident_lane_kind=""
192
- local resident_last_status=""
193
-
194
- [[ -n "${candidate_path}" && -d "${candidate_path}" ]] || return 1
195
- resolved_candidate="$(canonicalize_existing_dir "${candidate_path}" || true)"
196
- [[ -n "${resolved_candidate}" ]] || return 1
197
-
198
- resolved_state_root="$(derive_state_root || true)"
199
- [[ -n "${resolved_state_root}" && -d "${resolved_state_root}/resident-workers/issues" ]] || return 1
200
-
201
- for metadata_file in "${resolved_state_root}"/resident-workers/issues/*/metadata.env; do
202
- [[ -f "${metadata_file}" ]] || continue
203
- resident_worktree=""
204
- resident_realpath=""
205
- resident_lane_kind=""
206
- resident_last_status=""
207
- set +u
208
- set -a
209
- # shellcheck source=/dev/null
210
- source "${metadata_file}"
211
- set +a
212
- set -u
213
- resident_lane_kind="${RESIDENT_LANE_KIND:-}"
214
- resident_last_status="${LAST_STATUS:-}"
215
- if [[ "${resident_lane_kind}" != "recurring" && "${resident_last_status}" != "running" ]]; then
216
- continue
217
- fi
218
- resident_worktree="${WORKTREE_REALPATH:-${WORKTREE:-}}"
219
- [[ -n "${resident_worktree}" && -d "${resident_worktree}" ]] || continue
220
- resolved_resident="$(canonicalize_existing_dir "${resident_worktree}" || true)"
221
- [[ -n "${resolved_resident}" ]] || continue
222
- if [[ "${resolved_candidate}" == "${resolved_resident}" ]]; then
223
- return 0
224
- fi
225
- done
226
-
227
- return 1
228
- }
229
-
230
- active_resident_run_worktree_protected() {
231
- local candidate_path="${1:-}"
232
- local resolved_candidate=""
233
- local run_meta=""
234
- local run_dir=""
235
- local session_name=""
236
- local resident_enabled=""
237
- local resident_worktree=""
238
- local runner_state_file=""
239
- local runner_state=""
240
- local active_session="false"
241
- local resolved_resident=""
242
-
243
- [[ -n "${candidate_path}" && -d "${candidate_path}" ]] || return 1
244
- [[ -n "${runs_root}" && -d "${runs_root}" ]] || return 1
245
-
246
- resolved_candidate="$(canonicalize_existing_dir "${candidate_path}" || true)"
247
- [[ -n "${resolved_candidate}" ]] || return 1
248
-
249
- for run_meta in "${runs_root}"/*/run.env; do
250
- [[ -f "${run_meta}" ]] || continue
251
-
252
- session_name=""
253
- resident_enabled=""
254
- resident_worktree=""
255
- runner_state=""
256
- active_session="false"
257
-
258
- set +u
259
- set -a
260
- # shellcheck source=/dev/null
261
- source "${run_meta}"
262
- set +a
263
- set -u
264
-
265
- resident_enabled="${RESIDENT_WORKER_ENABLED:-no}"
266
- [[ "${resident_enabled}" == "yes" ]] || continue
267
-
268
- resident_worktree="${WORKTREE_REALPATH:-${WORKTREE:-}}"
269
- [[ -n "${resident_worktree}" && -d "${resident_worktree}" ]] || continue
270
- resolved_resident="$(canonicalize_existing_dir "${resident_worktree}" || true)"
271
- [[ -n "${resolved_resident}" ]] || continue
272
- [[ "${resolved_candidate}" == "${resolved_resident}" ]] || continue
273
-
274
- run_dir="$(dirname "${run_meta}")"
275
- runner_state_file="${run_dir}/runner.env"
276
- if [[ -f "${runner_state_file}" ]]; then
277
- set +u
278
- set -a
279
- # shellcheck source=/dev/null
280
- source "${runner_state_file}"
281
- set +a
282
- set -u
283
- runner_state="${RUNNER_STATE:-}"
284
- fi
285
-
286
- session_name="${SESSION:-}"
287
- if [[ -n "${session_name}" ]] && tmux has-session -t "${session_name}" 2>/dev/null; then
288
- active_session="true"
289
- fi
290
-
291
- if [[ "${runner_state}" == "running" || "${active_session}" == "true" ]]; then
292
- return 0
293
- fi
294
- done
295
-
296
- return 1
297
- }
298
-
299
- clear_resident_worktree_realpath_references() {
300
- local removed_path="${1:-}"
301
- local resolved_removed=""
302
- local resolved_state_root=""
303
- local metadata_file=""
304
- local resident_realpath=""
305
- local tmp_file=""
306
- local metadata_worktree_realpath=""
307
-
308
- [[ -n "${removed_path}" ]] || return 0
309
-
310
- resolved_removed="$(canonicalize_dir_or_parent_join "${removed_path}" || true)"
311
- [[ -n "${resolved_removed}" ]] || return 0
312
-
313
- resolved_state_root="$(derive_state_root || true)"
314
- [[ -n "${resolved_state_root}" && -d "${resolved_state_root}/resident-workers/issues" ]] || return 0
315
-
316
- for metadata_file in "${resolved_state_root}"/resident-workers/issues/*/metadata.env; do
317
- [[ -f "${metadata_file}" ]] || continue
318
- metadata_worktree_realpath=""
319
- set +u
320
- set -a
321
- # shellcheck source=/dev/null
322
- source "${metadata_file}"
323
- set +a
324
- set -u
325
- resident_realpath="${WORKTREE_REALPATH:-${metadata_worktree_realpath:-}}"
326
- [[ -n "${resident_realpath}" ]] || continue
327
- resident_realpath="$(canonicalize_dir_or_parent_join "${resident_realpath}" || true)"
328
- [[ -n "${resident_realpath}" ]] || continue
329
- if [[ "${resident_realpath}" != "${resolved_removed}" ]]; then
330
- continue
331
- fi
332
-
333
- tmp_file="${metadata_file}.tmp.$$"
334
- awk '
335
- BEGIN { replaced = 0 }
336
- /^WORKTREE_REALPATH=/ {
337
- print "WORKTREE_REALPATH='\'''\''"
338
- replaced = 1
339
- next
340
- }
341
- { print }
342
- END {
343
- if (replaced == 0) {
344
- print "WORKTREE_REALPATH='\'''\''"
345
- }
346
- }
347
- ' "${metadata_file}" >"${tmp_file}"
348
- mv "${tmp_file}" "${metadata_file}"
349
- done
350
- }
351
-
352
- cleanup_with_branch_tool() {
353
- local include_path="${1:-yes}"
354
- local -a cleanup_args
355
-
356
- cleanup_args=(--branch "$branch_name")
357
- if [[ -n "$remote_name" ]]; then
358
- cleanup_args+=(--remote "$remote_name")
359
- fi
360
- if [[ -n "$merged_base" ]]; then
361
- cleanup_args+=(--merged-base "$merged_base")
362
- fi
363
- if [[ "$include_path" == "yes" && -n "$worktree_path" && -d "$worktree_path" ]]; then
364
- cleanup_args+=(--path "$worktree_path")
365
- fi
366
- if [[ "$keep_remote" == "true" ]]; then
367
- cleanup_args+=(--keep-remote)
368
- fi
369
- if [[ "$allow_unmerged" == "true" ]]; then
370
- cleanup_args+=(--allow-unmerged)
371
- fi
372
-
373
- (
374
- cd "$repo_root"
375
- "$cleanup_tool" "${cleanup_args[@]}"
376
- )
377
- }
378
-
379
- cleanup_orphan_worktree_dir() {
380
- local worktree_root="${AGENT_PROJECT_WORKTREE_ROOT:-${F_LOSNING_WORKTREE_ROOT:-}}"
381
- local worktree_canonical=""
382
-
383
- [[ -n "$worktree_path" && -d "$worktree_path" ]] || return 1
384
- [[ ! -e "$worktree_path/.git" ]] || return 1
385
- [[ -n "$worktree_root" ]] || return 1
386
- path_is_within_root "$worktree_path" "$worktree_root" || return 1
387
-
388
- worktree_canonical="$(canonicalize_existing_dir "$worktree_path" || true)"
389
- [[ -n "$worktree_canonical" ]] || return 1
390
- rm -rf "$worktree_canonical"
391
- git -C "$repo_root" worktree prune >/dev/null 2>&1 || true
392
- }
393
-
394
- worktree_path_is_registered() {
395
- local candidate_path="${1:-}"
396
- [[ -n "${candidate_path}" ]] || return 1
397
-
398
- git -C "$repo_root" worktree list --porcelain 2>/dev/null \
399
- | grep -F -x -q -- "worktree ${candidate_path}"
400
- }
401
-
402
- write_cleanup_warning_artifact() {
403
- local target_dir=""
404
- local notice_file=""
405
- local cleanup_error_line=""
406
- local recorded_at=""
407
-
408
- [[ "${cleanup_status}" != "0" ]] || return 0
409
-
410
- if [[ -n "${archived_dir}" && -d "${archived_dir}" ]]; then
411
- target_dir="${archived_dir}"
412
- elif [[ -n "${session}" && -n "${runs_root}" && -d "${runs_root}/${session}" ]]; then
413
- target_dir="${runs_root}/${session}"
414
- fi
415
-
416
- [[ -n "${target_dir}" && -d "${target_dir}" ]] || return 0
417
-
418
- cleanup_error_line="$(printf '%s' "${cleanup_error}" | tr '\n' ' ' | sed 's/ */ /g')"
419
- recorded_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
420
- notice_file="${target_dir}/cleanup-warning.txt"
421
- {
422
- printf 'recorded_at=%s\n' "${recorded_at}"
423
- printf 'session=%s\n' "${session}"
424
- printf 'mode=%s\n' "${mode}"
425
- printf 'worktree=%s\n' "${worktree_path}"
426
- printf 'branch=%s\n' "${branch_name}"
427
- printf 'cleanup_mode=%s\n' "${cleanup_mode}"
428
- printf 'cleanup_status=%s\n' "${cleanup_status}"
429
- if [[ -n "${cleanup_error_line}" ]]; then
430
- printf 'cleanup_error=%s\n' "${cleanup_error_line}"
431
- fi
432
- } >"${notice_file}"
433
- }
434
-
435
- if [[ "$active_tmux_session" == "true" ]]; then
436
- cleanup_mode="deferred-active-session"
437
- elif [[ "$skip_worktree_cleanup" != "true" && -n "${worktree_path}" ]] \
438
- && { resident_worktree_protected "${worktree_path}" || active_resident_run_worktree_protected "${worktree_path}"; }; then
439
- cleanup_mode="protected-resident-worktree"
440
- elif [[ "$skip_worktree_cleanup" != "true" && -n "$branch_name" ]]; then
441
- if cleanup_output="$(cleanup_with_branch_tool yes 2>&1)"; then
442
- cleanup_mode="branch"
443
- else
444
- cleanup_status="$?"
445
- cleanup_error="${cleanup_output}"
446
- if cleanup_orphan_worktree_dir; then
447
- orphan_fallback_used="true"
448
- if cleanup_retry_output="$(cleanup_with_branch_tool no 2>&1)"; then
449
- cleanup_mode="orphan-worktree"
450
- cleanup_status="0"
451
- cleanup_warning="${cleanup_error}"
452
- else
453
- cleanup_status="$?"
454
- cleanup_error="${cleanup_error}"$'\n'"${cleanup_retry_output}"
455
- fi
456
- fi
457
- fi
458
- elif [[ "$skip_worktree_cleanup" != "true" && -n "$worktree_path" ]] && worktree_path_is_registered "$worktree_path"; then
459
- git -C "$repo_root" worktree remove "$worktree_path" --force || true
460
- git -C "$repo_root" worktree prune
461
- cleanup_mode="worktree"
462
- elif [[ "$skip_worktree_cleanup" != "true" ]] && cleanup_orphan_worktree_dir; then
463
- orphan_fallback_used="true"
464
- cleanup_mode="orphan-worktree"
465
- elif [[ "$skip_worktree_cleanup" == "true" ]]; then
466
- cleanup_mode="archived-only"
467
- fi
468
-
469
- archive_output=""
470
- if [[ -n "$session" && "$active_tmux_session" != "true" ]]; then
471
- archive_output="$(
472
- "$archive_tool" \
473
- --runs-root "$runs_root" \
474
- --history-root "$history_root" \
475
- --session "$session" \
476
- --remove-file "${remove_file:-}"
477
- )"
478
- archived_dir="$(awk -F= '/^ARCHIVED_DIR=/{print substr($0, index($0, "=") + 1); exit}' <<<"${archive_output}")"
479
- fi
480
-
481
- if [[ "$skip_worktree_cleanup" != "true" && -n "$worktree_path" && ! -d "$worktree_path" ]]; then
482
- clear_resident_worktree_realpath_references "$worktree_path"
483
- fi
484
-
485
- write_cleanup_warning_artifact
486
-
487
- printf 'SESSION=%s\n' "$session"
488
- printf 'MODE=%s\n' "$mode"
489
- printf 'WORKTREE=%s\n' "$worktree_path"
490
- printf 'BRANCH=%s\n' "$branch_name"
491
- printf 'KEEP_REMOTE=%s\n' "$keep_remote"
492
- printf 'ALLOW_UNMERGED=%s\n' "$allow_unmerged"
493
- printf 'SKIP_WORKTREE_CLEANUP=%s\n' "$skip_worktree_cleanup"
494
- printf 'ACTIVE_TMUX_SESSION=%s\n' "$active_tmux_session"
495
- printf 'CLEANUP_MODE=%s\n' "$cleanup_mode"
496
- printf 'ORPHAN_FALLBACK_USED=%s\n' "$orphan_fallback_used"
497
- printf 'CLEANUP_STATUS=%s\n' "$cleanup_status"
498
- if [[ -n "$meta_file" ]]; then
499
- printf 'META_FILE=%s\n' "$meta_file"
500
- fi
501
- if [[ -n "$archive_output" ]]; then
502
- printf '%s\n' "$archive_output"
503
- fi
504
- if [[ -n "$cleanup_warning" ]]; then
505
- printf 'CLEANUP_WARNING=%s\n' "$(printf '%s' "$cleanup_warning" | tr '\n' ' ' | sed 's/ */ /g')"
506
- fi
507
- if [[ "$cleanup_status" != "0" && -n "$cleanup_error" ]]; then
508
- printf 'CLEANUP_ERROR=%s\n' "$(printf '%s' "$cleanup_error" | tr '\n' ' ' | sed 's/ */ /g')"
509
- fi
510
-
511
- if [[ "$cleanup_status" != "0" ]]; then
512
- exit "$cleanup_status"
513
- fi
@@ -1,127 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
- # shellcheck source=/dev/null
6
- source "${SCRIPT_DIR}/flow-config-lib.sh"
7
- CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
8
- STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
9
- AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
10
- REPO_ROOT="$(flow_resolve_repo_root "${CONFIG_YAML}")"
11
-
12
- usage() {
13
- cat <<'EOF'
14
- Usage:
15
- agent-project-detached-launch [--pending-key <kind-id>] <name> <command> [args...]
16
-
17
- Detach a worker bootstrap command from the current heartbeat process so the
18
- heartbeat can keep launching additional workers without waiting for expensive
19
- worktree/bootstrap/setup steps to finish.
20
- EOF
21
- }
22
-
23
- pending_key=""
24
-
25
- while [[ $# -gt 0 ]]; do
26
- case "$1" in
27
- --pending-key)
28
- pending_key="${2:-}"
29
- shift 2
30
- ;;
31
- --help|-h)
32
- usage
33
- exit 0
34
- ;;
35
- *)
36
- break
37
- ;;
38
- esac
39
- done
40
-
41
- if [[ $# -lt 2 ]]; then
42
- usage >&2
43
- exit 1
44
- fi
45
-
46
- launch_name="${1:-}"
47
- shift
48
-
49
- if [[ -z "${launch_name}" ]]; then
50
- usage >&2
51
- exit 1
52
- fi
53
-
54
- log_root="${ACP_LAUNCHER_LOG_ROOT:-${F_LOSNING_LAUNCHER_LOG_ROOT:-${STATE_ROOT}/launcher-logs}}"
55
- mkdir -p "${log_root}"
56
-
57
- safe_name="$(printf '%s' "${launch_name}" | tr '/[:space:]' '__' | tr -cd '[:alnum:]_.-')"
58
- if [[ -z "${safe_name}" ]]; then
59
- safe_name="launch"
60
- fi
61
-
62
- timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
63
- log_file="${log_root}/${timestamp}-${safe_name}.log"
64
- pending_file=""
65
- pending_dir="${ACP_PENDING_LAUNCH_DIR:-${F_LOSNING_PENDING_LAUNCH_DIR:-${STATE_ROOT}/pending-launches}}"
66
- if [[ -n "${pending_key}" ]]; then
67
- mkdir -p "${pending_dir}"
68
- pending_file="${pending_dir}/${pending_key}.pid"
69
- fi
70
-
71
- launch_cwd="${ACP_LAUNCH_CWD:-${F_LOSNING_LAUNCH_CWD:-}}"
72
- if [[ -z "${launch_cwd}" ]]; then
73
- for candidate in "${AGENT_REPO_ROOT}" "${REPO_ROOT}" "${STATE_ROOT}" "${HOME:-}" "/"; do
74
- if [[ -n "${candidate}" && -d "${candidate}" ]]; then
75
- launch_cwd="${candidate}"
76
- break
77
- fi
78
- done
79
- fi
80
-
81
- if [[ -z "${launch_cwd}" || ! -d "${launch_cwd}" ]]; then
82
- echo "could not determine a stable working directory for detached launch" >&2
83
- exit 1
84
- fi
85
-
86
- python_bin="${PYTHON_BIN:-$(command -v python3 || true)}"
87
- if [[ -z "${python_bin}" ]]; then
88
- echo "python3 is required for detached launch" >&2
89
- exit 1
90
- fi
91
-
92
- launch_pid="$(
93
- "${python_bin}" - "${log_file}" "${pending_file}" "${launch_cwd}" "$@" <<'PY'
94
- import subprocess
95
- import sys
96
- from pathlib import Path
97
-
98
- log_file = Path(sys.argv[1])
99
- pending_file = sys.argv[2]
100
- launch_cwd = sys.argv[3]
101
- argv = sys.argv[4:]
102
-
103
- with log_file.open("ab", buffering=0) as log_handle:
104
- proc = subprocess.Popen(
105
- argv,
106
- stdin=subprocess.DEVNULL,
107
- stdout=log_handle,
108
- stderr=subprocess.STDOUT,
109
- cwd=launch_cwd,
110
- start_new_session=True,
111
- )
112
-
113
- if pending_file:
114
- Path(pending_file).write_text(f"{proc.pid}\n", encoding="utf-8")
115
-
116
- print(proc.pid)
117
- PY
118
- )"
119
-
120
- printf 'LAUNCH_MODE=detached\n'
121
- printf 'LAUNCH_NAME=%s\n' "${launch_name}"
122
- printf 'LAUNCH_PID=%s\n' "${launch_pid}"
123
- printf 'LAUNCH_LOG=%s\n' "${log_file}"
124
- printf 'LAUNCH_CWD=%s\n' "${launch_cwd}"
125
- if [[ -n "${pending_file}" ]]; then
126
- printf 'LAUNCH_PENDING_FILE=%s\n' "${pending_file}"
127
- fi