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.
- package/README.md +93 -14
- package/bin/pr-risk.sh +28 -6
- package/hooks/heartbeat-hooks.sh +62 -22
- package/npm/bin/agent-control-plane.js +360 -10
- package/package.json +6 -3
- package/references/architecture.md +8 -0
- package/references/control-plane-map.md +6 -2
- package/references/release-checklist.md +0 -2
- package/tools/bin/agent-github-update-labels +6 -1
- package/tools/bin/agent-project-catch-up-issue-pr-links +118 -0
- package/tools/bin/agent-project-catch-up-merged-prs +78 -21
- package/tools/bin/agent-project-catch-up-scheduled-issue-retries +123 -0
- package/tools/bin/agent-project-cleanup-session +132 -4
- package/tools/bin/agent-project-heartbeat-loop +116 -1461
- package/tools/bin/agent-project-reconcile-issue-session +90 -117
- package/tools/bin/agent-project-reconcile-pr-session +76 -111
- package/tools/bin/agent-project-run-claude-session +12 -2
- 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 +356 -14
- package/tools/bin/agent-project-run-ollama-session +658 -0
- package/tools/bin/agent-project-run-openclaw-session +37 -25
- package/tools/bin/agent-project-run-opencode-session +364 -14
- package/tools/bin/agent-project-run-pi-session +479 -0
- package/tools/bin/agent-project-worker-status +11 -8
- package/tools/bin/cleanup-worktree.sh +6 -1
- package/tools/bin/flow-config-lib.sh +196 -3
- package/tools/bin/flow-resident-worker-lib.sh +120 -2
- package/tools/bin/flow-shell-lib.sh +29 -2
- 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 +13 -1
- package/tools/bin/heartbeat-safe-auto.sh +119 -20
- package/tools/bin/install-project-launchd.sh +19 -2
- package/tools/bin/prepare-worktree.sh +4 -4
- package/tools/bin/profile-activate.sh +2 -2
- package/tools/bin/profile-adopt.sh +2 -2
- package/tools/bin/project-init.sh +1 -1
- package/tools/bin/project-launchd-bootstrap.sh +11 -8
- package/tools/bin/project-runtimectl.sh +90 -7
- package/tools/bin/provider-cooldown-state.sh +14 -14
- package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
- package/tools/bin/render-flow-config.sh +30 -33
- package/tools/bin/resident-issue-controller-lib.sh +448 -0
- package/tools/bin/resident-issue-queue-status.py +35 -0
- package/tools/bin/run-codex-task.sh +53 -4
- package/tools/bin/scaffold-profile.sh +18 -3
- package/tools/bin/start-issue-worker.sh +1 -1
- package/tools/bin/start-pr-fix-worker.sh +30 -0
- package/tools/bin/start-pr-review-worker.sh +31 -0
- package/tools/bin/start-resident-issue-loop.sh +27 -438
- package/tools/bin/sync-agent-repo.sh +2 -2
- package/tools/bin/sync-dependency-baseline.sh +3 -3
- package/tools/bin/sync-shared-agent-home.sh +4 -1
- package/tools/dashboard/app.js +7 -0
- package/tools/dashboard/dashboard_snapshot.py +13 -29
- package/tools/templates/pr-fix-template.md +3 -7
- package/tools/templates/pr-merge-repair-template.md +3 -7
- package/tools/templates/pr-review-template.md +2 -1
- package/SKILL.md +0 -149
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# heartbeat-loop-worker-lib.sh — tmux session queries, worker enumeration, and cache helpers
|
|
3
|
+
|
|
4
|
+
all_tmux_sessions() {
|
|
5
|
+
ensure_tmux_sessions_cache
|
|
6
|
+
printf '%s\n' "$tmux_sessions_cache"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
session_matches_prefix() {
|
|
10
|
+
local session="${1:?session required}"
|
|
11
|
+
[[ "$session" == "${issue_prefix}"* || "$session" == "${pr_prefix}"* ]]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
session_runner_state() {
|
|
15
|
+
local session="${1:?session required}"
|
|
16
|
+
local runner_state_file="${runs_root}/${session}/runner.env"
|
|
17
|
+
if [[ ! -f "$runner_state_file" ]]; then
|
|
18
|
+
return 1
|
|
19
|
+
fi
|
|
20
|
+
awk -F= '/^RUNNER_STATE=/{print $2; exit}' "$runner_state_file"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
session_is_auth_waiting() {
|
|
24
|
+
local session="${1:?session required}"
|
|
25
|
+
local runner_state=""
|
|
26
|
+
runner_state="$(session_runner_state "$session" || true)"
|
|
27
|
+
[[ "$runner_state" == "waiting-auth-refresh" || "$runner_state" == "switching-account" ]]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
all_running_workers() {
|
|
31
|
+
ensure_all_running_workers_cache
|
|
32
|
+
printf '%s\n' "$all_running_workers_cache"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
running_issue_workers() {
|
|
36
|
+
ensure_running_issue_workers_cache
|
|
37
|
+
printf '%s\n' "$running_issue_workers_cache"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
running_pr_workers() {
|
|
41
|
+
ensure_running_pr_workers_cache
|
|
42
|
+
printf '%s\n' "$running_pr_workers_cache"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
auth_wait_workers() {
|
|
46
|
+
ensure_auth_wait_workers_cache
|
|
47
|
+
printf '%s\n' "$auth_wait_workers_cache"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pending_launch_pid() {
|
|
51
|
+
local kind="${1:?kind required}"
|
|
52
|
+
local item_id="${2:?item id required}"
|
|
53
|
+
local pending_file pid
|
|
54
|
+
|
|
55
|
+
pending_file="${pending_launch_dir}/${kind}-${item_id}.pid"
|
|
56
|
+
if [[ ! -f "$pending_file" ]]; then
|
|
57
|
+
return 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
pid="$(tr -d '[:space:]' <"$pending_file" 2>/dev/null || true)"
|
|
61
|
+
if [[ -z "$pid" ]]; then
|
|
62
|
+
rm -f "$pending_file"
|
|
63
|
+
return 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
67
|
+
printf '%s\n' "$pid"
|
|
68
|
+
return 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
rm -f "$pending_file"
|
|
72
|
+
return 1
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pending_issue_launch_active() {
|
|
76
|
+
local issue_id="${1:?issue id required}"
|
|
77
|
+
if tmux has-session -t "${issue_prefix}${issue_id}" 2>/dev/null; then
|
|
78
|
+
rm -f "${pending_launch_dir}/issue-${issue_id}.pid" 2>/dev/null || true
|
|
79
|
+
return 1
|
|
80
|
+
fi
|
|
81
|
+
pending_launch_pid issue "$issue_id" >/dev/null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pending_pr_launch_active() {
|
|
85
|
+
local pr_id="${1:?pr id required}"
|
|
86
|
+
if tmux has-session -t "${pr_prefix}${pr_id}" 2>/dev/null; then
|
|
87
|
+
rm -f "${pending_launch_dir}/pr-${pr_id}.pid" 2>/dev/null || true
|
|
88
|
+
return 1
|
|
89
|
+
fi
|
|
90
|
+
pending_launch_pid pr "$pr_id" >/dev/null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pending_issue_launch_counts_toward_capacity() {
|
|
94
|
+
local issue_id="${1:?issue id required}"
|
|
95
|
+
local controller_state=""
|
|
96
|
+
|
|
97
|
+
if ! pending_issue_launch_active "${issue_id}"; then
|
|
98
|
+
return 1
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
controller_state="$(resident_issue_controller_state "${issue_id}" || true)"
|
|
102
|
+
if [[ -n "${controller_state}" ]]; then
|
|
103
|
+
case "${controller_state}" in
|
|
104
|
+
idle|sleeping|waiting-due|waiting-open-pr|waiting-provider)
|
|
105
|
+
return 1
|
|
106
|
+
;;
|
|
107
|
+
esac
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
resident_issue_controller_file() {
|
|
114
|
+
local issue_id="${1:?issue id required}"
|
|
115
|
+
printf '%s/resident-workers/issues/%s/controller.env\n' "${state_root}" "${issue_id}"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
resident_issue_controller_state() {
|
|
119
|
+
local issue_id="${1:?issue id required}"
|
|
120
|
+
local controller_file state=""
|
|
121
|
+
|
|
122
|
+
controller_file="$(resident_issue_controller_file "$issue_id")"
|
|
123
|
+
[[ -f "${controller_file}" ]] || return 1
|
|
124
|
+
|
|
125
|
+
state="$(awk -F= '/^CONTROLLER_STATE=/{print $2; exit}' "${controller_file}" 2>/dev/null | tr -d '"' || true)"
|
|
126
|
+
[[ -n "${state}" ]] || return 1
|
|
127
|
+
printf '%s\n' "${state}"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
issue_id_from_session() {
|
|
131
|
+
local session="${1:?session required}"
|
|
132
|
+
local issue_id=""
|
|
133
|
+
if [[ "$session" == "${issue_prefix}"* ]]; then
|
|
134
|
+
issue_id="${session#${issue_prefix}}"
|
|
135
|
+
fi
|
|
136
|
+
if [[ "$issue_id" =~ ^[0-9]+$ ]]; then
|
|
137
|
+
printf '%s\n' "$issue_id"
|
|
138
|
+
return 0
|
|
139
|
+
fi
|
|
140
|
+
return 1
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pr_id_from_session() {
|
|
144
|
+
local session="${1:?session required}"
|
|
145
|
+
local pr_id=""
|
|
146
|
+
if [[ "$session" == "${pr_prefix}"* ]]; then
|
|
147
|
+
pr_id="${session#${pr_prefix}}"
|
|
148
|
+
fi
|
|
149
|
+
if [[ "$pr_id" =~ ^[0-9]+$ ]]; then
|
|
150
|
+
printf '%s\n' "$pr_id"
|
|
151
|
+
return 0
|
|
152
|
+
fi
|
|
153
|
+
return 1
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
worker_count() {
|
|
157
|
+
local workers="${1:-}"
|
|
158
|
+
if [[ -z "$workers" ]]; then
|
|
159
|
+
printf '0\n'
|
|
160
|
+
return
|
|
161
|
+
fi
|
|
162
|
+
printf '%s\n' "$workers" | sed '/^$/d' | wc -l | tr -d ' '
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
retry_ready() {
|
|
166
|
+
local kind="${1:?kind required}"
|
|
167
|
+
local item_id="${2:?item id required}"
|
|
168
|
+
local retry_out ready
|
|
169
|
+
|
|
170
|
+
retry_out="$(
|
|
171
|
+
"${shared_agent_home}/tools/bin/agent-project-retry-state" \
|
|
172
|
+
--state-root "$state_root" \
|
|
173
|
+
--kind "$kind" \
|
|
174
|
+
--item-id "$item_id" \
|
|
175
|
+
--action get
|
|
176
|
+
)"
|
|
177
|
+
ready="$(awk -F= '/^READY=/{print $2}' <<<"$retry_out")"
|
|
178
|
+
[[ "$ready" == "yes" ]]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
provider_cooldown_state() {
|
|
182
|
+
"${shared_agent_home}/tools/bin/provider-cooldown-state.sh" get
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
completed_workers() {
|
|
186
|
+
ensure_completed_workers_cache
|
|
187
|
+
printf '%s\n' "$completed_workers_cache"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
reconciled_marker_matches_run() {
|
|
191
|
+
local run_dir="${1:?run dir required}"
|
|
192
|
+
local marker_file="${run_dir}/reconciled.ok"
|
|
193
|
+
local run_env="${run_dir}/run.env"
|
|
194
|
+
local marker_started_at=""
|
|
195
|
+
local run_started_at=""
|
|
196
|
+
|
|
197
|
+
[[ -f "${marker_file}" && -f "${run_env}" ]] || return 1
|
|
198
|
+
|
|
199
|
+
marker_started_at="$(awk -F= '/^STARTED_AT=/{print $2}' "${marker_file}" 2>/dev/null | tr -d '"' | tail -n 1 || true)"
|
|
200
|
+
run_started_at="$(awk -F= '/^STARTED_AT=/{print $2}' "${run_env}" 2>/dev/null | tr -d '"' | tail -n 1 || true)"
|
|
201
|
+
|
|
202
|
+
[[ -n "${marker_started_at}" && -n "${run_started_at}" && "${marker_started_at}" == "${run_started_at}" ]]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
ensure_tmux_sessions_cache() {
|
|
206
|
+
if [[ "$tmux_sessions_cache_loaded" != "yes" ]]; then
|
|
207
|
+
tmux_sessions_cache="$(tmux list-sessions -F '#S' 2>/dev/null || true)"
|
|
208
|
+
tmux_sessions_cache_loaded="yes"
|
|
209
|
+
fi
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
ensure_all_running_workers_cache() {
|
|
213
|
+
local session
|
|
214
|
+
if [[ "$all_running_workers_cache_loaded" == "yes" ]]; then
|
|
215
|
+
return 0
|
|
216
|
+
fi
|
|
217
|
+
ensure_tmux_sessions_cache
|
|
218
|
+
all_running_workers_cache=""
|
|
219
|
+
while IFS= read -r session; do
|
|
220
|
+
[[ -n "$session" ]] || continue
|
|
221
|
+
if session_matches_prefix "$session"; then
|
|
222
|
+
all_running_workers_cache+="${session}"$'\n'
|
|
223
|
+
fi
|
|
224
|
+
done <<<"$tmux_sessions_cache"
|
|
225
|
+
all_running_workers_cache="${all_running_workers_cache%$'\n'}"
|
|
226
|
+
all_running_workers_cache_loaded="yes"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
ensure_auth_wait_workers_cache() {
|
|
230
|
+
local session
|
|
231
|
+
if [[ "$auth_wait_workers_cache_loaded" == "yes" ]]; then
|
|
232
|
+
return 0
|
|
233
|
+
fi
|
|
234
|
+
ensure_tmux_sessions_cache
|
|
235
|
+
auth_wait_workers_cache=""
|
|
236
|
+
while IFS= read -r session; do
|
|
237
|
+
[[ -n "$session" ]] || continue
|
|
238
|
+
session_matches_prefix "$session" || continue
|
|
239
|
+
if session_is_auth_waiting "$session"; then
|
|
240
|
+
auth_wait_workers_cache+="${session}"$'\n'
|
|
241
|
+
fi
|
|
242
|
+
done <<<"$tmux_sessions_cache"
|
|
243
|
+
auth_wait_workers_cache="${auth_wait_workers_cache%$'\n'}"
|
|
244
|
+
auth_wait_workers_cache_loaded="yes"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
ensure_running_issue_workers_cache() {
|
|
248
|
+
local session
|
|
249
|
+
if [[ "$running_issue_workers_cache_loaded" == "yes" ]]; then
|
|
250
|
+
return 0
|
|
251
|
+
fi
|
|
252
|
+
ensure_tmux_sessions_cache
|
|
253
|
+
running_issue_workers_cache=""
|
|
254
|
+
while IFS= read -r session; do
|
|
255
|
+
[[ -n "$session" ]] || continue
|
|
256
|
+
if [[ "$session" == "${issue_prefix}"* ]]; then
|
|
257
|
+
if session_is_auth_waiting "$session"; then
|
|
258
|
+
continue
|
|
259
|
+
fi
|
|
260
|
+
running_issue_workers_cache+="${session}"$'\n'
|
|
261
|
+
fi
|
|
262
|
+
done <<<"$tmux_sessions_cache"
|
|
263
|
+
running_issue_workers_cache="${running_issue_workers_cache%$'\n'}"
|
|
264
|
+
running_issue_workers_cache_loaded="yes"
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
ensure_running_pr_workers_cache() {
|
|
268
|
+
local session
|
|
269
|
+
if [[ "$running_pr_workers_cache_loaded" == "yes" ]]; then
|
|
270
|
+
return 0
|
|
271
|
+
fi
|
|
272
|
+
ensure_tmux_sessions_cache
|
|
273
|
+
running_pr_workers_cache=""
|
|
274
|
+
while IFS= read -r session; do
|
|
275
|
+
[[ -n "$session" ]] || continue
|
|
276
|
+
if [[ "$session" == "${pr_prefix}"* ]]; then
|
|
277
|
+
if session_is_auth_waiting "$session"; then
|
|
278
|
+
continue
|
|
279
|
+
fi
|
|
280
|
+
running_pr_workers_cache+="${session}"$'\n'
|
|
281
|
+
fi
|
|
282
|
+
done <<<"$tmux_sessions_cache"
|
|
283
|
+
running_pr_workers_cache="${running_pr_workers_cache%$'\n'}"
|
|
284
|
+
running_pr_workers_cache_loaded="yes"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
ensure_completed_workers_cache() {
|
|
288
|
+
local dir session issue_id status_line status
|
|
289
|
+
if [[ "$completed_workers_cache_loaded" == "yes" ]]; then
|
|
290
|
+
return 0
|
|
291
|
+
fi
|
|
292
|
+
completed_workers_cache=""
|
|
293
|
+
for dir in "$runs_root"/*; do
|
|
294
|
+
[[ -d "$dir" ]] || continue
|
|
295
|
+
session="${dir##*/}"
|
|
296
|
+
session_matches_prefix "$session" || continue
|
|
297
|
+
if reconciled_marker_matches_run "$dir"; then
|
|
298
|
+
continue
|
|
299
|
+
fi
|
|
300
|
+
if [[ "$session" == "${issue_prefix}"* ]]; then
|
|
301
|
+
issue_id="$(issue_id_from_session "$session" || true)"
|
|
302
|
+
if [[ -n "${issue_id}" ]] && pending_issue_launch_active "${issue_id}"; then
|
|
303
|
+
continue
|
|
304
|
+
fi
|
|
305
|
+
fi
|
|
306
|
+
status_line="$(
|
|
307
|
+
"${shared_agent_home}/tools/bin/agent-project-worker-status" \
|
|
308
|
+
--runs-root "$runs_root" \
|
|
309
|
+
--session "$session" \
|
|
310
|
+
| awk -F= '/^STATUS=/{print $2}' || true
|
|
311
|
+
)"
|
|
312
|
+
status="${status_line:-UNKNOWN}"
|
|
313
|
+
if [[ "$status" == "SUCCEEDED" || "$status" == "FAILED" ]]; then
|
|
314
|
+
completed_workers_cache+="${session}"$'\n'
|
|
315
|
+
fi
|
|
316
|
+
done
|
|
317
|
+
completed_workers_cache="${completed_workers_cache%$'\n'}"
|
|
318
|
+
completed_workers_cache_loaded="yes"
|
|
319
|
+
}
|
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
FLOW_TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
FLOW_SHELL_LIB="${FLOW_TOOLS_DIR}/flow-shell-lib.sh"
|
|
5
6
|
TEST_DIR="${FLOW_TOOLS_DIR%/bin}/tests"
|
|
6
7
|
TEST_TIMEOUT_SECONDS="${F_LOSNING_HEARTBEAT_PREFLIGHT_TEST_TIMEOUT_SECONDS:-120}"
|
|
8
|
+
python_bin=""
|
|
9
|
+
|
|
10
|
+
# shellcheck source=/dev/null
|
|
11
|
+
source "${FLOW_SHELL_LIB}"
|
|
12
|
+
|
|
13
|
+
python_bin="$(flow_resolve_python_bin || true)"
|
|
14
|
+
if [[ -z "${python_bin}" || ! -x "${python_bin}" ]]; then
|
|
15
|
+
echo "unable to resolve a runnable python interpreter for heartbeat-recovery-preflight.sh" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
7
18
|
|
|
8
19
|
run_with_timeout() {
|
|
9
20
|
local timeout_seconds="${1:?timeout seconds required}"
|
|
10
21
|
shift
|
|
11
22
|
|
|
12
|
-
|
|
23
|
+
"${python_bin}" - "$timeout_seconds" "$@" <<'PY'
|
|
13
24
|
import os
|
|
14
25
|
import signal
|
|
15
26
|
import subprocess
|
|
@@ -80,6 +91,7 @@ run_preflight_test "heartbeat-no-tmux-sessions" "${TEST_DIR}/test-heartbeat-safe
|
|
|
80
91
|
run_preflight_test "heartbeat-static-capacity-without-quota-cache" "${TEST_DIR}/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh"
|
|
81
92
|
run_preflight_test "heartbeat-openclaw-skips-codex-quota" "${TEST_DIR}/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh"
|
|
82
93
|
run_preflight_test "heartbeat-empty-schedule-label-sync" "${TEST_DIR}/test-heartbeat-sync-issue-labels-empty-schedule.sh"
|
|
94
|
+
run_preflight_test "heartbeat-open-issue-terminal-sync" "${TEST_DIR}/test-heartbeat-sync-open-agent-issues-terminal-clears-running.sh"
|
|
83
95
|
run_preflight_test "heartbeat-open-pr-terminal-sync" "${TEST_DIR}/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh"
|
|
84
96
|
run_preflight_test "heartbeat-pr-launch-dedup" "${TEST_DIR}/test-heartbeat-loop-pr-launch-dedup.sh"
|
|
85
97
|
run_preflight_test "heartbeat-auth-wait-capacity" "${TEST_DIR}/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh"
|
|
@@ -17,6 +17,8 @@ flow_export_execution_env "${CONFIG_YAML}"
|
|
|
17
17
|
AGENT_ROOT="$(flow_resolve_agent_root "${CONFIG_YAML}")"
|
|
18
18
|
RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
|
|
19
19
|
STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
20
|
+
HISTORY_ROOT="$(flow_resolve_history_root "${CONFIG_YAML}")"
|
|
21
|
+
WORKTREE_ROOT="$(flow_resolve_worktree_root "${CONFIG_YAML}")"
|
|
20
22
|
MEMORY_DIR="${ACP_MEMORY_DIR:-${F_LOSNING_MEMORY_DIR:-${AGENT_CONTROL_PLANE_WORKSPACE:-$HOME/.agent-runtime/control-plane/workspace}/memory}}"
|
|
21
23
|
REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
|
|
22
24
|
MAX_CONCURRENT_WORKERS="${ACP_MAX_CONCURRENT_WORKERS:-${F_LOSNING_MAX_CONCURRENT_WORKERS:-20}}"
|
|
@@ -29,13 +31,14 @@ MAX_CONCURRENT_BLOCKED_RECOVERY_ISSUE_WORKERS="${ACP_MAX_CONCURRENT_BLOCKED_RECO
|
|
|
29
31
|
BLOCKED_RECOVERY_COOLDOWN_SECONDS="${ACP_BLOCKED_RECOVERY_COOLDOWN_SECONDS:-${F_LOSNING_BLOCKED_RECOVERY_COOLDOWN_SECONDS:-900}}"
|
|
30
32
|
MAX_OPEN_AGENT_PRS_FOR_RECURRING="${ACP_MAX_OPEN_AGENT_PRS_FOR_RECURRING:-${F_LOSNING_MAX_OPEN_AGENT_PRS_FOR_RECURRING:-12}}"
|
|
31
33
|
MAX_LAUNCHES_PER_HEARTBEAT="${ACP_MAX_LAUNCHES_PER_HEARTBEAT:-${F_LOSNING_MAX_LAUNCHES_PER_HEARTBEAT:-$MAX_CONCURRENT_WORKERS}}"
|
|
32
|
-
CODING_WORKER="${ACP_CODING_WORKER
|
|
34
|
+
CODING_WORKER="${ACP_CODING_WORKER:-codex}"
|
|
33
35
|
# The catchup and shared heartbeat passes can legitimately take a few minutes
|
|
34
36
|
# once they reconcile stale sessions, sync labels, and launch multiple workers.
|
|
35
37
|
CATCHUP_TIMEOUT_SECONDS="${ACP_CATCHUP_TIMEOUT_SECONDS:-${F_LOSNING_CATCHUP_TIMEOUT_SECONDS:-180}}"
|
|
36
38
|
HEARTBEAT_LOOP_TIMEOUT_SECONDS="${ACP_HEARTBEAT_LOOP_TIMEOUT_SECONDS:-${F_LOSNING_HEARTBEAT_LOOP_TIMEOUT_SECONDS:-720}}"
|
|
37
39
|
CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
38
40
|
SHARED_AGENT_HOME="$(resolve_shared_agent_home "${FLOW_SKILL_DIR}")"
|
|
41
|
+
RUNTIME_HOME_DIR="$(resolve_runtime_home)"
|
|
39
42
|
FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
|
|
40
43
|
ISSUE_SESSION_PREFIX="$(flow_resolve_issue_session_prefix "${CONFIG_YAML}")"
|
|
41
44
|
PR_SESSION_PREFIX="$(flow_resolve_pr_session_prefix "${CONFIG_YAML}")"
|
|
@@ -67,8 +70,17 @@ AGENT_WORKTREE_AUDIT_TIMEOUT_SECONDS="${ACP_AGENT_WORKTREE_AUDIT_TIMEOUT_SECONDS
|
|
|
67
70
|
LOCK_DIR="${STATE_ROOT}/heartbeat-loop.lock"
|
|
68
71
|
PID_FILE="${LOCK_DIR}/pid"
|
|
69
72
|
SHARED_LOOP_PID_FILE="${STATE_ROOT}/shared-heartbeat-loop.pid"
|
|
73
|
+
SHARED_LOOP_STATUS_FILE="${STATE_ROOT}/shared-heartbeat-loop.env"
|
|
70
74
|
QUOTA_LOCK_DIR="${STATE_ROOT}/quota-preflight.lock"
|
|
71
75
|
QUOTA_PID_FILE="${QUOTA_LOCK_DIR}/pid"
|
|
76
|
+
python_bin="$(flow_resolve_python_bin || true)"
|
|
77
|
+
|
|
78
|
+
mkdir -p "${AGENT_ROOT}" "${RUNS_ROOT}" "${STATE_ROOT}" "${HISTORY_ROOT}" "${WORKTREE_ROOT}" "${MEMORY_DIR}"
|
|
79
|
+
|
|
80
|
+
if [[ -z "${python_bin}" || ! -x "${python_bin}" ]]; then
|
|
81
|
+
echo "unable to resolve a runnable python interpreter for heartbeat-safe-auto.sh" >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
72
84
|
|
|
73
85
|
acquire_lock() {
|
|
74
86
|
mkdir -p "${STATE_ROOT}"
|
|
@@ -124,11 +136,34 @@ release_quota_lock() {
|
|
|
124
136
|
rm -rf "${QUOTA_LOCK_DIR}"
|
|
125
137
|
}
|
|
126
138
|
|
|
139
|
+
write_shared_loop_status() {
|
|
140
|
+
local state="${1:-}"
|
|
141
|
+
local status="${2:-}"
|
|
142
|
+
local timestamp=""
|
|
143
|
+
local tmp_file=""
|
|
144
|
+
|
|
145
|
+
timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
146
|
+
mkdir -p "${STATE_ROOT}"
|
|
147
|
+
tmp_file="$(mktemp)"
|
|
148
|
+
if [[ -f "${SHARED_LOOP_STATUS_FILE}" ]]; then
|
|
149
|
+
grep -Ev '^(STATE|STATUS|STARTED_AT|UPDATED_AT)=' "${SHARED_LOOP_STATUS_FILE}" >"${tmp_file}" || true
|
|
150
|
+
fi
|
|
151
|
+
printf 'STATE=%s\n' "${state}" >>"${tmp_file}"
|
|
152
|
+
if [[ -n "${status}" ]]; then
|
|
153
|
+
printf 'STATUS=%s\n' "${status}" >>"${tmp_file}"
|
|
154
|
+
fi
|
|
155
|
+
if [[ "${state}" == "running" ]]; then
|
|
156
|
+
printf 'STARTED_AT=%s\n' "${timestamp}" >>"${tmp_file}"
|
|
157
|
+
fi
|
|
158
|
+
printf 'UPDATED_AT=%s\n' "${timestamp}" >>"${tmp_file}"
|
|
159
|
+
mv "${tmp_file}" "${SHARED_LOOP_STATUS_FILE}"
|
|
160
|
+
}
|
|
161
|
+
|
|
127
162
|
run_with_timeout() {
|
|
128
163
|
local timeout_seconds="${1:?timeout seconds required}"
|
|
129
164
|
shift
|
|
130
165
|
|
|
131
|
-
|
|
166
|
+
"${python_bin}" - "${timeout_seconds}" "$@" <<'PY'
|
|
132
167
|
import os
|
|
133
168
|
from pathlib import Path
|
|
134
169
|
import signal
|
|
@@ -302,7 +337,7 @@ EFFECTIVE_QUOTA_POOLS=""
|
|
|
302
337
|
|
|
303
338
|
local quota_cache_age_seconds=""
|
|
304
339
|
quota_cache_age_seconds="$(
|
|
305
|
-
|
|
340
|
+
"${python_bin}" - "${CODEX_QUOTA_FULL_CACHE_FILE}" <<'PY' 2>/dev/null || true
|
|
306
341
|
import os
|
|
307
342
|
import sys
|
|
308
343
|
import time
|
|
@@ -478,7 +513,11 @@ run_codex_quota_preflight
|
|
|
478
513
|
# Sync skill files to runtime-home if source has changed since last sync.
|
|
479
514
|
# This ensures start-issue-worker.sh and other scripts are always up to date.
|
|
480
515
|
if [[ -x "${FLOW_TOOLS_DIR}/ensure-runtime-sync.sh" ]]; then
|
|
481
|
-
"${
|
|
516
|
+
if [[ "${FLOW_SKILL_DIR}" == "${RUNTIME_HOME_DIR}"/* ]]; then
|
|
517
|
+
printf 'RUNTIME_SYNC_SKIPPED=active-runtime-home\n'
|
|
518
|
+
else
|
|
519
|
+
"${FLOW_TOOLS_DIR}/ensure-runtime-sync.sh" --quiet 2>/dev/null || true
|
|
520
|
+
fi
|
|
482
521
|
fi
|
|
483
522
|
|
|
484
523
|
acquire_lock
|
|
@@ -530,6 +569,7 @@ fi
|
|
|
530
569
|
derive_dynamic_limits
|
|
531
570
|
|
|
532
571
|
printf '[%s] shared heartbeat loop start\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
572
|
+
write_shared_loop_status "running" ""
|
|
533
573
|
if ACP_TIMEOUT_CHILD_PID_FILE="${SHARED_LOOP_PID_FILE}" \
|
|
534
574
|
F_LOSNING_TIMEOUT_CHILD_PID_FILE="${SHARED_LOOP_PID_FILE}" \
|
|
535
575
|
run_with_timeout "${HEARTBEAT_LOOP_TIMEOUT_SECONDS}" \
|
|
@@ -563,9 +603,11 @@ printf '[%s] shared heartbeat loop start\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
|
563
603
|
--heavy-running-label "E2E_ISSUE" \
|
|
564
604
|
--heavy-deferred-key "E2E_DEFERRED" \
|
|
565
605
|
--heavy-deferred-message "E2E-heavy issues remain queued until the single e2e slot is free."; then
|
|
606
|
+
write_shared_loop_status "idle" "0"
|
|
566
607
|
printf '[%s] shared heartbeat loop end status=0\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
567
608
|
else
|
|
568
609
|
loop_status=$?
|
|
610
|
+
write_shared_loop_status "idle" "${loop_status}"
|
|
569
611
|
if [[ "${loop_status}" -eq 124 ]]; then
|
|
570
612
|
printf 'HEARTBEAT_LOOP_TIMEOUT=yes\n'
|
|
571
613
|
fi
|
|
@@ -573,21 +615,78 @@ else
|
|
|
573
615
|
exit "${loop_status}"
|
|
574
616
|
fi
|
|
575
617
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
618
|
+
# ── Throttled catch-up passes ──────────────────────────────────────────────────
|
|
619
|
+
# These scripts fetch merged/closed PRs and linked issues which change rarely.
|
|
620
|
+
# Run them at most once every CATCHUP_INTERVAL_SECONDS (default 300 = 5 min)
|
|
621
|
+
# to avoid burning API quota on every heartbeat cycle.
|
|
622
|
+
CATCHUP_INTERVAL_SECONDS="${ACP_CATCHUP_INTERVAL_SECONDS:-${F_LOSNING_CATCHUP_INTERVAL_SECONDS:-300}}"
|
|
623
|
+
CATCHUP_STAMP_FILE="${STATE_ROOT}/last-catchup-timestamp"
|
|
624
|
+
_catchup_now="$(date +%s)"
|
|
625
|
+
_catchup_last="0"
|
|
626
|
+
if [[ -f "${CATCHUP_STAMP_FILE}" ]]; then
|
|
627
|
+
_catchup_last="$(cat "${CATCHUP_STAMP_FILE}" 2>/dev/null || echo 0)"
|
|
628
|
+
fi
|
|
629
|
+
_catchup_age=$(( _catchup_now - _catchup_last ))
|
|
630
|
+
|
|
631
|
+
if [[ "${_catchup_age}" -ge "${CATCHUP_INTERVAL_SECONDS}" ]]; then
|
|
632
|
+
printf '[%s] merged-pr catchup start\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
633
|
+
if run_with_timeout "${CATCHUP_TIMEOUT_SECONDS}" \
|
|
634
|
+
env \
|
|
635
|
+
ACP_RUNS_ROOT="$RUNS_ROOT" \
|
|
636
|
+
F_LOSNING_RUNS_ROOT="$RUNS_ROOT" \
|
|
637
|
+
bash "${FLOW_TOOLS_DIR}/agent-project-catch-up-merged-prs" \
|
|
638
|
+
--repo-slug "$REPO_SLUG" \
|
|
639
|
+
--state-root "$STATE_ROOT" \
|
|
640
|
+
--hook-file "$HOOK_FILE" \
|
|
641
|
+
--limit 100; then
|
|
642
|
+
printf '[%s] merged-pr catchup end status=0\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
643
|
+
else
|
|
644
|
+
catchup_status=$?
|
|
645
|
+
if [[ "${catchup_status}" -eq 124 ]]; then
|
|
646
|
+
printf 'CATCHUP_TIMEOUT=yes\n'
|
|
647
|
+
fi
|
|
648
|
+
printf '[%s] merged-pr catchup end status=%s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "${catchup_status}"
|
|
649
|
+
fi
|
|
650
|
+
|
|
651
|
+
printf '[%s] linked-pr issue catchup start\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
652
|
+
if run_with_timeout "${CATCHUP_TIMEOUT_SECONDS}" \
|
|
653
|
+
env \
|
|
654
|
+
ACP_RUNS_ROOT="$RUNS_ROOT" \
|
|
655
|
+
F_LOSNING_RUNS_ROOT="$RUNS_ROOT" \
|
|
656
|
+
bash "${FLOW_TOOLS_DIR}/agent-project-catch-up-issue-pr-links" \
|
|
657
|
+
--repo-slug "$REPO_SLUG" \
|
|
658
|
+
--state-root "$STATE_ROOT" \
|
|
659
|
+
--hook-file "$HOOK_FILE" \
|
|
660
|
+
--limit 100; then
|
|
661
|
+
printf '[%s] linked-pr issue catchup end status=0\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
662
|
+
else
|
|
663
|
+
linked_issue_catchup_status=$?
|
|
664
|
+
if [[ "${linked_issue_catchup_status}" -eq 124 ]]; then
|
|
665
|
+
printf 'LINKED_ISSUE_CATCHUP_TIMEOUT=yes\n'
|
|
666
|
+
fi
|
|
667
|
+
printf '[%s] linked-pr issue catchup end status=%s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "${linked_issue_catchup_status}"
|
|
668
|
+
fi
|
|
669
|
+
|
|
670
|
+
printf '[%s] scheduled-issue retry catchup start\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
671
|
+
if run_with_timeout "${CATCHUP_TIMEOUT_SECONDS}" \
|
|
672
|
+
env \
|
|
673
|
+
ACP_RUNS_ROOT="$RUNS_ROOT" \
|
|
674
|
+
F_LOSNING_RUNS_ROOT="$RUNS_ROOT" \
|
|
675
|
+
bash "${FLOW_TOOLS_DIR}/agent-project-catch-up-scheduled-issue-retries" \
|
|
676
|
+
--repo-slug "$REPO_SLUG" \
|
|
677
|
+
--state-root "$STATE_ROOT" \
|
|
678
|
+
--hook-file "$HOOK_FILE" \
|
|
679
|
+
--limit 100; then
|
|
680
|
+
printf '[%s] scheduled-issue retry catchup end status=0\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
681
|
+
else
|
|
682
|
+
scheduled_issue_catchup_status=$?
|
|
683
|
+
if [[ "${scheduled_issue_catchup_status}" -eq 124 ]]; then
|
|
684
|
+
printf 'SCHEDULED_ISSUE_CATCHUP_TIMEOUT=yes\n'
|
|
685
|
+
fi
|
|
686
|
+
printf '[%s] scheduled-issue retry catchup end status=%s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "${scheduled_issue_catchup_status}"
|
|
591
687
|
fi
|
|
592
|
-
|
|
688
|
+
|
|
689
|
+
printf '%s' "${_catchup_now}" >"${CATCHUP_STAMP_FILE}"
|
|
690
|
+
else
|
|
691
|
+
printf '[%s] catchup skipped (age=%ss, interval=%ss)\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "${_catchup_age}" "${CATCHUP_INTERVAL_SECONDS}"
|
|
593
692
|
fi
|
|
@@ -54,7 +54,7 @@ build_launchd_base_path() {
|
|
|
54
54
|
local tool_name=""
|
|
55
55
|
local tool_dir=""
|
|
56
56
|
|
|
57
|
-
for tool_name in node gh git python3 openclaw codex claude; do
|
|
57
|
+
for tool_name in node gh git python3 openclaw codex claude ollama pi crush kilo; do
|
|
58
58
|
tool_dir="$(resolved_tool_dir "${tool_name}" || true)"
|
|
59
59
|
append_path_dir path_value "${tool_dir}"
|
|
60
60
|
done
|
|
@@ -112,7 +112,14 @@ fi
|
|
|
112
112
|
PROFILE_ID="$(flow_resolve_adapter_id "${CONFIG_YAML}")"
|
|
113
113
|
profile_slug="$(printf '%s' "${PROFILE_ID}" | tr -c 'A-Za-z0-9._-' '-')"
|
|
114
114
|
HOME_DIR="${ACP_PROJECT_RUNTIME_HOME_DIR:-${HOME:-}}"
|
|
115
|
-
SOURCE_HOME="${ACP_PROJECT_RUNTIME_SOURCE_HOME
|
|
115
|
+
SOURCE_HOME="${ACP_PROJECT_RUNTIME_SOURCE_HOME:-}"
|
|
116
|
+
if [[ -z "${SOURCE_HOME}" ]]; then
|
|
117
|
+
if flow_is_skill_root "${FLOW_SKILL_DIR}"; then
|
|
118
|
+
SOURCE_HOME="${FLOW_SKILL_DIR}"
|
|
119
|
+
else
|
|
120
|
+
SOURCE_HOME="$(cd "${FLOW_SKILL_DIR}/../../.." && pwd)"
|
|
121
|
+
fi
|
|
122
|
+
fi
|
|
116
123
|
RUNTIME_HOME="${ACP_PROJECT_RUNTIME_RUNTIME_HOME:-${HOME_DIR}/.agent-runtime/runtime-home}"
|
|
117
124
|
WORKSPACE_DIR="${ACP_PROJECT_RUNTIME_WORKSPACE_DIR:-${HOME_DIR}/.agent-runtime/control-plane/workspace}"
|
|
118
125
|
PROFILE_REGISTRY_ROOT="${ACP_PROJECT_RUNTIME_PROFILE_REGISTRY_ROOT:-${ACP_PROFILE_REGISTRY_ROOT:-${HOME_DIR}/.agent-runtime/control-plane/profiles}}"
|
|
@@ -120,6 +127,7 @@ LAUNCH_AGENTS_DIR="${ACP_PROJECT_RUNTIME_LAUNCH_AGENTS_DIR:-${HOME_DIR}/Library/
|
|
|
120
127
|
LOG_DIR="${ACP_PROJECT_RUNTIME_LOG_DIR:-${HOME_DIR}/.agent-runtime/logs}"
|
|
121
128
|
LABEL="${label_override:-${ACP_PROJECT_RUNTIME_LAUNCHD_LABEL:-ai.agent.project.${profile_slug}}}"
|
|
122
129
|
BASE_PATH="$(build_launchd_base_path)"
|
|
130
|
+
CODING_WORKER_OVERRIDE="${ACP_PROJECT_RUNTIME_CODING_WORKER:-${ACP_CODING_WORKER:-}}"
|
|
123
131
|
SYNC_SCRIPT="${ACP_PROJECT_RUNTIME_SYNC_SCRIPT:-${FLOW_SKILL_DIR}/tools/bin/sync-shared-agent-home.sh}"
|
|
124
132
|
BOOTSTRAP_SCRIPT="${ACP_PROJECT_RUNTIME_BOOTSTRAP_SCRIPT:-${FLOW_SKILL_DIR}/tools/bin/project-launchd-bootstrap.sh}"
|
|
125
133
|
SUPERVISOR_SCRIPT="${ACP_PROJECT_RUNTIME_SUPERVISOR_SCRIPT:-${FLOW_SKILL_DIR}/tools/bin/project-runtime-supervisor.sh}"
|
|
@@ -152,6 +160,15 @@ export AGENT_PROJECT_ID='${PROFILE_ID}'
|
|
|
152
160
|
export ACP_PROJECT_RUNTIME_PATH='${BASE_PATH}'
|
|
153
161
|
export ACP_PROJECT_RUNTIME_SYNC_SCRIPT='${SYNC_SCRIPT}'
|
|
154
162
|
export ACP_PROFILE_REGISTRY_ROOT='${PROFILE_REGISTRY_ROOT}'
|
|
163
|
+
EOF
|
|
164
|
+
|
|
165
|
+
if [[ -n "${CODING_WORKER_OVERRIDE}" ]]; then
|
|
166
|
+
cat >>"${WRAPPER_PATH}" <<EOF
|
|
167
|
+
export ACP_CODING_WORKER='${CODING_WORKER_OVERRIDE}'
|
|
168
|
+
EOF
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
cat >>"${WRAPPER_PATH}" <<EOF
|
|
155
172
|
exec bash '${SUPERVISOR_SCRIPT}' --bootstrap-script '${BOOTSTRAP_SCRIPT}' --pid-file '${SUPERVISOR_PID_FILE}' --delay-seconds '${delay_seconds}' --interval-seconds '${interval_seconds}'
|
|
156
173
|
EOF
|
|
157
174
|
chmod +x "${WRAPPER_PATH}"
|
|
@@ -9,10 +9,10 @@ CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
|
9
9
|
AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
|
|
10
10
|
CANONICAL_REPO_ROOT="$(flow_resolve_repo_root "${CONFIG_YAML}")"
|
|
11
11
|
DEFAULT_DEPENDENCY_SOURCE_ROOT="$CANONICAL_REPO_ROOT"
|
|
12
|
-
DEPENDENCY_SOURCE_ROOT="${ACP_DEPENDENCY_SOURCE_ROOT:-$
|
|
13
|
-
SYNC_DEPENDENCY_BASELINE_SCRIPT="${ACP_SYNC_DEPENDENCY_BASELINE_SCRIPT:-${
|
|
14
|
-
PACKAGE_MANAGER_BIN="${ACP_PACKAGE_MANAGER_BIN
|
|
15
|
-
LOCAL_WORKSPACE_INSTALL="${ACP_WORKTREE_LOCAL_INSTALL
|
|
12
|
+
DEPENDENCY_SOURCE_ROOT="${ACP_DEPENDENCY_SOURCE_ROOT:-$DEFAULT_DEPENDENCY_SOURCE_ROOT}"
|
|
13
|
+
SYNC_DEPENDENCY_BASELINE_SCRIPT="${ACP_SYNC_DEPENDENCY_BASELINE_SCRIPT:-${SCRIPT_DIR}/sync-dependency-baseline.sh}"
|
|
14
|
+
PACKAGE_MANAGER_BIN="${ACP_PACKAGE_MANAGER_BIN:-pnpm}"
|
|
15
|
+
LOCAL_WORKSPACE_INSTALL="${ACP_WORKTREE_LOCAL_INSTALL:-false}"
|
|
16
16
|
WORKTREE="${1:?usage: prepare-worktree.sh WORKTREE}"
|
|
17
17
|
|
|
18
18
|
realpath_safe() {
|
|
@@ -56,8 +56,8 @@ AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
|
|
|
56
56
|
WORKTREE_ROOT="$(flow_resolve_worktree_root "${CONFIG_YAML}")"
|
|
57
57
|
RUNS_ROOT="$(flow_resolve_runs_root "${CONFIG_YAML}")"
|
|
58
58
|
STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
59
|
-
CODING_WORKER="${ACP_CODING_WORKER
|
|
60
|
-
ACTIVE_PROVIDER_POOL_NAME="${ACP_ACTIVE_PROVIDER_POOL_NAME
|
|
59
|
+
CODING_WORKER="${ACP_CODING_WORKER:-codex}"
|
|
60
|
+
ACTIVE_PROVIDER_POOL_NAME="${ACP_ACTIVE_PROVIDER_POOL_NAME:-}"
|
|
61
61
|
|
|
62
62
|
if [[ "${exports_only}" == "1" ]]; then
|
|
63
63
|
printf 'export ACP_PROJECT_ID=%q
|
|
@@ -69,8 +69,8 @@ HISTORY_ROOT="$(flow_resolve_history_root "${CONFIG_YAML}")"
|
|
|
69
69
|
WORKTREE_ROOT="$(flow_resolve_worktree_root "${CONFIG_YAML}")"
|
|
70
70
|
RETAINED_REPO_ROOT="$(flow_resolve_retained_repo_root "${CONFIG_YAML}")"
|
|
71
71
|
VSCODE_WORKSPACE_FILE="$(flow_resolve_vscode_workspace_file "${CONFIG_YAML}")"
|
|
72
|
-
REMOTE_NAME="${ACP_REMOTE_NAME
|
|
73
|
-
SOURCE_REPO_ROOT="${source_repo_root_override:-${ACP_SOURCE_REPO_ROOT:-${
|
|
72
|
+
REMOTE_NAME="${ACP_REMOTE_NAME:-origin}"
|
|
73
|
+
SOURCE_REPO_ROOT="${source_repo_root_override:-${ACP_SOURCE_REPO_ROOT:-${RETAINED_REPO_ROOT}}}"
|
|
74
74
|
PROFILE_LINK="${AGENT_ROOT}/control-plane.yaml"
|
|
75
75
|
WORKSPACE_LINK="${AGENT_ROOT}/workspace.code-workspace"
|
|
76
76
|
INSTALLED_PROFILE_DIR="$(dirname "${CONFIG_YAML}")"
|
|
@@ -42,7 +42,7 @@ Common options:
|
|
|
42
42
|
--worktree-root <path> Worktree parent root
|
|
43
43
|
--retained-repo-root <path> Retained/manual checkout root
|
|
44
44
|
--vscode-workspace-file <path> VS Code workspace file
|
|
45
|
-
--coding-worker <codex|openclaw|claude>
|
|
45
|
+
--coding-worker <codex|openclaw|claude|ollama|pi|opencode|kilo>
|
|
46
46
|
--claude-model <model>
|
|
47
47
|
--claude-permission-mode <mode>
|
|
48
48
|
--claude-effort <level>
|