agent-control-plane 0.3.0 → 0.6.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 +141 -28
- 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 +257 -59
- package/package.json +39 -32
- package/tools/bin/debug-session.sh +106 -0
- package/tools/bin/flow-config-lib.sh +1203 -60
- package/tools/bin/flow-runtime-doctor-linux.sh +136 -0
- package/tools/bin/flow-runtime-doctor.sh +5 -1
- 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/install-project-systemd.sh +255 -0
- package/tools/bin/project-init.sh +21 -1
- package/tools/bin/project-launchd-bootstrap.sh +5 -1
- package/tools/bin/project-runtimectl.sh +91 -2
- package/tools/bin/project-systemd-bootstrap.sh +74 -0
- package/tools/bin/scaffold-profile.sh +61 -3
- package/tools/bin/uninstall-project-systemd.sh +87 -0
- package/tools/dashboard/app.js +228 -6
- package/tools/dashboard/dashboard_snapshot.py +55 -0
- package/tools/dashboard/issue_queue_state.py +101 -0
- package/tools/dashboard/server.py +123 -1
- package/tools/dashboard/styles.css +526 -455
- 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/agent-cleanup-worktree +0 -247
- package/tools/bin/agent-github-update-labels +0 -71
- package/tools/bin/agent-init-worktree +0 -216
- package/tools/bin/agent-project-archive-run +0 -52
- package/tools/bin/agent-project-capture-worker +0 -46
- package/tools/bin/agent-project-catch-up-issue-pr-links +0 -118
- package/tools/bin/agent-project-catch-up-merged-prs +0 -194
- package/tools/bin/agent-project-catch-up-scheduled-issue-retries +0 -123
- package/tools/bin/agent-project-cleanup-session +0 -513
- package/tools/bin/agent-project-detached-launch +0 -127
- package/tools/bin/agent-project-heartbeat-loop +0 -1029
- package/tools/bin/agent-project-open-issue-worktree +0 -89
- package/tools/bin/agent-project-open-pr-worktree +0 -80
- package/tools/bin/agent-project-publish-issue-pr +0 -465
- package/tools/bin/agent-project-reconcile-issue-session +0 -1398
- package/tools/bin/agent-project-reconcile-pr-session +0 -1230
- package/tools/bin/agent-project-retry-state +0 -147
- package/tools/bin/agent-project-run-claude-session +0 -805
- package/tools/bin/agent-project-run-codex-resilient +0 -955
- package/tools/bin/agent-project-run-codex-session +0 -435
- package/tools/bin/agent-project-run-kilo-session +0 -369
- package/tools/bin/agent-project-run-ollama-session +0 -658
- package/tools/bin/agent-project-run-openclaw-session +0 -1309
- package/tools/bin/agent-project-run-opencode-session +0 -377
- package/tools/bin/agent-project-run-pi-session +0 -479
- package/tools/bin/agent-project-sync-anchor-repo +0 -139
- package/tools/bin/agent-project-worker-status +0 -188
- package/tools/bin/branch-verification-guard.sh +0 -364
- package/tools/bin/capture-worker.sh +0 -18
- package/tools/bin/cleanup-worktree.sh +0 -52
- package/tools/bin/codex-quota +0 -31
- package/tools/bin/create-follow-up-issue.sh +0 -114
- package/tools/bin/dashboard-launchd-bootstrap.sh +0 -50
- package/tools/bin/issue-publish-localization-guard.sh +0 -142
- package/tools/bin/issue-publish-scope-guard.sh +0 -242
- package/tools/bin/issue-requires-local-workspace-install.sh +0 -31
- package/tools/bin/issue-resource-class.sh +0 -12
- package/tools/bin/kick-scheduler.sh +0 -75
- package/tools/bin/label-follow-up-issues.sh +0 -14
- package/tools/bin/new-pr-worktree.sh +0 -50
- package/tools/bin/new-worktree.sh +0 -49
- package/tools/bin/pr-risk.sh +0 -12
- package/tools/bin/prepare-worktree.sh +0 -142
- package/tools/bin/provider-cooldown-state.sh +0 -204
- package/tools/bin/publish-issue-worker.sh +0 -31
- package/tools/bin/reconcile-bootstrap-lib.sh +0 -113
- package/tools/bin/reconcile-issue-worker.sh +0 -34
- package/tools/bin/reconcile-pr-worker.sh +0 -34
- package/tools/bin/record-verification.sh +0 -71
- package/tools/bin/render-flow-config.sh +0 -98
- package/tools/bin/resident-issue-controller-lib.sh +0 -448
- package/tools/bin/resident-issue-queue-status.py +0 -35
- package/tools/bin/retry-state.sh +0 -31
- package/tools/bin/reuse-issue-worktree.sh +0 -121
- package/tools/bin/run-codex-bypass.sh +0 -3
- package/tools/bin/run-codex-safe.sh +0 -3
- package/tools/bin/run-codex-task.sh +0 -280
- package/tools/bin/serve-dashboard.sh +0 -5
- package/tools/bin/split-retained-slice.sh +0 -124
- package/tools/bin/start-issue-worker.sh +0 -943
- package/tools/bin/start-pr-fix-worker.sh +0 -491
- package/tools/bin/start-pr-merge-repair-worker.sh +0 -8
- package/tools/bin/start-pr-review-worker.sh +0 -261
- package/tools/bin/start-resident-issue-loop.sh +0 -499
- package/tools/bin/update-github-labels.sh +0 -14
- package/tools/bin/worker-status.sh +0 -19
- package/tools/bin/workflow-catalog.sh +0 -77
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
`agent-control-plane` (ACP) keeps your coding agents running reliably without
|
|
13
13
|
you having to stare at them all day.
|
|
14
14
|
|
|
15
|
+
**✅ ROADMAP COMPLETE (v0.6.0)** - All planned features delivered!
|
|
16
|
+
|
|
15
17
|
It is the operator layer for coding agents that need to keep running after the
|
|
16
18
|
novelty wears off — and the responsible adult in the room that stops them from
|
|
17
19
|
going completely off the rails.
|
|
@@ -33,7 +35,7 @@ however, and suddenly that "not smart enough" free model is grinding through
|
|
|
33
35
|
your issue backlog like a junior developer who is weirdly enthusiastic about
|
|
34
36
|
reading CI logs.
|
|
35
37
|
|
|
36
|
-
That is what ACP does. It turns a
|
|
38
|
+
That is what ACP does. It turns a forge-backed repo into a managed runtime: a
|
|
37
39
|
repeatable setup, a stable home for state, a heartbeat that keeps agents
|
|
38
40
|
scheduled and supervised, and a dashboard you can actually glance at without
|
|
39
41
|
spelunking through temp folders, worktrees, or half-remembered `tmux` sessions.
|
|
@@ -73,7 +75,7 @@ monthly API budget in a long weekend, or enter a retry loop that only stops when
|
|
|
73
75
|
the credit card does.
|
|
74
76
|
|
|
75
77
|
ACP is the person standing next to the fuse. It enforces launch limits,
|
|
76
|
-
reconciles outcomes before touching
|
|
78
|
+
reconciles outcomes before touching your forge, validates before it publishes, and
|
|
77
79
|
respects cooldowns instead of hammering a provider at full throttle. The agent
|
|
78
80
|
gets to be smart and fast. ACP makes sure "smart and fast" does not also mean
|
|
79
81
|
"unattended and irreversible."
|
|
@@ -98,6 +100,27 @@ it had a supervisor."
|
|
|
98
100
|
| Run reproducible agent research cheaply | Cost-controlled execution harness for studying agent behavior, output quality, or prompting strategies |
|
|
99
101
|
| Enforce safety by architecture, not by hope | Launch limits, reconcile gates, and cooldowns that are built into the runtime, not left to chance |
|
|
100
102
|
|
|
103
|
+
## Why Gitea Local-First
|
|
104
|
+
|
|
105
|
+
ACP started GitHub-first, but a local-first Gitea loop is often a better daily
|
|
106
|
+
working setup:
|
|
107
|
+
|
|
108
|
+
- It reduces dependence on GitHub API rate limits for routine issue and PR work.
|
|
109
|
+
- It lets agents collaborate against a local forge while you keep GitHub as the
|
|
110
|
+
public mirror or release boundary.
|
|
111
|
+
- It gives you a safer place to let ACP iterate quickly, because local Gitea is
|
|
112
|
+
cheaper to reset, inspect, and isolate than a live hosted repo.
|
|
113
|
+
- It matches how ACP now works best operationally: local runtime state,
|
|
114
|
+
local worktrees, local dashboard, and a forge that can live on the same
|
|
115
|
+
machine.
|
|
116
|
+
|
|
117
|
+
The intended model is:
|
|
118
|
+
|
|
119
|
+
- `Gitea main` is the working mainline ACP automates day to day.
|
|
120
|
+
- Your local source checkout auto-syncs from that forge mainline.
|
|
121
|
+
- GitHub becomes the publish/release mirror once the codebase is stable enough
|
|
122
|
+
to push outward.
|
|
123
|
+
|
|
101
124
|
## Use Cases
|
|
102
125
|
|
|
103
126
|
Teams and solo builders usually reach for ACP when one of these starts to feel
|
|
@@ -120,7 +143,7 @@ familiar:
|
|
|
120
143
|
## Roadmap
|
|
121
144
|
|
|
122
145
|
ACP is moving toward a true multi-backend control plane. The goal is one runtime
|
|
123
|
-
and one dashboard for many coding-agent backends, across macOS, Linux, and
|
|
146
|
+
and one dashboard for many coding-agent backends, across macOS, Linux, and Windows (WSL2)
|
|
124
147
|
Windows.
|
|
125
148
|
|
|
126
149
|
### Backend Status
|
|
@@ -129,14 +152,41 @@ Windows.
|
|
|
129
152
|
| --- | --- | --- |
|
|
130
153
|
| `codex` | Production-ready | Full ACP workflow support today. |
|
|
131
154
|
| `claude` | Production-ready | Full ACP workflow support today. |
|
|
132
|
-
| `openclaw` | Production-ready | Full ACP workflow
|
|
133
|
-
| `ollama` |
|
|
134
|
-
| `pi` | Experimental | Working adapter using the
|
|
135
|
-
| `opencode` | Experimental | Working adapter for
|
|
136
|
-
| `kilo` | Experimental | Working adapter for
|
|
137
|
-
| `gemini-cli` |
|
|
138
|
-
| `nanoclaw` |
|
|
139
|
-
| `picoclaw` |
|
|
155
|
+
| `openclaw` | Production-ready | Full ACP workflow, including resident-style runs. |
|
|
156
|
+
| `ollama` | **Hardening** | Working adapter with Node.js agentic loop. **v0.4.9+: Added health-check + context detection.** Moved toward production-ready. |
|
|
157
|
+
| `pi` | Experimental | Working adapter using the pi CLI. **Health-check + API key validation.** |
|
|
158
|
+
| `opencode` | Experimental | Working adapter for Crush. **Health-check (verify `crush` binary).** |
|
|
159
|
+
| `kilo` | Experimental | Working adapter for Kilo Code. **Health-check + JSON stream validation.** |
|
|
160
|
+
| `gemini-cli` | **Integrated** | Google's official terminal agent (v0.39.1+). Full ACP adapter with health-check, API key validation, and streaming JSON output. |
|
|
161
|
+
| `nanoclaw` | Not integrable | Standalone agent system (like ACP), not a CLI backend. Reference for container patterns. |
|
|
162
|
+
| `picoclaw` | Not integrable | Standalone agent system (Go-based). Runs on $10 hardware. Not a CLI backend. |
|
|
163
|
+
|
|
164
|
+
### Adapter Pattern
|
|
165
|
+
|
|
166
|
+
ACP uses a **standardized adapter interface** to support multiple backends. Every adapter implements these functions:
|
|
167
|
+
|
|
168
|
+
| Function | Purpose |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| `adapter_info()` | Print adapter metadata (id, name, type, version, model) |
|
|
171
|
+
| `adapter_health_check()` | Verify backend is available (exit 0 = healthy) |
|
|
172
|
+
| `adapter_run()` | Execute a task (params: MODE SESSION WORKTREE PROMPT_FILE) |
|
|
173
|
+
| `adapter_status()` | Get run status for a session |
|
|
174
|
+
|
|
175
|
+
**Available adapters:**
|
|
176
|
+
- `tools/bin/ollama-adapter.sh` - Ollama local models (implements health-check)
|
|
177
|
+
- All existing backends (`codex`, `claude`, `pi`, `opencode`, `kilo`) use the same interface via `run-codex-task.sh`, `run-claude-task.sh`, etc.
|
|
178
|
+
|
|
179
|
+
**Using adapters:**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Print adapter info
|
|
183
|
+
bash tools/bin/ollama-adapter.sh
|
|
184
|
+
|
|
185
|
+
# Run a task with generic runner
|
|
186
|
+
bash tools/bin/run-with-adapter.sh tools/bin/ollama-adapter.sh safe my-session /path/to/worktree /path/to/prompt.txt
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
See `tools/bin/adapter-interface.sh` for the full interface specification.
|
|
140
190
|
|
|
141
191
|
If you are trying ACP on a real repo right now, start with `codex`, `claude`,
|
|
142
192
|
or `openclaw`. Use `ollama` to run local models — useful for research, offline
|
|
@@ -211,7 +261,7 @@ flowchart LR
|
|
|
211
261
|
Scheduler --> Workers["issue / PR worker launchers"]
|
|
212
262
|
Workers --> Backends["codex / claude / openclaw / ollama / pi / opencode / kilo"]
|
|
213
263
|
Backends --> Reconcile["reconcile issue / PR session"]
|
|
214
|
-
Reconcile -->
|
|
264
|
+
Reconcile --> Forge["issues / PRs / labels / comments"]
|
|
215
265
|
Scheduler --> State["runs + state + history"]
|
|
216
266
|
State --> Dashboard["dashboard snapshot + UI"]
|
|
217
267
|
```
|
|
@@ -227,7 +277,7 @@ sequenceDiagram
|
|
|
227
277
|
participant Heartbeat
|
|
228
278
|
participant Worker
|
|
229
279
|
participant Reconcile
|
|
230
|
-
participant
|
|
280
|
+
participant Forge
|
|
231
281
|
|
|
232
282
|
Operator->>RuntimeCtl: runtime start --profile-id <id>
|
|
233
283
|
RuntimeCtl->>Supervisor: keep runtime alive
|
|
@@ -236,7 +286,7 @@ sequenceDiagram
|
|
|
236
286
|
Bootstrap->>Heartbeat: invoke published heartbeat
|
|
237
287
|
Heartbeat->>Worker: launch eligible issue/PR flow
|
|
238
288
|
Worker->>Reconcile: emit result artifacts
|
|
239
|
-
Reconcile->>
|
|
289
|
+
Reconcile->>Forge: labels, comments, PR actions
|
|
240
290
|
end
|
|
241
291
|
```
|
|
242
292
|
|
|
@@ -267,9 +317,9 @@ system.
|
|
|
267
317
|
| --- | --- | --- | --- |
|
|
268
318
|
| Node.js `>= 18` | yes | Runs the npm package entrypoint and `npx` wrapper. | CI runs on Node `22`. Node `20` or `22` both work fine. |
|
|
269
319
|
| `bash` | yes | All runtime, profile, and worker orchestration scripts are Bash. | Your login shell can be `zsh`; `bash` just needs to be on `PATH`. |
|
|
270
|
-
| `git` | yes | Manages worktrees, checks branch state, and coordinates repo automation. | Required even if you interact only through
|
|
271
|
-
| `gh` |
|
|
272
|
-
| `jq` | yes | Parses JSON from `gh` output and worker metadata throughout. | Missing `jq` will break GitHub-heavy and runtime flows. |
|
|
320
|
+
| `git` | yes | Manages worktrees, checks branch state, and coordinates repo automation. | Required even if you interact only through forge issues and PRs. |
|
|
321
|
+
| `gh` | for GitHub-first setups | GitHub CLI auth and API access for issues, PRs, labels, and metadata. | Run `gh auth login` before first use when `--forge-provider github`. |
|
|
322
|
+
| `jq` | yes | Parses JSON from `gh` output and worker metadata throughout. | Missing `jq` will break GitHub-heavy and Gitea-heavy runtime flows. |
|
|
273
323
|
| `python3` | yes | Powers the dashboard server, snapshot renderer, and config helpers. | Required for both dashboard use and several internal scripts. |
|
|
274
324
|
| `tmux` | yes | Runs long-lived worker sessions and captures their status. | Missing `tmux` means background worker workflows will not launch. |
|
|
275
325
|
| 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. |
|
|
@@ -279,8 +329,10 @@ system.
|
|
|
279
329
|
| `kilo` CLI | for `kilo` backend | TypeScript coding agent ([kilocode/cli](https://github.com/Kilo-Org/kilocode)). | Install via `npm i -g @kilocode/cli`. |
|
|
280
330
|
| 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. |
|
|
281
331
|
|
|
282
|
-
Make sure
|
|
283
|
-
|
|
332
|
+
Make sure your chosen worker backend is authenticated for the same OS user
|
|
333
|
+
before starting any background runtime. For GitHub-first setups, authenticate
|
|
334
|
+
`gh`. For Gitea local-first setups, provide `--gitea-base-url` plus a token or
|
|
335
|
+
username/password during setup so ACP can write issues, PR comments, and labels.
|
|
284
336
|
|
|
285
337
|
## Install
|
|
286
338
|
|
|
@@ -313,7 +365,7 @@ npx agent-control-plane@latest setup
|
|
|
313
365
|
The wizard walks you through the full setup in one pass:
|
|
314
366
|
|
|
315
367
|
1. Detects the current repo and suggests sane defaults
|
|
316
|
-
2.
|
|
368
|
+
2. Captures forge mode (`github` or `gitea`) and the auth/settings that mode needs
|
|
317
369
|
3. Checks backend readiness (API keys for openclaw/pi, local server for ollama)
|
|
318
370
|
4. Scaffolds the profile, runs health checks, starts the runtime
|
|
319
371
|
5. Launches the monitoring dashboard in the background
|
|
@@ -343,16 +395,41 @@ npx agent-control-plane@latest setup \
|
|
|
343
395
|
With `--json`, ACP emits a single structured object on `stdout` and sends
|
|
344
396
|
progress logs to `stderr`, which keeps parsing stable.
|
|
345
397
|
|
|
398
|
+
Example: local-first Gitea setup
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
npx agent-control-plane@latest setup \
|
|
402
|
+
--forge-provider gitea \
|
|
403
|
+
--repo-slug acp-admin/my-repo \
|
|
404
|
+
--gitea-base-url http://127.0.0.1:3000 \
|
|
405
|
+
--gitea-token <token> \
|
|
406
|
+
--start-runtime \
|
|
407
|
+
--start-dashboard
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
This writes the forge settings into the profile `runtime.env`, so later
|
|
411
|
+
heartbeat, reconcile, and publish steps keep talking to the same Gitea
|
|
412
|
+
instance without extra shell exports.
|
|
413
|
+
|
|
346
414
|
### Option B — Manual setup
|
|
347
415
|
|
|
348
416
|
If you prefer explicit control over each step:
|
|
349
417
|
|
|
350
|
-
**1. Authenticate
|
|
418
|
+
**1. Authenticate the working forge**
|
|
351
419
|
|
|
352
420
|
```bash
|
|
353
421
|
gh auth login
|
|
354
422
|
```
|
|
355
423
|
|
|
424
|
+
For Gitea local-first, skip `gh auth login` and pass Gitea settings directly to
|
|
425
|
+
`setup` or `init`:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
--forge-provider gitea \
|
|
429
|
+
--gitea-base-url http://127.0.0.1:3000 \
|
|
430
|
+
--gitea-token <token>
|
|
431
|
+
```
|
|
432
|
+
|
|
356
433
|
**2. Install the packaged runtime**
|
|
357
434
|
|
|
358
435
|
```bash
|
|
@@ -368,6 +445,7 @@ re-run after upgrades.
|
|
|
368
445
|
npx agent-control-plane@latest init \
|
|
369
446
|
--profile-id my-repo \
|
|
370
447
|
--repo-slug owner/my-repo \
|
|
448
|
+
--forge-provider github \
|
|
371
449
|
--repo-root ~/src/my-repo \
|
|
372
450
|
--agent-root ~/.agent-runtime/projects/my-repo \
|
|
373
451
|
--worktree-root ~/src/my-repo-worktrees \
|
|
@@ -377,7 +455,9 @@ npx agent-control-plane@latest init \
|
|
|
377
455
|
| Flag | Purpose |
|
|
378
456
|
| --- | --- |
|
|
379
457
|
| `--profile-id` | Short name used in all ACP commands |
|
|
380
|
-
| `--repo-slug` |
|
|
458
|
+
| `--repo-slug` | Forge repo ACP should track |
|
|
459
|
+
| `--forge-provider` | Which forge ACP should automate (`github` or `gitea`) |
|
|
460
|
+
| `--gitea-base-url` | Base URL when `--forge-provider gitea` |
|
|
381
461
|
| `--repo-root` | Path to your local checkout |
|
|
382
462
|
| `--agent-root` | Where ACP keeps per-project runtime state |
|
|
383
463
|
| `--worktree-root` | Where ACP places repo worktrees |
|
|
@@ -407,9 +487,8 @@ grouped and inspectable without digging through scattered temp files.
|
|
|
407
487
|
## Starter Issues
|
|
408
488
|
|
|
409
489
|
The setup wizard can create a set of recurring `agent-keep-open` issues on your
|
|
410
|
-
repo so ACP starts working immediately after installation.
|
|
411
|
-
|
|
412
|
-
heartbeat cycle.
|
|
490
|
+
repo so ACP starts working immediately after installation. ACP picks them up on
|
|
491
|
+
its next heartbeat cycle without requiring a separate readiness label.
|
|
413
492
|
|
|
414
493
|
Built-in templates:
|
|
415
494
|
|
|
@@ -421,9 +500,9 @@ Built-in templates:
|
|
|
421
500
|
| Dependency audit | Fix vulnerabilities and update safe patches |
|
|
422
501
|
| Refactoring sweep | Reduce complexity and duplication |
|
|
423
502
|
|
|
424
|
-
You can also create your own recurring issues
|
|
425
|
-
`agent-keep-open`
|
|
426
|
-
continuously.
|
|
503
|
+
You can also create your own recurring issues by adding the
|
|
504
|
+
`agent-keep-open` label to any GitHub issue. ACP will keep revisiting that
|
|
505
|
+
issue continuously unless it is blocked or already claimed by an open agent PR.
|
|
427
506
|
|
|
428
507
|
To skip this step during setup, pass `--no-create-starter-issues`.
|
|
429
508
|
|
|
@@ -472,6 +551,40 @@ npx agent-control-plane@latest launchd-uninstall --profile-id my-repo
|
|
|
472
551
|
|
|
473
552
|
These commands are macOS-only and manage per-user `launchd` agents.
|
|
474
553
|
|
|
554
|
+
## Windows (WSL2) Autostart
|
|
555
|
+
|
|
556
|
+
ACP runs inside WSL2 (Windows Subsystem for Linux) where it uses systemd for service management. This requires WSL2 with systemd enabled (Windows 11 22H2+).
|
|
557
|
+
|
|
558
|
+
**Prerequisites:**
|
|
559
|
+
1. Install WSL2 with Ubuntu: `wsl --install -d Ubuntu` (in PowerShell Admin)
|
|
560
|
+
2. Enable systemd in WSL2 (create `/etc/wsl.conf` with `[boot] systemd=true`)
|
|
561
|
+
3. Run `wsl --shutdown` from PowerShell, then restart WSL2
|
|
562
|
+
|
|
563
|
+
**Install project service in WSL2:**
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
# Inside WSL2 Ubuntu terminal
|
|
567
|
+
cd /mnt/c/Users/You/Projects/your-repo
|
|
568
|
+
|
|
569
|
+
# Bootstrap systemd service (same as Linux)
|
|
570
|
+
agent-control-plane project systemd-bootstrap \
|
|
571
|
+
--project-dir . \
|
|
572
|
+
--repo-url https://github.com/your-org/your-repo.git \
|
|
573
|
+
--worker-type claude \
|
|
574
|
+
--schedule "*/30 * * * *" \
|
|
575
|
+
--issues "1,2,3"
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Manage the service:**
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
systemctl --user daemon-reload
|
|
582
|
+
systemctl --user enable --now agent-control-plane@$(basename $(pwd)).timer
|
|
583
|
+
systemctl --user status agent-control-plane@$(basename $(pwd)).timer
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
See [WSL2_SETUP.md](docs/WSL2_SETUP.md) for full setup guide, Docker in WSL2, and troubleshooting.
|
|
587
|
+
|
|
475
588
|
## Update
|
|
476
589
|
|
|
477
590
|
After upgrading the package, refresh the runtime and verify health:
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
{
|
|
8
8
|
"id": "issue-implementation",
|
|
9
9
|
"kind": "issue",
|
|
10
|
-
"trigger": "Open issue
|
|
10
|
+
"trigger": "Open issue without agent-running, without an open agent PR claim, and not blocked by retry policy",
|
|
11
11
|
"entrypoint": "tools/bin/start-issue-worker.sh",
|
|
12
12
|
"summary": "Primary implementation loop for focused issues that should end in a PR or a blocked report."
|
|
13
13
|
},
|
package/bin/pr-risk.sh
CHANGED
|
@@ -26,10 +26,10 @@ fi
|
|
|
26
26
|
gh_api_json_matching_or_fallback() {
|
|
27
27
|
local fallback="${1:?fallback required}"
|
|
28
28
|
local jq_filter="${2:?jq filter required}"
|
|
29
|
-
|
|
29
|
+
local route="${3:?route required}"
|
|
30
30
|
local output=""
|
|
31
31
|
|
|
32
|
-
output="$(
|
|
32
|
+
output="$(flow_github_api_repo "${REPO_SLUG}" "${route}" 2>/dev/null || true)"
|
|
33
33
|
if jq -e "${jq_filter}" >/dev/null 2>&1 <<<"${output}"; then
|
|
34
34
|
printf '%s\n' "${output}"
|
|
35
35
|
return 0
|
|
@@ -38,17 +38,32 @@ gh_api_json_matching_or_fallback() {
|
|
|
38
38
|
printf '%s\n' "${fallback}"
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
PR_JSON="$(
|
|
42
|
-
|
|
41
|
+
PR_JSON="$(flow_github_pr_view_json "$REPO_SLUG" "$PR_NUMBER" 2>/dev/null || true)"
|
|
42
|
+
if ! jq -e '.number? != null' >/dev/null 2>&1 <<<"${PR_JSON:-}"; then
|
|
43
|
+
if flow_using_gitea; then
|
|
44
|
+
printf 'pr-risk: forge PR view failed for PR %s (repo: %s)\n' "$PR_NUMBER" "$REPO_SLUG" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
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)" \
|
|
48
|
+
|| { printf 'pr-risk: forge PR view failed for PR %s (repo: %s)\n' "$PR_NUMBER" "$REPO_SLUG" >&2; exit 1; }
|
|
49
|
+
fi
|
|
43
50
|
PR_HEAD_SHA="$(jq -r '.headRefOid // ""' <<<"$PR_JSON")"
|
|
44
51
|
PR_HEAD_COMMITTED_AT=""
|
|
45
52
|
if [[ -n "${PR_HEAD_SHA}" ]]; then
|
|
46
|
-
PR_HEAD_COMMITTED_AT="$(
|
|
53
|
+
PR_HEAD_COMMITTED_AT="$(
|
|
54
|
+
flow_github_api_repo "${REPO_SLUG}" "commits/${PR_HEAD_SHA}" 2>/dev/null \
|
|
55
|
+
| jq -r '.commit.committer.date // .commit.author.date // .created // .timestamp // ""' 2>/dev/null \
|
|
56
|
+
|| true
|
|
57
|
+
)"
|
|
58
|
+
fi
|
|
59
|
+
if flow_using_gitea; then
|
|
60
|
+
REVIEW_COMMENTS_JSON='[]'
|
|
61
|
+
else
|
|
62
|
+
REVIEW_COMMENTS_JSON="$(gh_api_json_matching_or_fallback '[]' 'type == "array"' "pulls/${PR_NUMBER}/comments")"
|
|
47
63
|
fi
|
|
48
|
-
REVIEW_COMMENTS_JSON="$(gh_api_json_matching_or_fallback '[]' 'type == "array"' "repos/${REPO_SLUG}/pulls/${PR_NUMBER}/comments")"
|
|
49
64
|
CHECK_RUNS_JSON='{"check_runs":[]}'
|
|
50
65
|
if [[ -n "${PR_HEAD_SHA}" ]]; then
|
|
51
|
-
CHECK_RUNS_JSON="$(gh_api_json_matching_or_fallback '{"check_runs":[]}' 'type == "object" and ((.check_runs // []) | type == "array")' "
|
|
66
|
+
CHECK_RUNS_JSON="$(gh_api_json_matching_or_fallback '{"check_runs":[]}' 'type == "object" and ((.check_runs // []) | type == "array")' "commits/${PR_HEAD_SHA}/check-runs")"
|
|
52
67
|
fi
|
|
53
68
|
|
|
54
69
|
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'
|
package/bin/sync-pr-labels.sh
CHANGED
|
@@ -100,7 +100,7 @@ fi
|
|
|
100
100
|
bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$PR_NUMBER" "${args[@]}" >/dev/null
|
|
101
101
|
|
|
102
102
|
if [[ -n "$linked_issue_id" ]]; then
|
|
103
|
-
bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$linked_issue_id" --remove agent-
|
|
103
|
+
bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$linked_issue_id" --remove agent-running >/dev/null || true
|
|
104
104
|
fi
|
|
105
105
|
|
|
106
106
|
printf 'PR_NUMBER=%s\n' "$PR_NUMBER"
|
package/hooks/heartbeat-hooks.sh
CHANGED
|
@@ -36,13 +36,115 @@ HEARTBEAT_SNAPSHOT_CACHE_DIR="${TMPDIR:-/tmp}/heartbeat-snapshot.$$"
|
|
|
36
36
|
# pipes), so in-memory variables would be lost immediately. We rely exclusively
|
|
37
37
|
# on the PID-scoped disk cache under HEARTBEAT_SNAPSHOT_CACHE_DIR.
|
|
38
38
|
|
|
39
|
+
heartbeat_github_mirror_dir() {
|
|
40
|
+
[[ -n "${STATE_ROOT:-}" ]] || return 1
|
|
41
|
+
printf '%s/github-mirror/heartbeat\n' "${STATE_ROOT}"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
heartbeat_github_mirror_file() {
|
|
45
|
+
local mirror_key="${1:?mirror key required}"
|
|
46
|
+
local mirror_dir=""
|
|
47
|
+
mirror_dir="$(heartbeat_github_mirror_dir)" || return 1
|
|
48
|
+
printf '%s/%s.json\n' "${mirror_dir}" "${mirror_key}"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
heartbeat_json_matches_kind() {
|
|
52
|
+
local payload="${1:-}"
|
|
53
|
+
local expected_kind="${2:-}"
|
|
54
|
+
|
|
55
|
+
[[ -n "${payload}" && -n "${expected_kind}" ]] || return 1
|
|
56
|
+
jq -e --arg kind "${expected_kind}" 'type == $kind' >/dev/null <<<"${payload}"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
heartbeat_write_json_mirror() {
|
|
60
|
+
local mirror_key="${1:?mirror key required}"
|
|
61
|
+
local payload="${2:-}"
|
|
62
|
+
local expected_kind="${3:?expected kind required}"
|
|
63
|
+
local source_mode="${4:-live}"
|
|
64
|
+
local mirror_file=""
|
|
65
|
+
local mirror_dir=""
|
|
66
|
+
local meta_file=""
|
|
67
|
+
local tmp_file=""
|
|
68
|
+
local meta_tmp_file=""
|
|
69
|
+
local updated_at=""
|
|
70
|
+
|
|
71
|
+
heartbeat_json_matches_kind "${payload}" "${expected_kind}" || return 1
|
|
72
|
+
mirror_dir="$(heartbeat_github_mirror_dir)" || return 1
|
|
73
|
+
mirror_file="${mirror_dir}/${mirror_key}.json"
|
|
74
|
+
meta_file="${mirror_dir}/${mirror_key}.env"
|
|
75
|
+
mkdir -p "${mirror_dir}"
|
|
76
|
+
updated_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
77
|
+
tmp_file="${mirror_file}.tmp.$$"
|
|
78
|
+
meta_tmp_file="${meta_file}.tmp.$$"
|
|
79
|
+
printf '%s' "${payload}" >"${tmp_file}"
|
|
80
|
+
{
|
|
81
|
+
printf 'MIRROR_KEY=%s\n' "${mirror_key}"
|
|
82
|
+
printf 'JSON_KIND=%s\n' "${expected_kind}"
|
|
83
|
+
printf 'SOURCE=%s\n' "${source_mode}"
|
|
84
|
+
printf 'UPDATED_AT=%s\n' "${updated_at}"
|
|
85
|
+
} >"${meta_tmp_file}"
|
|
86
|
+
mv "${tmp_file}" "${mirror_file}"
|
|
87
|
+
mv "${meta_tmp_file}" "${meta_file}"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
heartbeat_read_json_mirror() {
|
|
91
|
+
local mirror_key="${1:?mirror key required}"
|
|
92
|
+
local expected_kind="${2:?expected kind required}"
|
|
93
|
+
local default_json="${3:-}"
|
|
94
|
+
local mirror_file=""
|
|
95
|
+
local payload=""
|
|
96
|
+
|
|
97
|
+
mirror_file="$(heartbeat_github_mirror_file "${mirror_key}" 2>/dev/null || true)"
|
|
98
|
+
if [[ -n "${mirror_file}" && -f "${mirror_file}" ]]; then
|
|
99
|
+
payload="$(cat "${mirror_file}" 2>/dev/null || true)"
|
|
100
|
+
if heartbeat_json_matches_kind "${payload}" "${expected_kind}"; then
|
|
101
|
+
printf '%s\n' "${payload}"
|
|
102
|
+
return 0
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
printf '%s\n' "${default_json}"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
heartbeat_cached_json_with_local_mirror() {
|
|
110
|
+
local cache_file="${1:?cache file required}"
|
|
111
|
+
local mirror_key="${2:?mirror key required}"
|
|
112
|
+
local expected_kind="${3:?expected kind required}"
|
|
113
|
+
local default_json="${4:-}"
|
|
114
|
+
local live_fetch_fn="${5:?live fetch function required}"
|
|
115
|
+
local payload=""
|
|
116
|
+
|
|
117
|
+
shift 5
|
|
118
|
+
|
|
119
|
+
if [[ -f "${cache_file}" ]]; then
|
|
120
|
+
cat "${cache_file}"
|
|
121
|
+
return 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if payload="$("${live_fetch_fn}" "$@" 2>/dev/null)" && heartbeat_json_matches_kind "${payload}" "${expected_kind}"; then
|
|
125
|
+
heartbeat_write_json_mirror "${mirror_key}" "${payload}" "${expected_kind}" "live" || true
|
|
126
|
+
else
|
|
127
|
+
payload="$(heartbeat_read_json_mirror "${mirror_key}" "${expected_kind}" "${default_json}")"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
printf '%s' "${payload}" >"${cache_file}"
|
|
131
|
+
printf '%s\n' "${payload}"
|
|
132
|
+
}
|
|
133
|
+
|
|
39
134
|
heartbeat_cached_issue_list_json() {
|
|
40
135
|
mkdir -p "${HEARTBEAT_SNAPSHOT_CACHE_DIR}"
|
|
41
136
|
local cache_file="${HEARTBEAT_SNAPSHOT_CACHE_DIR}/issues.json"
|
|
42
137
|
if [[ ! -f "${cache_file}" ]]; then
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
138
|
+
heartbeat_cached_json_with_local_mirror \
|
|
139
|
+
"${cache_file}" \
|
|
140
|
+
"issues-open-100" \
|
|
141
|
+
"array" \
|
|
142
|
+
'[]' \
|
|
143
|
+
flow_github_issue_list_json_live \
|
|
144
|
+
"$REPO_SLUG" \
|
|
145
|
+
open \
|
|
146
|
+
100
|
|
147
|
+
return 0
|
|
46
148
|
fi
|
|
47
149
|
cat "${cache_file}"
|
|
48
150
|
}
|
|
@@ -51,9 +153,16 @@ heartbeat_cached_pr_list_json() {
|
|
|
51
153
|
mkdir -p "${HEARTBEAT_SNAPSHOT_CACHE_DIR}"
|
|
52
154
|
local cache_file="${HEARTBEAT_SNAPSHOT_CACHE_DIR}/prs.json"
|
|
53
155
|
if [[ ! -f "${cache_file}" ]]; then
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
156
|
+
heartbeat_cached_json_with_local_mirror \
|
|
157
|
+
"${cache_file}" \
|
|
158
|
+
"prs-open-100" \
|
|
159
|
+
"array" \
|
|
160
|
+
'[]' \
|
|
161
|
+
flow_github_pr_list_json_live \
|
|
162
|
+
"$REPO_SLUG" \
|
|
163
|
+
open \
|
|
164
|
+
100
|
|
165
|
+
return 0
|
|
57
166
|
fi
|
|
58
167
|
cat "${cache_file}"
|
|
59
168
|
}
|
|
@@ -111,7 +220,6 @@ heartbeat_retry_reason_is_baseline_blocked() {
|
|
|
111
220
|
heartbeat_issue_json_cached() {
|
|
112
221
|
local issue_id="${1:?issue id required}"
|
|
113
222
|
local cache_file=""
|
|
114
|
-
local issue_json=""
|
|
115
223
|
|
|
116
224
|
if [[ ! -d "${HEARTBEAT_ISSUE_JSON_CACHE_DIR}" ]]; then
|
|
117
225
|
mkdir -p "${HEARTBEAT_ISSUE_JSON_CACHE_DIR}"
|
|
@@ -123,9 +231,14 @@ heartbeat_issue_json_cached() {
|
|
|
123
231
|
return 0
|
|
124
232
|
fi
|
|
125
233
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
234
|
+
heartbeat_cached_json_with_local_mirror \
|
|
235
|
+
"${cache_file}" \
|
|
236
|
+
"issue-${issue_id}" \
|
|
237
|
+
"object" \
|
|
238
|
+
'{}' \
|
|
239
|
+
flow_github_issue_view_json_live \
|
|
240
|
+
"$REPO_SLUG" \
|
|
241
|
+
"$issue_id"
|
|
129
242
|
}
|
|
130
243
|
|
|
131
244
|
heartbeat_open_agent_pr_issue_ids() {
|
|
@@ -483,9 +596,9 @@ heartbeat_mark_issue_running() {
|
|
|
483
596
|
local cached_json
|
|
484
597
|
cached_json="$(heartbeat_issue_json_cached "$issue_id" 2>/dev/null || true)"
|
|
485
598
|
if [[ "$is_heavy" == "yes" ]]; then
|
|
486
|
-
ACP_CACHED_ISSUE_JSON="${cached_json}" bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-
|
|
599
|
+
ACP_CACHED_ISSUE_JSON="${cached_json}" bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-blocked --add agent-running --add agent-e2e-heavy >/dev/null || true
|
|
487
600
|
else
|
|
488
|
-
ACP_CACHED_ISSUE_JSON="${cached_json}" bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-
|
|
601
|
+
ACP_CACHED_ISSUE_JSON="${cached_json}" bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "$REPO_SLUG" --number "$issue_id" --remove agent-blocked --add agent-running >/dev/null || true
|
|
489
602
|
fi
|
|
490
603
|
}
|
|
491
604
|
|
|
@@ -218,7 +218,7 @@ issue_publish_extra_args() {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
issue_remove_running() {
|
|
221
|
-
bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "${REPO_SLUG}" --number "$ISSUE_ID" --remove agent-
|
|
221
|
+
bash "${FLOW_TOOLS_DIR}/agent-github-update-labels" --repo-slug "${REPO_SLUG}" --number "$ISSUE_ID" --remove agent-running --remove agent-blocked >/dev/null || true
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
issue_mark_blocked() {
|
|
@@ -75,7 +75,7 @@ pr_cleanup_linked_issue_session() {
|
|
|
75
75
|
|
|
76
76
|
local should_close
|
|
77
77
|
should_close="$(pr_linked_issue_should_close "$issue_id")"
|
|
78
|
-
update_args=(--remove agent-
|
|
78
|
+
update_args=(--remove agent-running --remove agent-blocked --remove agent-e2e-heavy --remove agent-automerge --remove agent-exclusive)
|
|
79
79
|
pr_best_effort_update_labels --repo-slug "${REPO_SLUG}" --number "$issue_id" "${update_args[@]}"
|
|
80
80
|
|
|
81
81
|
local issue_session="${ISSUE_SESSION_PREFIX}${issue_id}"
|