agent-control-plane 0.1.16 → 0.2.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.
- package/README.md +93 -14
- package/bin/pr-risk.sh +28 -6
- package/hooks/heartbeat-hooks.sh +62 -22
- package/npm/bin/agent-control-plane.js +322 -9
- package/package.json +1 -1
- package/references/architecture.md +8 -0
- package/references/control-plane-map.md +6 -2
- package/references/release-checklist.md +0 -2
- package/tools/bin/agent-github-update-labels +6 -1
- package/tools/bin/agent-project-catch-up-issue-pr-links +118 -0
- package/tools/bin/agent-project-catch-up-merged-prs +77 -21
- package/tools/bin/agent-project-catch-up-scheduled-issue-retries +123 -0
- package/tools/bin/agent-project-cleanup-session +84 -0
- package/tools/bin/agent-project-heartbeat-loop +10 -3
- package/tools/bin/agent-project-reconcile-issue-session +24 -12
- package/tools/bin/agent-project-run-claude-session +2 -2
- package/tools/bin/agent-project-run-kilo-session +346 -14
- package/tools/bin/agent-project-run-ollama-session +658 -0
- package/tools/bin/agent-project-run-openclaw-session +27 -25
- package/tools/bin/agent-project-run-opencode-session +354 -14
- package/tools/bin/agent-project-run-pi-session +479 -0
- package/tools/bin/agent-project-worker-status +1 -1
- package/tools/bin/flow-config-lib.sh +116 -3
- package/tools/bin/flow-resident-worker-lib.sh +1 -1
- package/tools/bin/flow-shell-lib.sh +5 -2
- package/tools/bin/heartbeat-recovery-preflight.sh +1 -0
- package/tools/bin/heartbeat-safe-auto.sh +105 -17
- package/tools/bin/install-project-launchd.sh +19 -2
- package/tools/bin/prepare-worktree.sh +4 -4
- package/tools/bin/profile-activate.sh +2 -2
- package/tools/bin/profile-adopt.sh +2 -2
- package/tools/bin/project-init.sh +1 -1
- package/tools/bin/project-runtimectl.sh +90 -7
- package/tools/bin/provider-cooldown-state.sh +14 -14
- package/tools/bin/render-flow-config.sh +30 -33
- package/tools/bin/run-codex-task.sh +53 -4
- package/tools/bin/scaffold-profile.sh +18 -3
- package/tools/bin/start-issue-worker.sh +1 -1
- package/tools/bin/start-pr-fix-worker.sh +30 -0
- package/tools/bin/start-pr-review-worker.sh +31 -0
- package/tools/bin/start-resident-issue-loop.sh +4 -4
- package/tools/bin/sync-agent-repo.sh +2 -2
- package/tools/bin/sync-dependency-baseline.sh +3 -3
- package/tools/bin/sync-shared-agent-home.sh +4 -1
- package/tools/templates/pr-fix-template.md +3 -7
- package/tools/templates/pr-merge-repair-template.md +3 -7
- package/tools/templates/pr-review-template.md +2 -1
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ it had a supervisor."
|
|
|
94
94
|
| Get real value out of free-tier models | Quota cooldowns, stall detection, provider failover, and retry backoff that free-tier models need to be actually useful |
|
|
95
95
|
| Manage multiple repos cleanly | One profile per repo with isolated runtime state, each with its own identity and status |
|
|
96
96
|
| Observe what is happening without digging through files | Dashboard and `runtime status` that show the real state without spelunking through `tmux` or temp folders |
|
|
97
|
-
| Compare worker backends on real workloads | Swap between `codex`, `claude`, and `
|
|
97
|
+
| Compare worker backends on real workloads | Swap between `codex`, `claude`, `openclaw`, `ollama`, `pi`, `opencode`, and `kilo` without rebuilding your runtime habits |
|
|
98
98
|
| Run reproducible agent research cheaply | Cost-controlled execution harness for studying agent behavior, output quality, or prompting strategies |
|
|
99
99
|
| Enforce safety by architecture, not by hope | Launch limits, reconcile gates, and cooldowns that are built into the runtime, not left to chance |
|
|
100
100
|
|
|
@@ -130,19 +130,61 @@ Windows.
|
|
|
130
130
|
| `codex` | Production-ready | Full ACP workflow support today. |
|
|
131
131
|
| `claude` | Production-ready | Full ACP workflow support today. |
|
|
132
132
|
| `openclaw` | Production-ready | Full ACP workflow support, including resident-style runs. |
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
133
|
+
| `ollama` | Experimental | Working adapter with Node.js agentic loop. Runs any model served by a local Ollama instance. Output quality depends on model size — 7–9B models handle exploration well but struggle with complex multi-step tasks. |
|
|
134
|
+
| `pi` | Experimental | Working adapter using the [pi CLI](https://github.com/mariozechner/pi) in `--print --no-session` mode. Connects to any OpenRouter-compatible model. Useful as a lightweight alternative to openclaw for free-tier model testing. |
|
|
135
|
+
| `opencode` | Experimental | Working adapter for [Crush](https://github.com/charmbracelet/crush) (formerly opencode). Non-interactive `crush run` with full tool execution. |
|
|
136
|
+
| `kilo` | Experimental | Working adapter for [Kilo Code](https://github.com/Kilo-Org/kilocode). Non-interactive `kilo run --auto` with JSON event stream. |
|
|
135
137
|
| `gemini-cli` | Roadmap | Strong future candidate; not wired into ACP yet. |
|
|
136
|
-
| `ollama` | Research | Candidate local-model substrate for future ACP integrations. |
|
|
137
138
|
| `nanoclaw` | Exploratory | Ecosystem reference for containerized and WSL2-friendly workflows. |
|
|
138
139
|
| `picoclaw` | Exploratory | Ecosystem reference for lightweight Linux and edge-style runtimes. |
|
|
139
140
|
|
|
140
141
|
If you are trying ACP on a real repo right now, start with `codex`, `claude`,
|
|
141
|
-
or `openclaw`.
|
|
142
|
-
|
|
142
|
+
or `openclaw`. Use `ollama` to run local models — useful for research, offline
|
|
143
|
+
workflows, or comparing local model output against cloud backends without
|
|
144
|
+
incurring API costs. Use `pi` to experiment with OpenRouter-hosted free-tier
|
|
145
|
+
models via the pi CLI. The remaining entries show the direction of travel, not
|
|
146
|
+
finished support.
|
|
143
147
|
|
|
144
148
|
See [ROADMAP.md](./ROADMAP.md) for the fuller public roadmap.
|
|
145
149
|
|
|
150
|
+
### Using Ollama (local models)
|
|
151
|
+
|
|
152
|
+
To run ACP with a local model via [Ollama](https://ollama.com):
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# 1. Install Ollama and pull a model
|
|
156
|
+
ollama pull qwen3.5:9b
|
|
157
|
+
|
|
158
|
+
# 2. Init a profile with ollama backend
|
|
159
|
+
npx agent-control-plane@latest init \
|
|
160
|
+
--profile-id my-repo \
|
|
161
|
+
--repo-slug owner/my-repo \
|
|
162
|
+
--repo-root ~/src/my-repo \
|
|
163
|
+
--agent-root ~/.agent-runtime/projects/my-repo \
|
|
164
|
+
--worktree-root ~/src/my-repo-worktrees \
|
|
165
|
+
--coding-worker ollama
|
|
166
|
+
|
|
167
|
+
# 3. Configure the model in your profile YAML
|
|
168
|
+
# ~/.agent-runtime/control-plane/profiles/my-repo/control-plane.yaml
|
|
169
|
+
#
|
|
170
|
+
# execution:
|
|
171
|
+
# coding_worker: "ollama"
|
|
172
|
+
# ollama:
|
|
173
|
+
# model: "qwen3.5:9b"
|
|
174
|
+
# base_url: "http://localhost:11434"
|
|
175
|
+
# timeout_seconds: 900
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The Ollama adapter runs a Node.js agentic loop that calls the Ollama API with
|
|
179
|
+
tool-use support. It handles both native tool-call responses and models that
|
|
180
|
+
return tool calls as JSON text in the content field.
|
|
181
|
+
|
|
182
|
+
**Model guidance:** Models in the 7–14B range can explore codebases and run
|
|
183
|
+
commands, but may struggle with complex multi-step repair tasks. Larger models
|
|
184
|
+
(27B+) produce significantly better results if your hardware supports them.
|
|
185
|
+
Thinking mode is disabled by default (`think: false`) and context is set to
|
|
186
|
+
32K tokens to balance speed and capability.
|
|
187
|
+
|
|
146
188
|
## See It Running
|
|
147
189
|
|
|
148
190
|
The dashboard gives you a single view across all active profiles — running
|
|
@@ -167,7 +209,7 @@ flowchart LR
|
|
|
167
209
|
Supervisor --> Heartbeat["heartbeat-safe-auto.sh"]
|
|
168
210
|
Heartbeat --> Scheduler["agent-project-heartbeat-loop"]
|
|
169
211
|
Scheduler --> Workers["issue / PR worker launchers"]
|
|
170
|
-
Workers --> Backends["codex / claude / openclaw"]
|
|
212
|
+
Workers --> Backends["codex / claude / openclaw / ollama / pi / opencode / kilo"]
|
|
171
213
|
Backends --> Reconcile["reconcile issue / PR session"]
|
|
172
214
|
Reconcile --> GitHub["issues / PRs / labels / comments"]
|
|
173
215
|
Scheduler --> State["runs + state + history"]
|
|
@@ -230,7 +272,11 @@ system.
|
|
|
230
272
|
| `jq` | yes | Parses JSON from `gh` output and worker metadata throughout. | Missing `jq` will break GitHub-heavy and runtime flows. |
|
|
231
273
|
| `python3` | yes | Powers the dashboard server, snapshot renderer, and config helpers. | Required for both dashboard use and several internal scripts. |
|
|
232
274
|
| `tmux` | yes | Runs long-lived worker sessions and captures their status. | Missing `tmux` means background worker workflows will not launch. |
|
|
233
|
-
| Worker CLI (
|
|
275
|
+
| Worker CLI (backend-specific) | depends on backend | The coding agent for a profile. Supported: `codex`, `claude`, `openclaw` (production); `ollama`, `pi`, `opencode`, `kilo` (experimental). | Install and authenticate your chosen backend before starting background runs. |
|
|
276
|
+
| `ollama` | for `ollama` backend | Serves local models via OpenAI-compatible API at `http://localhost:11434`. | Install from [ollama.com](https://ollama.com) and pull a model (e.g. `ollama pull qwen3.5:9b`) before use. |
|
|
277
|
+
| `pi` CLI | for `pi` backend | Lightweight coding agent using OpenRouter-compatible APIs. | Install via `npm i -g @mariozechner/pi-coding-agent`. Set `OPENROUTER_API_KEY` before use. |
|
|
278
|
+
| `crush` (opencode) | for `opencode` backend | Go-based coding agent by Charm ([charmbracelet/crush](https://github.com/charmbracelet/crush)). | Install via `brew install charmbracelet/tap/crush`. |
|
|
279
|
+
| `kilo` CLI | for `kilo` backend | TypeScript coding agent ([kilocode/cli](https://github.com/Kilo-Org/kilocode)). | Install via `npm i -g @kilocode/cli`. |
|
|
234
280
|
| Bundled `codex-quota` + ACP quota manager | automatic for Codex | Quota-aware failover and health signals for Codex profiles. | Bundled by default. Override with `ACP_CODEX_QUOTA_BIN` only if you have a custom setup. |
|
|
235
281
|
|
|
236
282
|
Make sure `gh` and your chosen worker backend are both authenticated for the
|
|
@@ -264,9 +310,17 @@ The fastest path is the interactive wizard:
|
|
|
264
310
|
npx agent-control-plane@latest setup
|
|
265
311
|
```
|
|
266
312
|
|
|
267
|
-
The wizard
|
|
268
|
-
|
|
269
|
-
|
|
313
|
+
The wizard walks you through the full setup in one pass:
|
|
314
|
+
|
|
315
|
+
1. Detects the current repo and suggests sane defaults
|
|
316
|
+
2. Installs missing dependencies and authenticates `gh`
|
|
317
|
+
3. Checks backend readiness (API keys for openclaw/pi, local server for ollama)
|
|
318
|
+
4. Scaffolds the profile, runs health checks, starts the runtime
|
|
319
|
+
5. Launches the monitoring dashboard in the background
|
|
320
|
+
6. Offers to create recurring starter issues so ACP starts working immediately
|
|
321
|
+
|
|
322
|
+
After the wizard finishes, your repo has a running agent, a live dashboard,
|
|
323
|
+
and a set of `agent-keep-open` issues that ACP will continuously work through.
|
|
270
324
|
|
|
271
325
|
To preview exactly what it would do before touching anything:
|
|
272
326
|
|
|
@@ -281,6 +335,8 @@ npx agent-control-plane@latest setup \
|
|
|
281
335
|
--non-interactive \
|
|
282
336
|
--install-missing-deps \
|
|
283
337
|
--start-runtime \
|
|
338
|
+
--start-dashboard \
|
|
339
|
+
--create-starter-issues \
|
|
284
340
|
--json
|
|
285
341
|
```
|
|
286
342
|
|
|
@@ -325,7 +381,7 @@ npx agent-control-plane@latest init \
|
|
|
325
381
|
| `--repo-root` | Path to your local checkout |
|
|
326
382
|
| `--agent-root` | Where ACP keeps per-project runtime state |
|
|
327
383
|
| `--worktree-root` | Where ACP places repo worktrees |
|
|
328
|
-
| `--coding-worker` | Backend to orchestrate (`codex`, `claude`, or `
|
|
384
|
+
| `--coding-worker` | Backend to orchestrate (`codex`, `claude`, `openclaw`, `ollama`, `pi`, `opencode`, or `kilo`) |
|
|
329
385
|
|
|
330
386
|
**4. Validate before trusting it**
|
|
331
387
|
|
|
@@ -348,6 +404,29 @@ Once `runtime status` returns clean output, ACP is actively managing the
|
|
|
348
404
|
runtime for that profile. Per-profile state lives under `~/.agent-runtime`,
|
|
349
405
|
grouped and inspectable without digging through scattered temp files.
|
|
350
406
|
|
|
407
|
+
## Starter Issues
|
|
408
|
+
|
|
409
|
+
The setup wizard can create a set of recurring `agent-keep-open` issues on your
|
|
410
|
+
repo so ACP starts working immediately after installation. Each issue carries the
|
|
411
|
+
`agent-ready` and `agent-keep-open` labels, and ACP picks them up on its next
|
|
412
|
+
heartbeat cycle.
|
|
413
|
+
|
|
414
|
+
Built-in templates:
|
|
415
|
+
|
|
416
|
+
| Issue | What ACP does |
|
|
417
|
+
| --- | --- |
|
|
418
|
+
| Code quality sweep | Fix lint warnings, type errors, and dead code |
|
|
419
|
+
| Test coverage improvement | Add tests for critical untested modules |
|
|
420
|
+
| Documentation refresh | Keep README and inline docs accurate |
|
|
421
|
+
| Dependency audit | Fix vulnerabilities and update safe patches |
|
|
422
|
+
| Refactoring sweep | Reduce complexity and duplication |
|
|
423
|
+
|
|
424
|
+
You can also create your own recurring issues — just add the `agent-ready` and
|
|
425
|
+
`agent-keep-open` labels to any GitHub issue and ACP will work on it
|
|
426
|
+
continuously.
|
|
427
|
+
|
|
428
|
+
To skip this step during setup, pass `--no-create-starter-issues`.
|
|
429
|
+
|
|
351
430
|
## Everyday Usage
|
|
352
431
|
|
|
353
432
|
```bash
|
|
@@ -427,7 +506,7 @@ Use `--purge-paths` only when you want ACP-managed directories deleted too.
|
|
|
427
506
|
| `explicit profile selection required` | Pass `--profile-id <id>` to `runtime`, `launchd-install`, `launchd-uninstall`, and `remove`. |
|
|
428
507
|
| `gh` cannot access the repo | Re-run `gh auth login` and confirm the repo slug in the profile is correct. |
|
|
429
508
|
| Setup deferred anchor repo sync | ACP could not reach the repo remote. Fix Git access or the remote URL, then re-run `setup` or `init` without `--skip-anchor-sync`. |
|
|
430
|
-
| Backend auth failures
|
|
509
|
+
| Backend auth failures | Authenticate the backend before starting ACP. For `openclaw`/`pi`, set `OPENROUTER_API_KEY`. For `ollama`, ensure the server is running. For `opencode`/`kilo`, install and authenticate the CLI. |
|
|
431
510
|
| Node older than `18` | Upgrade Node first. ACP's minimum version is `18+`. |
|
|
432
511
|
| Missing `jq` | Install `jq`, then retry the failed command. |
|
|
433
512
|
| Runtime or source drift after an update | Run `sync`, then `doctor`. |
|
|
@@ -440,7 +519,7 @@ Use `--purge-paths` only when you want ACP-managed directories deleted too.
|
|
|
440
519
|
| --- | --- |
|
|
441
520
|
| `help` | Show the full CLI surface. Good first command on a new machine. |
|
|
442
521
|
| `version` | Print the running package version. |
|
|
443
|
-
| `setup [--dry-run] [--json]` | Guided bootstrap wizard. Detects repo, installs deps, scaffolds profile, starts runtime. `--dry-run` previews
|
|
522
|
+
| `setup [--dry-run] [--json]` | Guided bootstrap wizard. Detects repo, installs deps, scaffolds profile, starts runtime and dashboard, creates starter issues. `--dry-run` previews. `--json` emits structured output. |
|
|
444
523
|
| `sync` / `install` | Stage or refresh the packaged runtime into `~/.agent-runtime/runtime-home`. Run after install or upgrade. |
|
|
445
524
|
| `init ...` | Scaffold one repo profile manually. Requires `--profile-id`, `--repo-slug`, `--repo-root`, `--agent-root`, `--worktree-root`, `--coding-worker`. |
|
|
446
525
|
| `doctor` | Inspect runtime and source installation health. |
|
package/bin/pr-risk.sh
CHANGED
|
@@ -6,14 +6,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
6
6
|
source "${SCRIPT_DIR}/../tools/bin/flow-config-lib.sh"
|
|
7
7
|
|
|
8
8
|
PR_NUMBER="${1:?usage: pr-risk.sh PR_NUMBER}"
|
|
9
|
+
[[ "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]] || { printf 'pr-risk: PR_NUMBER must be a positive integer, got: %s\n' "${PR_NUMBER}" >&2; exit 1; }
|
|
9
10
|
CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
10
11
|
MANAGED_PR_PREFIXES_JSON="$(flow_managed_pr_prefixes_json "${CONFIG_YAML}")"
|
|
11
12
|
MANAGED_PR_ISSUE_CAPTURE_REGEX="$(flow_managed_issue_branch_regex "${CONFIG_YAML}")"
|
|
12
13
|
REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
|
|
13
14
|
AGENT_ROOT="$(flow_resolve_agent_root "${CONFIG_YAML}")"
|
|
14
15
|
STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
15
|
-
ALLOW_INFRA_CI_BYPASS="${ACP_ALLOW_INFRA_CI_BYPASS
|
|
16
|
-
LOCAL_FIRST_PR_POLICY="${ACP_LOCAL_FIRST_PR_POLICY
|
|
16
|
+
ALLOW_INFRA_CI_BYPASS="${ACP_ALLOW_INFRA_CI_BYPASS:-1}"
|
|
17
|
+
LOCAL_FIRST_PR_POLICY="${ACP_LOCAL_FIRST_PR_POLICY:-1}"
|
|
17
18
|
PR_LANE_OVERRIDE_FILE="${STATE_ROOT}/pr-lane-overrides/${PR_NUMBER}.env"
|
|
18
19
|
PR_LANE_OVERRIDE=""
|
|
19
20
|
|
|
@@ -37,7 +38,8 @@ gh_api_json_matching_or_fallback() {
|
|
|
37
38
|
printf '%s\n' "${fallback}"
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
PR_JSON="$(gh pr view "$PR_NUMBER" -R "$REPO_SLUG" --json number,title,url,body,isDraft,headRefName,headRefOid,baseRefName,labels,files,mergeStateStatus,reviewDecision,reviewRequests,statusCheckRollup,comments)"
|
|
41
|
+
PR_JSON="$(gh pr view "$PR_NUMBER" -R "$REPO_SLUG" --json number,title,url,body,isDraft,headRefName,headRefOid,baseRefName,labels,files,mergeStateStatus,reviewDecision,reviewRequests,statusCheckRollup,comments 2>/dev/null)" \
|
|
42
|
+
|| { printf 'pr-risk: gh pr view failed for PR %s (repo: %s)\n' "$PR_NUMBER" "$REPO_SLUG" >&2; exit 1; }
|
|
41
43
|
PR_HEAD_SHA="$(jq -r '.headRefOid // ""' <<<"$PR_JSON")"
|
|
42
44
|
PR_HEAD_COMMITTED_AT=""
|
|
43
45
|
if [[ -n "${PR_HEAD_SHA}" ]]; then
|
|
@@ -51,9 +53,24 @@ fi
|
|
|
51
53
|
|
|
52
54
|
PR_JSON="$PR_JSON" PR_HEAD_SHA="$PR_HEAD_SHA" PR_HEAD_COMMITTED_AT="$PR_HEAD_COMMITTED_AT" REVIEW_COMMENTS_JSON="$REVIEW_COMMENTS_JSON" CHECK_RUNS_JSON="$CHECK_RUNS_JSON" PR_LANE_OVERRIDE="${PR_LANE_OVERRIDE:-}" MANAGED_PR_PREFIXES_JSON="$MANAGED_PR_PREFIXES_JSON" MANAGED_PR_ISSUE_CAPTURE_REGEX="$MANAGED_PR_ISSUE_CAPTURE_REGEX" ALLOW_INFRA_CI_BYPASS="$ALLOW_INFRA_CI_BYPASS" LOCAL_FIRST_PR_POLICY="$LOCAL_FIRST_PR_POLICY" node <<'EOF'
|
|
53
55
|
const { execFileSync } = require('node:child_process');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
let data;
|
|
57
|
+
try {
|
|
58
|
+
data = JSON.parse(process.env.PR_JSON);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
process.stderr.write(`pr-risk: failed to parse PR_JSON: ${err.message}\n`);
|
|
61
|
+
process.stdout.write(JSON.stringify({ agentLane: 'ignore', error: `parse-error: ${err.message}` }));
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
let reviewComments, checkRunsPayload;
|
|
65
|
+
try {
|
|
66
|
+
reviewComments = JSON.parse(process.env.REVIEW_COMMENTS_JSON || '[]');
|
|
67
|
+
checkRunsPayload = JSON.parse(process.env.CHECK_RUNS_JSON || '{"check_runs":[]}');
|
|
68
|
+
} catch (err) {
|
|
69
|
+
process.stderr.write(`pr-risk: failed to parse auxiliary JSON env: ${err.message}\n`);
|
|
70
|
+
reviewComments = [];
|
|
71
|
+
checkRunsPayload = { check_runs: [] };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
57
74
|
const checkRuns = checkRunsPayload.check_runs || [];
|
|
58
75
|
const files = (data.files || []).map((file) => file.path);
|
|
59
76
|
const labelNames = (data.labels || []).map((label) => label.name);
|
|
@@ -573,4 +590,9 @@ const result = {
|
|
|
573
590
|
};
|
|
574
591
|
|
|
575
592
|
process.stdout.write(JSON.stringify(result));
|
|
593
|
+
} catch (err) {
|
|
594
|
+
process.stderr.write(`pr-risk: unexpected error: ${err.message}\n${err.stack || ''}\n`);
|
|
595
|
+
process.stdout.write(JSON.stringify({ agentLane: 'ignore', error: `runtime-error: ${err.message}` }));
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
576
598
|
EOF
|
package/hooks/heartbeat-hooks.sh
CHANGED
|
@@ -18,14 +18,51 @@ REPO_SLUG="$(flow_resolve_repo_slug "${CONFIG_YAML}")"
|
|
|
18
18
|
AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
|
|
19
19
|
DEFAULT_BRANCH="$(flow_resolve_default_branch "${CONFIG_YAML}")"
|
|
20
20
|
STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
21
|
-
PENDING_LAUNCH_DIR="${ACP_PENDING_LAUNCH_DIR:-${
|
|
21
|
+
PENDING_LAUNCH_DIR="${ACP_PENDING_LAUNCH_DIR:-${STATE_ROOT}/pending-launches}"
|
|
22
22
|
AGENT_PR_PREFIXES_JSON="$(flow_managed_pr_prefixes_json "${CONFIG_YAML}")"
|
|
23
23
|
AGENT_PR_ISSUE_CAPTURE_REGEX="$(flow_managed_issue_branch_regex "${CONFIG_YAML}")"
|
|
24
24
|
AGENT_PR_HANDOFF_LABEL="${AGENT_PR_HANDOFF_LABEL:-agent-handoff}"
|
|
25
25
|
AGENT_EXCLUSIVE_LABEL="${AGENT_EXCLUSIVE_LABEL:-agent-exclusive}"
|
|
26
|
-
CODING_WORKER="${ACP_CODING_WORKER
|
|
26
|
+
CODING_WORKER="${ACP_CODING_WORKER:-codex}"
|
|
27
27
|
HEARTBEAT_ISSUE_JSON_CACHE_DIR="${TMPDIR:-/tmp}/heartbeat-issue-json.$$"
|
|
28
28
|
|
|
29
|
+
# ── Per-heartbeat snapshot cache ──────────────────────────────────────────────
|
|
30
|
+
# Fetch open issues and open PRs once per heartbeat cycle and reuse the
|
|
31
|
+
# snapshot for every list query. This eliminates 4x issue_list + 3x pr_list
|
|
32
|
+
# redundant GitHub API calls per cycle.
|
|
33
|
+
HEARTBEAT_SNAPSHOT_CACHE_DIR="${TMPDIR:-/tmp}/heartbeat-snapshot.$$"
|
|
34
|
+
|
|
35
|
+
# Snapshot functions are always called from subshells (command substitution or
|
|
36
|
+
# pipes), so in-memory variables would be lost immediately. We rely exclusively
|
|
37
|
+
# on the PID-scoped disk cache under HEARTBEAT_SNAPSHOT_CACHE_DIR.
|
|
38
|
+
|
|
39
|
+
heartbeat_cached_issue_list_json() {
|
|
40
|
+
mkdir -p "${HEARTBEAT_SNAPSHOT_CACHE_DIR}"
|
|
41
|
+
local cache_file="${HEARTBEAT_SNAPSHOT_CACHE_DIR}/issues.json"
|
|
42
|
+
if [[ ! -f "${cache_file}" ]]; then
|
|
43
|
+
local snapshot
|
|
44
|
+
snapshot="$(flow_github_issue_list_json "$REPO_SLUG" open 100 2>/dev/null || true)"
|
|
45
|
+
printf '%s' "${snapshot}" >"${cache_file}"
|
|
46
|
+
fi
|
|
47
|
+
cat "${cache_file}"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
heartbeat_cached_pr_list_json() {
|
|
51
|
+
mkdir -p "${HEARTBEAT_SNAPSHOT_CACHE_DIR}"
|
|
52
|
+
local cache_file="${HEARTBEAT_SNAPSHOT_CACHE_DIR}/prs.json"
|
|
53
|
+
if [[ ! -f "${cache_file}" ]]; then
|
|
54
|
+
local snapshot
|
|
55
|
+
snapshot="$(flow_github_pr_list_json "$REPO_SLUG" open 100 2>/dev/null || true)"
|
|
56
|
+
printf '%s' "${snapshot}" >"${cache_file}"
|
|
57
|
+
fi
|
|
58
|
+
cat "${cache_file}"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
heartbeat_invalidate_snapshot_cache() {
|
|
62
|
+
rm -rf "${HEARTBEAT_SNAPSHOT_CACHE_DIR}" 2>/dev/null || true
|
|
63
|
+
rm -rf "${HEARTBEAT_ISSUE_JSON_CACHE_DIR}" 2>/dev/null || true
|
|
64
|
+
}
|
|
65
|
+
|
|
29
66
|
heartbeat_issue_retry_state_file() {
|
|
30
67
|
local issue_id="${1:?issue id required}"
|
|
31
68
|
printf '%s/retries/issues/%s.env\n' "${STATE_ROOT}" "${issue_id}"
|
|
@@ -92,10 +129,15 @@ heartbeat_issue_json_cached() {
|
|
|
92
129
|
}
|
|
93
130
|
|
|
94
131
|
heartbeat_open_agent_pr_issue_ids() {
|
|
132
|
+
mkdir -p "${HEARTBEAT_SNAPSHOT_CACHE_DIR}"
|
|
133
|
+
local cache_file="${HEARTBEAT_SNAPSHOT_CACHE_DIR}/pr_issue_ids.json"
|
|
134
|
+
if [[ -f "${cache_file}" ]]; then
|
|
135
|
+
cat "${cache_file}"
|
|
136
|
+
return 0
|
|
137
|
+
fi
|
|
95
138
|
local pr_issue_ids_json=""
|
|
96
139
|
pr_issue_ids_json="$(
|
|
97
|
-
|
|
98
|
-
2>/dev/null \
|
|
140
|
+
heartbeat_cached_pr_list_json \
|
|
99
141
|
| jq --argjson agentPrPrefixes "${AGENT_PR_PREFIXES_JSON}" --arg handoffLabel "${AGENT_PR_HANDOFF_LABEL}" --arg branchIssueRegex "${AGENT_PR_ISSUE_CAPTURE_REGEX}" '
|
|
100
142
|
map(
|
|
101
143
|
. as $pr
|
|
@@ -124,10 +166,10 @@ heartbeat_open_agent_pr_issue_ids() {
|
|
|
124
166
|
)"
|
|
125
167
|
|
|
126
168
|
if [[ -z "${pr_issue_ids_json:-}" ]]; then
|
|
127
|
-
|
|
128
|
-
else
|
|
129
|
-
printf '%s\n' "${pr_issue_ids_json}"
|
|
169
|
+
pr_issue_ids_json="[]"
|
|
130
170
|
fi
|
|
171
|
+
printf '%s' "${pr_issue_ids_json}" >"${cache_file}"
|
|
172
|
+
printf '%s\n' "${pr_issue_ids_json}"
|
|
131
173
|
}
|
|
132
174
|
|
|
133
175
|
heartbeat_list_ready_issue_ids() {
|
|
@@ -136,8 +178,7 @@ heartbeat_list_ready_issue_ids() {
|
|
|
136
178
|
open_agent_pr_issue_ids="$(heartbeat_open_agent_pr_issue_ids)"
|
|
137
179
|
|
|
138
180
|
ready_issue_rows="$(
|
|
139
|
-
|
|
140
|
-
2>/dev/null \
|
|
181
|
+
heartbeat_cached_issue_list_json \
|
|
141
182
|
| jq -r --argjson openAgentPrIssueIds "${open_agent_pr_issue_ids}" '
|
|
142
183
|
map(select(
|
|
143
184
|
(any(.labels[]?; .name == "agent-running") | not)
|
|
@@ -171,8 +212,7 @@ heartbeat_list_blocked_recovery_issue_ids() {
|
|
|
171
212
|
open_agent_pr_issue_ids="$(heartbeat_open_agent_pr_issue_ids)"
|
|
172
213
|
|
|
173
214
|
blocked_issue_rows="$(
|
|
174
|
-
|
|
175
|
-
2>/dev/null \
|
|
215
|
+
heartbeat_cached_issue_list_json \
|
|
176
216
|
| jq -r --argjson openAgentPrIssueIds "${open_agent_pr_issue_ids}" '
|
|
177
217
|
map(select(
|
|
178
218
|
any(.labels[]?; .name == "agent-blocked")
|
|
@@ -270,8 +310,7 @@ heartbeat_list_exclusive_issue_ids() {
|
|
|
270
310
|
local open_agent_pr_issue_ids
|
|
271
311
|
open_agent_pr_issue_ids="$(heartbeat_open_agent_pr_issue_ids)"
|
|
272
312
|
|
|
273
|
-
|
|
274
|
-
2>/dev/null \
|
|
313
|
+
heartbeat_cached_issue_list_json \
|
|
275
314
|
| jq -r --arg exclusiveLabel "${AGENT_EXCLUSIVE_LABEL}" --argjson openAgentPrIssueIds "${open_agent_pr_issue_ids}" '
|
|
276
315
|
map(select(
|
|
277
316
|
any(.labels[]?; .name == $exclusiveLabel)
|
|
@@ -285,8 +324,7 @@ heartbeat_list_exclusive_issue_ids() {
|
|
|
285
324
|
}
|
|
286
325
|
|
|
287
326
|
heartbeat_list_running_issue_ids() {
|
|
288
|
-
|
|
289
|
-
2>/dev/null \
|
|
327
|
+
heartbeat_cached_issue_list_json \
|
|
290
328
|
| jq -r '
|
|
291
329
|
map(select(any(.labels[]?; .name == "agent-running")))
|
|
292
330
|
| sort_by(.createdAt, .number)
|
|
@@ -295,8 +333,7 @@ heartbeat_list_running_issue_ids() {
|
|
|
295
333
|
}
|
|
296
334
|
|
|
297
335
|
heartbeat_list_open_agent_pr_ids() {
|
|
298
|
-
|
|
299
|
-
2>/dev/null \
|
|
336
|
+
heartbeat_cached_pr_list_json \
|
|
300
337
|
| jq -r --argjson agentPrPrefixes "${AGENT_PR_PREFIXES_JSON}" --arg handoffLabel "${AGENT_PR_HANDOFF_LABEL}" '
|
|
301
338
|
map(select(
|
|
302
339
|
. as $pr
|
|
@@ -312,8 +349,7 @@ heartbeat_list_open_agent_pr_ids() {
|
|
|
312
349
|
}
|
|
313
350
|
|
|
314
351
|
heartbeat_list_exclusive_pr_ids() {
|
|
315
|
-
|
|
316
|
-
2>/dev/null \
|
|
352
|
+
heartbeat_cached_pr_list_json \
|
|
317
353
|
| jq -r --argjson agentPrPrefixes "${AGENT_PR_PREFIXES_JSON}" --arg handoffLabel "${AGENT_PR_HANDOFF_LABEL}" --arg exclusiveLabel "${AGENT_EXCLUSIVE_LABEL}" '
|
|
318
354
|
map(select(
|
|
319
355
|
. as $pr
|
|
@@ -444,16 +480,20 @@ heartbeat_pr_risk_json() {
|
|
|
444
480
|
heartbeat_mark_issue_running() {
|
|
445
481
|
local issue_id="${1:?issue id required}"
|
|
446
482
|
local is_heavy="${2:-no}"
|
|
483
|
+
local cached_json
|
|
484
|
+
cached_json="$(heartbeat_issue_json_cached "$issue_id" 2>/dev/null || true)"
|
|
447
485
|
if [[ "$is_heavy" == "yes" ]]; then
|
|
448
|
-
bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-ready --remove agent-blocked --add agent-running --add agent-e2e-heavy >/dev/null || true
|
|
486
|
+
ACP_CACHED_ISSUE_JSON="${cached_json}" bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-ready --remove agent-blocked --add agent-running --add agent-e2e-heavy >/dev/null || true
|
|
449
487
|
else
|
|
450
|
-
bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-ready --remove agent-blocked --add agent-running >/dev/null || true
|
|
488
|
+
ACP_CACHED_ISSUE_JSON="${cached_json}" bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-ready --remove agent-blocked --add agent-running >/dev/null || true
|
|
451
489
|
fi
|
|
452
490
|
}
|
|
453
491
|
|
|
454
492
|
heartbeat_issue_launch_failed() {
|
|
455
493
|
local issue_id="${1:?issue id required}"
|
|
456
|
-
|
|
494
|
+
local cached_json
|
|
495
|
+
cached_json="$(heartbeat_issue_json_cached "$issue_id" 2>/dev/null || true)"
|
|
496
|
+
ACP_CACHED_ISSUE_JSON="${cached_json}" bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-running >/dev/null || true
|
|
457
497
|
}
|
|
458
498
|
|
|
459
499
|
heartbeat_ensure_issue_label_exists() {
|