agent-control-plane 0.3.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 +256 -58
- package/package.json +7 -6
- package/tools/bin/agent-github-update-labels +36 -2
- package/tools/bin/agent-project-catch-up-merged-prs +3 -2
- package/tools/bin/agent-project-publish-issue-pr +6 -3
- package/tools/bin/agent-project-reconcile-issue-session +12 -1
- package/tools/bin/agent-project-reconcile-pr-session +90 -32
- package/tools/bin/agent-project-retry-state +18 -7
- package/tools/bin/agent-project-run-codex-resilient +13 -5
- package/tools/bin/agent-project-sync-source-repo-main +163 -0
- package/tools/bin/flow-config-lib.sh +1203 -60
- package/tools/bin/flow-shell-lib.sh +32 -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-scheduling-lib.sh +7 -7
- package/tools/bin/heartbeat-safe-auto.sh +42 -0
- package/tools/bin/install-project-launchd.sh +17 -2
- package/tools/bin/project-init.sh +21 -1
- package/tools/bin/project-launchd-bootstrap.sh +5 -1
- package/tools/bin/project-runtimectl.sh +46 -2
- package/tools/bin/resident-issue-controller-lib.sh +2 -2
- 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 +2 -2
- package/tools/dashboard/app.js +30 -1
- package/tools/dashboard/dashboard_snapshot.py +55 -0
- package/tools/templates/pr-fix-template.md +3 -1
- package/tools/templates/pr-merge-repair-template.md +2 -1
- 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/resident-issue-queue-status.py +0 -35
- package/tools/bin/split-retained-slice.sh +0 -124
|
@@ -129,8 +129,23 @@ LABEL="${label_override:-${ACP_PROJECT_RUNTIME_LAUNCHD_LABEL:-ai.agent.project.$
|
|
|
129
129
|
BASE_PATH="$(build_launchd_base_path)"
|
|
130
130
|
CODING_WORKER_OVERRIDE="${ACP_PROJECT_RUNTIME_CODING_WORKER:-${ACP_CODING_WORKER:-}}"
|
|
131
131
|
SYNC_SCRIPT="${ACP_PROJECT_RUNTIME_SYNC_SCRIPT:-${FLOW_SKILL_DIR}/tools/bin/sync-shared-agent-home.sh}"
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
RUNTIME_SKILL_DIR="${RUNTIME_HOME}/skills/openclaw/agent-control-plane"
|
|
133
|
+
BOOTSTRAP_SCRIPT="${ACP_PROJECT_RUNTIME_BOOTSTRAP_SCRIPT:-}"
|
|
134
|
+
if [[ -z "${BOOTSTRAP_SCRIPT}" ]]; then
|
|
135
|
+
if [[ -x "${RUNTIME_SKILL_DIR}/tools/bin/project-launchd-bootstrap.sh" ]]; then
|
|
136
|
+
BOOTSTRAP_SCRIPT="${RUNTIME_SKILL_DIR}/tools/bin/project-launchd-bootstrap.sh"
|
|
137
|
+
else
|
|
138
|
+
BOOTSTRAP_SCRIPT="${FLOW_SKILL_DIR}/tools/bin/project-launchd-bootstrap.sh"
|
|
139
|
+
fi
|
|
140
|
+
fi
|
|
141
|
+
SUPERVISOR_SCRIPT="${ACP_PROJECT_RUNTIME_SUPERVISOR_SCRIPT:-}"
|
|
142
|
+
if [[ -z "${SUPERVISOR_SCRIPT}" ]]; then
|
|
143
|
+
if [[ -x "${RUNTIME_SKILL_DIR}/tools/bin/project-runtime-supervisor.sh" ]]; then
|
|
144
|
+
SUPERVISOR_SCRIPT="${RUNTIME_SKILL_DIR}/tools/bin/project-runtime-supervisor.sh"
|
|
145
|
+
else
|
|
146
|
+
SUPERVISOR_SCRIPT="${FLOW_SKILL_DIR}/tools/bin/project-runtime-supervisor.sh"
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
134
149
|
STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
135
150
|
SUPERVISOR_PID_FILE="${STATE_ROOT}/runtime-supervisor.pid"
|
|
136
151
|
ENV_FILE="${ACP_PROJECT_RUNTIME_ENV_FILE:-${PROFILE_REGISTRY_ROOT}/${PROFILE_ID}/runtime.env}"
|
|
@@ -34,7 +34,12 @@ runtime copy.
|
|
|
34
34
|
|
|
35
35
|
Common options:
|
|
36
36
|
--profile-id <id> Profile id, e.g. billing-api
|
|
37
|
-
--repo-slug <owner/repo>
|
|
37
|
+
--repo-slug <owner/repo> Forge repo slug
|
|
38
|
+
--forge-provider <github|gitea> Forge provider for this profile
|
|
39
|
+
--gitea-base-url <url> Base URL for a local/self-hosted Gitea instance
|
|
40
|
+
--gitea-token <token> Gitea API token written to profile runtime.env
|
|
41
|
+
--gitea-username <user> Gitea username written to profile runtime.env
|
|
42
|
+
--gitea-password <pass> Gitea password written to profile runtime.env
|
|
38
43
|
--profile-home <path> Installed profile registry root
|
|
39
44
|
--repo-root <path> Canonical repo root
|
|
40
45
|
--agent-repo-root <path> Agent-owned anchor repo root
|
|
@@ -68,6 +73,11 @@ EOF
|
|
|
68
73
|
|
|
69
74
|
profile_id=""
|
|
70
75
|
repo_slug=""
|
|
76
|
+
forge_provider=""
|
|
77
|
+
gitea_base_url=""
|
|
78
|
+
gitea_token=""
|
|
79
|
+
gitea_username=""
|
|
80
|
+
gitea_password=""
|
|
71
81
|
profile_home=""
|
|
72
82
|
repo_root=""
|
|
73
83
|
agent_repo_root=""
|
|
@@ -98,6 +108,11 @@ while [[ $# -gt 0 ]]; do
|
|
|
98
108
|
case "$1" in
|
|
99
109
|
--profile-id) profile_id="${2:-}"; shift 2 ;;
|
|
100
110
|
--repo-slug) repo_slug="${2:-}"; shift 2 ;;
|
|
111
|
+
--forge-provider) forge_provider="${2:-}"; shift 2 ;;
|
|
112
|
+
--gitea-base-url) gitea_base_url="${2:-}"; shift 2 ;;
|
|
113
|
+
--gitea-token) gitea_token="${2:-}"; shift 2 ;;
|
|
114
|
+
--gitea-username) gitea_username="${2:-}"; shift 2 ;;
|
|
115
|
+
--gitea-password) gitea_password="${2:-}"; shift 2 ;;
|
|
101
116
|
--profile-home) profile_home="${2:-}"; shift 2 ;;
|
|
102
117
|
--repo-root) repo_root="${2:-}"; shift 2 ;;
|
|
103
118
|
--agent-repo-root) agent_repo_root="${2:-}"; shift 2 ;;
|
|
@@ -144,6 +159,11 @@ SOURCE_HOME="${source_home:-${ACP_PROJECT_INIT_SOURCE_HOME:-$(cd "${FLOW_SKILL_D
|
|
|
144
159
|
RUNTIME_HOME="${runtime_home:-${ACP_PROJECT_INIT_RUNTIME_HOME:-${HOME}/.agent-runtime/runtime-home}}"
|
|
145
160
|
|
|
146
161
|
scaffold_cmd=(bash "${SCAFFOLD_SCRIPT}" --profile-id "${profile_id}" --repo-slug "${repo_slug}")
|
|
162
|
+
[[ -n "${forge_provider}" ]] && scaffold_cmd+=(--forge-provider "${forge_provider}")
|
|
163
|
+
[[ -n "${gitea_base_url}" ]] && scaffold_cmd+=(--gitea-base-url "${gitea_base_url}")
|
|
164
|
+
[[ -n "${gitea_token}" ]] && scaffold_cmd+=(--gitea-token "${gitea_token}")
|
|
165
|
+
[[ -n "${gitea_username}" ]] && scaffold_cmd+=(--gitea-username "${gitea_username}")
|
|
166
|
+
[[ -n "${gitea_password}" ]] && scaffold_cmd+=(--gitea-password "${gitea_password}")
|
|
147
167
|
[[ -n "${profile_home}" ]] && scaffold_cmd+=(--profile-home "${profile_home}")
|
|
148
168
|
[[ -n "${repo_root}" ]] && scaffold_cmd+=(--repo-root "${repo_root}")
|
|
149
169
|
[[ -n "${agent_repo_root}" ]] && scaffold_cmd+=(--agent-repo-root "${agent_repo_root}")
|
|
@@ -54,7 +54,11 @@ if [[ -x "${ENSURE_SYNC_SCRIPT}" ]]; then
|
|
|
54
54
|
if [[ "${ALWAYS_SYNC}" == "1" ]]; then
|
|
55
55
|
ensure_args=(--force "${ensure_args[@]}")
|
|
56
56
|
fi
|
|
57
|
-
|
|
57
|
+
if [[ "${FLOW_SKILL_DIR}" == "${RUNTIME_HOME}"/* ]]; then
|
|
58
|
+
printf 'RUNTIME_SYNC_SKIPPED=active-runtime-home\n'
|
|
59
|
+
else
|
|
60
|
+
bash "${ENSURE_SYNC_SCRIPT}" "${ensure_args[@]}"
|
|
61
|
+
fi
|
|
58
62
|
elif [[ "${ALWAYS_SYNC}" == "1" || ! -x "${RUNTIME_HEARTBEAT_SCRIPT}" ]]; then
|
|
59
63
|
if [[ -z "${SOURCE_HOME}" ]]; then
|
|
60
64
|
SOURCE_HOME="${FLOW_SKILL_DIR}"
|
|
@@ -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
|
|
@@ -401,7 +401,7 @@ controller_wait_for_provider_capacity() {
|
|
|
401
401
|
if (( wait_completed_epoch >= wait_started_epoch )); then
|
|
402
402
|
PROVIDER_LAST_WAIT_SECONDS=$((wait_completed_epoch - wait_started_epoch))
|
|
403
403
|
PROVIDER_WAIT_TOTAL_SECONDS=$((PROVIDER_WAIT_TOTAL_SECONDS + PROVIDER_LAST_WAIT_SECONDS))
|
|
404
|
-
PROVIDER_LAST_WAIT_COMPLETED_AT="$(
|
|
404
|
+
PROVIDER_LAST_WAIT_COMPLETED_AT="$(flow_format_epoch_utc "${wait_completed_epoch}")"
|
|
405
405
|
fi
|
|
406
406
|
fi
|
|
407
407
|
NEXT_WAKE_EPOCH=""
|
|
@@ -418,7 +418,7 @@ controller_wait_for_provider_capacity() {
|
|
|
418
418
|
if [[ -z "${wait_started_epoch}" ]]; then
|
|
419
419
|
wait_started_epoch="$(date +%s)"
|
|
420
420
|
PROVIDER_WAIT_COUNT=$((PROVIDER_WAIT_COUNT + 1))
|
|
421
|
-
PROVIDER_LAST_WAIT_STARTED_AT="$(
|
|
421
|
+
PROVIDER_LAST_WAIT_STARTED_AT="$(flow_format_epoch_utc "${wait_started_epoch}")"
|
|
422
422
|
fi
|
|
423
423
|
|
|
424
424
|
PROVIDER_WAITED="yes"
|
|
@@ -14,7 +14,12 @@ Create a new installed project profile, profile templates, and profile notes.
|
|
|
14
14
|
|
|
15
15
|
Options:
|
|
16
16
|
--profile-id <id> Profile id, e.g. billing-api
|
|
17
|
-
--repo-slug <owner/repo>
|
|
17
|
+
--repo-slug <owner/repo> Forge repo slug
|
|
18
|
+
--forge-provider <github|gitea> Forge provider (default: github)
|
|
19
|
+
--gitea-base-url <url> Base URL for a local/self-hosted Gitea instance
|
|
20
|
+
--gitea-token <token> Gitea API token written to profile runtime.env
|
|
21
|
+
--gitea-username <user> Gitea username written to profile runtime.env
|
|
22
|
+
--gitea-password <pass> Gitea password written to profile runtime.env
|
|
18
23
|
--profile-home <path> Profile registry root (default: ~/.agent-runtime/control-plane/profiles)
|
|
19
24
|
--repo-root <path> Canonical repo root
|
|
20
25
|
--agent-repo-root <path> Agent-owned anchor repo root (defaults to repo root)
|
|
@@ -40,6 +45,11 @@ EOF
|
|
|
40
45
|
|
|
41
46
|
profile_id=""
|
|
42
47
|
repo_slug=""
|
|
48
|
+
forge_provider="github"
|
|
49
|
+
gitea_base_url=""
|
|
50
|
+
gitea_token=""
|
|
51
|
+
gitea_username=""
|
|
52
|
+
gitea_password=""
|
|
43
53
|
profile_home=""
|
|
44
54
|
repo_root=""
|
|
45
55
|
agent_repo_root=""
|
|
@@ -63,6 +73,11 @@ while [[ $# -gt 0 ]]; do
|
|
|
63
73
|
case "$1" in
|
|
64
74
|
--profile-id) profile_id="${2:-}"; shift 2 ;;
|
|
65
75
|
--repo-slug) repo_slug="${2:-}"; shift 2 ;;
|
|
76
|
+
--forge-provider) forge_provider="${2:-}"; shift 2 ;;
|
|
77
|
+
--gitea-base-url) gitea_base_url="${2:-}"; shift 2 ;;
|
|
78
|
+
--gitea-token) gitea_token="${2:-}"; shift 2 ;;
|
|
79
|
+
--gitea-username) gitea_username="${2:-}"; shift 2 ;;
|
|
80
|
+
--gitea-password) gitea_password="${2:-}"; shift 2 ;;
|
|
66
81
|
--profile-home) profile_home="${2:-}"; shift 2 ;;
|
|
67
82
|
--repo-root) repo_root="${2:-}"; shift 2 ;;
|
|
68
83
|
--agent-repo-root) agent_repo_root="${2:-}"; shift 2 ;;
|
|
@@ -104,6 +119,14 @@ case "$coding_worker" in
|
|
|
104
119
|
;;
|
|
105
120
|
esac
|
|
106
121
|
|
|
122
|
+
case "$forge_provider" in
|
|
123
|
+
github|gitea) ;;
|
|
124
|
+
*)
|
|
125
|
+
echo "--forge-provider must be github or gitea" >&2
|
|
126
|
+
exit 1
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
|
|
107
130
|
case "$claude_effort" in
|
|
108
131
|
low|medium|high|max) ;;
|
|
109
132
|
*)
|
|
@@ -133,6 +156,7 @@ profile_home="${profile_home:-$(resolve_flow_profile_registry_root)}"
|
|
|
133
156
|
profiles_dir="${profile_home}"
|
|
134
157
|
profile_dir="${profiles_dir}/${profile_id}"
|
|
135
158
|
profile_yaml="${profile_dir}/control-plane.yaml"
|
|
159
|
+
profile_runtime_env="${profile_dir}/runtime.env"
|
|
136
160
|
profile_templates_dir="${profile_dir}/templates"
|
|
137
161
|
profile_readme="${profile_dir}/README.md"
|
|
138
162
|
|
|
@@ -228,9 +252,9 @@ session_naming:
|
|
|
228
252
|
pr_worktree_branch_prefix: "${pr_worktree_branch_prefix}"
|
|
229
253
|
managed_pr_branch_globs: "${managed_pr_branch_globs}"
|
|
230
254
|
queue:
|
|
231
|
-
source: "
|
|
255
|
+
source: "${forge_provider}"
|
|
232
256
|
issue_labels:
|
|
233
|
-
ready: "
|
|
257
|
+
ready: ""
|
|
234
258
|
running: "agent-running"
|
|
235
259
|
blocked: "agent-blocked"
|
|
236
260
|
heavy: "agent-e2e-heavy"
|
|
@@ -366,7 +390,38 @@ policies:
|
|
|
366
390
|
EOF
|
|
367
391
|
}
|
|
368
392
|
|
|
393
|
+
write_profile_runtime_env() {
|
|
394
|
+
local target_file="${1:?target file required}"
|
|
395
|
+
|
|
396
|
+
: >"$target_file"
|
|
397
|
+
{
|
|
398
|
+
printf 'ACP_FORGE_PROVIDER=%s\n' "${forge_provider}"
|
|
399
|
+
printf 'F_LOSNING_FORGE_PROVIDER=%s\n' "${forge_provider}"
|
|
400
|
+
if [[ "${forge_provider}" == "gitea" ]]; then
|
|
401
|
+
if [[ -n "${gitea_base_url}" ]]; then
|
|
402
|
+
printf 'ACP_GITEA_BASE_URL=%s\n' "${gitea_base_url}"
|
|
403
|
+
printf 'GITEA_BASE_URL=%s\n' "${gitea_base_url}"
|
|
404
|
+
fi
|
|
405
|
+
if [[ -n "${gitea_token}" ]]; then
|
|
406
|
+
printf 'ACP_GITEA_TOKEN=%s\n' "${gitea_token}"
|
|
407
|
+
printf 'GITEA_TOKEN=%s\n' "${gitea_token}"
|
|
408
|
+
fi
|
|
409
|
+
if [[ -n "${gitea_username}" ]]; then
|
|
410
|
+
printf 'ACP_GITEA_USERNAME=%s\n' "${gitea_username}"
|
|
411
|
+
printf 'GITEA_USERNAME=%s\n' "${gitea_username}"
|
|
412
|
+
fi
|
|
413
|
+
if [[ -n "${gitea_password}" ]]; then
|
|
414
|
+
printf 'ACP_GITEA_PASSWORD=%s\n' "${gitea_password}"
|
|
415
|
+
printf 'GITEA_PASSWORD=%s\n' "${gitea_password}"
|
|
416
|
+
fi
|
|
417
|
+
printf 'ACP_SOURCE_SYNC_REMOTE=gitea\n'
|
|
418
|
+
printf 'F_LOSNING_SOURCE_SYNC_REMOTE=gitea\n'
|
|
419
|
+
fi
|
|
420
|
+
} >"$target_file"
|
|
421
|
+
}
|
|
422
|
+
|
|
369
423
|
write_profile_yaml "$profile_yaml"
|
|
424
|
+
write_profile_runtime_env "$profile_runtime_env"
|
|
370
425
|
write_profile_readme "$profile_readme"
|
|
371
426
|
|
|
372
427
|
if compgen -G "${flow_skill_dir}/tools/templates/*.md" >/dev/null; then
|
|
@@ -375,15 +430,18 @@ fi
|
|
|
375
430
|
|
|
376
431
|
profile_home_real="$(mkdir -p "$profile_home" && cd "$profile_home" && pwd -P)"
|
|
377
432
|
profile_yaml_real="$(cd "$(dirname "$profile_yaml")" && pwd -P)/$(basename "$profile_yaml")"
|
|
433
|
+
profile_runtime_env_real="$(cd "$(dirname "$profile_runtime_env")" && pwd -P)/$(basename "$profile_runtime_env")"
|
|
378
434
|
profile_templates_dir_real="$(cd "$profile_templates_dir" && pwd -P)"
|
|
379
435
|
profile_readme_real="$(cd "$(dirname "$profile_readme")" && pwd -P)/$(basename "$profile_readme")"
|
|
380
436
|
|
|
381
437
|
printf 'PROFILE_ID=%s\n' "$profile_id"
|
|
382
438
|
printf 'PROFILE_HOME=%s\n' "$profile_home_real"
|
|
383
439
|
printf 'PROFILE_YAML=%s\n' "$profile_yaml_real"
|
|
440
|
+
printf 'PROFILE_RUNTIME_ENV=%s\n' "$profile_runtime_env_real"
|
|
384
441
|
printf 'PROFILE_TEMPLATE_DIR=%s\n' "$profile_templates_dir_real"
|
|
385
442
|
printf 'PROFILE_README=%s\n' "$profile_readme_real"
|
|
386
443
|
printf 'REPO_SLUG=%s\n' "$repo_slug"
|
|
444
|
+
printf 'FORGE_PROVIDER=%s\n' "$forge_provider"
|
|
387
445
|
printf 'CODING_WORKER=%s\n' "$coding_worker"
|
|
388
446
|
printf 'NEXT_STEP=ACP_PROJECT_ID=%s bash %s/tools/bin/render-flow-config.sh\n' "$profile_id" "$flow_skill_dir"
|
|
389
447
|
printf 'NEXT_STEP=bash %s/tools/bin/sync-shared-agent-home.sh\n' "$flow_skill_dir"
|
|
@@ -169,11 +169,41 @@ PR_MISSING_REASONS_TEXT="$(jq -r '.missingReasons[]? | "- " + .' <<<"$RISK_JSON"
|
|
|
169
169
|
if [[ -z "$PR_MISSING_REASONS_TEXT" ]]; then
|
|
170
170
|
PR_MISSING_REASONS_TEXT="- none"
|
|
171
171
|
fi
|
|
172
|
-
PR_PULL_JSON="$(flow_github_api_repo "${REPO_SLUG}" "pulls/${PR_NUMBER}")"
|
|
173
|
-
PR_HEAD_SHA="$(jq -r '.head.sha' <<<"$PR_PULL_JSON")"
|
|
174
|
-
PR_MERGEABLE_STATUS="$(jq -r 'if .mergeable == null then "UNKNOWN" else (.mergeable | tostring | ascii_upcase) end' <<<"$PR_PULL_JSON")"
|
|
172
|
+
PR_PULL_JSON="$(flow_github_api_repo "${REPO_SLUG}" "pulls/${PR_NUMBER}" 2>/dev/null || printf '{}\n')"
|
|
173
|
+
PR_HEAD_SHA="$(jq -r '.head.sha // .headRefOid // ""' <<<"$PR_PULL_JSON")"
|
|
174
|
+
PR_MERGEABLE_STATUS="$(jq -r 'if .mergeable == null then "UNKNOWN" else (.mergeable | tostring | ascii_upcase) end' <<<"$PR_PULL_JSON" 2>/dev/null || printf 'UNKNOWN\n')"
|
|
175
|
+
|
|
176
|
+
pr_comments_json() {
|
|
177
|
+
local review_route="pulls/${PR_NUMBER}/comments"
|
|
178
|
+
local issue_route="issues/${PR_NUMBER}/comments"
|
|
179
|
+
local payload=""
|
|
180
|
+
|
|
181
|
+
if flow_using_gitea; then
|
|
182
|
+
payload="$(flow_github_api_repo "${REPO_SLUG}" "${issue_route}" 2>/dev/null || true)"
|
|
183
|
+
else
|
|
184
|
+
payload="$(flow_github_api_repo "${REPO_SLUG}" "${review_route}" 2>/dev/null || true)"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
if jq -e 'type == "array"' >/dev/null 2>&1 <<<"${payload}"; then
|
|
188
|
+
printf '%s\n' "${payload}"
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
printf '[]\n'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
pr_issue_comments_json() {
|
|
196
|
+
local payload=""
|
|
197
|
+
payload="$(flow_github_api_repo "${REPO_SLUG}" "issues/${PR_NUMBER}/comments" 2>/dev/null || true)"
|
|
198
|
+
if jq -e 'type == "array"' >/dev/null 2>&1 <<<"${payload}"; then
|
|
199
|
+
printf '%s\n' "${payload}"
|
|
200
|
+
return 0
|
|
201
|
+
fi
|
|
202
|
+
printf '[]\n'
|
|
203
|
+
}
|
|
204
|
+
|
|
175
205
|
PR_REVIEW_FINDINGS_TEXT="$(
|
|
176
|
-
|
|
206
|
+
pr_comments_json \
|
|
177
207
|
| jq -r --arg head_sha "$PR_HEAD_SHA" '
|
|
178
208
|
map(select(
|
|
179
209
|
(.user.login == "chatgpt-codex-connector[bot]")
|
|
@@ -195,7 +225,7 @@ PR_REVIEW_FINDINGS_TEXT="$(
|
|
|
195
225
|
'
|
|
196
226
|
)"
|
|
197
227
|
PR_BLOCKER_SUMMARY_TEXT="$(
|
|
198
|
-
|
|
228
|
+
pr_issue_comments_json \
|
|
199
229
|
| jq -r '
|
|
200
230
|
map(select((.body // "") | startswith("## PR final review blocker")))
|
|
201
231
|
| if length == 0 then
|
|
@@ -225,12 +255,17 @@ PR_LOCAL_HOST_BLOCKER_SUMMARY_TEXT="$(latest_history_artifact_content "host-bloc
|
|
|
225
255
|
|
|
226
256
|
WORKTREE_OUT="$("${WORKSPACE_DIR}/bin/new-pr-worktree.sh" "$PR_NUMBER" "$PR_HEAD_REF")"
|
|
227
257
|
WORKTREE="$(awk -F= '/^WORKTREE=/{print $2}' <<<"$WORKTREE_OUT")"
|
|
258
|
+
PR_BASE_REMOTE="$(flow_resolve_forge_primary_remote "${WORKTREE}" "${REPO_SLUG}" 2>/dev/null || true)"
|
|
259
|
+
if [[ -z "${PR_BASE_REMOTE}" ]]; then
|
|
260
|
+
PR_BASE_REMOTE="origin"
|
|
261
|
+
fi
|
|
262
|
+
PR_BASE_TRACKING_REF="${PR_BASE_REMOTE}/${PR_BASE_REF}"
|
|
228
263
|
PR_HOST_MERGE_STATUS="not-applicable"
|
|
229
264
|
PR_HOST_MERGE_SUMMARY_TEXT="- not-applicable"
|
|
230
265
|
|
|
231
266
|
materialize_host_merge_repair() {
|
|
232
267
|
local merge_output=""
|
|
233
|
-
if merge_output="$(git -C "$WORKTREE" merge --no-commit --no-ff "
|
|
268
|
+
if merge_output="$(git -C "$WORKTREE" merge --no-commit --no-ff "${PR_BASE_TRACKING_REF}" 2>&1)"; then
|
|
234
269
|
PR_HOST_MERGE_STATUS="clean"
|
|
235
270
|
if [[ -n "$merge_output" ]]; then
|
|
236
271
|
PR_HOST_MERGE_SUMMARY_TEXT="$(printf '%s\n' "$merge_output")"
|
|
@@ -271,14 +306,14 @@ else
|
|
|
271
306
|
PR_CONFLICT_PATHS_TEXT="$(
|
|
272
307
|
(
|
|
273
308
|
cd "$WORKTREE"
|
|
274
|
-
base_sha="$(git merge-base HEAD "
|
|
309
|
+
base_sha="$(git merge-base HEAD "${PR_BASE_TRACKING_REF}" 2>/dev/null || true)"
|
|
275
310
|
if [[ -z "$base_sha" ]]; then
|
|
276
311
|
printf '%s\n' "- unable to compute merge-base"
|
|
277
312
|
exit 0
|
|
278
313
|
fi
|
|
279
314
|
|
|
280
315
|
conflict_paths="$(
|
|
281
|
-
git merge-tree "$base_sha" HEAD "
|
|
316
|
+
git merge-tree "$base_sha" HEAD "${PR_BASE_TRACKING_REF}" \
|
|
282
317
|
| awk '
|
|
283
318
|
/^changed in both$/ { capture=1; next }
|
|
284
319
|
capture && /^( base| our| their) / {
|
|
@@ -333,6 +368,7 @@ PR_HOST_MERGE_SUMMARY_TEXT="$PR_HOST_MERGE_SUMMARY_TEXT" \
|
|
|
333
368
|
PR_REPO_ROOT="$PR_REPO_ROOT" \
|
|
334
369
|
PR_DEPENDENCY_SOURCE_ROOT="$PR_DEPENDENCY_SOURCE_ROOT" \
|
|
335
370
|
PR_WORKTREE="$WORKTREE" \
|
|
371
|
+
PR_BASE_TRACKING_REF="$PR_BASE_TRACKING_REF" \
|
|
336
372
|
PR_WEB_PLAYWRIGHT_COMMAND="$WEB_PLAYWRIGHT_COMMAND" \
|
|
337
373
|
REPO_SLUG="$REPO_SLUG" \
|
|
338
374
|
TEMPLATE_FILE="$TEMPLATE_FILE" \
|
|
@@ -359,11 +395,11 @@ let requiredTargetedVerificationText = '- none';
|
|
|
359
395
|
let preApprovedVerificationFallbacksText = '- none';
|
|
360
396
|
try {
|
|
361
397
|
const worktree = process.env.PR_WORKTREE || '';
|
|
362
|
-
const
|
|
398
|
+
const baseTrackingRef = process.env.PR_BASE_TRACKING_REF || `origin/${process.env.PR_BASE_REF || 'main'}`;
|
|
363
399
|
if (worktree) {
|
|
364
400
|
const changedFiles = execFileSync(
|
|
365
401
|
'git',
|
|
366
|
-
['-C', worktree, 'diff', '--name-only', '--diff-filter=ACMR',
|
|
402
|
+
['-C', worktree, 'diff', '--name-only', '--diff-filter=ACMR', `${baseTrackingRef}...HEAD`],
|
|
367
403
|
{ encoding: 'utf8' },
|
|
368
404
|
)
|
|
369
405
|
.split('\n')
|
|
@@ -423,6 +459,7 @@ const replacements = {
|
|
|
423
459
|
'{PR_URL}': process.env.PR_URL || '',
|
|
424
460
|
'{PR_HEAD_REF}': process.env.PR_HEAD_REF || '',
|
|
425
461
|
'{PR_BASE_REF}': process.env.PR_BASE_REF || '',
|
|
462
|
+
'{PR_BASE_TRACKING_REF}': process.env.PR_BASE_TRACKING_REF || '',
|
|
426
463
|
'{PR_BODY}': process.env.PR_BODY || '',
|
|
427
464
|
'{REPO_SLUG}': process.env.REPO_SLUG || '',
|
|
428
465
|
'{PR_RISK}': process.env.PR_RISK || '',
|
|
@@ -243,12 +243,12 @@ record_scheduled_next_due() {
|
|
|
243
243
|
now_epoch="$(date +%s)"
|
|
244
244
|
next_due_epoch=$((now_epoch + interval_seconds))
|
|
245
245
|
NEXT_WAKE_EPOCH="${next_due_epoch}"
|
|
246
|
-
NEXT_WAKE_AT="$(
|
|
246
|
+
NEXT_WAKE_AT="$(flow_format_epoch_utc "${next_due_epoch}")"
|
|
247
247
|
state_file="${SCHEDULED_STATE_DIR}/${ISSUE_ID}.env"
|
|
248
248
|
cat >"${state_file}" <<EOF
|
|
249
249
|
INTERVAL_SECONDS=${interval_seconds}
|
|
250
250
|
LAST_STARTED_EPOCH=${now_epoch}
|
|
251
|
-
LAST_STARTED_AT=$(
|
|
251
|
+
LAST_STARTED_AT=$(flow_format_epoch_utc "${now_epoch}")
|
|
252
252
|
NEXT_DUE_EPOCH=${next_due_epoch}
|
|
253
253
|
NEXT_DUE_AT=${NEXT_WAKE_AT}
|
|
254
254
|
UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
package/tools/dashboard/app.js
CHANGED
|
@@ -111,9 +111,10 @@ function renderOverview(snapshot) {
|
|
|
111
111
|
acc.cooldowns += profile.counts.provider_cooldowns;
|
|
112
112
|
acc.queue += profile.counts.queued_issues;
|
|
113
113
|
acc.alerts += profile.counts.alerts || 0;
|
|
114
|
+
acc.pendingGithubWrites += profile.counts.pending_github_writes || 0;
|
|
114
115
|
return acc;
|
|
115
116
|
},
|
|
116
|
-
{ activeRuns: 0, runningRuns: 0, implementedRuns: 0, reportedRuns: 0, blockedRuns: 0, controllers: 0, cooldowns: 0, queue: 0, alerts: 0 },
|
|
117
|
+
{ activeRuns: 0, runningRuns: 0, implementedRuns: 0, reportedRuns: 0, blockedRuns: 0, controllers: 0, cooldowns: 0, queue: 0, alerts: 0, pendingGithubWrites: 0 },
|
|
117
118
|
);
|
|
118
119
|
|
|
119
120
|
overviewNode.innerHTML = [
|
|
@@ -125,6 +126,7 @@ function renderOverview(snapshot) {
|
|
|
125
126
|
["Blocked", totals.blockedRuns],
|
|
126
127
|
["Live Controllers", totals.controllers],
|
|
127
128
|
["Provider Cooldowns", totals.cooldowns],
|
|
129
|
+
["Pending GitHub Writes", totals.pendingGithubWrites],
|
|
128
130
|
["Alerts", totals.alerts],
|
|
129
131
|
["Queued Issues", totals.queue],
|
|
130
132
|
]
|
|
@@ -237,6 +239,8 @@ function renderProfile(profile) {
|
|
|
237
239
|
["Live controllers", profile.counts.live_resident_controllers],
|
|
238
240
|
["Stale controllers", profile.counts.stale_resident_controllers],
|
|
239
241
|
["Provider cooldowns", profile.counts.provider_cooldowns],
|
|
242
|
+
["Pending GitHub writes", profile.counts.pending_github_writes || 0],
|
|
243
|
+
["Failed GitHub writes", profile.counts.failed_github_writes || 0],
|
|
240
244
|
["Alerts", profile.counts.alerts || 0],
|
|
241
245
|
["Issue retries", profile.counts.active_retries],
|
|
242
246
|
["Queued issues", profile.counts.queued_issues],
|
|
@@ -378,6 +382,26 @@ function renderProfile(profile) {
|
|
|
378
382
|
"No claimed issues.",
|
|
379
383
|
);
|
|
380
384
|
|
|
385
|
+
const githubOutbox = profile.github_outbox || { counts: {}, pending: [] };
|
|
386
|
+
const githubOutboxTable = renderTable(
|
|
387
|
+
[
|
|
388
|
+
{ label: "Type", render: (row) => row.type || "n/a" },
|
|
389
|
+
{ label: "Target", render: (row) => `${row.kind || row.type || "write"} #${row.number || "?"}` },
|
|
390
|
+
{
|
|
391
|
+
label: "Payload",
|
|
392
|
+
render: (row) => {
|
|
393
|
+
if (row.type === "labels") {
|
|
394
|
+
return `+${row.add_count || 0} / -${row.remove_count || 0}`;
|
|
395
|
+
}
|
|
396
|
+
return row.body_preview || "n/a";
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{ label: "Created", render: (row) => row.created_at ? `${relativeTime(row.created_at)}<div class="muted">${row.created_at}</div>` : "n/a" },
|
|
400
|
+
],
|
|
401
|
+
githubOutbox.pending || [],
|
|
402
|
+
"No pending GitHub write intents.",
|
|
403
|
+
);
|
|
404
|
+
|
|
381
405
|
const codexRotationPanel =
|
|
382
406
|
profile.coding_worker === "codex"
|
|
383
407
|
? `
|
|
@@ -452,6 +476,11 @@ function renderProfile(profile) {
|
|
|
452
476
|
<h3>Claimed Issues</h3>
|
|
453
477
|
${claimsTable}
|
|
454
478
|
</section>
|
|
479
|
+
<section class="panel">
|
|
480
|
+
<h3>GitHub Outbox</h3>
|
|
481
|
+
<p class="panel-subtitle">Local write intents queued while ACP defers or retries GitHub sync. Pending ${githubOutbox.counts?.pending || 0}, sent ${githubOutbox.counts?.sent || 0}, failed ${githubOutbox.counts?.failed || 0}.</p>
|
|
482
|
+
${githubOutboxTable}
|
|
483
|
+
</section>
|
|
455
484
|
</section>
|
|
456
485
|
</article>
|
|
457
486
|
`;
|
|
@@ -697,6 +697,57 @@ def collect_pr_retries(state_root: Path) -> list[dict[str, Any]]:
|
|
|
697
697
|
return items
|
|
698
698
|
|
|
699
699
|
|
|
700
|
+
def collect_github_outbox(state_root: Path) -> dict[str, Any]:
|
|
701
|
+
outbox_root = state_root / "github-outbox"
|
|
702
|
+
pending_root = outbox_root / "pending"
|
|
703
|
+
sent_root = outbox_root / "sent"
|
|
704
|
+
failed_root = outbox_root / "failed"
|
|
705
|
+
|
|
706
|
+
def list_items(root: Path, limit: int | None = None) -> list[dict[str, Any]]:
|
|
707
|
+
if not root.is_dir():
|
|
708
|
+
return []
|
|
709
|
+
|
|
710
|
+
items: list[dict[str, Any]] = []
|
|
711
|
+
for path in sorted(root.glob("*.json"), key=lambda item: item.stat().st_mtime, reverse=True):
|
|
712
|
+
payload = read_json_file(path)
|
|
713
|
+
items.append(
|
|
714
|
+
{
|
|
715
|
+
"type": str(payload.get("type", "")),
|
|
716
|
+
"repo_slug": str(payload.get("repo_slug", "")),
|
|
717
|
+
"number": str(payload.get("number", "")),
|
|
718
|
+
"kind": str(payload.get("kind", "")),
|
|
719
|
+
"created_at": str(payload.get("created_at", "")),
|
|
720
|
+
"updated_at": file_mtime_iso(path),
|
|
721
|
+
"file": str(path),
|
|
722
|
+
"add_count": len(payload.get("add", []) or []),
|
|
723
|
+
"remove_count": len(payload.get("remove", []) or []),
|
|
724
|
+
"body_preview": summarize_whitespace(str(payload.get("body", "")))[:120],
|
|
725
|
+
}
|
|
726
|
+
)
|
|
727
|
+
if limit is not None and len(items) >= limit:
|
|
728
|
+
break
|
|
729
|
+
return items
|
|
730
|
+
|
|
731
|
+
all_pending_items = list_items(pending_root)
|
|
732
|
+
pending_items = all_pending_items[:20]
|
|
733
|
+
sent_items = list_items(sent_root, limit=5)
|
|
734
|
+
failed_items = list_items(failed_root, limit=5)
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
"pending": pending_items,
|
|
738
|
+
"sent_recent": sent_items,
|
|
739
|
+
"failed_recent": failed_items,
|
|
740
|
+
"counts": {
|
|
741
|
+
"pending": len(all_pending_items),
|
|
742
|
+
"sent": len(list(sent_root.glob("*.json"))) if sent_root.is_dir() else 0,
|
|
743
|
+
"failed": len(list(failed_root.glob("*.json"))) if failed_root.is_dir() else 0,
|
|
744
|
+
"pending_comments": sum(1 for item in all_pending_items if item["type"] == "comment"),
|
|
745
|
+
"pending_approvals": sum(1 for item in all_pending_items if item["type"] == "approval"),
|
|
746
|
+
"pending_label_updates": sum(1 for item in all_pending_items if item["type"] == "labels"),
|
|
747
|
+
},
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
|
|
700
751
|
def resolve_history_root(render_env: dict[str, str], yaml_env: dict[str, str], runs_root: Path) -> Path:
|
|
701
752
|
configured = (
|
|
702
753
|
render_env.get("EFFECTIVE_HISTORY_ROOT", "").strip()
|
|
@@ -726,6 +777,7 @@ def build_profile_snapshot(profile_id: str, registry_root: Path) -> dict[str, An
|
|
|
726
777
|
retries = collect_issue_retries(state_root)
|
|
727
778
|
pr_retries = collect_pr_retries(state_root)
|
|
728
779
|
queue = collect_issue_queue(state_root)
|
|
780
|
+
github_outbox = collect_github_outbox(state_root)
|
|
729
781
|
alerts = [alert for run in (runs + recent_history) for alert in run.get("alerts", [])]
|
|
730
782
|
codex_rotation = collect_codex_rotation(render_env)
|
|
731
783
|
|
|
@@ -773,6 +825,8 @@ def build_profile_snapshot(profile_id: str, registry_root: Path) -> dict[str, An
|
|
|
773
825
|
"active_retries": sum(1 for item in retries if not item.get("ready", True)),
|
|
774
826
|
"scheduled_issues": len(scheduled),
|
|
775
827
|
"alerts": len(alerts),
|
|
828
|
+
"pending_github_writes": github_outbox["counts"]["pending"],
|
|
829
|
+
"failed_github_writes": github_outbox["counts"]["failed"],
|
|
776
830
|
},
|
|
777
831
|
"runs": runs,
|
|
778
832
|
"recent_history": recent_history,
|
|
@@ -784,6 +838,7 @@ def build_profile_snapshot(profile_id: str, registry_root: Path) -> dict[str, An
|
|
|
784
838
|
"issue_retries": retries,
|
|
785
839
|
"pr_retries": pr_retries,
|
|
786
840
|
"issue_queue": queue,
|
|
841
|
+
"github_outbox": github_outbox,
|
|
787
842
|
}
|
|
788
843
|
|
|
789
844
|
|
|
@@ -11,6 +11,7 @@ PR metadata:
|
|
|
11
11
|
- PR: {PR_NUMBER} - {PR_TITLE}
|
|
12
12
|
- URL: {PR_URL}
|
|
13
13
|
- Base branch: {PR_BASE_REF}
|
|
14
|
+
- Base tracking ref: {PR_BASE_TRACKING_REF}
|
|
14
15
|
- Head branch: {PR_HEAD_REF}
|
|
15
16
|
- Linked issue: {PR_LINKED_ISSUE_ID}
|
|
16
17
|
- Risk classification: {PR_RISK}
|
|
@@ -55,7 +56,7 @@ Required flow:
|
|
|
55
56
|
|
|
56
57
|
1. Inspect the current diff and the failing/pending CI signals first:
|
|
57
58
|
- `openspec list` if the repo uses OpenSpec
|
|
58
|
-
- `git diff --stat
|
|
59
|
+
- `git diff --stat {PR_BASE_TRACKING_REF}...HEAD`
|
|
59
60
|
- `git status --short`
|
|
60
61
|
- if `Merge state` is not `CLEAN` or `Mergeable` is `FALSE`, treat branch drift/conflicts as the concrete blocker first
|
|
61
62
|
- if `Actionable current-head review findings` is not `- none`, treat those findings as the concrete blockers to address first
|
|
@@ -68,6 +69,7 @@ Required flow:
|
|
|
68
69
|
- do not run `git fetch`, `git merge`, `git rebase`, `git commit`, `git push`, or other Git metadata-writing commands from inside this worker; host-side wrappers own those steps
|
|
69
70
|
3. If the blocker is branch drift or a merge conflict, use the already-prepared local refs and make the smallest branch-local source update needed to restore mergeability on this PR branch. Keep the resolution scoped to the PR intent; do not rewrite unrelated code.
|
|
70
71
|
- Treat `Current local merge-conflict paths` as the authoritative conflict list to clear.
|
|
72
|
+
- Treat `{PR_BASE_TRACKING_REF}` as the authoritative base ref for any read-only diff or merge-base inspection.
|
|
71
73
|
- Do not stop after fixing only one file if other conflict paths remain.
|
|
72
74
|
- Before you declare success, rerun local merge simulation and confirm there are no remaining conflict paths for this branch against `{PR_BASE_REF}`.
|
|
73
75
|
4. Make the smallest change that fixes the concrete PR blockers on this existing branch.
|