@jiggai/recipes 0.3.6 → 0.3.8
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/index.ts +14 -0
- package/package.json +1 -1
- package/recipes/default/business-team.md +15 -0
- package/recipes/default/clinic-team.md +15 -0
- package/recipes/default/construction-team.md +15 -0
- package/recipes/default/crypto-trader-team.md +15 -0
- package/recipes/default/financial-planner-team.md +15 -0
- package/recipes/default/law-firm-team.md +15 -0
- package/recipes/default/swarm-orchestrator.md +485 -0
- package/src/handlers/scaffold.ts +43 -4
- package/src/handlers/tickets.ts +56 -0
package/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "./src/handlers/install";
|
|
27
27
|
import {
|
|
28
28
|
handleAssign,
|
|
29
|
+
handleCleanupClosedAssignments,
|
|
29
30
|
handleDispatch,
|
|
30
31
|
handleHandoff,
|
|
31
32
|
handleMoveTicket,
|
|
@@ -482,6 +483,19 @@ const recipesPlugin = {
|
|
|
482
483
|
console.log(JSON.stringify({ ok: true, moved: { from: res.from, to: res.to } }, null, 2));
|
|
483
484
|
});
|
|
484
485
|
|
|
486
|
+
cmd
|
|
487
|
+
.command("cleanup-closed-assignments")
|
|
488
|
+
.description("Archive assignment stubs for tickets already in work/done (prevents done work resurfacing)")
|
|
489
|
+
.requiredOption("--team-id <teamId>", "Team id")
|
|
490
|
+
.option("--ticket <ticketNums...>", "Optional ticket numbers to target (e.g. 0050 0064)")
|
|
491
|
+
.action(async (options: { teamId?: string; ticket?: string[] }) => {
|
|
492
|
+
if (!options.teamId) throw new Error("--team-id is required");
|
|
493
|
+
const res = await handleCleanupClosedAssignments(api, {
|
|
494
|
+
teamId: options.teamId,
|
|
495
|
+
ticketNums: options.ticket,
|
|
496
|
+
});
|
|
497
|
+
console.log(JSON.stringify(res, null, 2));
|
|
498
|
+
});
|
|
485
499
|
|
|
486
500
|
cmd
|
|
487
501
|
.command("assign")
|
package/package.json
CHANGED
|
@@ -59,6 +59,21 @@ agents:
|
|
|
59
59
|
deny: ["exec"]
|
|
60
60
|
|
|
61
61
|
templates:
|
|
62
|
+
tools: |
|
|
63
|
+
# TOOLS.md
|
|
64
|
+
|
|
65
|
+
# Agent-local notes (paths, conventions, env quirks).
|
|
66
|
+
|
|
67
|
+
status: |
|
|
68
|
+
# STATUS.md
|
|
69
|
+
|
|
70
|
+
- (empty)
|
|
71
|
+
|
|
72
|
+
notes: |
|
|
73
|
+
# NOTES.md
|
|
74
|
+
|
|
75
|
+
- (empty)
|
|
76
|
+
|
|
62
77
|
lead.soul: |
|
|
63
78
|
# SOUL.md
|
|
64
79
|
|
|
@@ -59,6 +59,21 @@ agents:
|
|
|
59
59
|
deny: ["exec"]
|
|
60
60
|
|
|
61
61
|
templates:
|
|
62
|
+
tools: |
|
|
63
|
+
# TOOLS.md
|
|
64
|
+
|
|
65
|
+
# Agent-local notes (paths, conventions, env quirks).
|
|
66
|
+
|
|
67
|
+
status: |
|
|
68
|
+
# STATUS.md
|
|
69
|
+
|
|
70
|
+
- (empty)
|
|
71
|
+
|
|
72
|
+
notes: |
|
|
73
|
+
# NOTES.md
|
|
74
|
+
|
|
75
|
+
- (empty)
|
|
76
|
+
|
|
62
77
|
lead.soul: |
|
|
63
78
|
# SOUL.md
|
|
64
79
|
|
|
@@ -59,6 +59,21 @@ agents:
|
|
|
59
59
|
deny: ["exec"]
|
|
60
60
|
|
|
61
61
|
templates:
|
|
62
|
+
tools: |
|
|
63
|
+
# TOOLS.md
|
|
64
|
+
|
|
65
|
+
# Agent-local notes (paths, conventions, env quirks).
|
|
66
|
+
|
|
67
|
+
status: |
|
|
68
|
+
# STATUS.md
|
|
69
|
+
|
|
70
|
+
- (empty)
|
|
71
|
+
|
|
72
|
+
notes: |
|
|
73
|
+
# NOTES.md
|
|
74
|
+
|
|
75
|
+
- (empty)
|
|
76
|
+
|
|
62
77
|
lead.soul: |
|
|
63
78
|
# SOUL.md
|
|
64
79
|
|
|
@@ -59,6 +59,21 @@ agents:
|
|
|
59
59
|
deny: ["exec"]
|
|
60
60
|
|
|
61
61
|
templates:
|
|
62
|
+
tools: |
|
|
63
|
+
# TOOLS.md
|
|
64
|
+
|
|
65
|
+
# Agent-local notes (paths, conventions, env quirks).
|
|
66
|
+
|
|
67
|
+
status: |
|
|
68
|
+
# STATUS.md
|
|
69
|
+
|
|
70
|
+
- (empty)
|
|
71
|
+
|
|
72
|
+
notes: |
|
|
73
|
+
# NOTES.md
|
|
74
|
+
|
|
75
|
+
- (empty)
|
|
76
|
+
|
|
62
77
|
lead.soul: |
|
|
63
78
|
# SOUL.md
|
|
64
79
|
|
|
@@ -59,6 +59,21 @@ agents:
|
|
|
59
59
|
deny: ["exec"]
|
|
60
60
|
|
|
61
61
|
templates:
|
|
62
|
+
tools: |
|
|
63
|
+
# TOOLS.md
|
|
64
|
+
|
|
65
|
+
# Agent-local notes (paths, conventions, env quirks).
|
|
66
|
+
|
|
67
|
+
status: |
|
|
68
|
+
# STATUS.md
|
|
69
|
+
|
|
70
|
+
- (empty)
|
|
71
|
+
|
|
72
|
+
notes: |
|
|
73
|
+
# NOTES.md
|
|
74
|
+
|
|
75
|
+
- (empty)
|
|
76
|
+
|
|
62
77
|
lead.soul: |
|
|
63
78
|
# SOUL.md
|
|
64
79
|
|
|
@@ -59,6 +59,21 @@ agents:
|
|
|
59
59
|
deny: ["exec"]
|
|
60
60
|
|
|
61
61
|
templates:
|
|
62
|
+
tools: |
|
|
63
|
+
# TOOLS.md
|
|
64
|
+
|
|
65
|
+
# Agent-local notes (paths, conventions, env quirks).
|
|
66
|
+
|
|
67
|
+
status: |
|
|
68
|
+
# STATUS.md
|
|
69
|
+
|
|
70
|
+
- (empty)
|
|
71
|
+
|
|
72
|
+
notes: |
|
|
73
|
+
# NOTES.md
|
|
74
|
+
|
|
75
|
+
- (empty)
|
|
76
|
+
|
|
62
77
|
lead.soul: |
|
|
63
78
|
# SOUL.md
|
|
64
79
|
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: swarm-orchestrator
|
|
3
|
+
name: Swarm Orchestrator
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
description: Scaffold an OpenClaw “orchestrator” workspace that spawns coding agents in tmux + git worktrees and monitors them via a lightweight task registry.
|
|
6
|
+
kind: agent
|
|
7
|
+
requiredSkills: []
|
|
8
|
+
cronJobs:
|
|
9
|
+
- id: swarm-monitor-loop
|
|
10
|
+
name: "Swarm monitor loop"
|
|
11
|
+
schedule: "*/10 * * * *"
|
|
12
|
+
timezone: "America/New_York"
|
|
13
|
+
message: "Reminder: swarm monitor loop — run .clawdbot/check-agents.sh to detect stuck/failed tmux agents, PR/CI state, and decide whether to notify or retry. Update .clawdbot/active-tasks.json as needed."
|
|
14
|
+
enabledByDefault: false
|
|
15
|
+
- id: swarm-cleanup-loop
|
|
16
|
+
name: "Swarm cleanup loop"
|
|
17
|
+
schedule: "17 4 * * *"
|
|
18
|
+
timezone: "America/New_York"
|
|
19
|
+
message: "Reminder: swarm cleanup loop — consider running .clawdbot/cleanup.sh to prune completed worktrees, closed PR branches, and dead tmux sessions (safe-by-default; no deletes unless explicitly enabled)."
|
|
20
|
+
enabledByDefault: false
|
|
21
|
+
|
|
22
|
+
templates:
|
|
23
|
+
soul: |
|
|
24
|
+
# SOUL.md
|
|
25
|
+
|
|
26
|
+
You are an orchestration agent (“swarm orchestrator”).
|
|
27
|
+
|
|
28
|
+
You do NOT primarily write code.
|
|
29
|
+
|
|
30
|
+
Your job is to:
|
|
31
|
+
- translate business context into sharp prompts + constraints
|
|
32
|
+
- spawn focused coding agents into isolated git worktrees + tmux sessions
|
|
33
|
+
- monitor them and steer when needed
|
|
34
|
+
- only notify a human when a PR is truly ready for review
|
|
35
|
+
|
|
36
|
+
Guardrails:
|
|
37
|
+
- Keep changes small and PR-shaped.
|
|
38
|
+
- Don’t delete worktrees/branches unless the user explicitly opts in.
|
|
39
|
+
- Always use the prompt template in `.clawdbot/PROMPT_TEMPLATE.md`.
|
|
40
|
+
|
|
41
|
+
agents: |
|
|
42
|
+
# AGENTS.md
|
|
43
|
+
|
|
44
|
+
## Operating loop
|
|
45
|
+
|
|
46
|
+
1) Read `.clawdbot/active-tasks.json`.
|
|
47
|
+
2) For each task:
|
|
48
|
+
- confirm tmux session is alive
|
|
49
|
+
- confirm branch/worktree exists
|
|
50
|
+
- if configured, check PR/CI status
|
|
51
|
+
3) Only ping the human when:
|
|
52
|
+
- a task is blocked and needs a decision
|
|
53
|
+
- a PR meets the default Definition of Done
|
|
54
|
+
|
|
55
|
+
## Key files
|
|
56
|
+
|
|
57
|
+
- `.clawdbot/README.md` — setup + how to use
|
|
58
|
+
- `.clawdbot/CONVENTIONS.md` — default naming + how to change
|
|
59
|
+
- `.clawdbot/PROMPT_TEMPLATE.md` — required spawn prompt template
|
|
60
|
+
- `.clawdbot/TEMPLATE.md` — copy/paste helper for new tasks
|
|
61
|
+
- `.clawdbot/env.sh` — portable env configuration
|
|
62
|
+
- `.clawdbot/active-tasks.json` — task registry
|
|
63
|
+
- `.clawdbot/spawn.sh` — create worktree + start tmux session
|
|
64
|
+
- `.clawdbot/check-agents.sh` — monitor loop (token-efficient)
|
|
65
|
+
- `.clawdbot/cleanup.sh` — safe-by-default cleanup scaffold
|
|
66
|
+
|
|
67
|
+
readme: |
|
|
68
|
+
# Swarm Orchestrator
|
|
69
|
+
|
|
70
|
+
This scaffold gives you a lightweight “swarm” workflow:
|
|
71
|
+
|
|
72
|
+
- each coding agent runs in its own **git worktree** + **branch**
|
|
73
|
+
- each agent runs in its own **tmux session** (attach + steer mid-flight)
|
|
74
|
+
- a simple JSON registry (`active-tasks.json`) makes monitoring deterministic
|
|
75
|
+
|
|
76
|
+
The orchestrator’s job is to:
|
|
77
|
+
1) translate a request into a tight prompt + constraints
|
|
78
|
+
2) spawn 1+ coding agents in parallel
|
|
79
|
+
3) monitor progress until a PR is truly ready for review
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 0) Prerequisites
|
|
84
|
+
|
|
85
|
+
Required:
|
|
86
|
+
- `git`
|
|
87
|
+
- `tmux`
|
|
88
|
+
- `jq`
|
|
89
|
+
|
|
90
|
+
Optional (recommended):
|
|
91
|
+
- GitHub CLI `gh` (for PR + CI status checks)
|
|
92
|
+
- run `gh auth login` to authenticate
|
|
93
|
+
|
|
94
|
+
Quick check:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
command -v git >/dev/null || echo "missing: git"
|
|
98
|
+
command -v tmux >/dev/null || echo "missing: tmux"
|
|
99
|
+
command -v jq >/dev/null || echo "missing: jq"
|
|
100
|
+
|
|
101
|
+
# optional:
|
|
102
|
+
command -v gh >/dev/null || echo "optional missing: gh (PR/CI monitoring)"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 1) One-time setup
|
|
108
|
+
|
|
109
|
+
### 1.1 Make scripts executable (manual)
|
|
110
|
+
|
|
111
|
+
This scaffold does **not** change file permissions automatically.
|
|
112
|
+
|
|
113
|
+
Run:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
chmod +x .clawdbot/*.sh
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 1.2 Configure environment
|
|
120
|
+
|
|
121
|
+
Edit: `.clawdbot/env.sh`
|
|
122
|
+
|
|
123
|
+
Set:
|
|
124
|
+
- `SWARM_REPO_DIR` — absolute path to the repo you want agents to work on
|
|
125
|
+
- `SWARM_WORKTREE_ROOT` — absolute path where worktrees will be created
|
|
126
|
+
- recommended: a dedicated folder (NOT inside your repo folder, and NOT inside the OpenClaw workspace)
|
|
127
|
+
- `SWARM_BASE_REF` — base ref to branch from (default: `origin/main`)
|
|
128
|
+
|
|
129
|
+
Optional:
|
|
130
|
+
- `SWARM_AGENT_RUNNER` — wrapper to start your chosen coding agent CLI (Codex / Claude Code / etc.)
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 2) Conventions (defaults)
|
|
135
|
+
|
|
136
|
+
Default conventions are in:
|
|
137
|
+
- `.clawdbot/CONVENTIONS.md`
|
|
138
|
+
|
|
139
|
+
If you want to customize naming or the Definition of Done, change it there.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 3) Spawning agents
|
|
144
|
+
|
|
145
|
+
Basic spawn:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
./.clawdbot/spawn.sh <branch-slug> <codex|claude> <tmux-session> [model] [reasoning]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
./.clawdbot/spawn.sh feat/0082-attempt-a codex swarm-0082-a gpt-5.3-codex high
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Attach:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
tmux attach -t swarm-0082-a
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Spawning more than one agent = run `spawn.sh` multiple times with different branch slugs + tmux session names.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 4) Task registry
|
|
168
|
+
|
|
169
|
+
File: `.clawdbot/active-tasks.json`
|
|
170
|
+
|
|
171
|
+
Keep it accurate. Monitoring should read the registry, not guess.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 5) Monitoring
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
./.clawdbot/check-agents.sh
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This is intentionally simple and deterministic. Extend it to include PR/CI checks if desired.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 6) Default Definition of Done (notify-ready)
|
|
186
|
+
|
|
187
|
+
A PR is **ready for human review** when:
|
|
188
|
+
- PR exists
|
|
189
|
+
- branch is mergeable and up to date with base
|
|
190
|
+
- CI is green (lint/types/tests as appropriate)
|
|
191
|
+
- if the PR includes **UI changes**, include screenshots in the PR description
|
|
192
|
+
- a human still performs the final review + merge decision (default gate)
|
|
193
|
+
|
|
194
|
+
conventions: |
|
|
195
|
+
# Swarm Conventions (Defaults)
|
|
196
|
+
|
|
197
|
+
These defaults are designed to be portable across users and repos.
|
|
198
|
+
|
|
199
|
+
## Naming
|
|
200
|
+
|
|
201
|
+
### Branch naming
|
|
202
|
+
|
|
203
|
+
Recommended:
|
|
204
|
+
- `feat/<ticket>-<slug>`
|
|
205
|
+
- `fix/<ticket>-<slug>`
|
|
206
|
+
|
|
207
|
+
Examples:
|
|
208
|
+
- `feat/0082-swarm-orchestrator`
|
|
209
|
+
- `fix/0141-login-redirect`
|
|
210
|
+
|
|
211
|
+
If you do not use tickets:
|
|
212
|
+
- `feat/<slug>` / `fix/<slug>` is fine.
|
|
213
|
+
|
|
214
|
+
### tmux session naming
|
|
215
|
+
|
|
216
|
+
Recommended:
|
|
217
|
+
- `swarm-<ticket>-<suffix>`
|
|
218
|
+
|
|
219
|
+
Examples:
|
|
220
|
+
- `swarm-0082-a`
|
|
221
|
+
- `swarm-0082-b`
|
|
222
|
+
|
|
223
|
+
## Directory layout
|
|
224
|
+
|
|
225
|
+
- Orchestrator workspace: OpenClaw agent workspace (scaffold output)
|
|
226
|
+
- Worktrees root: set via `SWARM_WORKTREE_ROOT` (recommended: dedicated folder outside repo + outside OpenClaw workspace)
|
|
227
|
+
|
|
228
|
+
## Definition of Done (default)
|
|
229
|
+
|
|
230
|
+
A PR is ready for human review when:
|
|
231
|
+
- PR exists
|
|
232
|
+
- mergeable + up-to-date
|
|
233
|
+
- CI green
|
|
234
|
+
- screenshots included *only if UI changes*
|
|
235
|
+
- human gate remains (default)
|
|
236
|
+
|
|
237
|
+
promptTemplate: |
|
|
238
|
+
# Swarm Coding-Agent Prompt Template
|
|
239
|
+
|
|
240
|
+
Copy this template when spawning a coding agent.
|
|
241
|
+
|
|
242
|
+
IMPORTANT:
|
|
243
|
+
- Do not freestyle prompts.
|
|
244
|
+
- Keep scope tight.
|
|
245
|
+
- Optimize for a small, reviewable PR.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Ticket / Goal
|
|
250
|
+
|
|
251
|
+
- Ticket (optional): <NNNN>
|
|
252
|
+
- Goal: <one sentence>
|
|
253
|
+
|
|
254
|
+
## Context
|
|
255
|
+
|
|
256
|
+
<why this matters / user story / product intent>
|
|
257
|
+
|
|
258
|
+
## Requirements
|
|
259
|
+
|
|
260
|
+
- <requirement 1>
|
|
261
|
+
- <requirement 2>
|
|
262
|
+
|
|
263
|
+
## Constraints (very important)
|
|
264
|
+
|
|
265
|
+
- Keep changes small and PR-shaped.
|
|
266
|
+
- Avoid refactors unless required to meet the requirements.
|
|
267
|
+
- If you need clarification, STOP and ask.
|
|
268
|
+
- If you touch UI, screenshots are required (otherwise omit screenshots).
|
|
269
|
+
|
|
270
|
+
## Definition of Done (default)
|
|
271
|
+
|
|
272
|
+
- Code compiles/builds.
|
|
273
|
+
- CI is green (lint/types/tests as appropriate for this repo).
|
|
274
|
+
- PR is opened with a clear description.
|
|
275
|
+
- If UI changed: include before/after screenshots in the PR description.
|
|
276
|
+
- Do NOT merge. Leave it for human review.
|
|
277
|
+
|
|
278
|
+
## File / Area hints (optional)
|
|
279
|
+
|
|
280
|
+
- Focus files:
|
|
281
|
+
- <path>
|
|
282
|
+
- <path>
|
|
283
|
+
|
|
284
|
+
## Suggested plan
|
|
285
|
+
|
|
286
|
+
1) <step>
|
|
287
|
+
2) <step>
|
|
288
|
+
|
|
289
|
+
## Deliverables
|
|
290
|
+
|
|
291
|
+
- A PR implementing the requirements.
|
|
292
|
+
- Notes in the PR description: what changed + how to verify.
|
|
293
|
+
|
|
294
|
+
taskTemplate: |
|
|
295
|
+
# Swarm Task Template
|
|
296
|
+
|
|
297
|
+
Use this as a starting point for a new entry in `active-tasks.json`.
|
|
298
|
+
|
|
299
|
+
- Decide the branch name using `CONVENTIONS.md`.
|
|
300
|
+
- Decide a unique tmux session name.
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"id": "0082-attempt-a",
|
|
305
|
+
"ticket": "0082",
|
|
306
|
+
"description": "<short description>",
|
|
307
|
+
"branch": "feat/0082-attempt-a",
|
|
308
|
+
"worktree": "feat/0082-attempt-a",
|
|
309
|
+
"tmuxSession": "swarm-0082-a",
|
|
310
|
+
"agent": "codex",
|
|
311
|
+
"model": "gpt-5.3-codex",
|
|
312
|
+
"startedAt": 0,
|
|
313
|
+
"status": "queued",
|
|
314
|
+
"notifyOnComplete": true,
|
|
315
|
+
"prUrl": null,
|
|
316
|
+
"checks": {}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
env: |
|
|
321
|
+
# .clawdbot/env.sh
|
|
322
|
+
#
|
|
323
|
+
# Configure these for your environment.
|
|
324
|
+
|
|
325
|
+
# Absolute path to the repo you want to operate on.
|
|
326
|
+
export SWARM_REPO_DIR=""
|
|
327
|
+
|
|
328
|
+
# Absolute path where worktrees will be created.
|
|
329
|
+
# Recommended: a dedicated folder (NOT inside the repo folder, NOT inside the OpenClaw workspace).
|
|
330
|
+
export SWARM_WORKTREE_ROOT=""
|
|
331
|
+
|
|
332
|
+
# Default base ref to branch from.
|
|
333
|
+
export SWARM_BASE_REF="origin/main"
|
|
334
|
+
|
|
335
|
+
# Optional: path to your agent runner wrapper.
|
|
336
|
+
# This script/command should start Codex/Claude Code/etc inside the worktree.
|
|
337
|
+
export SWARM_AGENT_RUNNER=""
|
|
338
|
+
|
|
339
|
+
activeTasks: |
|
|
340
|
+
[
|
|
341
|
+
{
|
|
342
|
+
"id": "example-task",
|
|
343
|
+
"ticket": "",
|
|
344
|
+
"description": "Replace me",
|
|
345
|
+
"repo": "",
|
|
346
|
+
"worktree": "",
|
|
347
|
+
"branch": "",
|
|
348
|
+
"tmuxSession": "",
|
|
349
|
+
"agent": "codex",
|
|
350
|
+
"model": "",
|
|
351
|
+
"startedAt": 0,
|
|
352
|
+
"status": "queued",
|
|
353
|
+
"notifyOnComplete": true,
|
|
354
|
+
"prUrl": null,
|
|
355
|
+
"checks": {}
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
spawn: |
|
|
360
|
+
#!/usr/bin/env bash
|
|
361
|
+
set -euo pipefail
|
|
362
|
+
|
|
363
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
364
|
+
# shellcheck disable=SC1091
|
|
365
|
+
source "$HERE/env.sh"
|
|
366
|
+
|
|
367
|
+
if [[ -z "${SWARM_REPO_DIR:-}" || -z "${SWARM_WORKTREE_ROOT:-}" || -z "${SWARM_BASE_REF:-}" ]]; then
|
|
368
|
+
echo "Missing env. Edit $HERE/env.sh (SWARM_REPO_DIR, SWARM_WORKTREE_ROOT, SWARM_BASE_REF)." >&2
|
|
369
|
+
exit 2
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
BRANCH_SLUG="${1:-}"
|
|
373
|
+
AGENT_KIND="${2:-codex}" # codex|claude
|
|
374
|
+
TMUX_SESSION="${3:-}"
|
|
375
|
+
MODEL="${4:-}"
|
|
376
|
+
REASONING="${5:-medium}"
|
|
377
|
+
|
|
378
|
+
if [[ -z "$BRANCH_SLUG" || -z "$TMUX_SESSION" ]]; then
|
|
379
|
+
echo "Usage: $0 <branch-slug> <codex|claude> <tmux-session> [model] [reasoning]" >&2
|
|
380
|
+
exit 2
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
WORKTREE_DIR="$SWARM_WORKTREE_ROOT/$BRANCH_SLUG"
|
|
384
|
+
|
|
385
|
+
echo "[swarm] Creating worktree: $WORKTREE_DIR"
|
|
386
|
+
mkdir -p "$SWARM_WORKTREE_ROOT"
|
|
387
|
+
cd "$SWARM_REPO_DIR"
|
|
388
|
+
|
|
389
|
+
git worktree add "$WORKTREE_DIR" -b "$BRANCH_SLUG" "$SWARM_BASE_REF"
|
|
390
|
+
|
|
391
|
+
echo "[swarm] Starting tmux session: $TMUX_SESSION"
|
|
392
|
+
if [[ -z "${SWARM_AGENT_RUNNER:-}" ]]; then
|
|
393
|
+
echo "SWARM_AGENT_RUNNER not set. Starting a shell in tmux; run your agent CLI manually." >&2
|
|
394
|
+
tmux new-session -d -s "$TMUX_SESSION" -c "$WORKTREE_DIR" "bash"
|
|
395
|
+
else
|
|
396
|
+
tmux new-session -d -s "$TMUX_SESSION" -c "$WORKTREE_DIR" \
|
|
397
|
+
"$SWARM_AGENT_RUNNER $AGENT_KIND ${MODEL:-} ${REASONING:-medium}"
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
echo "[swarm] Done. Attach with: tmux attach -t $TMUX_SESSION"
|
|
401
|
+
|
|
402
|
+
checkAgents: |
|
|
403
|
+
#!/usr/bin/env bash
|
|
404
|
+
set -euo pipefail
|
|
405
|
+
|
|
406
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
407
|
+
REG="$HERE/active-tasks.json"
|
|
408
|
+
|
|
409
|
+
if [[ ! -f "$REG" ]]; then
|
|
410
|
+
echo "Missing $REG" >&2
|
|
411
|
+
exit 2
|
|
412
|
+
fi
|
|
413
|
+
|
|
414
|
+
echo "[swarm] Checking tmux sessions listed in active-tasks.json ..."
|
|
415
|
+
|
|
416
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
417
|
+
echo "jq is required" >&2
|
|
418
|
+
exit 2
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
mapfile -t sessions < <(jq -r '.[].tmuxSession // empty' "$REG" | sort -u)
|
|
422
|
+
if [[ ${#sessions[@]} -eq 0 ]]; then
|
|
423
|
+
echo "[swarm] No tmux sessions found in registry."
|
|
424
|
+
exit 0
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
for s in "${sessions[@]}"; do
|
|
428
|
+
if tmux has-session -t "$s" 2>/dev/null; then
|
|
429
|
+
echo "[ok] tmux session alive: $s"
|
|
430
|
+
else
|
|
431
|
+
echo "[dead] tmux session missing: $s"
|
|
432
|
+
fi
|
|
433
|
+
done
|
|
434
|
+
|
|
435
|
+
cleanup: |
|
|
436
|
+
#!/usr/bin/env bash
|
|
437
|
+
set -euo pipefail
|
|
438
|
+
|
|
439
|
+
echo "[swarm] Cleanup scaffold (safe-by-default)."
|
|
440
|
+
echo "- This script currently does NOT delete anything automatically."
|
|
441
|
+
echo "- Extend it to prune worktrees only after PRs are merged and branches are removed."
|
|
442
|
+
|
|
443
|
+
files:
|
|
444
|
+
- path: SOUL.md
|
|
445
|
+
template: soul
|
|
446
|
+
mode: createOnly
|
|
447
|
+
- path: AGENTS.md
|
|
448
|
+
template: agents
|
|
449
|
+
mode: createOnly
|
|
450
|
+
- path: .clawdbot/README.md
|
|
451
|
+
template: readme
|
|
452
|
+
mode: createOnly
|
|
453
|
+
- path: .clawdbot/CONVENTIONS.md
|
|
454
|
+
template: conventions
|
|
455
|
+
mode: createOnly
|
|
456
|
+
- path: .clawdbot/PROMPT_TEMPLATE.md
|
|
457
|
+
template: promptTemplate
|
|
458
|
+
mode: createOnly
|
|
459
|
+
- path: .clawdbot/TEMPLATE.md
|
|
460
|
+
template: taskTemplate
|
|
461
|
+
mode: createOnly
|
|
462
|
+
- path: .clawdbot/env.sh
|
|
463
|
+
template: env
|
|
464
|
+
mode: createOnly
|
|
465
|
+
- path: .clawdbot/active-tasks.json
|
|
466
|
+
template: activeTasks
|
|
467
|
+
mode: createOnly
|
|
468
|
+
- path: .clawdbot/spawn.sh
|
|
469
|
+
template: spawn
|
|
470
|
+
mode: createOnly
|
|
471
|
+
- path: .clawdbot/check-agents.sh
|
|
472
|
+
template: checkAgents
|
|
473
|
+
mode: createOnly
|
|
474
|
+
- path: .clawdbot/cleanup.sh
|
|
475
|
+
template: cleanup
|
|
476
|
+
mode: createOnly
|
|
477
|
+
|
|
478
|
+
tools:
|
|
479
|
+
profile: "coding"
|
|
480
|
+
allow: ["group:fs", "group:web", "group:runtime", "group:automation", "cron", "message"]
|
|
481
|
+
deny: []
|
|
482
|
+
---
|
|
483
|
+
# Swarm Orchestrator
|
|
484
|
+
|
|
485
|
+
This is a workflow scaffold recipe. It creates a portable, file-first setup for running multiple coding agents in parallel using git worktrees + tmux.
|
package/src/handlers/scaffold.ts
CHANGED
|
@@ -34,17 +34,56 @@ export async function scaffoldAgentFromRecipe(
|
|
|
34
34
|
) {
|
|
35
35
|
await ensureDir(opts.filesRootDir);
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
type RecipeFileMode = "createOnly" | "overwrite";
|
|
38
|
+
type RecipeFileSpec = {
|
|
39
|
+
path: string;
|
|
40
|
+
template: string;
|
|
41
|
+
mode?: RecipeFileMode;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function normalizeTemplates(input: unknown): Record<string, string> {
|
|
45
|
+
if (!input) return {};
|
|
46
|
+
if (typeof input !== "object") throw new Error("recipe.templates must be an object");
|
|
47
|
+
const out: Record<string, string> = {};
|
|
48
|
+
for (const [k, v] of Object.entries(input as Record<string, unknown>)) {
|
|
49
|
+
if (typeof v === "string") out[k] = v;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeFiles(input: unknown): RecipeFileSpec[] {
|
|
55
|
+
if (!input) return [];
|
|
56
|
+
if (!Array.isArray(input)) throw new Error("recipe.files must be an array");
|
|
57
|
+
|
|
58
|
+
return input.map((raw, idx) => {
|
|
59
|
+
if (!raw || typeof raw !== "object") throw new Error(`recipe.files[${idx}] must be an object`);
|
|
60
|
+
const o = raw as Record<string, unknown>;
|
|
61
|
+
const filePath = String(o.path ?? "").trim();
|
|
62
|
+
const template = String(o.template ?? "").trim();
|
|
63
|
+
const modeRaw = o.mode != null ? String(o.mode).trim() : "";
|
|
64
|
+
|
|
65
|
+
if (!filePath) throw new Error(`recipe.files[${idx}].path is required`);
|
|
66
|
+
if (!template) throw new Error(`recipe.files[${idx}].template is required`);
|
|
67
|
+
|
|
68
|
+
const mode: RecipeFileMode | undefined =
|
|
69
|
+
modeRaw === "createOnly" || modeRaw === "overwrite" ? (modeRaw as RecipeFileMode) : undefined;
|
|
70
|
+
if (modeRaw && !mode) throw new Error(`recipe.files[${idx}].mode must be createOnly|overwrite`);
|
|
71
|
+
|
|
72
|
+
return { path: filePath, template, mode };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const templates = normalizeTemplates(recipe.templates);
|
|
77
|
+
const files = normalizeFiles(recipe.files);
|
|
39
78
|
const vars = opts.vars ?? {};
|
|
40
79
|
|
|
41
80
|
const fileResults: Array<{ path: string; wrote: boolean; reason: string }> = [];
|
|
42
81
|
for (const f of files) {
|
|
43
82
|
const raw = templates[f.template];
|
|
44
|
-
if (typeof raw !== "string") throw new Error(`Missing template: ${f.template}`);
|
|
83
|
+
if (typeof raw !== "string") throw new Error(`Missing template: ${String(f.template)}`);
|
|
45
84
|
const rendered = renderTemplate(raw, vars);
|
|
46
85
|
const target = path.join(opts.filesRootDir, f.path);
|
|
47
|
-
const mode = opts.update ? (f.mode ?? "overwrite") : (f.mode ?? "createOnly");
|
|
86
|
+
const mode: RecipeFileMode = opts.update ? (f.mode ?? "overwrite") : (f.mode ?? "createOnly");
|
|
48
87
|
const r = await writeFileSafely(target, rendered, mode);
|
|
49
88
|
fileResults.push({ path: target, wrote: r.wrote, reason: r.reason });
|
|
50
89
|
}
|
package/src/handlers/tickets.ts
CHANGED
|
@@ -325,3 +325,59 @@ export async function handleDispatch(
|
|
|
325
325
|
}
|
|
326
326
|
return { ok: true as const, wrote: plan.files.map((f) => f.path), nudgeQueued };
|
|
327
327
|
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Cleanup assignment stubs for tickets that are already closed (in work/done).
|
|
331
|
+
*
|
|
332
|
+
* Why: some automation/board views treat assignment stubs as active work signals.
|
|
333
|
+
* If a ticket is manually moved to done (outside `openclaw recipes move-ticket`),
|
|
334
|
+
* its `work/assignments/<num>-assigned-*.md` stubs may linger and resurface the ticket.
|
|
335
|
+
*
|
|
336
|
+
* This command archives any matching assignment stubs into `work/assignments/archive/`.
|
|
337
|
+
*/
|
|
338
|
+
export async function handleCleanupClosedAssignments(
|
|
339
|
+
api: OpenClawPluginApi,
|
|
340
|
+
options: { teamId: string; ticketNums?: string[] }
|
|
341
|
+
): Promise<{ ok: true; teamId: string; archived: Array<{ from: string; to: string }> }> {
|
|
342
|
+
const teamId = String(options.teamId);
|
|
343
|
+
const { teamDir } = await resolveTeamContext(api, teamId);
|
|
344
|
+
|
|
345
|
+
const assignmentsDir = ticketStageDir(teamDir, "assignments");
|
|
346
|
+
const archiveDir = path.join(assignmentsDir, "archive");
|
|
347
|
+
const doneDir = ticketStageDir(teamDir, "done");
|
|
348
|
+
|
|
349
|
+
const archived: Array<{ from: string; to: string }> = [];
|
|
350
|
+
if (!(await fileExists(assignmentsDir))) return { ok: true, teamId, archived };
|
|
351
|
+
await ensureDir(archiveDir);
|
|
352
|
+
|
|
353
|
+
const ticketNumsFilter = Array.isArray(options.ticketNums) && options.ticketNums.length
|
|
354
|
+
? new Set(options.ticketNums.map((n) => String(n).padStart(4, "0")))
|
|
355
|
+
: null;
|
|
356
|
+
|
|
357
|
+
const doneFiles = (await fileExists(doneDir)) ? await fs.readdir(doneDir) : [];
|
|
358
|
+
const doneNums = new Set(
|
|
359
|
+
doneFiles
|
|
360
|
+
.map((f) => f.match(/^([0-9]{4})-/)?.[1])
|
|
361
|
+
.filter((x): x is string => !!x)
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const files = (await fs.readdir(assignmentsDir)).filter((f) => f.endsWith(".md"));
|
|
365
|
+
for (const f of files) {
|
|
366
|
+
if (f === "archive") continue;
|
|
367
|
+
if (f.startsWith("archive" + path.sep)) continue;
|
|
368
|
+
const m = f.match(/^([0-9]{4})-assigned-.*\.md$/);
|
|
369
|
+
if (!m) continue;
|
|
370
|
+
const num = m[1];
|
|
371
|
+
if (ticketNumsFilter && !ticketNumsFilter.has(num)) continue;
|
|
372
|
+
|
|
373
|
+
// If the ticket number is present in done/, this assignment is considered closed.
|
|
374
|
+
if (!doneNums.has(num)) continue;
|
|
375
|
+
|
|
376
|
+
const from = path.join(assignmentsDir, f);
|
|
377
|
+
const to = path.join(archiveDir, f);
|
|
378
|
+
await fs.rename(from, to);
|
|
379
|
+
archived.push({ from, to });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return { ok: true, teamId, archived };
|
|
383
|
+
}
|