agent-control-plane 0.2.0 → 0.4.9
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 +69 -19
- package/assets/workflow-catalog.json +1 -1
- package/bin/pr-risk.sh +22 -7
- package/bin/sync-pr-labels.sh +1 -1
- package/hooks/heartbeat-hooks.sh +125 -12
- package/hooks/issue-reconcile-hooks.sh +1 -1
- package/hooks/pr-reconcile-hooks.sh +1 -1
- package/npm/bin/agent-control-plane.js +296 -61
- package/package.json +11 -7
- package/tools/bin/agent-github-update-labels +36 -2
- package/tools/bin/agent-project-catch-up-merged-prs +4 -2
- package/tools/bin/agent-project-cleanup-session +49 -5
- package/tools/bin/agent-project-heartbeat-loop +119 -1471
- package/tools/bin/agent-project-publish-issue-pr +6 -3
- package/tools/bin/agent-project-reconcile-issue-session +78 -106
- package/tools/bin/agent-project-reconcile-pr-session +166 -143
- package/tools/bin/agent-project-retry-state +18 -7
- package/tools/bin/agent-project-run-claude-session +10 -0
- package/tools/bin/agent-project-run-codex-resilient +99 -14
- package/tools/bin/agent-project-run-codex-session +16 -5
- package/tools/bin/agent-project-run-kilo-session +10 -0
- package/tools/bin/agent-project-run-openclaw-session +10 -0
- package/tools/bin/agent-project-run-opencode-session +10 -0
- package/tools/bin/agent-project-sync-source-repo-main +163 -0
- package/tools/bin/agent-project-worker-status +10 -7
- package/tools/bin/cleanup-worktree.sh +6 -1
- package/tools/bin/flow-config-lib.sh +1257 -34
- package/tools/bin/flow-resident-worker-lib.sh +119 -1
- package/tools/bin/flow-shell-lib.sh +56 -0
- package/tools/bin/github-core-rate-limit-state.sh +77 -0
- package/tools/bin/github-write-outbox.sh +470 -0
- 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 +12 -1
- package/tools/bin/heartbeat-safe-auto.sh +56 -3
- package/tools/bin/install-project-launchd.sh +17 -2
- package/tools/bin/project-init.sh +21 -1
- package/tools/bin/project-launchd-bootstrap.sh +16 -9
- package/tools/bin/project-runtimectl.sh +46 -2
- package/tools/bin/reconcile-bootstrap-lib.sh +113 -0
- package/tools/bin/resident-issue-controller-lib.sh +448 -0
- package/tools/bin/scaffold-profile.sh +61 -3
- package/tools/bin/start-pr-fix-worker.sh +47 -10
- package/tools/bin/start-resident-issue-loop.sh +28 -439
- package/tools/dashboard/app.js +37 -1
- package/tools/dashboard/dashboard_snapshot.py +65 -26
- package/tools/templates/pr-fix-template.md +3 -1
- package/tools/templates/pr-merge-repair-template.md +2 -1
- package/SKILL.md +0 -149
- package/references/architecture.md +0 -217
- package/references/commands.md +0 -128
- package/references/control-plane-map.md +0 -124
- package/references/docs-map.md +0 -73
- package/references/release-checklist.md +0 -65
- package/references/repo-map.md +0 -36
- package/tools/bin/split-retained-slice.sh +0 -124
|
@@ -110,6 +110,7 @@ LAUNCHD_PLIST="${ACP_PROJECT_RUNTIME_LAUNCHD_PLIST:-${LAUNCH_AGENTS_DIR}/${LAUNC
|
|
|
110
110
|
SOURCE_HOME="${ACP_PROJECT_RUNTIME_SOURCE_HOME:-}"
|
|
111
111
|
RUNTIME_HOME="${ACP_PROJECT_RUNTIME_RUNTIME_HOME:-$(resolve_runtime_home)}"
|
|
112
112
|
SYNC_STAMP_FILE="${RUNTIME_HOME}/.agent-control-plane-runtime-sync.env"
|
|
113
|
+
SOURCE_REPO_SYNC_STATE_FILE="${STATE_ROOT}/source-repo-main-sync.env"
|
|
113
114
|
|
|
114
115
|
case "${delay_seconds}" in
|
|
115
116
|
''|*[!0-9]*) echo "--delay-seconds must be numeric" >&2; exit 64 ;;
|
|
@@ -199,6 +200,13 @@ sync_stamp_value() {
|
|
|
199
200
|
| sed -e "s/^'//" -e "s/'$//"
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
source_repo_sync_value() {
|
|
204
|
+
local key="${1:?key required}"
|
|
205
|
+
[[ -f "${SOURCE_REPO_SYNC_STATE_FILE}" ]] || return 1
|
|
206
|
+
awk -F= -v target="${key}" '$1 == target {print $2; exit}' "${SOURCE_REPO_SYNC_STATE_FILE}" 2>/dev/null \
|
|
207
|
+
| sed -e "s/^'//" -e "s/'$//"
|
|
208
|
+
}
|
|
209
|
+
|
|
202
210
|
shared_loop_status_value() {
|
|
203
211
|
local key="${1:?key required}"
|
|
204
212
|
local file="${STATE_ROOT}/shared-heartbeat-loop.env"
|
|
@@ -399,6 +407,15 @@ print_status() {
|
|
|
399
407
|
local shared_loop_last_status=""
|
|
400
408
|
local shared_loop_started_at=""
|
|
401
409
|
local shared_loop_updated_at=""
|
|
410
|
+
local source_repo_sync_status=""
|
|
411
|
+
local source_repo_sync_updated_at=""
|
|
412
|
+
local source_repo_sync_root=""
|
|
413
|
+
local source_repo_sync_branch=""
|
|
414
|
+
local source_repo_sync_remote=""
|
|
415
|
+
local source_repo_sync_remote_sha=""
|
|
416
|
+
local source_repo_sync_local_sha=""
|
|
417
|
+
local source_repo_sync_detail=""
|
|
418
|
+
local source_repo_sync_aligned="unknown"
|
|
402
419
|
|
|
403
420
|
heartbeat="$(heartbeat_pid)"
|
|
404
421
|
shared_loop="$(shared_loop_pid)"
|
|
@@ -429,6 +446,23 @@ print_status() {
|
|
|
429
446
|
shared_loop_last_status="$(shared_loop_status_value "STATUS" || true)"
|
|
430
447
|
shared_loop_started_at="$(shared_loop_status_value "STARTED_AT" || true)"
|
|
431
448
|
shared_loop_updated_at="$(shared_loop_status_value "UPDATED_AT" || true)"
|
|
449
|
+
source_repo_sync_status="$(source_repo_sync_value "STATUS" || true)"
|
|
450
|
+
source_repo_sync_updated_at="$(source_repo_sync_value "UPDATED_AT" || true)"
|
|
451
|
+
source_repo_sync_root="$(source_repo_sync_value "SOURCE_REPO_ROOT" || true)"
|
|
452
|
+
source_repo_sync_branch="$(source_repo_sync_value "DEFAULT_BRANCH" || true)"
|
|
453
|
+
source_repo_sync_remote="$(source_repo_sync_value "REMOTE_NAME" || true)"
|
|
454
|
+
source_repo_sync_remote_sha="$(source_repo_sync_value "REMOTE_SHA" || true)"
|
|
455
|
+
source_repo_sync_local_sha="$(source_repo_sync_value "LOCAL_SHA" || true)"
|
|
456
|
+
source_repo_sync_detail="$(source_repo_sync_value "DETAIL" || true)"
|
|
457
|
+
if [[ -n "${source_repo_sync_status}" ]]; then
|
|
458
|
+
if [[ -n "${source_repo_sync_remote_sha}" && -n "${source_repo_sync_local_sha}" && "${source_repo_sync_remote_sha}" == "${source_repo_sync_local_sha}" ]]; then
|
|
459
|
+
source_repo_sync_aligned="yes"
|
|
460
|
+
elif [[ "${source_repo_sync_status}" == "blocked" || "${source_repo_sync_status}" == "failed" ]]; then
|
|
461
|
+
source_repo_sync_aligned="no"
|
|
462
|
+
else
|
|
463
|
+
source_repo_sync_aligned="unknown"
|
|
464
|
+
fi
|
|
465
|
+
fi
|
|
432
466
|
|
|
433
467
|
printf 'PROFILE_ID=%s\n' "${PROFILE_ID}"
|
|
434
468
|
printf 'CONFIG_YAML=%s\n' "${CONFIG_YAML}"
|
|
@@ -459,6 +493,16 @@ print_status() {
|
|
|
459
493
|
printf 'RUNTIME_SYNC_STATUS=%s\n' "${runtime_sync_status}"
|
|
460
494
|
printf 'RUNTIME_SYNC_UPDATED_AT=%s\n' "${runtime_sync_updated_at}"
|
|
461
495
|
printf 'RUNTIME_SYNC_FINGERPRINT=%s\n' "${runtime_sync_fingerprint}"
|
|
496
|
+
printf 'SOURCE_REPO_SYNC_STATE_FILE=%s\n' "${SOURCE_REPO_SYNC_STATE_FILE}"
|
|
497
|
+
printf 'SOURCE_REPO_SYNC_STATUS=%s\n' "${source_repo_sync_status}"
|
|
498
|
+
printf 'SOURCE_REPO_SYNC_UPDATED_AT=%s\n' "${source_repo_sync_updated_at}"
|
|
499
|
+
printf 'SOURCE_REPO_SYNC_ROOT=%s\n' "${source_repo_sync_root}"
|
|
500
|
+
printf 'SOURCE_REPO_SYNC_BRANCH=%s\n' "${source_repo_sync_branch}"
|
|
501
|
+
printf 'SOURCE_REPO_SYNC_REMOTE=%s\n' "${source_repo_sync_remote}"
|
|
502
|
+
printf 'SOURCE_REPO_SYNC_REMOTE_SHA=%s\n' "${source_repo_sync_remote_sha}"
|
|
503
|
+
printf 'SOURCE_REPO_SYNC_LOCAL_SHA=%s\n' "${source_repo_sync_local_sha}"
|
|
504
|
+
printf 'SOURCE_REPO_SYNC_DETAIL=%s\n' "${source_repo_sync_detail}"
|
|
505
|
+
printf 'SOURCE_REPO_SYNC_ALIGNED=%s\n' "${source_repo_sync_aligned}"
|
|
462
506
|
}
|
|
463
507
|
|
|
464
508
|
terminate_pid_list() {
|
|
@@ -518,7 +562,7 @@ clear_running_labels_after_stop() {
|
|
|
518
562
|
fi
|
|
519
563
|
|
|
520
564
|
issue_json="$(flow_github_issue_list_json "${REPO_SLUG}" open 100 2>/dev/null || printf '[]\n')"
|
|
521
|
-
if [[ "${issue_json}" == "[]" ]]; then
|
|
565
|
+
if [[ "${issue_json}" == "[]" ]] && ! flow_using_gitea; then
|
|
522
566
|
issue_json="$(gh issue list -R "${REPO_SLUG}" --state open --limit 100 --json number,labels 2>/dev/null || printf '[]\n')"
|
|
523
567
|
fi
|
|
524
568
|
while IFS= read -r number; do
|
|
@@ -527,7 +571,7 @@ clear_running_labels_after_stop() {
|
|
|
527
571
|
done < <(jq -r '.[] | select(any(.labels[]?; .name == "agent-running")) | .number' <<<"${issue_json}" 2>/dev/null || true)
|
|
528
572
|
|
|
529
573
|
pr_json="$(flow_github_pr_list_json "${REPO_SLUG}" open 100 2>/dev/null || printf '[]\n')"
|
|
530
|
-
if [[ "${pr_json}" == "[]" ]]; then
|
|
574
|
+
if [[ "${pr_json}" == "[]" ]] && ! flow_using_gitea; then
|
|
531
575
|
pr_json="$(gh pr list -R "${REPO_SLUG}" --state open --limit 100 --json number,labels 2>/dev/null || printf '[]\n')"
|
|
532
576
|
fi
|
|
533
577
|
while IFS= read -r number; do
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# reconcile-bootstrap-lib.sh — shared bootstrap helpers for reconcile scripts.
|
|
3
|
+
# Sourced by both agent-project-reconcile-pr-session and
|
|
4
|
+
# agent-project-reconcile-issue-session to avoid duplicating the bootstrap
|
|
5
|
+
# preamble.
|
|
6
|
+
|
|
7
|
+
bootstrap_flow_shell_lib() {
|
|
8
|
+
local candidate=""
|
|
9
|
+
local skill_name=""
|
|
10
|
+
|
|
11
|
+
for candidate in \
|
|
12
|
+
"${SCRIPT_DIR}/flow-shell-lib.sh" \
|
|
13
|
+
"${AGENT_CONTROL_PLANE_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
14
|
+
"${ACP_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
15
|
+
"${F_LOSNING_FLOW_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
16
|
+
"${AGENT_FLOW_SKILL_ROOT:-}/tools/bin/flow-shell-lib.sh" \
|
|
17
|
+
"${SHARED_AGENT_HOME:-}/tools/bin/flow-shell-lib.sh" \
|
|
18
|
+
"$(pwd)/tools/bin/flow-shell-lib.sh"; do
|
|
19
|
+
if [[ -n "${candidate}" && -f "${candidate}" ]]; then
|
|
20
|
+
printf '%s\n' "${candidate}"
|
|
21
|
+
return 0
|
|
22
|
+
fi
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
|
|
26
|
+
for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
|
|
27
|
+
[[ -n "${skill_name}" ]] || continue
|
|
28
|
+
candidate="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}/tools/bin/flow-shell-lib.sh"
|
|
29
|
+
if [[ -f "${candidate}" ]]; then
|
|
30
|
+
printf '%s\n' "${candidate}"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
echo "unable to locate flow-shell-lib.sh for reconcile bootstrap" >&2
|
|
37
|
+
return 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
FLOW_SHELL_LIB_PATH="$(bootstrap_flow_shell_lib)"
|
|
41
|
+
BOOTSTRAP_TOOLS_DIR="$(cd "$(dirname "${FLOW_SHELL_LIB_PATH}")" && pwd)"
|
|
42
|
+
# shellcheck source=/dev/null
|
|
43
|
+
source "${FLOW_SHELL_LIB_PATH}"
|
|
44
|
+
|
|
45
|
+
resolve_reconcile_tools_dir() {
|
|
46
|
+
local candidate_root=""
|
|
47
|
+
local skill_name=""
|
|
48
|
+
|
|
49
|
+
for candidate_root in \
|
|
50
|
+
"${AGENT_CONTROL_PLANE_ROOT:-}" \
|
|
51
|
+
"${ACP_ROOT:-}" \
|
|
52
|
+
"${F_LOSNING_FLOW_ROOT:-}" \
|
|
53
|
+
"${AGENT_FLOW_SKILL_ROOT:-}"; do
|
|
54
|
+
if [[ -n "${candidate_root}" && -d "${candidate_root}/tools/bin" ]]; then
|
|
55
|
+
printf '%s/tools/bin\n' "${candidate_root}"
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
if [[ -n "${SHARED_AGENT_HOME:-}" ]]; then
|
|
61
|
+
if [[ -d "${SHARED_AGENT_HOME}/tools/bin" ]]; then
|
|
62
|
+
printf '%s/tools/bin\n' "${SHARED_AGENT_HOME}"
|
|
63
|
+
return 0
|
|
64
|
+
fi
|
|
65
|
+
for skill_name in "${AGENT_CONTROL_PLANE_SKILL_NAME:-agent-control-plane}" "${AGENT_CONTROL_PLANE_COMPAT_ALIAS:-}"; do
|
|
66
|
+
[[ -n "${skill_name}" ]] || continue
|
|
67
|
+
candidate_root="${SHARED_AGENT_HOME}/skills/openclaw/${skill_name}"
|
|
68
|
+
if [[ -d "${candidate_root}/tools/bin" ]]; then
|
|
69
|
+
printf '%s/tools/bin\n' "${candidate_root}"
|
|
70
|
+
return 0
|
|
71
|
+
fi
|
|
72
|
+
done
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if [[ -d "${SCRIPT_DIR}" ]]; then
|
|
76
|
+
printf '%s\n' "${SCRIPT_DIR}"
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
printf '%s\n' "${BOOTSTRAP_TOOLS_DIR}"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
shared_tools_dir="$(resolve_reconcile_tools_dir)"
|
|
84
|
+
resolve_reconcile_helper_path() {
|
|
85
|
+
local helper_name="${1:?helper name required}"
|
|
86
|
+
local candidate=""
|
|
87
|
+
|
|
88
|
+
for candidate in \
|
|
89
|
+
"${SCRIPT_DIR}/${helper_name}" \
|
|
90
|
+
"${BOOTSTRAP_TOOLS_DIR}/${helper_name}" \
|
|
91
|
+
"${shared_tools_dir}/${helper_name}"; do
|
|
92
|
+
if [[ -n "${candidate}" && -f "${candidate}" ]]; then
|
|
93
|
+
printf '%s\n' "${candidate}"
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
echo "unable to locate ${helper_name} for reconcile bootstrap" >&2
|
|
99
|
+
return 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
FLOW_CONFIG_LIB_PATH="$(resolve_reconcile_helper_path "flow-config-lib.sh")"
|
|
103
|
+
# shellcheck source=/dev/null
|
|
104
|
+
source "${FLOW_CONFIG_LIB_PATH}"
|
|
105
|
+
|
|
106
|
+
require_transition() {
|
|
107
|
+
local step="${1:?step required}"
|
|
108
|
+
shift
|
|
109
|
+
if ! "$@"; then
|
|
110
|
+
echo "reconcile transition failed: ${step}" >&2
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# resident-issue-controller-lib.sh — controller_* functions for the resident
|
|
3
|
+
# issue loop. Sourced by start-resident-issue-loop.sh to keep the main script
|
|
4
|
+
# focused on the top-level loop logic.
|
|
5
|
+
|
|
6
|
+
controller_unregister_pending_issue() {
|
|
7
|
+
local issue_id="${1:-${ISSUE_ID:-}}"
|
|
8
|
+
[[ -n "${issue_id}" ]] || return 0
|
|
9
|
+
rm -f "$(issue_pending_file "${issue_id}")"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
controller_register_pending_issue() {
|
|
13
|
+
[[ -n "${ISSUE_ID:-}" ]] || return 0
|
|
14
|
+
printf '%s\n' "$$" >"$(issue_pending_file "${ISSUE_ID}")"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
controller_refresh_execution_context() {
|
|
18
|
+
unset \
|
|
19
|
+
ACP_CODING_WORKER \
|
|
20
|
+
ACP_CODEX_PROFILE_SAFE F_LOSNING_CODEX_PROFILE_SAFE \
|
|
21
|
+
ACP_CODEX_PROFILE_BYPASS F_LOSNING_CODEX_PROFILE_BYPASS \
|
|
22
|
+
ACP_CLAUDE_MODEL F_LOSNING_CLAUDE_MODEL \
|
|
23
|
+
ACP_CLAUDE_PERMISSION_MODE F_LOSNING_CLAUDE_PERMISSION_MODE \
|
|
24
|
+
ACP_CLAUDE_EFFORT F_LOSNING_CLAUDE_EFFORT \
|
|
25
|
+
ACP_CLAUDE_TIMEOUT_SECONDS F_LOSNING_CLAUDE_TIMEOUT_SECONDS \
|
|
26
|
+
ACP_CLAUDE_MAX_ATTEMPTS F_LOSNING_CLAUDE_MAX_ATTEMPTS \
|
|
27
|
+
ACP_CLAUDE_RETRY_BACKOFF_SECONDS F_LOSNING_CLAUDE_RETRY_BACKOFF_SECONDS \
|
|
28
|
+
ACP_OPENCLAW_MODEL F_LOSNING_OPENCLAW_MODEL \
|
|
29
|
+
ACP_OPENCLAW_THINKING F_LOSNING_OPENCLAW_THINKING \
|
|
30
|
+
ACP_OPENCLAW_TIMEOUT_SECONDS F_LOSNING_OPENCLAW_TIMEOUT_SECONDS \
|
|
31
|
+
ACP_OPENCLAW_STALL_SECONDS F_LOSNING_OPENCLAW_STALL_SECONDS \
|
|
32
|
+
ACP_ACTIVE_PROVIDER_POOL_NAME F_LOSNING_ACTIVE_PROVIDER_POOL_NAME \
|
|
33
|
+
ACP_ACTIVE_PROVIDER_BACKEND F_LOSNING_ACTIVE_PROVIDER_BACKEND \
|
|
34
|
+
ACP_ACTIVE_PROVIDER_MODEL F_LOSNING_ACTIVE_PROVIDER_MODEL \
|
|
35
|
+
ACP_ACTIVE_PROVIDER_KEY F_LOSNING_ACTIVE_PROVIDER_KEY \
|
|
36
|
+
ACP_PROVIDER_POOLS_EXHAUSTED F_LOSNING_PROVIDER_POOLS_EXHAUSTED \
|
|
37
|
+
ACP_PROVIDER_POOL_SELECTION_REASON F_LOSNING_PROVIDER_POOL_SELECTION_REASON \
|
|
38
|
+
ACP_PROVIDER_POOL_NEXT_ATTEMPT_EPOCH F_LOSNING_PROVIDER_POOL_NEXT_ATTEMPT_EPOCH \
|
|
39
|
+
ACP_PROVIDER_POOL_NEXT_ATTEMPT_AT F_LOSNING_PROVIDER_POOL_NEXT_ATTEMPT_AT \
|
|
40
|
+
ACP_PROVIDER_POOL_LAST_REASON F_LOSNING_PROVIDER_POOL_LAST_REASON
|
|
41
|
+
flow_export_execution_env "${CONFIG_YAML}"
|
|
42
|
+
flow_export_project_env_aliases
|
|
43
|
+
CODING_WORKER="${ACP_CODING_WORKER:-codex}"
|
|
44
|
+
controller_capture_active_provider_context
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
controller_refresh_issue_lane_context() {
|
|
48
|
+
local is_scheduled="${1:-no}"
|
|
49
|
+
local schedule_interval_seconds="${2:-0}"
|
|
50
|
+
|
|
51
|
+
if [[ "${is_scheduled}" == "yes" ]]; then
|
|
52
|
+
ACTIVE_RESIDENT_LANE_KIND="scheduled"
|
|
53
|
+
ACTIVE_RESIDENT_LANE_VALUE="${schedule_interval_seconds}"
|
|
54
|
+
else
|
|
55
|
+
ACTIVE_RESIDENT_LANE_KIND="recurring"
|
|
56
|
+
ACTIVE_RESIDENT_LANE_VALUE="general"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
ACTIVE_RESIDENT_WORKER_KEY="$(flow_resident_issue_lane_key "${CODING_WORKER}" "${MODE}" "${ACTIVE_RESIDENT_LANE_KIND}" "${ACTIVE_RESIDENT_LANE_VALUE}")"
|
|
60
|
+
ACTIVE_RESIDENT_META_FILE="$(flow_resident_issue_lane_meta_file "${CONFIG_YAML}" "${ACTIVE_RESIDENT_WORKER_KEY}")"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
controller_live_lane_peer() {
|
|
64
|
+
[[ -n "${ACTIVE_RESIDENT_WORKER_KEY}" ]] || return 1
|
|
65
|
+
flow_resident_live_issue_controller_for_key "${CONFIG_YAML}" "${ACTIVE_RESIDENT_WORKER_KEY}" "$$" || return 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
controller_yield_to_live_lane_peer() {
|
|
69
|
+
local live_controller=""
|
|
70
|
+
local controller_issue_id=""
|
|
71
|
+
local controller_state=""
|
|
72
|
+
|
|
73
|
+
live_controller="$(controller_live_lane_peer || true)"
|
|
74
|
+
[[ -n "${live_controller}" ]] || return 1
|
|
75
|
+
|
|
76
|
+
controller_issue_id="$(awk -F= '/^ISSUE_ID=/{print $2; exit}' <<<"${live_controller}")"
|
|
77
|
+
controller_state="$(awk -F= '/^CONTROLLER_STATE=/{print $2; exit}' <<<"${live_controller}")"
|
|
78
|
+
|
|
79
|
+
if [[ -n "${controller_issue_id}" && "${controller_issue_id}" != "${ISSUE_ID}" ]]; then
|
|
80
|
+
flow_resident_issue_enqueue "${CONFIG_YAML}" "${ISSUE_ID}" "resident-live-lane" >/dev/null || true
|
|
81
|
+
CONTROLLER_REASON="live-lane-controller-${controller_issue_id}-${controller_state:-running}"
|
|
82
|
+
else
|
|
83
|
+
CONTROLLER_REASON="duplicate-live-lane-controller"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
controller_capture_active_provider_context() {
|
|
90
|
+
ACTIVE_PROVIDER_POOL_NAME="${ACP_ACTIVE_PROVIDER_POOL_NAME:-${F_LOSNING_ACTIVE_PROVIDER_POOL_NAME:-}}"
|
|
91
|
+
ACTIVE_PROVIDER_BACKEND="${ACP_ACTIVE_PROVIDER_BACKEND:-${F_LOSNING_ACTIVE_PROVIDER_BACKEND:-${CODING_WORKER:-}}}"
|
|
92
|
+
ACTIVE_PROVIDER_MODEL="${ACP_ACTIVE_PROVIDER_MODEL:-${F_LOSNING_ACTIVE_PROVIDER_MODEL:-}}"
|
|
93
|
+
ACTIVE_PROVIDER_KEY="${ACP_ACTIVE_PROVIDER_KEY:-${F_LOSNING_ACTIVE_PROVIDER_KEY:-}}"
|
|
94
|
+
ACTIVE_PROVIDER_SELECTION_REASON="${ACP_PROVIDER_POOL_SELECTION_REASON:-${F_LOSNING_PROVIDER_POOL_SELECTION_REASON:-}}"
|
|
95
|
+
ACTIVE_PROVIDER_NEXT_ATTEMPT_EPOCH="${ACP_PROVIDER_POOL_NEXT_ATTEMPT_EPOCH:-${F_LOSNING_PROVIDER_POOL_NEXT_ATTEMPT_EPOCH:-}}"
|
|
96
|
+
ACTIVE_PROVIDER_NEXT_ATTEMPT_AT="${ACP_PROVIDER_POOL_NEXT_ATTEMPT_AT:-${F_LOSNING_PROVIDER_POOL_NEXT_ATTEMPT_AT:-}}"
|
|
97
|
+
ACTIVE_PROVIDER_LAST_REASON="${ACP_PROVIDER_POOL_LAST_REASON:-${F_LOSNING_PROVIDER_POOL_LAST_REASON:-}}"
|
|
98
|
+
|
|
99
|
+
if [[ -z "${ACTIVE_PROVIDER_MODEL}" ]]; then
|
|
100
|
+
case "${ACTIVE_PROVIDER_BACKEND}" in
|
|
101
|
+
openclaw)
|
|
102
|
+
ACTIVE_PROVIDER_MODEL="${ACP_OPENCLAW_MODEL:-${F_LOSNING_OPENCLAW_MODEL:-}}"
|
|
103
|
+
;;
|
|
104
|
+
claude)
|
|
105
|
+
ACTIVE_PROVIDER_MODEL="${ACP_CLAUDE_MODEL:-${F_LOSNING_CLAUDE_MODEL:-}}"
|
|
106
|
+
;;
|
|
107
|
+
codex)
|
|
108
|
+
ACTIVE_PROVIDER_MODEL="${ACP_CODEX_PROFILE_SAFE:-${F_LOSNING_CODEX_PROFILE_SAFE:-}}"
|
|
109
|
+
;;
|
|
110
|
+
opencode)
|
|
111
|
+
ACTIVE_PROVIDER_MODEL="${ACP_OPENCODE_MODEL:-${F_LOSNING_OPENCODE_MODEL:-}}"
|
|
112
|
+
;;
|
|
113
|
+
kilo)
|
|
114
|
+
ACTIVE_PROVIDER_MODEL="${ACP_KILO_MODEL:-${F_LOSNING_KILO_MODEL:-}}"
|
|
115
|
+
;;
|
|
116
|
+
esac
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if [[ -z "${ACTIVE_PROVIDER_KEY}" && -n "${ACTIVE_PROVIDER_BACKEND}" && -n "${ACTIVE_PROVIDER_MODEL}" ]]; then
|
|
120
|
+
ACTIVE_PROVIDER_KEY="$(flow_sanitize_provider_key "${ACTIVE_PROVIDER_BACKEND}-${ACTIVE_PROVIDER_MODEL}")"
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
controller_set_recorded_provider_from_active() {
|
|
125
|
+
LAST_RECORDED_PROVIDER_POOL_NAME="${ACTIVE_PROVIDER_POOL_NAME}"
|
|
126
|
+
LAST_RECORDED_PROVIDER_BACKEND="${ACTIVE_PROVIDER_BACKEND}"
|
|
127
|
+
LAST_RECORDED_PROVIDER_MODEL="${ACTIVE_PROVIDER_MODEL}"
|
|
128
|
+
LAST_RECORDED_PROVIDER_KEY="${ACTIVE_PROVIDER_KEY}"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
controller_mark_provider_launched() {
|
|
132
|
+
LAST_LAUNCHED_PROVIDER_POOL_NAME="${ACTIVE_PROVIDER_POOL_NAME}"
|
|
133
|
+
LAST_LAUNCHED_PROVIDER_BACKEND="${ACTIVE_PROVIDER_BACKEND}"
|
|
134
|
+
LAST_LAUNCHED_PROVIDER_MODEL="${ACTIVE_PROVIDER_MODEL}"
|
|
135
|
+
LAST_LAUNCHED_PROVIDER_KEY="${ACTIVE_PROVIDER_KEY}"
|
|
136
|
+
|
|
137
|
+
if [[ -z "${LAST_RECORDED_PROVIDER_KEY}" ]]; then
|
|
138
|
+
controller_set_recorded_provider_from_active
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
controller_track_provider_selection() {
|
|
143
|
+
local reason="${1:-provider-selection}"
|
|
144
|
+
local now_at=""
|
|
145
|
+
|
|
146
|
+
[[ -n "${ACTIVE_PROVIDER_KEY}" ]] || return 0
|
|
147
|
+
|
|
148
|
+
if [[ -z "${LAST_RECORDED_PROVIDER_KEY}" ]]; then
|
|
149
|
+
controller_set_recorded_provider_from_active
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
if [[ "${ACTIVE_PROVIDER_KEY}" == "${LAST_RECORDED_PROVIDER_KEY}" ]]; then
|
|
154
|
+
return 0
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
now_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
158
|
+
PROVIDER_SWITCH_COUNT=$((PROVIDER_SWITCH_COUNT + 1))
|
|
159
|
+
LAST_PROVIDER_SWITCH_AT="${now_at}"
|
|
160
|
+
LAST_PROVIDER_SWITCH_REASON="${reason}"
|
|
161
|
+
LAST_PROVIDER_FROM_POOL_NAME="${LAST_RECORDED_PROVIDER_POOL_NAME}"
|
|
162
|
+
LAST_PROVIDER_FROM_BACKEND="${LAST_RECORDED_PROVIDER_BACKEND}"
|
|
163
|
+
LAST_PROVIDER_FROM_MODEL="${LAST_RECORDED_PROVIDER_MODEL}"
|
|
164
|
+
LAST_PROVIDER_FROM_KEY="${LAST_RECORDED_PROVIDER_KEY}"
|
|
165
|
+
LAST_PROVIDER_TO_POOL_NAME="${ACTIVE_PROVIDER_POOL_NAME}"
|
|
166
|
+
LAST_PROVIDER_TO_BACKEND="${ACTIVE_PROVIDER_BACKEND}"
|
|
167
|
+
LAST_PROVIDER_TO_MODEL="${ACTIVE_PROVIDER_MODEL}"
|
|
168
|
+
LAST_PROVIDER_TO_KEY="${ACTIVE_PROVIDER_KEY}"
|
|
169
|
+
|
|
170
|
+
if [[ "${reason}" == "provider-failover" ]]; then
|
|
171
|
+
PROVIDER_FAILOVER_COUNT=$((PROVIDER_FAILOVER_COUNT + 1))
|
|
172
|
+
LAST_PROVIDER_FAILOVER_AT="${now_at}"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
controller_set_recorded_provider_from_active
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
controller_adopt_issue() {
|
|
179
|
+
local next_issue_id="${1:?issue id required}"
|
|
180
|
+
local previous_issue_id="${ISSUE_ID:-}"
|
|
181
|
+
local previous_controller_file="${CONTROLLER_FILE:-}"
|
|
182
|
+
|
|
183
|
+
if [[ -n "${previous_issue_id}" && "${previous_issue_id}" != "${next_issue_id}" ]]; then
|
|
184
|
+
controller_unregister_pending_issue "${previous_issue_id}"
|
|
185
|
+
if [[ -n "${previous_controller_file}" && -f "${previous_controller_file}" ]]; then
|
|
186
|
+
rm -f "${previous_controller_file}"
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
ISSUE_ID="${next_issue_id}"
|
|
191
|
+
SESSION="${ISSUE_SESSION_PREFIX}${ISSUE_ID}"
|
|
192
|
+
CONTROLLER_FILE="$(flow_resident_issue_controller_file "${CONFIG_YAML}" "${ISSUE_ID}")"
|
|
193
|
+
RESIDENT_META_FILE="$(flow_resident_issue_meta_file "${CONFIG_YAML}" "${ISSUE_ID}")"
|
|
194
|
+
CONTROLLER_LOOP_COUNT="0"
|
|
195
|
+
NEXT_WAKE_EPOCH=""
|
|
196
|
+
NEXT_WAKE_AT=""
|
|
197
|
+
IDLE_WAIT_STARTED_EPOCH=""
|
|
198
|
+
ACTIVE_RESIDENT_WORKER_KEY=""
|
|
199
|
+
ACTIVE_RESIDENT_META_FILE=""
|
|
200
|
+
ACTIVE_RESIDENT_LANE_KIND=""
|
|
201
|
+
ACTIVE_RESIDENT_LANE_VALUE=""
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
controller_mark_issue_running() {
|
|
205
|
+
local is_heavy="no"
|
|
206
|
+
|
|
207
|
+
if declare -F heartbeat_issue_is_heavy >/dev/null 2>&1; then
|
|
208
|
+
is_heavy="$(heartbeat_issue_is_heavy "${ISSUE_ID}" 2>/dev/null || printf 'no\n')"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
if declare -F heartbeat_mark_issue_running >/dev/null 2>&1; then
|
|
212
|
+
heartbeat_mark_issue_running "${ISSUE_ID}" "${is_heavy}" >/dev/null 2>&1 || true
|
|
213
|
+
fi
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
controller_rollback_issue_launch() {
|
|
217
|
+
if declare -F heartbeat_issue_launch_failed >/dev/null 2>&1; then
|
|
218
|
+
heartbeat_issue_launch_failed "${ISSUE_ID}" >/dev/null 2>&1 || true
|
|
219
|
+
fi
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
controller_adopt_next_recurring_issue() {
|
|
223
|
+
local next_issue_id=""
|
|
224
|
+
local claim_out=""
|
|
225
|
+
local claim_file=""
|
|
226
|
+
|
|
227
|
+
claim_out="$(flow_resident_issue_claim_next "${CONFIG_YAML}" "${SESSION}" "${ISSUE_ID}" || true)"
|
|
228
|
+
next_issue_id="$(awk -F= '/^ISSUE_ID=/{print $2}' <<<"${claim_out}")"
|
|
229
|
+
claim_file="$(awk -F= '/^CLAIM_FILE=/{print $2}' <<<"${claim_out}")"
|
|
230
|
+
if [[ -z "${next_issue_id}" ]]; then
|
|
231
|
+
next_issue_id="$(select_next_recurring_issue_id || true)"
|
|
232
|
+
fi
|
|
233
|
+
[[ -n "${next_issue_id}" ]] || return 1
|
|
234
|
+
|
|
235
|
+
controller_adopt_issue "${next_issue_id}"
|
|
236
|
+
flow_resident_issue_release_claim "${claim_file}"
|
|
237
|
+
CONTROLLER_REASON="adopted-next-recurring-issue"
|
|
238
|
+
controller_write_state "adopting-issue" ""
|
|
239
|
+
return 0
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
controller_wait_for_leased_issue() {
|
|
243
|
+
local idle_timeout="${IDLE_TIMEOUT_SECONDS:-0}"
|
|
244
|
+
local now_epoch=""
|
|
245
|
+
|
|
246
|
+
case "${idle_timeout}" in
|
|
247
|
+
''|*[!0-9]*) idle_timeout="0" ;;
|
|
248
|
+
esac
|
|
249
|
+
|
|
250
|
+
if [[ "${idle_timeout}" -le 0 ]]; then
|
|
251
|
+
return 1
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
if [[ -z "${IDLE_WAIT_STARTED_EPOCH}" ]]; then
|
|
255
|
+
IDLE_WAIT_STARTED_EPOCH="$(date +%s)"
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
while true; do
|
|
259
|
+
if controller_adopt_next_recurring_issue; then
|
|
260
|
+
return 0
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
now_epoch="$(date +%s)"
|
|
264
|
+
if (( now_epoch - IDLE_WAIT_STARTED_EPOCH >= idle_timeout )); then
|
|
265
|
+
CONTROLLER_REASON="idle-timeout"
|
|
266
|
+
return 1
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
controller_write_state "idle" ""
|
|
270
|
+
sleep "${POLL_SECONDS}"
|
|
271
|
+
done
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
controller_write_state() {
|
|
275
|
+
local state="${1:?state required}"
|
|
276
|
+
local reason="${2:-${CONTROLLER_REASON}}"
|
|
277
|
+
|
|
278
|
+
CONTROLLER_STATE="${state}"
|
|
279
|
+
CONTROLLER_REASON="${reason}"
|
|
280
|
+
flow_resident_write_metadata "${CONTROLLER_FILE}" \
|
|
281
|
+
"ISSUE_ID=${ISSUE_ID}" \
|
|
282
|
+
"SESSION=${SESSION}" \
|
|
283
|
+
"CONTROLLER_PID=$$" \
|
|
284
|
+
"CONTROLLER_MODE=${MODE}" \
|
|
285
|
+
"CONTROLLER_LOOP_COUNT=${CONTROLLER_LOOP_COUNT}" \
|
|
286
|
+
"CONTROLLER_STATE=${CONTROLLER_STATE}" \
|
|
287
|
+
"CONTROLLER_REASON=${CONTROLLER_REASON}" \
|
|
288
|
+
"ACTIVE_RESIDENT_WORKER_KEY=${ACTIVE_RESIDENT_WORKER_KEY}" \
|
|
289
|
+
"ACTIVE_RESIDENT_LANE_KIND=${ACTIVE_RESIDENT_LANE_KIND}" \
|
|
290
|
+
"ACTIVE_RESIDENT_LANE_VALUE=${ACTIVE_RESIDENT_LANE_VALUE}" \
|
|
291
|
+
"ACTIVE_PROVIDER_POOL_NAME=${ACTIVE_PROVIDER_POOL_NAME}" \
|
|
292
|
+
"ACTIVE_PROVIDER_BACKEND=${ACTIVE_PROVIDER_BACKEND}" \
|
|
293
|
+
"ACTIVE_PROVIDER_MODEL=${ACTIVE_PROVIDER_MODEL}" \
|
|
294
|
+
"ACTIVE_PROVIDER_KEY=${ACTIVE_PROVIDER_KEY}" \
|
|
295
|
+
"ACTIVE_PROVIDER_SELECTION_REASON=${ACTIVE_PROVIDER_SELECTION_REASON}" \
|
|
296
|
+
"ACTIVE_PROVIDER_NEXT_ATTEMPT_EPOCH=${ACTIVE_PROVIDER_NEXT_ATTEMPT_EPOCH}" \
|
|
297
|
+
"ACTIVE_PROVIDER_NEXT_ATTEMPT_AT=${ACTIVE_PROVIDER_NEXT_ATTEMPT_AT}" \
|
|
298
|
+
"ACTIVE_PROVIDER_LAST_REASON=${ACTIVE_PROVIDER_LAST_REASON}" \
|
|
299
|
+
"LAST_LAUNCHED_PROVIDER_POOL_NAME=${LAST_LAUNCHED_PROVIDER_POOL_NAME}" \
|
|
300
|
+
"LAST_LAUNCHED_PROVIDER_BACKEND=${LAST_LAUNCHED_PROVIDER_BACKEND}" \
|
|
301
|
+
"LAST_LAUNCHED_PROVIDER_MODEL=${LAST_LAUNCHED_PROVIDER_MODEL}" \
|
|
302
|
+
"LAST_LAUNCHED_PROVIDER_KEY=${LAST_LAUNCHED_PROVIDER_KEY}" \
|
|
303
|
+
"PROVIDER_SWITCH_COUNT=${PROVIDER_SWITCH_COUNT}" \
|
|
304
|
+
"PROVIDER_FAILOVER_COUNT=${PROVIDER_FAILOVER_COUNT}" \
|
|
305
|
+
"LAST_PROVIDER_SWITCH_AT=${LAST_PROVIDER_SWITCH_AT}" \
|
|
306
|
+
"LAST_PROVIDER_SWITCH_REASON=${LAST_PROVIDER_SWITCH_REASON}" \
|
|
307
|
+
"LAST_PROVIDER_FROM_POOL_NAME=${LAST_PROVIDER_FROM_POOL_NAME}" \
|
|
308
|
+
"LAST_PROVIDER_FROM_BACKEND=${LAST_PROVIDER_FROM_BACKEND}" \
|
|
309
|
+
"LAST_PROVIDER_FROM_MODEL=${LAST_PROVIDER_FROM_MODEL}" \
|
|
310
|
+
"LAST_PROVIDER_FROM_KEY=${LAST_PROVIDER_FROM_KEY}" \
|
|
311
|
+
"LAST_PROVIDER_TO_POOL_NAME=${LAST_PROVIDER_TO_POOL_NAME}" \
|
|
312
|
+
"LAST_PROVIDER_TO_BACKEND=${LAST_PROVIDER_TO_BACKEND}" \
|
|
313
|
+
"LAST_PROVIDER_TO_MODEL=${LAST_PROVIDER_TO_MODEL}" \
|
|
314
|
+
"LAST_PROVIDER_TO_KEY=${LAST_PROVIDER_TO_KEY}" \
|
|
315
|
+
"LAST_PROVIDER_FAILOVER_AT=${LAST_PROVIDER_FAILOVER_AT}" \
|
|
316
|
+
"PROVIDER_WAIT_COUNT=${PROVIDER_WAIT_COUNT}" \
|
|
317
|
+
"PROVIDER_WAIT_TOTAL_SECONDS=${PROVIDER_WAIT_TOTAL_SECONDS}" \
|
|
318
|
+
"PROVIDER_LAST_WAIT_SECONDS=${PROVIDER_LAST_WAIT_SECONDS}" \
|
|
319
|
+
"PROVIDER_LAST_WAIT_STARTED_AT=${PROVIDER_LAST_WAIT_STARTED_AT}" \
|
|
320
|
+
"PROVIDER_LAST_WAIT_COMPLETED_AT=${PROVIDER_LAST_WAIT_COMPLETED_AT}" \
|
|
321
|
+
"NEXT_WAKE_EPOCH=${NEXT_WAKE_EPOCH}" \
|
|
322
|
+
"NEXT_WAKE_AT=${NEXT_WAKE_AT}" \
|
|
323
|
+
"UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
324
|
+
|
|
325
|
+
if [[ "${CONTROLLER_STATE}" == "stopped" ]]; then
|
|
326
|
+
controller_unregister_pending_issue "${ISSUE_ID}"
|
|
327
|
+
elif flow_resident_issue_controller_counts_as_pending "${CONTROLLER_STATE}"; then
|
|
328
|
+
controller_register_pending_issue
|
|
329
|
+
else
|
|
330
|
+
controller_unregister_pending_issue "${ISSUE_ID}"
|
|
331
|
+
fi
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
controller_last_failure_reason() {
|
|
335
|
+
local metadata_file="${ACTIVE_RESIDENT_META_FILE:-${RESIDENT_META_FILE:-}}"
|
|
336
|
+
[[ -n "${metadata_file}" && -f "${metadata_file}" ]] || return 1
|
|
337
|
+
awk -F= '/^LAST_FAILURE_REASON=/{print $2; exit}' "${metadata_file}" 2>/dev/null | tr -d '"' || true
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
controller_provider_state() {
|
|
341
|
+
local provider_state_script="${FLOW_TOOLS_DIR}/provider-cooldown-state.sh"
|
|
342
|
+
local provider_state=""
|
|
343
|
+
|
|
344
|
+
if [[ ! -x "${provider_state_script}" ]]; then
|
|
345
|
+
printf 'READY=yes\n'
|
|
346
|
+
return 0
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
provider_state="$(
|
|
350
|
+
env \
|
|
351
|
+
-u ACP_CODING_WORKER \
|
|
352
|
+
-u ACP_CODEX_PROFILE_SAFE -u F_LOSNING_CODEX_PROFILE_SAFE \
|
|
353
|
+
-u ACP_CODEX_PROFILE_BYPASS -u F_LOSNING_CODEX_PROFILE_BYPASS \
|
|
354
|
+
-u ACP_CLAUDE_MODEL -u F_LOSNING_CLAUDE_MODEL \
|
|
355
|
+
-u ACP_CLAUDE_PERMISSION_MODE -u F_LOSNING_CLAUDE_PERMISSION_MODE \
|
|
356
|
+
-u ACP_CLAUDE_EFFORT -u F_LOSNING_CLAUDE_EFFORT \
|
|
357
|
+
-u ACP_CLAUDE_TIMEOUT_SECONDS -u F_LOSNING_CLAUDE_TIMEOUT_SECONDS \
|
|
358
|
+
-u ACP_CLAUDE_MAX_ATTEMPTS -u F_LOSNING_CLAUDE_MAX_ATTEMPTS \
|
|
359
|
+
-u ACP_CLAUDE_RETRY_BACKOFF_SECONDS -u F_LOSNING_CLAUDE_RETRY_BACKOFF_SECONDS \
|
|
360
|
+
-u ACP_OPENCLAW_MODEL -u F_LOSNING_OPENCLAW_MODEL \
|
|
361
|
+
-u ACP_OPENCLAW_THINKING -u F_LOSNING_OPENCLAW_THINKING \
|
|
362
|
+
-u ACP_OPENCLAW_TIMEOUT_SECONDS -u F_LOSNING_OPENCLAW_TIMEOUT_SECONDS \
|
|
363
|
+
-u ACP_ACTIVE_PROVIDER_POOL_NAME -u F_LOSNING_ACTIVE_PROVIDER_POOL_NAME \
|
|
364
|
+
-u ACP_ACTIVE_PROVIDER_BACKEND -u F_LOSNING_ACTIVE_PROVIDER_BACKEND \
|
|
365
|
+
-u ACP_ACTIVE_PROVIDER_MODEL -u F_LOSNING_ACTIVE_PROVIDER_MODEL \
|
|
366
|
+
-u ACP_ACTIVE_PROVIDER_KEY -u F_LOSNING_ACTIVE_PROVIDER_KEY \
|
|
367
|
+
-u ACP_PROVIDER_POOLS_EXHAUSTED -u F_LOSNING_PROVIDER_POOLS_EXHAUSTED \
|
|
368
|
+
-u ACP_PROVIDER_POOL_SELECTION_REASON -u F_LOSNING_PROVIDER_POOL_SELECTION_REASON \
|
|
369
|
+
-u ACP_PROVIDER_POOL_NEXT_ATTEMPT_EPOCH -u F_LOSNING_PROVIDER_POOL_NEXT_ATTEMPT_EPOCH \
|
|
370
|
+
-u ACP_PROVIDER_POOL_NEXT_ATTEMPT_AT -u F_LOSNING_PROVIDER_POOL_NEXT_ATTEMPT_AT \
|
|
371
|
+
-u ACP_PROVIDER_POOL_LAST_REASON -u F_LOSNING_PROVIDER_POOL_LAST_REASON \
|
|
372
|
+
"${provider_state_script}" get 2>/dev/null || true
|
|
373
|
+
)"
|
|
374
|
+
if [[ -z "${provider_state}" ]]; then
|
|
375
|
+
printf 'READY=yes\n'
|
|
376
|
+
return 0
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
printf '%s\n' "${provider_state}"
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
controller_wait_for_provider_capacity() {
|
|
383
|
+
local provider_state=""
|
|
384
|
+
local provider_ready=""
|
|
385
|
+
local provider_next_epoch=""
|
|
386
|
+
local provider_next_at=""
|
|
387
|
+
local now_epoch=""
|
|
388
|
+
local remaining=""
|
|
389
|
+
local sleep_seconds=""
|
|
390
|
+
local wait_started_epoch=""
|
|
391
|
+
local wait_completed_epoch=""
|
|
392
|
+
|
|
393
|
+
PROVIDER_WAITED="no"
|
|
394
|
+
|
|
395
|
+
while true; do
|
|
396
|
+
provider_state="$(controller_provider_state)"
|
|
397
|
+
provider_ready="$(flow_kv_get "${provider_state}" "READY")"
|
|
398
|
+
if [[ "${provider_ready}" == "yes" ]]; then
|
|
399
|
+
if [[ -n "${wait_started_epoch}" ]]; then
|
|
400
|
+
wait_completed_epoch="$(date +%s)"
|
|
401
|
+
if (( wait_completed_epoch >= wait_started_epoch )); then
|
|
402
|
+
PROVIDER_LAST_WAIT_SECONDS=$((wait_completed_epoch - wait_started_epoch))
|
|
403
|
+
PROVIDER_WAIT_TOTAL_SECONDS=$((PROVIDER_WAIT_TOTAL_SECONDS + PROVIDER_LAST_WAIT_SECONDS))
|
|
404
|
+
PROVIDER_LAST_WAIT_COMPLETED_AT="$(flow_format_epoch_utc "${wait_completed_epoch}")"
|
|
405
|
+
fi
|
|
406
|
+
fi
|
|
407
|
+
NEXT_WAKE_EPOCH=""
|
|
408
|
+
NEXT_WAKE_AT=""
|
|
409
|
+
return 0
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
provider_next_epoch="$(flow_kv_get "${provider_state}" "NEXT_ATTEMPT_EPOCH")"
|
|
413
|
+
provider_next_at="$(flow_kv_get "${provider_state}" "NEXT_ATTEMPT_AT")"
|
|
414
|
+
if ! [[ "${provider_next_epoch}" =~ ^[0-9]+$ ]] || [[ "${provider_next_epoch}" == "0" ]]; then
|
|
415
|
+
return 1
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
if [[ -z "${wait_started_epoch}" ]]; then
|
|
419
|
+
wait_started_epoch="$(date +%s)"
|
|
420
|
+
PROVIDER_WAIT_COUNT=$((PROVIDER_WAIT_COUNT + 1))
|
|
421
|
+
PROVIDER_LAST_WAIT_STARTED_AT="$(flow_format_epoch_utc "${wait_started_epoch}")"
|
|
422
|
+
fi
|
|
423
|
+
|
|
424
|
+
PROVIDER_WAITED="yes"
|
|
425
|
+
NEXT_WAKE_EPOCH="${provider_next_epoch}"
|
|
426
|
+
NEXT_WAKE_AT="${provider_next_at}"
|
|
427
|
+
CONTROLLER_REASON="provider-cooldown"
|
|
428
|
+
controller_write_state "waiting-provider" ""
|
|
429
|
+
|
|
430
|
+
now_epoch="$(date +%s)"
|
|
431
|
+
remaining=$((provider_next_epoch - now_epoch))
|
|
432
|
+
sleep_seconds="${POLL_SECONDS}"
|
|
433
|
+
if ! [[ "${sleep_seconds}" =~ ^[1-9][0-9]*$ ]]; then
|
|
434
|
+
sleep_seconds="60"
|
|
435
|
+
fi
|
|
436
|
+
if (( remaining > 0 && remaining < sleep_seconds )); then
|
|
437
|
+
sleep_seconds="${remaining}"
|
|
438
|
+
fi
|
|
439
|
+
if (( sleep_seconds <= 0 )); then
|
|
440
|
+
sleep_seconds="1"
|
|
441
|
+
fi
|
|
442
|
+
sleep "${sleep_seconds}"
|
|
443
|
+
done
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
controller_cleanup() {
|
|
447
|
+
controller_write_state "stopped" "${CONTROLLER_REASON:-stopped}"
|
|
448
|
+
}
|