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.
@@ -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 // "main"' <<<"$pr_view_json")"
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
- flow_github_api_repo "${repo_slug}" "issues/${pr_number}/comments" --method POST -f body="$(cat "$comment_file")" >/dev/null
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 ! approve_output="$(
780
- flow_github_api_repo "${repo_slug}" "pulls/${pr_number}/reviews" \
781
- --method POST \
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
- flow_github_pr_merge "$repo_slug" "$pr_number" "squash" "yes"
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
- if ! review_pass_action="$(review_pass_action_from_result_action "${result_action:-}" 2>/dev/null)"; then
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
- pr_state="$(flow_github_pr_view_json "$repo_slug" "$pr_number" | jq -r '.state')"
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 (!(hasScopedCommand(apiScopePattern, /\btypecheck\b/, /\btsc --noemit\b/, /\btsc --noemit\b/) || rootTypecheck)) {
258
+ if (!(scopedApiTypecheck || rootTypecheck || apiNarrowSliceTargetedCoverage)) {
246
259
  reasons.push('missing API typecheck or repo typecheck for API changes');
247
260
  }
248
- if (!(hasScopedCommand(apiScopePattern, /\blint\b/, /\bbuild\b/, /\btest\b/, /\bjest\b/, /\bvitest\b/) || rootBuild || rootLint || rootTest)) {
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
- group_by(.accountId)
348
+ map(. + {poolKey: (.label // .trackedLabel // .email // .accountId // "")})
349
349
  | map(select(
350
- ((.[0].usage.rate_limit.limit_reached // false) | not)
351
- and ((.[0].usage.rate_limit.primary_window.used_percent // 100) < $primaryThresh)
352
- and ((.[0].usage.rate_limit.secondary_window.used_percent // 100) < $weeklyThresh)
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
- group_by(.accountId)
362
+ map(. + {poolKey: (.label // .trackedLabel // .email // .accountId // "")})
361
363
  | map(select(
362
- ((.[0].usage.rate_limit.limit_reached // false) | not)
363
- and ((.[0].usage.rate_limit.secondary_window.used_percent // 100) < $weeklyThresh)
364
- and ((.[0].planType // "") != "free")
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
- provider_key="$(flow_sanitize_provider_key "${backend}-${model}")"
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}"