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
|
@@ -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 || '',
|