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.
Files changed (63) hide show
  1. package/README.md +93 -14
  2. package/bin/pr-risk.sh +28 -6
  3. package/hooks/heartbeat-hooks.sh +62 -22
  4. package/npm/bin/agent-control-plane.js +360 -10
  5. package/package.json +6 -3
  6. package/references/architecture.md +8 -0
  7. package/references/control-plane-map.md +6 -2
  8. package/references/release-checklist.md +0 -2
  9. package/tools/bin/agent-github-update-labels +6 -1
  10. package/tools/bin/agent-project-catch-up-issue-pr-links +118 -0
  11. package/tools/bin/agent-project-catch-up-merged-prs +78 -21
  12. package/tools/bin/agent-project-catch-up-scheduled-issue-retries +123 -0
  13. package/tools/bin/agent-project-cleanup-session +132 -4
  14. package/tools/bin/agent-project-heartbeat-loop +116 -1461
  15. package/tools/bin/agent-project-reconcile-issue-session +90 -117
  16. package/tools/bin/agent-project-reconcile-pr-session +76 -111
  17. package/tools/bin/agent-project-run-claude-session +12 -2
  18. package/tools/bin/agent-project-run-codex-resilient +86 -9
  19. package/tools/bin/agent-project-run-codex-session +16 -5
  20. package/tools/bin/agent-project-run-kilo-session +356 -14
  21. package/tools/bin/agent-project-run-ollama-session +658 -0
  22. package/tools/bin/agent-project-run-openclaw-session +37 -25
  23. package/tools/bin/agent-project-run-opencode-session +364 -14
  24. package/tools/bin/agent-project-run-pi-session +479 -0
  25. package/tools/bin/agent-project-worker-status +11 -8
  26. package/tools/bin/cleanup-worktree.sh +6 -1
  27. package/tools/bin/flow-config-lib.sh +196 -3
  28. package/tools/bin/flow-resident-worker-lib.sh +120 -2
  29. package/tools/bin/flow-shell-lib.sh +29 -2
  30. package/tools/bin/heartbeat-loop-cache-lib.sh +164 -0
  31. package/tools/bin/heartbeat-loop-counting-lib.sh +306 -0
  32. package/tools/bin/heartbeat-loop-pr-strategy-lib.sh +199 -0
  33. package/tools/bin/heartbeat-loop-scheduling-lib.sh +506 -0
  34. package/tools/bin/heartbeat-loop-worker-lib.sh +319 -0
  35. package/tools/bin/heartbeat-recovery-preflight.sh +13 -1
  36. package/tools/bin/heartbeat-safe-auto.sh +119 -20
  37. package/tools/bin/install-project-launchd.sh +19 -2
  38. package/tools/bin/prepare-worktree.sh +4 -4
  39. package/tools/bin/profile-activate.sh +2 -2
  40. package/tools/bin/profile-adopt.sh +2 -2
  41. package/tools/bin/project-init.sh +1 -1
  42. package/tools/bin/project-launchd-bootstrap.sh +11 -8
  43. package/tools/bin/project-runtimectl.sh +90 -7
  44. package/tools/bin/provider-cooldown-state.sh +14 -14
  45. package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
  46. package/tools/bin/render-flow-config.sh +30 -33
  47. package/tools/bin/resident-issue-controller-lib.sh +448 -0
  48. package/tools/bin/resident-issue-queue-status.py +35 -0
  49. package/tools/bin/run-codex-task.sh +53 -4
  50. package/tools/bin/scaffold-profile.sh +18 -3
  51. package/tools/bin/start-issue-worker.sh +1 -1
  52. package/tools/bin/start-pr-fix-worker.sh +30 -0
  53. package/tools/bin/start-pr-review-worker.sh +31 -0
  54. package/tools/bin/start-resident-issue-loop.sh +27 -438
  55. package/tools/bin/sync-agent-repo.sh +2 -2
  56. package/tools/bin/sync-dependency-baseline.sh +3 -3
  57. package/tools/bin/sync-shared-agent-home.sh +4 -1
  58. package/tools/dashboard/app.js +7 -0
  59. package/tools/dashboard/dashboard_snapshot.py +13 -29
  60. package/tools/templates/pr-fix-template.md +3 -7
  61. package/tools/templates/pr-merge-repair-template.md +3 -7
  62. package/tools/templates/pr-review-template.md +2 -1
  63. package/SKILL.md +0 -149
@@ -1699,6 +1699,76 @@ flow_provider_pool_openclaw_timeout_seconds() {
1699
1699
  flow_provider_pool_value "${config_file}" "${pool_name}" "openclaw.timeout_seconds"
1700
1700
  }
1701
1701
 
1702
+ flow_provider_pool_ollama_model() {
1703
+ local config_file="${1:?config file required}"
1704
+ local pool_name="${2:?pool name required}"
1705
+
1706
+ flow_provider_pool_value "${config_file}" "${pool_name}" "ollama.model"
1707
+ }
1708
+
1709
+ flow_provider_pool_ollama_base_url() {
1710
+ local config_file="${1:?config file required}"
1711
+ local pool_name="${2:?pool name required}"
1712
+
1713
+ flow_provider_pool_value "${config_file}" "${pool_name}" "ollama.base_url"
1714
+ }
1715
+
1716
+ flow_provider_pool_ollama_timeout_seconds() {
1717
+ local config_file="${1:?config file required}"
1718
+ local pool_name="${2:?pool name required}"
1719
+
1720
+ flow_provider_pool_value "${config_file}" "${pool_name}" "ollama.timeout_seconds"
1721
+ }
1722
+
1723
+ flow_provider_pool_pi_model() {
1724
+ local config_file="${1:?config file required}"
1725
+ local pool_name="${2:?pool name required}"
1726
+
1727
+ flow_provider_pool_value "${config_file}" "${pool_name}" "pi.model"
1728
+ }
1729
+
1730
+ flow_provider_pool_pi_thinking() {
1731
+ local config_file="${1:?config file required}"
1732
+ local pool_name="${2:?pool name required}"
1733
+
1734
+ flow_provider_pool_value "${config_file}" "${pool_name}" "pi.thinking"
1735
+ }
1736
+
1737
+ flow_provider_pool_pi_timeout_seconds() {
1738
+ local config_file="${1:?config file required}"
1739
+ local pool_name="${2:?pool name required}"
1740
+
1741
+ flow_provider_pool_value "${config_file}" "${pool_name}" "pi.timeout_seconds"
1742
+ }
1743
+
1744
+ flow_provider_pool_opencode_model() {
1745
+ local config_file="${1:?config file required}"
1746
+ local pool_name="${2:?pool name required}"
1747
+
1748
+ flow_provider_pool_value "${config_file}" "${pool_name}" "opencode.model"
1749
+ }
1750
+
1751
+ flow_provider_pool_opencode_timeout_seconds() {
1752
+ local config_file="${1:?config file required}"
1753
+ local pool_name="${2:?pool name required}"
1754
+
1755
+ flow_provider_pool_value "${config_file}" "${pool_name}" "opencode.timeout_seconds"
1756
+ }
1757
+
1758
+ flow_provider_pool_kilo_model() {
1759
+ local config_file="${1:?config file required}"
1760
+ local pool_name="${2:?pool name required}"
1761
+
1762
+ flow_provider_pool_value "${config_file}" "${pool_name}" "kilo.model"
1763
+ }
1764
+
1765
+ flow_provider_pool_kilo_timeout_seconds() {
1766
+ local config_file="${1:?config file required}"
1767
+ local pool_name="${2:?pool name required}"
1768
+
1769
+ flow_provider_pool_value "${config_file}" "${pool_name}" "kilo.timeout_seconds"
1770
+ }
1771
+
1702
1772
  flow_sanitize_provider_key() {
1703
1773
  local raw_key="${1:?raw key required}"
1704
1774
 
@@ -1723,6 +1793,18 @@ flow_provider_pool_model_identity() {
1723
1793
  openclaw)
1724
1794
  flow_provider_pool_openclaw_model "${config_file}" "${pool_name}"
1725
1795
  ;;
1796
+ ollama)
1797
+ flow_provider_pool_ollama_model "${config_file}" "${pool_name}"
1798
+ ;;
1799
+ pi)
1800
+ flow_provider_pool_pi_model "${config_file}" "${pool_name}"
1801
+ ;;
1802
+ opencode)
1803
+ flow_provider_pool_opencode_model "${config_file}" "${pool_name}"
1804
+ ;;
1805
+ kilo)
1806
+ flow_provider_pool_kilo_model "${config_file}" "${pool_name}"
1807
+ ;;
1726
1808
  *)
1727
1809
  printf '\n'
1728
1810
  ;;
@@ -1756,6 +1838,16 @@ flow_provider_pool_state_get() {
1756
1838
  local openclaw_model=""
1757
1839
  local openclaw_thinking=""
1758
1840
  local openclaw_timeout_seconds=""
1841
+ local ollama_model=""
1842
+ local ollama_base_url=""
1843
+ local ollama_timeout_seconds=""
1844
+ local pi_model=""
1845
+ local pi_thinking=""
1846
+ local pi_timeout_seconds=""
1847
+ local opencode_model=""
1848
+ local opencode_timeout_seconds=""
1849
+ local kilo_model=""
1850
+ local kilo_timeout_seconds=""
1759
1851
 
1760
1852
  backend="$(flow_provider_pool_backend "${config_file}" "${pool_name}")"
1761
1853
  safe_profile="$(flow_provider_pool_safe_profile "${config_file}" "${pool_name}")"
@@ -1769,6 +1861,16 @@ flow_provider_pool_state_get() {
1769
1861
  openclaw_model="$(flow_provider_pool_openclaw_model "${config_file}" "${pool_name}")"
1770
1862
  openclaw_thinking="$(flow_provider_pool_openclaw_thinking "${config_file}" "${pool_name}")"
1771
1863
  openclaw_timeout_seconds="$(flow_provider_pool_openclaw_timeout_seconds "${config_file}" "${pool_name}")"
1864
+ ollama_model="$(flow_provider_pool_ollama_model "${config_file}" "${pool_name}")"
1865
+ ollama_base_url="$(flow_provider_pool_ollama_base_url "${config_file}" "${pool_name}")"
1866
+ ollama_timeout_seconds="$(flow_provider_pool_ollama_timeout_seconds "${config_file}" "${pool_name}")"
1867
+ pi_model="$(flow_provider_pool_pi_model "${config_file}" "${pool_name}")"
1868
+ pi_thinking="$(flow_provider_pool_pi_thinking "${config_file}" "${pool_name}")"
1869
+ pi_timeout_seconds="$(flow_provider_pool_pi_timeout_seconds "${config_file}" "${pool_name}")"
1870
+ opencode_model="$(flow_provider_pool_opencode_model "${config_file}" "${pool_name}")"
1871
+ opencode_timeout_seconds="$(flow_provider_pool_opencode_timeout_seconds "${config_file}" "${pool_name}")"
1872
+ kilo_model="$(flow_provider_pool_kilo_model "${config_file}" "${pool_name}")"
1873
+ kilo_timeout_seconds="$(flow_provider_pool_kilo_timeout_seconds "${config_file}" "${pool_name}")"
1772
1874
  model="$(flow_provider_pool_model_identity "${config_file}" "${pool_name}")"
1773
1875
 
1774
1876
  case "${backend}" in
@@ -1781,6 +1883,18 @@ flow_provider_pool_state_get() {
1781
1883
  openclaw)
1782
1884
  [[ -n "${openclaw_model}" && -n "${openclaw_thinking}" && -n "${openclaw_timeout_seconds}" ]] || valid="no"
1783
1885
  ;;
1886
+ ollama)
1887
+ [[ -n "${ollama_model}" ]] || valid="no"
1888
+ ;;
1889
+ pi)
1890
+ [[ -n "${pi_model}" ]] || valid="no"
1891
+ ;;
1892
+ opencode)
1893
+ [[ -n "${opencode_model}" && -n "${opencode_timeout_seconds}" ]] || valid="no"
1894
+ ;;
1895
+ kilo)
1896
+ [[ -n "${kilo_model}" && -n "${kilo_timeout_seconds}" ]] || valid="no"
1897
+ ;;
1784
1898
  *)
1785
1899
  valid="no"
1786
1900
  ;;
@@ -1833,6 +1947,16 @@ flow_provider_pool_state_get() {
1833
1947
  printf 'OPENCLAW_MODEL=%s\n' "${openclaw_model}"
1834
1948
  printf 'OPENCLAW_THINKING=%s\n' "${openclaw_thinking}"
1835
1949
  printf 'OPENCLAW_TIMEOUT_SECONDS=%s\n' "${openclaw_timeout_seconds}"
1950
+ printf 'OLLAMA_MODEL=%s\n' "${ollama_model}"
1951
+ printf 'OLLAMA_BASE_URL=%s\n' "${ollama_base_url}"
1952
+ printf 'OLLAMA_TIMEOUT_SECONDS=%s\n' "${ollama_timeout_seconds}"
1953
+ printf 'PI_MODEL=%s\n' "${pi_model}"
1954
+ printf 'PI_THINKING=%s\n' "${pi_thinking}"
1955
+ printf 'PI_TIMEOUT_SECONDS=%s\n' "${pi_timeout_seconds}"
1956
+ printf 'OPENCODE_MODEL=%s\n' "${opencode_model}"
1957
+ printf 'OPENCODE_TIMEOUT_SECONDS=%s\n' "${opencode_timeout_seconds}"
1958
+ printf 'KILO_MODEL=%s\n' "${kilo_model}"
1959
+ printf 'KILO_TIMEOUT_SECONDS=%s\n' "${kilo_timeout_seconds}"
1836
1960
  }
1837
1961
 
1838
1962
  flow_selected_provider_pool_env() {
@@ -2040,11 +2164,21 @@ flow_export_execution_env() {
2040
2164
  local openclaw_thinking=""
2041
2165
  local openclaw_timeout=""
2042
2166
  local openclaw_stall=""
2167
+ local ollama_model=""
2168
+ local ollama_base_url=""
2169
+ local ollama_timeout=""
2170
+ local pi_model=""
2171
+ local pi_thinking=""
2172
+ local pi_timeout=""
2173
+ local opencode_model=""
2174
+ local opencode_timeout=""
2175
+ local kilo_model=""
2176
+ local kilo_timeout=""
2043
2177
 
2044
2178
  repo_id="$(flow_resolve_repo_id "${config_file}")"
2045
2179
  provider_quota_cooldowns="$(flow_resolve_provider_quota_cooldowns "${config_file}")"
2046
2180
  provider_pool_order="$(flow_resolve_provider_pool_order "${config_file}")"
2047
- explicit_coding_worker="${ACP_CODING_WORKER:-${F_LOSNING_CODING_WORKER:-}}"
2181
+ explicit_coding_worker="${ACP_CODING_WORKER:-}"
2048
2182
  if [[ -z "${explicit_coding_worker}" && -n "${provider_pool_order}" ]]; then
2049
2183
  provider_pool_selection="$(flow_selected_provider_pool_env "${config_file}" || true)"
2050
2184
  fi
@@ -2073,11 +2207,21 @@ flow_export_execution_env() {
2073
2207
  openclaw_thinking="$(flow_kv_get "${provider_pool_selection}" "OPENCLAW_THINKING")"
2074
2208
  openclaw_timeout="$(flow_kv_get "${provider_pool_selection}" "OPENCLAW_TIMEOUT_SECONDS")"
2075
2209
  openclaw_stall="$(flow_kv_get "${provider_pool_selection}" "OPENCLAW_STALL_SECONDS")"
2210
+ ollama_model="$(flow_kv_get "${provider_pool_selection}" "OLLAMA_MODEL")"
2211
+ ollama_base_url="$(flow_kv_get "${provider_pool_selection}" "OLLAMA_BASE_URL")"
2212
+ ollama_timeout="$(flow_kv_get "${provider_pool_selection}" "OLLAMA_TIMEOUT_SECONDS")"
2213
+ pi_model="$(flow_kv_get "${provider_pool_selection}" "PI_MODEL")"
2214
+ pi_thinking="$(flow_kv_get "${provider_pool_selection}" "PI_THINKING")"
2215
+ pi_timeout="$(flow_kv_get "${provider_pool_selection}" "PI_TIMEOUT_SECONDS")"
2216
+ opencode_model="$(flow_kv_get "${provider_pool_selection}" "OPENCODE_MODEL")"
2217
+ opencode_timeout="$(flow_kv_get "${provider_pool_selection}" "OPENCODE_TIMEOUT_SECONDS")"
2218
+ kilo_model="$(flow_kv_get "${provider_pool_selection}" "KILO_MODEL")"
2219
+ kilo_timeout="$(flow_kv_get "${provider_pool_selection}" "KILO_TIMEOUT_SECONDS")"
2076
2220
  else
2077
2221
  if [[ -n "${explicit_coding_worker}" ]]; then
2078
2222
  active_provider_selection_reason="env-override"
2079
2223
  fi
2080
- coding_worker="$(flow_env_or_config "${config_file}" "ACP_CODING_WORKER F_LOSNING_CODING_WORKER" "execution.coding_worker" "")"
2224
+ coding_worker="$(flow_env_or_config "${config_file}" "ACP_CODING_WORKER" "execution.coding_worker" "")"
2081
2225
  safe_profile="$(flow_env_or_config "${config_file}" "ACP_CODEX_PROFILE_SAFE F_LOSNING_CODEX_PROFILE_SAFE" "execution.safe_profile" "")"
2082
2226
  bypass_profile="$(flow_env_or_config "${config_file}" "ACP_CODEX_PROFILE_BYPASS F_LOSNING_CODEX_PROFILE_BYPASS" "execution.bypass_profile" "")"
2083
2227
  claude_model="$(flow_env_or_config "${config_file}" "ACP_CLAUDE_MODEL F_LOSNING_CLAUDE_MODEL" "execution.claude.model" "")"
@@ -2090,10 +2234,19 @@ flow_export_execution_env() {
2090
2234
  openclaw_thinking="$(flow_env_or_config "${config_file}" "ACP_OPENCLAW_THINKING F_LOSNING_OPENCLAW_THINKING" "execution.openclaw.thinking" "")"
2091
2235
  openclaw_timeout="$(flow_env_or_config "${config_file}" "ACP_OPENCLAW_TIMEOUT_SECONDS F_LOSNING_OPENCLAW_TIMEOUT_SECONDS" "execution.openclaw.timeout_seconds" "")"
2092
2236
  openclaw_stall="$(flow_env_or_config "${config_file}" "ACP_OPENCLAW_STALL_SECONDS F_LOSNING_OPENCLAW_STALL_SECONDS" "execution.openclaw.stall_seconds" "")"
2237
+ ollama_model="$(flow_env_or_config "${config_file}" "ACP_OLLAMA_MODEL F_LOSNING_OLLAMA_MODEL" "execution.ollama.model" "")"
2238
+ ollama_base_url="$(flow_env_or_config "${config_file}" "ACP_OLLAMA_BASE_URL F_LOSNING_OLLAMA_BASE_URL" "execution.ollama.base_url" "")"
2239
+ ollama_timeout="$(flow_env_or_config "${config_file}" "ACP_OLLAMA_TIMEOUT_SECONDS F_LOSNING_OLLAMA_TIMEOUT_SECONDS" "execution.ollama.timeout_seconds" "")"
2240
+ pi_model="$(flow_env_or_config "${config_file}" "ACP_PI_MODEL F_LOSNING_PI_MODEL" "execution.pi.model" "")"
2241
+ pi_thinking="$(flow_env_or_config "${config_file}" "ACP_PI_THINKING F_LOSNING_PI_THINKING" "execution.pi.thinking" "")"
2242
+ pi_timeout="$(flow_env_or_config "${config_file}" "ACP_PI_TIMEOUT_SECONDS F_LOSNING_PI_TIMEOUT_SECONDS" "execution.pi.timeout_seconds" "")"
2243
+ opencode_model="$(flow_env_or_config "${config_file}" "ACP_OPENCODE_MODEL F_LOSNING_OPENCODE_MODEL" "execution.opencode.model" "")"
2244
+ opencode_timeout="$(flow_env_or_config "${config_file}" "ACP_OPENCODE_TIMEOUT_SECONDS F_LOSNING_OPENCODE_TIMEOUT_SECONDS" "execution.opencode.timeout_seconds" "")"
2245
+ kilo_model="$(flow_env_or_config "${config_file}" "ACP_KILO_MODEL F_LOSNING_KILO_MODEL" "execution.kilo.model" "")"
2246
+ kilo_timeout="$(flow_env_or_config "${config_file}" "ACP_KILO_TIMEOUT_SECONDS F_LOSNING_KILO_TIMEOUT_SECONDS" "execution.kilo.timeout_seconds" "")"
2093
2247
  fi
2094
2248
 
2095
2249
  if [[ -n "${coding_worker}" ]]; then
2096
- export F_LOSNING_CODING_WORKER="${coding_worker}"
2097
2250
  export ACP_CODING_WORKER="${coding_worker}"
2098
2251
  fi
2099
2252
  if [[ -n "${repo_id}" ]]; then
@@ -2174,6 +2327,46 @@ flow_export_execution_env() {
2174
2327
  export F_LOSNING_OPENCLAW_STALL_SECONDS="${openclaw_stall}"
2175
2328
  export ACP_OPENCLAW_STALL_SECONDS="${openclaw_stall}"
2176
2329
  fi
2330
+ if [[ -n "${ollama_model}" ]]; then
2331
+ export F_LOSNING_OLLAMA_MODEL="${ollama_model}"
2332
+ export ACP_OLLAMA_MODEL="${ollama_model}"
2333
+ fi
2334
+ if [[ -n "${ollama_base_url}" ]]; then
2335
+ export F_LOSNING_OLLAMA_BASE_URL="${ollama_base_url}"
2336
+ export ACP_OLLAMA_BASE_URL="${ollama_base_url}"
2337
+ fi
2338
+ if [[ -n "${ollama_timeout}" ]]; then
2339
+ export F_LOSNING_OLLAMA_TIMEOUT_SECONDS="${ollama_timeout}"
2340
+ export ACP_OLLAMA_TIMEOUT_SECONDS="${ollama_timeout}"
2341
+ fi
2342
+ if [[ -n "${pi_model}" ]]; then
2343
+ export F_LOSNING_PI_MODEL="${pi_model}"
2344
+ export ACP_PI_MODEL="${pi_model}"
2345
+ fi
2346
+ if [[ -n "${pi_thinking}" ]]; then
2347
+ export F_LOSNING_PI_THINKING="${pi_thinking}"
2348
+ export ACP_PI_THINKING="${pi_thinking}"
2349
+ fi
2350
+ if [[ -n "${pi_timeout}" ]]; then
2351
+ export F_LOSNING_PI_TIMEOUT_SECONDS="${pi_timeout}"
2352
+ export ACP_PI_TIMEOUT_SECONDS="${pi_timeout}"
2353
+ fi
2354
+ if [[ -n "${opencode_model}" ]]; then
2355
+ export F_LOSNING_OPENCODE_MODEL="${opencode_model}"
2356
+ export ACP_OPENCODE_MODEL="${opencode_model}"
2357
+ fi
2358
+ if [[ -n "${opencode_timeout}" ]]; then
2359
+ export F_LOSNING_OPENCODE_TIMEOUT_SECONDS="${opencode_timeout}"
2360
+ export ACP_OPENCODE_TIMEOUT_SECONDS="${opencode_timeout}"
2361
+ fi
2362
+ if [[ -n "${kilo_model}" ]]; then
2363
+ export F_LOSNING_KILO_MODEL="${kilo_model}"
2364
+ export ACP_KILO_MODEL="${kilo_model}"
2365
+ fi
2366
+ if [[ -n "${kilo_timeout}" ]]; then
2367
+ export F_LOSNING_KILO_TIMEOUT_SECONDS="${kilo_timeout}"
2368
+ export ACP_KILO_TIMEOUT_SECONDS="${kilo_timeout}"
2369
+ fi
2177
2370
 
2178
2371
  flow_export_github_cli_auth_env "$(flow_resolve_repo_slug "${config_file}")"
2179
2372
  flow_export_project_env_aliases
@@ -118,7 +118,7 @@ flow_resident_issue_backend_supported() {
118
118
  local backend="${1:-}"
119
119
 
120
120
  case "${backend}" in
121
- codex|openclaw|claude)
121
+ codex|openclaw|claude|ollama|pi)
122
122
  return 0
123
123
  ;;
124
124
  *)
@@ -285,6 +285,22 @@ flow_resident_issue_queue_file() {
285
285
  printf '%s/issue-%s.env\n' "$(flow_resident_issue_queue_pending_dir "${config_file}")" "${issue_id}"
286
286
  }
287
287
 
288
+ flow_resident_issue_claim_file() {
289
+ local config_file="${1:-}"
290
+ local issue_id="${2:?issue id required}"
291
+ local claimer_key="${3:?claimer key required}"
292
+
293
+ if [[ -z "${config_file}" ]]; then
294
+ config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
295
+ fi
296
+
297
+ printf '%s/issue-%s.%s.%s.env\n' \
298
+ "$(flow_resident_issue_queue_claims_dir "${config_file}")" \
299
+ "${issue_id}" \
300
+ "${claimer_key}" \
301
+ "$$"
302
+ }
303
+
288
304
  flow_resident_issue_controller_file() {
289
305
  local config_file="${1:-}"
290
306
  local issue_id="${2:?issue id required}"
@@ -342,8 +358,11 @@ flow_resident_issue_enqueue() {
342
358
 
343
359
  tmp_file="${queue_file}.tmp.$$"
344
360
  flow_resident_write_metadata "${tmp_file}" \
361
+ "STATE_FORMAT_VERSION=1" \
362
+ "STATE_KIND=pending" \
345
363
  "ISSUE_ID=${issue_id}" \
346
364
  "QUEUED_BY=${queued_by}" \
365
+ "UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
347
366
  "QUEUED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
348
367
  mv "${tmp_file}" "${queue_file}"
349
368
 
@@ -361,6 +380,9 @@ flow_resident_issue_claim_next() {
361
380
  local issue_id=""
362
381
  local claim_file=""
363
382
  local claim_key=""
383
+ local queued_by=""
384
+ local queued_at=""
385
+ local claimed_at=""
364
386
 
365
387
  if [[ -z "${config_file}" ]]; then
366
388
  config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
@@ -378,8 +400,22 @@ flow_resident_issue_claim_next() {
378
400
  [[ -n "${issue_id}" ]] || continue
379
401
  [[ "${issue_id}" != "${skip_issue_id}" ]] || continue
380
402
 
381
- claim_file="${claims_dir}/issue-${issue_id}.${claim_key}.$$"
403
+ queued_by="$(flow_resident_metadata_value "${queue_file}" "QUEUED_BY" || true)"
404
+ queued_at="$(flow_resident_metadata_value "${queue_file}" "QUEUED_AT" || true)"
405
+ claimed_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
406
+ claim_file="$(flow_resident_issue_claim_file "${config_file}" "${issue_id}" "${claim_key}")"
382
407
  if mv "${queue_file}" "${claim_file}" 2>/dev/null; then
408
+ flow_resident_write_metadata "${claim_file}" \
409
+ "STATE_FORMAT_VERSION=1" \
410
+ "STATE_KIND=claim" \
411
+ "ISSUE_ID=${issue_id}" \
412
+ "QUEUED_BY=${queued_by}" \
413
+ "QUEUED_AT=${queued_at}" \
414
+ "SESSION=${claimer_key}" \
415
+ "CLAIMED_BY=${claim_key}" \
416
+ "CLAIMED_AT=${claimed_at}" \
417
+ "UPDATED_AT=${claimed_at}" \
418
+ "CLAIM_FILE=${claim_file}"
383
419
  printf 'ISSUE_ID=%s\n' "${issue_id}"
384
420
  printf 'CLAIM_FILE=%s\n' "${claim_file}"
385
421
  return 0
@@ -441,6 +477,87 @@ flow_resident_issue_controller_reap_file() {
441
477
  return 0
442
478
  }
443
479
 
480
+ flow_resident_issue_reap_stale_claims() {
481
+ local config_file="${1:-}"
482
+ local claims_dir=""
483
+ local claim_file=""
484
+ local claim_token=""
485
+ local claim_pid=""
486
+ local issue_id=""
487
+ local queued_by=""
488
+ local queued_at=""
489
+ local claimed_at=""
490
+ local existing_pending_file=""
491
+ local other_claim=""
492
+ local other_token=""
493
+ local other_pid=""
494
+
495
+ if [[ -z "${config_file}" ]]; then
496
+ config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
497
+ fi
498
+
499
+ claims_dir="$(flow_resident_issue_queue_claims_dir "${config_file}")"
500
+ mkdir -p "${claims_dir}"
501
+
502
+ for claim_file in "${claims_dir}"/issue-*.env; do
503
+ [[ -f "${claim_file}" ]] || continue
504
+
505
+ claim_token="${claim_file##*/}"
506
+ claim_token="${claim_token%.env}"
507
+ claim_pid="${claim_token##*.}"
508
+ [[ "${claim_pid}" =~ ^[0-9]+$ ]] || continue
509
+
510
+ if flow_resident_controller_pid_live "${claim_pid}" "start-resident-issue-loop.sh"; then
511
+ continue
512
+ fi
513
+
514
+ issue_id="$(flow_resident_metadata_value "${claim_file}" "ISSUE_ID" || true)"
515
+ [[ -n "${issue_id}" ]] || issue_id="${claim_token#issue-}"
516
+ issue_id="${issue_id%%.*}"
517
+ [[ -n "${issue_id}" ]] || continue
518
+
519
+ # If another live claim exists for the same issue, do not re-queue this one.
520
+ for other_claim in "${claims_dir}/issue-${issue_id}."*; do
521
+ [[ -f "${other_claim}" ]] || continue
522
+ other_token="${other_claim##*/}"
523
+ other_token="${other_token%.env}"
524
+ other_pid="${other_token##*.}"
525
+ [[ "${other_pid}" =~ ^[0-9]+$ ]] || continue
526
+ if [[ "${other_pid}" == "${claim_pid}" ]]; then
527
+ continue
528
+ fi
529
+ if flow_resident_controller_pid_live "${other_pid}" "start-resident-issue-loop.sh"; then
530
+ issue_id=""
531
+ break
532
+ fi
533
+ done
534
+ [[ -n "${issue_id}" ]] || continue
535
+
536
+ existing_pending_file="$(flow_resident_issue_queue_file "${config_file}" "${issue_id}")"
537
+ if [[ -f "${existing_pending_file}" ]]; then
538
+ rm -f "${claim_file}"
539
+ continue
540
+ fi
541
+
542
+ queued_by="$(flow_resident_metadata_value "${claim_file}" "QUEUED_BY" || true)"
543
+ queued_at="$(flow_resident_metadata_value "${claim_file}" "QUEUED_AT" || true)"
544
+ claimed_at="$(flow_resident_metadata_value "${claim_file}" "CLAIMED_AT" || true)"
545
+
546
+ [[ -n "${queued_by}" ]] || queued_by="heartbeat"
547
+ [[ -n "${queued_at}" ]] || queued_at="${claimed_at:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")}"
548
+
549
+ flow_resident_write_metadata "${existing_pending_file}" \
550
+ "STATE_FORMAT_VERSION=1" \
551
+ "STATE_KIND=pending" \
552
+ "ISSUE_ID=${issue_id}" \
553
+ "QUEUED_BY=${queued_by}" \
554
+ "QUEUED_AT=${queued_at}" \
555
+ "UPDATED_AT=${claimed_at:-${queued_at}}"
556
+ rm -f "${claim_file}"
557
+
558
+ done
559
+ }
560
+
444
561
  flow_resident_issue_reap_stale_state() {
445
562
  local config_file="${1:-}"
446
563
  local resident_root=""
@@ -458,6 +575,7 @@ flow_resident_issue_reap_stale_state() {
458
575
  reaped=$((reaped + 1))
459
576
  fi
460
577
  done
578
+ flow_resident_issue_reap_stale_claims "${config_file}" || true
461
579
 
462
580
  printf '%s\n' "${reaped}"
463
581
  }
@@ -5,6 +5,30 @@ flow_canonical_skill_name() {
5
5
  printf '%s\n' "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}"
6
6
  }
7
7
 
8
+ flow_resolve_python_bin() {
9
+ if [[ -n "${PYTHON_BIN:-}" && -x "${PYTHON_BIN:-}" ]]; then
10
+ printf '%s\n' "${PYTHON_BIN}"
11
+ return 0
12
+ fi
13
+
14
+ if command -v python3 >/dev/null 2>&1; then
15
+ command -v python3
16
+ return 0
17
+ fi
18
+
19
+ if [[ -x /opt/homebrew/bin/python3 ]]; then
20
+ printf '%s\n' "/opt/homebrew/bin/python3"
21
+ return 0
22
+ fi
23
+
24
+ if command -v python >/dev/null 2>&1; then
25
+ command -v python
26
+ return 0
27
+ fi
28
+
29
+ return 1
30
+ }
31
+
8
32
  flow_compat_skill_alias() {
9
33
  printf '%s\n' "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"
10
34
  }
@@ -35,7 +59,6 @@ flow_export_compat_env_aliases() {
35
59
  flow_export_env_alias_if_unset F_LOSNING_MEMORY_DIR ACP_MEMORY_DIR
36
60
  flow_export_env_alias_if_unset F_LOSNING_RETAINED_REPO_ROOT ACP_RETAINED_REPO_ROOT
37
61
  flow_export_env_alias_if_unset F_LOSNING_VSCODE_WORKSPACE_FILE ACP_VSCODE_WORKSPACE_FILE
38
- flow_export_env_alias_if_unset F_LOSNING_CODING_WORKER ACP_CODING_WORKER
39
62
  flow_export_env_alias_if_unset F_LOSNING_CODEX_PROFILE_SAFE ACP_CODEX_PROFILE_SAFE
40
63
  flow_export_env_alias_if_unset F_LOSNING_CODEX_PROFILE_BYPASS ACP_CODEX_PROFILE_BYPASS
41
64
  flow_export_env_alias_if_unset F_LOSNING_CLAUDE_MODEL ACP_CLAUDE_MODEL
@@ -84,7 +107,6 @@ flow_export_canonical_env_aliases() {
84
107
  flow_export_env_alias_if_unset ACP_MEMORY_DIR F_LOSNING_MEMORY_DIR
85
108
  flow_export_env_alias_if_unset ACP_RETAINED_REPO_ROOT F_LOSNING_RETAINED_REPO_ROOT
86
109
  flow_export_env_alias_if_unset ACP_VSCODE_WORKSPACE_FILE F_LOSNING_VSCODE_WORKSPACE_FILE
87
- flow_export_env_alias_if_unset ACP_CODING_WORKER F_LOSNING_CODING_WORKER
88
110
  flow_export_env_alias_if_unset ACP_CODEX_PROFILE_SAFE F_LOSNING_CODEX_PROFILE_SAFE
89
111
  flow_export_env_alias_if_unset ACP_CODEX_PROFILE_BYPASS F_LOSNING_CODEX_PROFILE_BYPASS
90
112
  flow_export_env_alias_if_unset ACP_CLAUDE_MODEL F_LOSNING_CLAUDE_MODEL
@@ -223,6 +245,11 @@ resolve_shared_agent_home() {
223
245
  flow_root="$(resolve_flow_skill_dir "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
224
246
  fi
225
247
 
248
+ if flow_is_skill_root "${flow_root}"; then
249
+ flow_print_dir "${flow_root}"
250
+ return 0
251
+ fi
252
+
226
253
  flow_print_dir "${flow_root}/../../.."
227
254
  }
228
255
 
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bash
2
+ # heartbeat-loop-cache-lib.sh — scheduler cache management and attribute caching
3
+
4
+ cleanup_scheduler_caches() {
5
+ tmux_sessions_cache=""
6
+ tmux_sessions_cache_loaded="no"
7
+ all_running_workers_cache=""
8
+ all_running_workers_cache_loaded="no"
9
+ running_issue_workers_cache=""
10
+ running_issue_workers_cache_loaded="no"
11
+ running_pr_workers_cache=""
12
+ running_pr_workers_cache_loaded="no"
13
+ completed_workers_cache=""
14
+ completed_workers_cache_loaded="no"
15
+ ready_issue_ids_cache=""
16
+ ready_issue_ids_cache_loaded="no"
17
+ open_agent_pr_ids_cache=""
18
+ open_agent_pr_ids_cache_loaded="no"
19
+ running_issue_ids_cache=""
20
+ running_issue_ids_cache_loaded="no"
21
+ exclusive_issue_ids_cache=""
22
+ exclusive_issue_ids_cache_loaded="no"
23
+ exclusive_pr_ids_cache=""
24
+ exclusive_pr_ids_cache_loaded="no"
25
+ blocked_recovery_issue_ids_cache=""
26
+ blocked_recovery_issue_ids_cache_loaded="no"
27
+ ordered_ready_issue_ids_cache=""
28
+ ordered_ready_issue_ids_cache_loaded="no"
29
+ due_scheduled_issue_ids_cache=""
30
+ due_scheduled_issue_ids_cache_loaded="no"
31
+ due_blocked_recovery_issue_ids_cache=""
32
+ due_blocked_recovery_issue_ids_cache_loaded="no"
33
+ if [[ -n "${issue_attr_cache_dir:-}" && -d "${issue_attr_cache_dir}" ]]; then
34
+ rm -rf "${issue_attr_cache_dir}" || true
35
+ fi
36
+ if [[ -n "${pr_attr_cache_dir:-}" && -d "${pr_attr_cache_dir}" ]]; then
37
+ rm -rf "${pr_attr_cache_dir}" || true
38
+ fi
39
+ if [[ -n "${pr_risk_cache_dir:-}" && -d "${pr_risk_cache_dir}" ]]; then
40
+ rm -rf "${pr_risk_cache_dir}" || true
41
+ fi
42
+ if declare -F heartbeat_invalidate_snapshot_cache >/dev/null 2>&1; then
43
+ heartbeat_invalidate_snapshot_cache
44
+ fi
45
+ }
46
+
47
+ cache_prefix() {
48
+ local raw_prefix="${issue_prefix:-${pr_prefix:-agent-control-plane}}"
49
+ local sanitized=""
50
+
51
+ sanitized="$(printf '%s' "${raw_prefix}" | tr '/[:space:]' '-' | tr -cd '[:alnum:]_.-')"
52
+ if [[ -z "${sanitized}" ]]; then
53
+ sanitized="agent-control-plane"
54
+ fi
55
+
56
+ printf '%s\n' "${sanitized}"
57
+ }
58
+
59
+ ensure_issue_attr_cache_dir() {
60
+ if [[ -z "${issue_attr_cache_dir:-}" || ! -d "${issue_attr_cache_dir:-}" ]]; then
61
+ issue_attr_cache_dir="$(mktemp -d "${TMPDIR:-/tmp}/$(cache_prefix)-issue-attrs.XXXXXX")"
62
+ fi
63
+ }
64
+
65
+ ensure_pr_attr_cache_dir() {
66
+ if [[ -z "${pr_attr_cache_dir:-}" || ! -d "${pr_attr_cache_dir:-}" ]]; then
67
+ pr_attr_cache_dir="$(mktemp -d "${TMPDIR:-/tmp}/$(cache_prefix)-pr-attrs.XXXXXX")"
68
+ fi
69
+ }
70
+
71
+ ensure_pr_risk_cache_dir() {
72
+ if [[ -z "${pr_risk_cache_dir:-}" || ! -d "${pr_risk_cache_dir:-}" ]]; then
73
+ pr_risk_cache_dir="$(mktemp -d "${TMPDIR:-/tmp}/$(cache_prefix)-pr-risk.XXXXXX")"
74
+ fi
75
+ }
76
+
77
+ pr_risk_runtime_cache_fresh() {
78
+ local cache_file="${1:?cache file required}"
79
+ local modified_at now age
80
+ [[ -f "$cache_file" ]] || return 1
81
+ modified_at="$(stat -f '%m' "$cache_file" 2>/dev/null || true)"
82
+ [[ "$modified_at" =~ ^[0-9]+$ ]] || return 1
83
+ now="$(date +%s)"
84
+ age=$((now - modified_at))
85
+ (( age >= 0 && age <= pr_risk_runtime_cache_ttl_seconds ))
86
+ }
87
+
88
+ cached_issue_attr() {
89
+ local attr_name="${1:?attr name required}"
90
+ local issue_id="${2:?issue id required}"
91
+ local cache_file attr_value
92
+
93
+ ensure_issue_attr_cache_dir
94
+ cache_file="${issue_attr_cache_dir}/${issue_id}.${attr_name}"
95
+ if [[ -f "${cache_file}" ]]; then
96
+ cat "${cache_file}"
97
+ return 0
98
+ fi
99
+
100
+ case "${attr_name}" in
101
+ heavy)
102
+ attr_value="$(heartbeat_issue_is_heavy "${issue_id}")"
103
+ ;;
104
+ recurring)
105
+ attr_value="$(heartbeat_issue_is_recurring "${issue_id}")"
106
+ ;;
107
+ scheduled)
108
+ attr_value="$(heartbeat_issue_is_scheduled "${issue_id}")"
109
+ ;;
110
+ schedule_interval_seconds)
111
+ attr_value="$(heartbeat_issue_schedule_interval_seconds "${issue_id}")"
112
+ ;;
113
+ exclusive)
114
+ attr_value="$(heartbeat_issue_is_exclusive "${issue_id}")"
115
+ ;;
116
+ *)
117
+ echo "unsupported issue cache attr: ${attr_name}" >&2
118
+ return 1
119
+ ;;
120
+ esac
121
+
122
+ printf '%s\n' "${attr_value}" >"${cache_file}"
123
+ printf '%s\n' "${attr_value}"
124
+ }
125
+
126
+ cached_pr_is_exclusive() {
127
+ local pr_number="${1:?pr number required}"
128
+ local cache_file attr_value
129
+
130
+ ensure_pr_attr_cache_dir
131
+ cache_file="${pr_attr_cache_dir}/${pr_number}.exclusive"
132
+ if [[ -f "${cache_file}" ]]; then
133
+ cat "${cache_file}"
134
+ return 0
135
+ fi
136
+
137
+ attr_value="$(heartbeat_pr_is_exclusive "${pr_number}")"
138
+ printf '%s\n' "${attr_value}" >"${cache_file}"
139
+ printf '%s\n' "${attr_value}"
140
+ }
141
+
142
+ cached_pr_risk_json() {
143
+ local pr_number="${1:?pr number required}"
144
+ local cache_file runtime_cache_file risk_json
145
+
146
+ ensure_pr_risk_cache_dir
147
+ cache_file="${pr_risk_cache_dir}/${pr_number}.json"
148
+ runtime_cache_file="${pr_risk_runtime_cache_dir}/${pr_number}.json"
149
+ if [[ -f "${cache_file}" ]]; then
150
+ cat "${cache_file}"
151
+ return 0
152
+ fi
153
+
154
+ if pr_risk_runtime_cache_fresh "${runtime_cache_file}"; then
155
+ cp "${runtime_cache_file}" "${cache_file}"
156
+ cat "${cache_file}"
157
+ return 0
158
+ fi
159
+
160
+ risk_json="$(heartbeat_pr_risk_json "${pr_number}")"
161
+ printf '%s\n' "${risk_json}" >"${cache_file}"
162
+ printf '%s\n' "${risk_json}" >"${runtime_cache_file}"
163
+ printf '%s\n' "${risk_json}"
164
+ }