agent-control-plane 0.1.12 → 0.1.14
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/SKILL.md +1 -1
- package/hooks/heartbeat-hooks.sh +64 -7
- package/hooks/issue-reconcile-hooks.sh +46 -0
- package/npm/bin/agent-control-plane.js +89 -8
- package/package.json +3 -1
- package/references/commands.md +2 -2
- package/references/control-plane-map.md +1 -1
- package/tools/bin/agent-project-reconcile-issue-session +2 -0
- package/tools/bin/agent-project-reconcile-pr-session +166 -22
- package/tools/bin/agent-project-run-codex-session +58 -0
- package/tools/bin/branch-verification-guard.sh +15 -2
- package/tools/bin/ensure-runtime-sync.sh +5 -5
- package/tools/bin/heartbeat-safe-auto.sh +20 -10
- package/tools/bin/provider-cooldown-state.sh +39 -1
- package/tools/bin/start-issue-worker.sh +66 -24
- package/tools/bin/sync-shared-agent-home.sh +24 -10
- package/tools/dashboard/app.js +74 -0
- package/tools/dashboard/dashboard_snapshot.py +202 -1
- package/tools/templates/issue-prompt-template.md +4 -1
- package/tools/vendor/codex-quota-manager/scripts/auto-switch.sh +8 -6
- package/tools/bin/render-dashboard-snapshot.py +0 -16
- package/tools/templates/legacy/issue-prompt-template-pre-slim.md +0 -109
|
@@ -227,23 +227,30 @@ schedule_provider_quota_cooldown() {
|
|
|
227
227
|
local reason="${1:-provider-quota-limit}"
|
|
228
228
|
[[ "${failure_reason:-}" == "provider-quota-limit" ]] || return 0
|
|
229
229
|
[[ -x "${provider_cooldown_script}" ]] || return 0
|
|
230
|
+
[[ "${CODING_WORKER:-}" == "codex" ]] && return 0
|
|
230
231
|
|
|
231
232
|
"${provider_cooldown_script}" schedule "${reason}" >/dev/null || true
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
clear_provider_quota_cooldown() {
|
|
235
236
|
[[ -x "${provider_cooldown_script}" ]] || return 0
|
|
237
|
+
[[ "${CODING_WORKER:-}" == "codex" ]] && return 0
|
|
236
238
|
|
|
237
239
|
"${provider_cooldown_script}" clear >/dev/null || true
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
blocked_runtime_reason=""
|
|
243
|
+
host_github_rate_limited="no"
|
|
244
|
+
host_github_rate_limit_detail=""
|
|
241
245
|
|
|
242
246
|
owner="${repo_slug%%/*}"
|
|
243
247
|
repo="${repo_slug#*/}"
|
|
244
248
|
pr_view_json="$(flow_github_pr_view_json "$repo_slug" "$pr_number")"
|
|
245
249
|
pr_state="$(jq -r '.state' <<<"$pr_view_json")"
|
|
246
|
-
pr_base_ref="$(jq -r '.baseRefName //
|
|
250
|
+
pr_base_ref="$(jq -r '.baseRefName // empty' <<<"$pr_view_json")"
|
|
251
|
+
if [[ -z "${pr_base_ref}" ]]; then
|
|
252
|
+
pr_base_ref="main"
|
|
253
|
+
fi
|
|
247
254
|
|
|
248
255
|
if [[ "$status" == "RUNNING" && "$pr_state" != "MERGED" && "$pr_state" != "CLOSED" ]]; then
|
|
249
256
|
printf 'STATUS=%s\n' "$status"
|
|
@@ -395,7 +402,9 @@ post_pr_comment_if_present() {
|
|
|
395
402
|
if pr_comment_already_posted; then
|
|
396
403
|
return 0
|
|
397
404
|
fi
|
|
398
|
-
|
|
405
|
+
if ! host_github_post_issue_comment "${pr_number}" "$(cat "$comment_file")"; then
|
|
406
|
+
return 1
|
|
407
|
+
fi
|
|
399
408
|
}
|
|
400
409
|
|
|
401
410
|
pr_comment_already_posted() {
|
|
@@ -408,6 +417,120 @@ pr_comment_already_posted() {
|
|
|
408
417
|
jq -e --arg body "$comment_body" 'any(.comments[]?; .body == $body)' >/dev/null <<<"$comments_json"
|
|
409
418
|
}
|
|
410
419
|
|
|
420
|
+
host_github_output_indicates_rate_limit() {
|
|
421
|
+
grep -Eiq 'API rate limit exceeded|secondary rate limit|rate limit exceeded|HTTP 403' <<<"${1:-}"
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
record_host_github_rate_limit() {
|
|
425
|
+
local output="${1:-}"
|
|
426
|
+
local detail_file="${run_dir}/host-github-rate-limit.log"
|
|
427
|
+
host_github_rate_limited="yes"
|
|
428
|
+
host_github_rate_limit_detail="${output}"
|
|
429
|
+
printf '%s\n' "${output}" >"${detail_file}"
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
host_github_post_issue_comment() {
|
|
433
|
+
local issue_number="${1:?issue number required}"
|
|
434
|
+
local body="${2:-}"
|
|
435
|
+
local output=""
|
|
436
|
+
|
|
437
|
+
flow_export_github_cli_auth_env "${repo_slug}"
|
|
438
|
+
if output="$(
|
|
439
|
+
gh api "repos/${repo_slug}/issues/${issue_number}/comments" \
|
|
440
|
+
--method POST \
|
|
441
|
+
-f body="${body}" 2>&1
|
|
442
|
+
)"; then
|
|
443
|
+
return 0
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
if host_github_output_indicates_rate_limit "${output}"; then
|
|
447
|
+
record_host_github_rate_limit "${output}"
|
|
448
|
+
return 1
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
printf '%s\n' "${output}" >&2
|
|
452
|
+
return 1
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
host_github_submit_pr_approval() {
|
|
456
|
+
local output=""
|
|
457
|
+
|
|
458
|
+
flow_export_github_cli_auth_env "${repo_slug}"
|
|
459
|
+
if output="$(
|
|
460
|
+
gh api "repos/${repo_slug}/pulls/${pr_number}/reviews" \
|
|
461
|
+
--method POST \
|
|
462
|
+
-f event=APPROVE \
|
|
463
|
+
-f body="Automated final review passed. Safe low-risk scope, green checks, and host-side merge approved." \
|
|
464
|
+
2>&1
|
|
465
|
+
)"; then
|
|
466
|
+
return 0
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
if grep -q "Can not approve your own pull request" <<<"${output}"; then
|
|
470
|
+
return 0
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
if host_github_output_indicates_rate_limit "${output}"; then
|
|
474
|
+
record_host_github_rate_limit "${output}"
|
|
475
|
+
return 1
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
printf '%s\n' "${output}" >&2
|
|
479
|
+
return 1
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
append_host_rate_limit_comment() {
|
|
483
|
+
local detail="${1:-GitHub API rate limit blocked host actions.}"
|
|
484
|
+
local reset_line=""
|
|
485
|
+
|
|
486
|
+
if grep -Eiq 'resets at ' <<<"${detail}"; then
|
|
487
|
+
reset_line="$(grep -Eio 'resets at [^.]+' <<<"${detail}" | head -n 1 || true)"
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
{
|
|
491
|
+
if [[ -s "${pr_comment_file}" ]]; then
|
|
492
|
+
printf '\n\n'
|
|
493
|
+
fi
|
|
494
|
+
printf '## Host action blocked\n\n'
|
|
495
|
+
printf 'GitHub API rate limit blocked ACP from posting the PR review outcome or merge action.\n'
|
|
496
|
+
if [[ -n "${reset_line}" ]]; then
|
|
497
|
+
printf '\n- %s\n' "${reset_line}"
|
|
498
|
+
fi
|
|
499
|
+
printf -- '- ACP kept the local review artifacts and scheduled an automatic retry for the host action.\n'
|
|
500
|
+
} >>"${pr_comment_file}"
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
handle_host_github_rate_limit_retry() {
|
|
504
|
+
local reason="${1:-github-api-rate-limit}"
|
|
505
|
+
local result_action_override="${2:-host-rate-limit-retry}"
|
|
506
|
+
|
|
507
|
+
append_host_rate_limit_comment "${host_github_rate_limit_detail:-}"
|
|
508
|
+
require_transition "pr_schedule_retry" pr_schedule_retry "${reason}"
|
|
509
|
+
require_transition "pr_after_blocked" pr_after_blocked "${pr_number}"
|
|
510
|
+
cleanup_pr_session
|
|
511
|
+
result_outcome="blocked"
|
|
512
|
+
result_action="${result_action_override}"
|
|
513
|
+
failure_reason="${reason}"
|
|
514
|
+
notify_pr_reconciled
|
|
515
|
+
mark_reconciled
|
|
516
|
+
printf 'STATUS=FAILED\n'
|
|
517
|
+
printf 'PR_NUMBER=%s\n' "${pr_number}"
|
|
518
|
+
printf 'PR_STATE=%s\n' "${pr_state}"
|
|
519
|
+
printf 'OUTCOME=%s\n' "${result_outcome}"
|
|
520
|
+
printf 'ACTION=%s\n' "${result_action}"
|
|
521
|
+
printf 'FAILURE_REASON=%s\n' "${failure_reason}"
|
|
522
|
+
exit 0
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
maybe_handle_host_github_rate_limit() {
|
|
526
|
+
local reason="${1:-github-api-rate-limit}"
|
|
527
|
+
local result_action_override="${2:-host-rate-limit-retry}"
|
|
528
|
+
if [[ "${host_github_rate_limited}" == "yes" ]]; then
|
|
529
|
+
handle_host_github_rate_limit_retry "${reason}" "${result_action_override}"
|
|
530
|
+
fi
|
|
531
|
+
return 1
|
|
532
|
+
}
|
|
533
|
+
|
|
411
534
|
blocked_result_indicates_local_bind_failure() {
|
|
412
535
|
local candidate_file
|
|
413
536
|
for candidate_file in "$pr_comment_file" "$session_log_file"; do
|
|
@@ -774,23 +897,38 @@ pr_remote_already_has_final_head() {
|
|
|
774
897
|
}
|
|
775
898
|
|
|
776
899
|
approve_and_merge() {
|
|
777
|
-
local approve_output
|
|
778
900
|
if ! pr_is_self_authored_for_current_actor; then
|
|
779
|
-
if !
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
-f event=APPROVE \
|
|
783
|
-
-f body="Automated final review passed. Safe low-risk scope, green checks, and host-side merge approved." \
|
|
784
|
-
2>&1
|
|
785
|
-
)"; then
|
|
786
|
-
if ! grep -q "Can not approve your own pull request" <<<"$approve_output"; then
|
|
787
|
-
printf '%s\n' "$approve_output" >&2
|
|
788
|
-
return 1
|
|
901
|
+
if ! host_github_submit_pr_approval; then
|
|
902
|
+
if [[ "${host_github_rate_limited}" == "yes" ]]; then
|
|
903
|
+
return 2
|
|
789
904
|
fi
|
|
905
|
+
return 1
|
|
790
906
|
fi
|
|
791
907
|
fi
|
|
792
908
|
|
|
793
|
-
|
|
909
|
+
flow_export_github_cli_auth_env "${repo_slug}"
|
|
910
|
+
if ! gh pr merge "${pr_number}" -R "${repo_slug}" --squash --delete-branch --admin >"${run_dir}/host-github-merge.out" 2>"${run_dir}/host-github-merge.err"; then
|
|
911
|
+
local merge_output=""
|
|
912
|
+
merge_output="$(cat "${run_dir}/host-github-merge.err" 2>/dev/null || true)"
|
|
913
|
+
if host_github_output_indicates_rate_limit "${merge_output}"; then
|
|
914
|
+
record_host_github_rate_limit "${merge_output}"
|
|
915
|
+
return 2
|
|
916
|
+
fi
|
|
917
|
+
if flow_github_pr_merge "$repo_slug" "$pr_number" "squash" "yes" 2>"${run_dir}/host-github-merge.err"; then
|
|
918
|
+
return 0
|
|
919
|
+
fi
|
|
920
|
+
merge_output="$(cat "${run_dir}/host-github-merge.err" 2>/dev/null || true)"
|
|
921
|
+
if host_github_output_indicates_rate_limit "${merge_output}"; then
|
|
922
|
+
record_host_github_rate_limit "${merge_output}"
|
|
923
|
+
return 2
|
|
924
|
+
fi
|
|
925
|
+
if [[ -n "${merge_output}" ]]; then
|
|
926
|
+
printf '%s\n' "${merge_output}" >&2
|
|
927
|
+
fi
|
|
928
|
+
return 1
|
|
929
|
+
fi
|
|
930
|
+
|
|
931
|
+
return 0
|
|
794
932
|
}
|
|
795
933
|
|
|
796
934
|
cleanup_pr_session() {
|
|
@@ -829,7 +967,7 @@ handle_linked_issue_merge_cleanup() {
|
|
|
829
967
|
handle_updated_branch_result() {
|
|
830
968
|
if [[ -z "$pr_worktree" || ! -d "$pr_worktree" ]]; then
|
|
831
969
|
if pr_remote_already_has_final_head; then
|
|
832
|
-
post_pr_comment_if_present
|
|
970
|
+
post_pr_comment_if_present || maybe_handle_host_github_rate_limit "github-api-rate-limit" "host-comment-rate-limit-retry"
|
|
833
971
|
require_transition "pr_clear_retry" pr_clear_retry
|
|
834
972
|
require_transition "pr_after_updated_branch" pr_after_updated_branch "$pr_number"
|
|
835
973
|
cleanup_pr_session
|
|
@@ -895,7 +1033,7 @@ handle_updated_branch_result() {
|
|
|
895
1033
|
fi
|
|
896
1034
|
|
|
897
1035
|
push_pr_branch
|
|
898
|
-
post_pr_comment_if_present
|
|
1036
|
+
post_pr_comment_if_present || maybe_handle_host_github_rate_limit "github-api-rate-limit" "host-comment-rate-limit-retry"
|
|
899
1037
|
require_transition "pr_clear_retry" pr_clear_retry
|
|
900
1038
|
require_transition "pr_after_updated_branch" pr_after_updated_branch "$pr_number"
|
|
901
1039
|
cleanup_pr_session
|
|
@@ -936,7 +1074,7 @@ elif [[ "$pr_state" == "CLOSED" ]]; then
|
|
|
936
1074
|
result_action="${result_action:-cleaned-closed-pr}"
|
|
937
1075
|
notify_pr_reconciled
|
|
938
1076
|
elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "approved-local-review-passed" ]]; then
|
|
939
|
-
|
|
1077
|
+
if ! review_pass_action="$(review_pass_action_from_result_action "${result_action:-}" 2>/dev/null)"; then
|
|
940
1078
|
review_pass_action="$(pr_review_pass_action "$pr_number")"
|
|
941
1079
|
fi
|
|
942
1080
|
case "$review_pass_action" in
|
|
@@ -963,8 +1101,13 @@ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "approved-local-review-
|
|
|
963
1101
|
fi
|
|
964
1102
|
|
|
965
1103
|
require_transition "pr_clear_retry" pr_clear_retry
|
|
966
|
-
approve_and_merge
|
|
967
|
-
|
|
1104
|
+
if ! approve_and_merge; then
|
|
1105
|
+
if [[ "${host_github_rate_limited}" == "yes" ]]; then
|
|
1106
|
+
handle_host_github_rate_limit_retry "github-api-rate-limit" "host-merge-rate-limit-retry"
|
|
1107
|
+
fi
|
|
1108
|
+
exit 1
|
|
1109
|
+
fi
|
|
1110
|
+
pr_state="MERGED"
|
|
968
1111
|
if [[ "$pr_state" != "MERGED" ]]; then
|
|
969
1112
|
echo "PR ${pr_number} did not merge successfully" >&2
|
|
970
1113
|
exit 1
|
|
@@ -1018,7 +1161,7 @@ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "no-change-needed" ]];
|
|
|
1018
1161
|
result_action="host-rejected-noop-promotion"
|
|
1019
1162
|
else
|
|
1020
1163
|
push_pr_branch
|
|
1021
|
-
post_pr_comment_if_present
|
|
1164
|
+
post_pr_comment_if_present || maybe_handle_host_github_rate_limit "github-api-rate-limit" "host-comment-rate-limit-retry"
|
|
1022
1165
|
require_transition "pr_clear_retry" pr_clear_retry
|
|
1023
1166
|
require_transition "pr_after_updated_branch" pr_after_updated_branch "$pr_number"
|
|
1024
1167
|
cleanup_pr_session
|
|
@@ -1041,7 +1184,7 @@ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "no-change-needed" ]];
|
|
|
1041
1184
|
result_action="host-rejected-no-change-needed"
|
|
1042
1185
|
notify_pr_reconciled
|
|
1043
1186
|
else
|
|
1044
|
-
post_pr_comment_if_present
|
|
1187
|
+
post_pr_comment_if_present || maybe_handle_host_github_rate_limit "github-api-rate-limit" "host-comment-rate-limit-retry"
|
|
1045
1188
|
require_transition "pr_clear_retry" pr_clear_retry
|
|
1046
1189
|
require_transition "pr_after_succeeded" pr_after_succeeded "$pr_number"
|
|
1047
1190
|
cleanup_pr_session
|
|
@@ -1062,7 +1205,7 @@ elif [[ "$status" == "SUCCEEDED" && "$result_outcome" == "blocked" ]]; then
|
|
|
1062
1205
|
elif attempt_blocked_pr_host_verification_recovery; then
|
|
1063
1206
|
handle_updated_branch_result
|
|
1064
1207
|
else
|
|
1065
|
-
post_pr_comment_if_present
|
|
1208
|
+
post_pr_comment_if_present || maybe_handle_host_github_rate_limit "github-api-rate-limit" "host-comment-rate-limit-retry"
|
|
1066
1209
|
require_transition "pr_clear_retry" pr_clear_retry
|
|
1067
1210
|
require_transition "pr_after_blocked" pr_after_blocked "$pr_number"
|
|
1068
1211
|
cleanup_pr_session
|
|
@@ -1079,6 +1222,7 @@ elif [[ "$status" == "FAILED" ]]; then
|
|
|
1079
1222
|
schedule_provider_quota_cooldown "${failure_reason:-worker-exit-failed}"
|
|
1080
1223
|
require_transition "pr_schedule_retry" pr_schedule_retry "${failure_reason:-worker-exit-failed}"
|
|
1081
1224
|
require_transition "pr_after_failed" pr_after_failed "$pr_number"
|
|
1225
|
+
cleanup_pr_session
|
|
1082
1226
|
notify_pr_reconciled
|
|
1083
1227
|
fi
|
|
1084
1228
|
|
|
@@ -167,6 +167,8 @@ printf -v codex_bin_q '%q' "$codex_bin"
|
|
|
167
167
|
printf -v runner_bin_q '%q' "$runner_bin"
|
|
168
168
|
printf -v safe_profile_q '%q' "$safe_profile"
|
|
169
169
|
printf -v bypass_profile_q '%q' "$bypass_profile"
|
|
170
|
+
helper_bin_dir="${artifact_dir}/worker-bin"
|
|
171
|
+
printf -v helper_bin_dir_q '%q' "$helper_bin_dir"
|
|
170
172
|
|
|
171
173
|
{
|
|
172
174
|
printf 'TASK_KIND=%s\n' "$task_kind_q"
|
|
@@ -274,6 +276,61 @@ cat >"$inner_script" <<EOF
|
|
|
274
276
|
set -euo pipefail
|
|
275
277
|
${runtime_exports}
|
|
276
278
|
${context_exports}cd ${worktree_realpath_q}
|
|
279
|
+
bootstrap_codex_helper_env() {
|
|
280
|
+
local helper_bin_dir=${helper_bin_dir_q}
|
|
281
|
+
local openspec_shim=""
|
|
282
|
+
|
|
283
|
+
mkdir -p "\${helper_bin_dir}"
|
|
284
|
+
|
|
285
|
+
if [[ -d ${worktree_realpath_q}/node_modules/.bin ]]; then
|
|
286
|
+
export PATH="${worktree_realpath_q}/node_modules/.bin:\${PATH}"
|
|
287
|
+
fi
|
|
288
|
+
export PATH="\${helper_bin_dir}:\${PATH}"
|
|
289
|
+
|
|
290
|
+
if command -v openspec >/dev/null 2>&1; then
|
|
291
|
+
return 0
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
if [[ ! -d ${worktree_realpath_q}/openspec ]]; then
|
|
295
|
+
return 0
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
openspec_shim="\${helper_bin_dir}/openspec"
|
|
299
|
+
cat >"\${openspec_shim}" <<'SHIM'
|
|
300
|
+
#!/usr/bin/env bash
|
|
301
|
+
set -euo pipefail
|
|
302
|
+
|
|
303
|
+
repo_root="\${ACP_REPO_ROOT:-\${F_LOSNING_REPO_ROOT:-\$(pwd)}}"
|
|
304
|
+
openspec_root="\${repo_root}/openspec"
|
|
305
|
+
|
|
306
|
+
if [[ ! -d "\${openspec_root}" ]]; then
|
|
307
|
+
echo "openspec directory not found: \${openspec_root}" >&2
|
|
308
|
+
exit 1
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
command_name="\${1:-}"
|
|
312
|
+
case "\${command_name}" in
|
|
313
|
+
list)
|
|
314
|
+
shift || true
|
|
315
|
+
if [[ "\${1:-}" == "--specs" ]]; then
|
|
316
|
+
find "\${openspec_root}/specs" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \
|
|
317
|
+
| xargs -n1 basename 2>/dev/null \
|
|
318
|
+
| sort
|
|
319
|
+
exit 0
|
|
320
|
+
fi
|
|
321
|
+
find "\${openspec_root}/changes" -mindepth 1 -maxdepth 1 -type d ! -name archive 2>/dev/null \
|
|
322
|
+
| xargs -n1 basename 2>/dev/null \
|
|
323
|
+
| sort
|
|
324
|
+
exit 0
|
|
325
|
+
;;
|
|
326
|
+
*)
|
|
327
|
+
echo "openspec shim only supports 'list' and 'list --specs'; use direct file reads for other operations" >&2
|
|
328
|
+
exit 64
|
|
329
|
+
;;
|
|
330
|
+
esac
|
|
331
|
+
SHIM
|
|
332
|
+
chmod +x "\${openspec_shim}"
|
|
333
|
+
}
|
|
277
334
|
reset_sandbox_run_dir() {
|
|
278
335
|
mkdir -p ${sandbox_run_dir_q}
|
|
279
336
|
find ${sandbox_run_dir_q} -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
|
|
@@ -331,6 +388,7 @@ record_final_git_state() {
|
|
|
331
388
|
} >>"\${tmp_file}"
|
|
332
389
|
mv "\${tmp_file}" ${meta_file_q}
|
|
333
390
|
}
|
|
391
|
+
bootstrap_codex_helper_env
|
|
334
392
|
reset_sandbox_run_dir
|
|
335
393
|
set +e
|
|
336
394
|
bash ${runner_bin_q} \\
|
|
@@ -108,6 +108,7 @@ const dependencyInputsChanged = files.some(isDependencyManifest);
|
|
|
108
108
|
const apiTouched = productNonTestFiles.some((file) => /^apps\/api\//.test(file));
|
|
109
109
|
const webTouched = productNonTestFiles.some((file) => /^apps\/web\//.test(file));
|
|
110
110
|
const mobileTouched = productNonTestFiles.some((file) => /^apps\/mobile\//.test(file));
|
|
111
|
+
const apiProductFiles = productNonTestFiles.filter((file) => /^apps\/api\//.test(file));
|
|
111
112
|
const packageNames = [
|
|
112
113
|
...new Set(
|
|
113
114
|
productNonTestFiles
|
|
@@ -199,11 +200,23 @@ const changedTestCoverage = changedTestFiles.map((file) => {
|
|
|
199
200
|
return { file, anchors, covered };
|
|
200
201
|
});
|
|
201
202
|
const missingChangedTestFiles = changedTestCoverage.filter(({ covered }) => !covered);
|
|
203
|
+
const apiChangedTests = changedTestCoverage.filter(({ file }) => /^apps\/api\//.test(file));
|
|
202
204
|
|
|
203
205
|
const rootTypecheck = hasCommand(/\bpnpm (?:run )?typecheck\b/, /\bturbo\b.*\btypecheck\b/);
|
|
204
206
|
const rootBuild = hasCommand(/\bpnpm (?:run )?build\b/, /\bturbo\b.*\bbuild\b/);
|
|
205
207
|
const rootLint = hasCommand(/\bpnpm (?:run )?lint\b/, /\bturbo\b.*\blint\b/);
|
|
206
208
|
const rootTest = hasCommand(/\bpnpm (?:run )?test\b/, /\bturbo\b.*\btest\b/);
|
|
209
|
+
const scopedApiTypecheck =
|
|
210
|
+
hasScopedCommand(apiScopePattern, /\btypecheck\b/, /\btsc --noemit\b/, /\btsc --noemit\b/);
|
|
211
|
+
const scopedApiConfidence =
|
|
212
|
+
hasScopedCommand(apiScopePattern, /\blint\b/, /\bbuild\b/, /\btest\b/, /\bjest\b/, /\bvitest\b/);
|
|
213
|
+
const apiNarrowSliceTargetedCoverage =
|
|
214
|
+
apiProductFiles.length > 0 &&
|
|
215
|
+
apiProductFiles.length <= 2 &&
|
|
216
|
+
apiProductFiles.every((file) => /(?:^|\/)(?:services?|utils?|helpers?|policies?)\/|(?:\.service|\.util|\.helper|\.policy)\.[cm]?[jt]s$/.test(file)) &&
|
|
217
|
+
apiChangedTests.length > 0 &&
|
|
218
|
+
apiChangedTests.every(({ covered }) => covered) &&
|
|
219
|
+
scopedApiConfidence;
|
|
207
220
|
|
|
208
221
|
const reasons = [];
|
|
209
222
|
if (passedCommands.length === 0) {
|
|
@@ -242,10 +255,10 @@ if (localeTouched) {
|
|
|
242
255
|
}
|
|
243
256
|
|
|
244
257
|
if (apiTouched) {
|
|
245
|
-
if (!(
|
|
258
|
+
if (!(scopedApiTypecheck || rootTypecheck || apiNarrowSliceTargetedCoverage)) {
|
|
246
259
|
reasons.push('missing API typecheck or repo typecheck for API changes');
|
|
247
260
|
}
|
|
248
|
-
if (!(
|
|
261
|
+
if (!(scopedApiConfidence || rootBuild || rootLint || rootTest)) {
|
|
249
262
|
reasons.push('missing API confidence verification (lint, build, or test) for API changes');
|
|
250
263
|
}
|
|
251
264
|
}
|
|
@@ -84,6 +84,11 @@ resolve_source_skill_dir() {
|
|
|
84
84
|
local skill_name=""
|
|
85
85
|
local root="${1:?source home required}"
|
|
86
86
|
|
|
87
|
+
if flow_is_skill_root "${root}"; then
|
|
88
|
+
printf '%s\n' "${root}"
|
|
89
|
+
return 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
87
92
|
for skill_name in "$(flow_canonical_skill_name)" "$(flow_compat_skill_alias)"; do
|
|
88
93
|
[[ -n "${skill_name}" ]] || continue
|
|
89
94
|
candidate="${root}/skills/openclaw/${skill_name}"
|
|
@@ -93,11 +98,6 @@ resolve_source_skill_dir() {
|
|
|
93
98
|
fi
|
|
94
99
|
done
|
|
95
100
|
|
|
96
|
-
if flow_is_skill_root "${root}"; then
|
|
97
|
-
printf '%s\n' "${root}"
|
|
98
|
-
return 0
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
101
|
return 1
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -345,24 +345,28 @@ PY
|
|
|
345
345
|
local effective_pools=""
|
|
346
346
|
healthy_pools="$(
|
|
347
347
|
jq -r --argjson primaryThresh "${CODEX_QUOTA_THRESHOLD}" --argjson weeklyThresh "${CODEX_QUOTA_WEEKLY_THRESHOLD}" '
|
|
348
|
-
|
|
348
|
+
map(. + {poolKey: (.label // .trackedLabel // .email // .accountId // "")})
|
|
349
349
|
| map(select(
|
|
350
|
-
(
|
|
351
|
-
and ((.
|
|
352
|
-
and ((.
|
|
353
|
-
|
|
350
|
+
(.poolKey != "")
|
|
351
|
+
and ((.usage.rate_limit.limit_reached // false) | not)
|
|
352
|
+
and ((.usage.rate_limit.primary_window.used_percent // 100) < $primaryThresh)
|
|
353
|
+
and ((.usage.rate_limit.secondary_window.used_percent // 100) < $weeklyThresh)
|
|
354
|
+
) | .poolKey)
|
|
355
|
+
| unique
|
|
354
356
|
| length
|
|
355
357
|
' "${CODEX_QUOTA_FULL_CACHE_FILE}" 2>/dev/null || true
|
|
356
358
|
)"
|
|
357
359
|
|
|
358
360
|
rotation_pools="$(
|
|
359
361
|
jq -r --argjson weeklyThresh "${CODEX_QUOTA_WEEKLY_THRESHOLD}" '
|
|
360
|
-
|
|
362
|
+
map(. + {poolKey: (.label // .trackedLabel // .email // .accountId // "")})
|
|
361
363
|
| map(select(
|
|
362
|
-
(
|
|
363
|
-
and ((.
|
|
364
|
-
and ((.
|
|
365
|
-
|
|
364
|
+
(.poolKey != "")
|
|
365
|
+
and ((.usage.rate_limit.limit_reached // false) | not)
|
|
366
|
+
and ((.usage.rate_limit.secondary_window.used_percent // 100) < $weeklyThresh)
|
|
367
|
+
and ((.planType // "") != "free")
|
|
368
|
+
) | .poolKey)
|
|
369
|
+
| unique
|
|
366
370
|
| length
|
|
367
371
|
' "${CODEX_QUOTA_FULL_CACHE_FILE}" 2>/dev/null || true
|
|
368
372
|
)"
|
|
@@ -471,6 +475,12 @@ fi
|
|
|
471
475
|
|
|
472
476
|
run_codex_quota_preflight
|
|
473
477
|
|
|
478
|
+
# Sync skill files to runtime-home if source has changed since last sync.
|
|
479
|
+
# This ensures start-issue-worker.sh and other scripts are always up to date.
|
|
480
|
+
if [[ -x "${FLOW_TOOLS_DIR}/ensure-runtime-sync.sh" ]]; then
|
|
481
|
+
"${FLOW_TOOLS_DIR}/ensure-runtime-sync.sh" --quiet 2>/dev/null || true
|
|
482
|
+
fi
|
|
483
|
+
|
|
474
484
|
acquire_lock
|
|
475
485
|
|
|
476
486
|
reap_orphan_shared_loop_groups
|
|
@@ -20,6 +20,7 @@ backend=""
|
|
|
20
20
|
model=""
|
|
21
21
|
action=""
|
|
22
22
|
reason=""
|
|
23
|
+
label=""
|
|
23
24
|
|
|
24
25
|
case "$#" in
|
|
25
26
|
1)
|
|
@@ -77,6 +78,33 @@ resolve_backend() {
|
|
|
77
78
|
flow_config_get "${CONFIG_YAML}" "execution.coding_worker"
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
resolve_codex_label() {
|
|
82
|
+
local configured_label="${ACP_ACTIVE_PROVIDER_LABEL:-${F_LOSNING_ACTIVE_PROVIDER_LABEL:-}}"
|
|
83
|
+
local codex_quota_bin=""
|
|
84
|
+
local active_label=""
|
|
85
|
+
|
|
86
|
+
if [[ -n "${configured_label}" ]]; then
|
|
87
|
+
printf '%s\n' "${configured_label}"
|
|
88
|
+
return 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [[ -n "${ACP_CODEX_QUOTA_LABEL:-${F_LOSNING_CODEX_QUOTA_LABEL:-}}" ]]; then
|
|
92
|
+
printf '%s\n' "${ACP_CODEX_QUOTA_LABEL:-${F_LOSNING_CODEX_QUOTA_LABEL:-}}"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
codex_quota_bin="$(flow_resolve_codex_quota_bin "${SCRIPT_DIR}")"
|
|
97
|
+
if [[ -n "${codex_quota_bin}" && -x "${codex_quota_bin}" ]]; then
|
|
98
|
+
active_label="$("${codex_quota_bin}" codex list --json 2>/dev/null | jq -r '.activeInfo.trackedLabel // .activeInfo.activeLabel // empty' 2>/dev/null || true)"
|
|
99
|
+
if [[ -n "${active_label}" ]]; then
|
|
100
|
+
printf '%s\n' "${active_label}"
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
resolve_model() {
|
|
81
109
|
local resolved_backend="${1:?backend required}"
|
|
82
110
|
local raw_model="${2:-}"
|
|
@@ -147,7 +175,16 @@ case "${action}" in
|
|
|
147
175
|
;;
|
|
148
176
|
esac
|
|
149
177
|
|
|
150
|
-
|
|
178
|
+
if [[ "${backend}" == "codex" ]]; then
|
|
179
|
+
label="$(resolve_codex_label || true)"
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
provider_key_source="${backend}-${model}"
|
|
183
|
+
if [[ "${backend}" == "codex" && -n "${label}" ]]; then
|
|
184
|
+
provider_key_source="${provider_key_source}-${label}"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
provider_key="$(flow_sanitize_provider_key "${provider_key_source}")"
|
|
151
188
|
out="$(
|
|
152
189
|
ACP_STATE_ROOT="${STATE_ROOT}" \
|
|
153
190
|
ACP_PROVIDER_QUOTA_COOLDOWNS="${COOLDOWNS}" \
|
|
@@ -162,5 +199,6 @@ out="$(
|
|
|
162
199
|
|
|
163
200
|
printf 'BACKEND=%s\n' "${backend}"
|
|
164
201
|
printf 'MODEL=%s\n' "${model}"
|
|
202
|
+
printf 'LABEL=%s\n' "${label}"
|
|
165
203
|
printf 'PROVIDER_KEY=%s\n' "${provider_key}"
|
|
166
204
|
printf '%s\n' "${out}"
|