@jiggai/recipes 0.4.24 → 0.4.26
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 +3 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/recipes/default/business-team.md +50 -4
- package/recipes/default/customer-support-team.md +34 -4
- package/recipes/default/development-team.md +47 -40
- package/recipes/default/marketing-team.md +27 -14
- package/recipes/default/product-team.md +42 -4
- package/recipes/default/research-team.md +34 -4
- package/recipes/default/social-team.md +106 -4
- package/recipes/default/writing-team.md +34 -4
- package/src/handlers/cron.ts +17 -7
- package/src/handlers/workflows.ts +2 -1
- package/src/lib/recipe-frontmatter.ts +4 -0
- package/src/lib/workflows/workflow-runner.ts +9 -0
- package/src/lib/workflows/workflow-worker.ts +50 -3
package/index.ts
CHANGED
|
@@ -654,10 +654,12 @@ const recipesPlugin = {
|
|
|
654
654
|
.description("Claim and execute a single queued workflow run (intended for cron-driven runner)")
|
|
655
655
|
.requiredOption("--team-id <teamId>", "Team id (workspace-<teamId>)")
|
|
656
656
|
.option("--lease-seconds <n>", "Lease duration in seconds", (v: string) => Number(v))
|
|
657
|
-
.
|
|
657
|
+
.option("--run-id <runId>", "Only claim this specific run id")
|
|
658
|
+
.action(async (options: { teamId?: string; leaseSeconds?: number; runId?: string }) => {
|
|
658
659
|
const res = await handleWorkflowsRunnerOnce(api, {
|
|
659
660
|
teamId: String(options.teamId ?? ""),
|
|
660
661
|
leaseSeconds: typeof options.leaseSeconds === "number" ? options.leaseSeconds : undefined,
|
|
662
|
+
runId: options.runId,
|
|
661
663
|
});
|
|
662
664
|
console.log(JSON.stringify(res, null, 2));
|
|
663
665
|
});
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jiggai/recipes",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.26",
|
|
4
4
|
"description": "ClawRecipes plugin for OpenClaw (markdown recipes -> scaffold agents/teams)",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"lint:fix": "eslint src/ index.ts --fix",
|
|
38
38
|
"smell-check": "node scripts/smell-check.mjs",
|
|
39
39
|
"jscpd": "jscpd src/ index.ts --min-lines 8 --min-tokens 50",
|
|
40
|
-
"prepare": "husky",
|
|
40
|
+
"prepare": "husky || true",
|
|
41
41
|
"check:plugin-version": "node scripts/check-openclaw-plugin-version.mjs",
|
|
42
42
|
"prepack": "npm run -s sync:plugin-version && npm run -s check:plugin-version",
|
|
43
43
|
"prepublishOnly": "npm run -s sync:plugin-version && npm run -s check:plugin-version",
|
|
@@ -9,13 +9,58 @@ cronJobs:
|
|
|
9
9
|
name: "Lead triage loop"
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
|
-
|
|
12
|
+
agentId: "{{teamId}}-lead"
|
|
13
|
+
timeoutSeconds: 1800
|
|
14
|
+
message: "Lead triage loop (Business Team): triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing."
|
|
15
|
+
enabledByDefault: false
|
|
16
|
+
|
|
17
|
+
- id: ops-work-loop
|
|
18
|
+
name: "Operations work loop (safe-idle)"
|
|
19
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
20
|
+
timezone: "America/New_York"
|
|
21
|
+
agentId: "{{teamId}}-ops"
|
|
22
|
+
timeoutSeconds: 1800
|
|
23
|
+
message: "Work loop: check for operations-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/ops/agent-outputs/."
|
|
24
|
+
enabledByDefault: false
|
|
25
|
+
- id: sales-work-loop
|
|
26
|
+
name: "Sales work loop (safe-idle)"
|
|
27
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
28
|
+
timezone: "America/New_York"
|
|
29
|
+
agentId: "{{teamId}}-sales"
|
|
30
|
+
timeoutSeconds: 1800
|
|
31
|
+
message: "Work loop: check for sales-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/sales/agent-outputs/."
|
|
32
|
+
enabledByDefault: false
|
|
33
|
+
- id: marketing-work-loop
|
|
34
|
+
name: "Marketing work loop (safe-idle)"
|
|
35
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
36
|
+
timezone: "America/New_York"
|
|
37
|
+
agentId: "{{teamId}}-marketing"
|
|
38
|
+
timeoutSeconds: 1800
|
|
39
|
+
message: "Work loop: check for marketing-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/marketing/agent-outputs/."
|
|
40
|
+
enabledByDefault: false
|
|
41
|
+
- id: finance-work-loop
|
|
42
|
+
name: "Finance work loop (safe-idle)"
|
|
43
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
44
|
+
timezone: "America/New_York"
|
|
45
|
+
agentId: "{{teamId}}-finance"
|
|
46
|
+
timeoutSeconds: 1800
|
|
47
|
+
message: "Work loop: check for finance-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/finance/agent-outputs/."
|
|
48
|
+
enabledByDefault: false
|
|
49
|
+
- id: analyst-work-loop
|
|
50
|
+
name: "Analyst work loop (safe-idle)"
|
|
51
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
52
|
+
timezone: "America/New_York"
|
|
53
|
+
agentId: "{{teamId}}-analyst"
|
|
54
|
+
timeoutSeconds: 1800
|
|
55
|
+
message: "Work loop: check for analytics-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/analyst/agent-outputs/."
|
|
13
56
|
enabledByDefault: false
|
|
14
57
|
- id: execution-loop
|
|
15
58
|
name: "Execution loop"
|
|
16
59
|
schedule: "*/30 7-23 * * 1-5"
|
|
17
60
|
timezone: "America/New_York"
|
|
18
|
-
|
|
61
|
+
agentId: "{{teamId}}-lead"
|
|
62
|
+
timeoutSeconds: 1800
|
|
63
|
+
message: "Execution loop (Business Team): complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on."
|
|
19
64
|
enabledByDefault: false
|
|
20
65
|
requiredSkills: []
|
|
21
66
|
team:
|
|
@@ -117,9 +162,10 @@ templates:
|
|
|
117
162
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
118
163
|
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
119
164
|
|
|
120
|
-
## Role work loop contract
|
|
165
|
+
## Role work loop contract
|
|
121
166
|
- No-op unless explicit queued work exists for the role.
|
|
122
|
-
- If work
|
|
167
|
+
- If work exists, complete it fully. If too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
168
|
+
- Write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
123
169
|
|
|
124
170
|
sharedContext.priorities: |
|
|
125
171
|
# Priorities (lead-curated)
|
|
@@ -9,13 +9,42 @@ cronJobs:
|
|
|
9
9
|
name: "Lead triage loop"
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
|
-
|
|
12
|
+
agentId: "{{teamId}}-lead"
|
|
13
|
+
timeoutSeconds: 1800
|
|
14
|
+
message: "Lead triage loop (Customer Support Team): triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing."
|
|
15
|
+
enabledByDefault: false
|
|
16
|
+
|
|
17
|
+
- id: triage-work-loop
|
|
18
|
+
name: "Triage work loop (safe-idle)"
|
|
19
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
20
|
+
timezone: "America/New_York"
|
|
21
|
+
agentId: "{{teamId}}-triage"
|
|
22
|
+
timeoutSeconds: 1800
|
|
23
|
+
message: "Work loop: check for triage-assigned tickets (categorize, route, escalate). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/triage/agent-outputs/."
|
|
24
|
+
enabledByDefault: false
|
|
25
|
+
- id: resolver-work-loop
|
|
26
|
+
name: "Resolver work loop (safe-idle)"
|
|
27
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
28
|
+
timezone: "America/New_York"
|
|
29
|
+
agentId: "{{teamId}}-resolver"
|
|
30
|
+
timeoutSeconds: 1800
|
|
31
|
+
message: "Work loop: check for resolver-assigned tickets (investigate, resolve, follow up). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/resolver/agent-outputs/."
|
|
32
|
+
enabledByDefault: false
|
|
33
|
+
- id: kb-writer-work-loop
|
|
34
|
+
name: "KB Writer work loop (safe-idle)"
|
|
35
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
36
|
+
timezone: "America/New_York"
|
|
37
|
+
agentId: "{{teamId}}-kb-writer"
|
|
38
|
+
timeoutSeconds: 1800
|
|
39
|
+
message: "Work loop: check for knowledge-base work (document solutions, update FAQs, maintain docs). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/kb-writer/agent-outputs/."
|
|
13
40
|
enabledByDefault: false
|
|
14
41
|
- id: execution-loop
|
|
15
42
|
name: "Execution loop"
|
|
16
43
|
schedule: "*/30 7-23 * * 1-5"
|
|
17
44
|
timezone: "America/New_York"
|
|
18
|
-
|
|
45
|
+
agentId: "{{teamId}}-lead"
|
|
46
|
+
timeoutSeconds: 1800
|
|
47
|
+
message: "Execution loop (Customer Support Team): complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on."
|
|
19
48
|
enabledByDefault: false
|
|
20
49
|
# pr-watcher omitted (enable only when a real PR integration exists)
|
|
21
50
|
requiredSkills: []
|
|
@@ -106,9 +135,10 @@ templates:
|
|
|
106
135
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
107
136
|
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
108
137
|
|
|
109
|
-
## Role work loop contract
|
|
138
|
+
## Role work loop contract
|
|
110
139
|
- No-op unless explicit queued work exists for the role.
|
|
111
|
-
- If work
|
|
140
|
+
- If work exists, complete it fully. If too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
141
|
+
- Write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
112
142
|
|
|
113
143
|
sharedContext.priorities: |
|
|
114
144
|
# Priorities (lead-curated)
|
|
@@ -10,8 +10,9 @@ cronJobs:
|
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
12
|
agentId: "{{teamId}}-lead"
|
|
13
|
+
timeoutSeconds: 1800
|
|
13
14
|
message: |
|
|
14
|
-
|
|
15
|
+
Lead triage loop: triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing.
|
|
15
16
|
|
|
16
17
|
QA-gated PR rule:
|
|
17
18
|
- Dev/DevOps must NOT open PRs.
|
|
@@ -35,8 +36,9 @@ cronJobs:
|
|
|
35
36
|
schedule: "*/30 7-23 * * 1-5"
|
|
36
37
|
timezone: "America/New_York"
|
|
37
38
|
agentId: "{{teamId}}-dev"
|
|
39
|
+
timeoutSeconds: 1800
|
|
38
40
|
message: |
|
|
39
|
-
|
|
41
|
+
Work loop: check for dev-assigned tickets. If you have one, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece (not a fragment) and update the ticket with what's done and what remains. Always leave work in a clean, consistent state.
|
|
40
42
|
|
|
41
43
|
Constraints:
|
|
42
44
|
- Do NOT open PRs. Dev hands off to QA by moving ticket to work/testing and setting Owner=test.
|
|
@@ -49,7 +51,8 @@ cronJobs:
|
|
|
49
51
|
schedule: "*/30 7-23 * * 1-5"
|
|
50
52
|
timezone: "America/New_York"
|
|
51
53
|
agentId: "{{teamId}}-devops"
|
|
52
|
-
|
|
54
|
+
timeoutSeconds: 1800
|
|
55
|
+
message: "Work loop: check for devops-assigned tickets/runs. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/devops/agent-outputs/."
|
|
53
56
|
enabledByDefault: true
|
|
54
57
|
|
|
55
58
|
- id: test-work-loop
|
|
@@ -57,8 +60,9 @@ cronJobs:
|
|
|
57
60
|
schedule: "*/30 7-23 * * 1-5"
|
|
58
61
|
timezone: "America/New_York"
|
|
59
62
|
agentId: "{{teamId}}-test"
|
|
63
|
+
timeoutSeconds: 1800
|
|
60
64
|
message: |
|
|
61
|
-
|
|
65
|
+
Work loop: drain work/testing tickets assigned to test. Complete each ticket's QA fully before moving on.
|
|
62
66
|
|
|
63
67
|
Workflow:
|
|
64
68
|
- On PASS: keep ticket in work/testing but set Owner=lead (ready-for-pr) and add a `QA: PASS` comment + evidence.
|
|
@@ -75,8 +79,9 @@ cronJobs:
|
|
|
75
79
|
schedule: "*/30 7-23 * * 1-5"
|
|
76
80
|
timezone: "America/New_York"
|
|
77
81
|
agentId: "{{teamId}}-lead"
|
|
82
|
+
timeoutSeconds: 1800
|
|
78
83
|
message: |
|
|
79
|
-
|
|
84
|
+
Execution loop: complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on. If a task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
80
85
|
|
|
81
86
|
CWD guardrail (team root): run:
|
|
82
87
|
cd "$(bash ../../scripts/team-root.sh 2>/dev/null || bash ./scripts/team-root.sh)"
|
|
@@ -85,7 +90,7 @@ cronJobs:
|
|
|
85
90
|
Guardrail: run ./scripts/ticket-hygiene-dev.sh each loop; if it fails, fix lane/status/owner mismatches before proceeding (assignment stubs are deprecated).
|
|
86
91
|
|
|
87
92
|
LEAD-OWNED TICKETS RULE (must follow)
|
|
88
|
-
- Do NOT automatically move a ticket just because Owner=lead
|
|
93
|
+
- Do NOT automatically move a ticket just because Owner=lead "expects backlog".
|
|
89
94
|
- If you encounter a lead-owned ticket in work/in-progress or work/testing that seems misassigned:
|
|
90
95
|
- LEAVE IT IN PLACE.
|
|
91
96
|
- Add a dated comment to the ticket explaining what you observed and what should change.
|
|
@@ -158,7 +163,7 @@ cronJobs:
|
|
|
158
163
|
|
|
159
164
|
- id: backup-devteam-work
|
|
160
165
|
name: "Backup dev-team work (every 3h, off-hours avoided)"
|
|
161
|
-
# Every 3h during 07:00
|
|
166
|
+
# Every 3h during 07:00-22:00 America/New_York (avoids 02:00-07:00 blackout)
|
|
162
167
|
schedule: "0 7,10,13,16,19,22 * * *"
|
|
163
168
|
timezone: "America/New_York"
|
|
164
169
|
agentId: "{{teamId}}-lead"
|
|
@@ -220,29 +225,29 @@ templates:
|
|
|
220
225
|
sharedContext.memoryPolicy: |
|
|
221
226
|
# Team Memory Policy (File-first)
|
|
222
227
|
|
|
223
|
-
Quick link: see `shared-context/MEMORY_PLAN.md` for the canonical
|
|
228
|
+
Quick link: see `shared-context/MEMORY_PLAN.md` for the canonical "what goes where" map.
|
|
224
229
|
|
|
225
230
|
This team is run **file-first**. Chat is not the system of record.
|
|
226
231
|
|
|
227
|
-
## Where memory lives (and what it
|
|
232
|
+
## Where memory lives (and what it's for)
|
|
228
233
|
|
|
229
234
|
### 1) Team knowledge memory (Kitchen UI)
|
|
230
235
|
- `shared-context/memory/team.jsonl` (append-only)
|
|
231
236
|
- `shared-context/memory/pinned.jsonl` (append-only)
|
|
232
237
|
|
|
233
|
-
Kitchen
|
|
238
|
+
Kitchen's Team Editor → Memory tab reads/writes these JSONL streams.
|
|
234
239
|
|
|
235
240
|
### 2) Per-role continuity memory (agents)
|
|
236
241
|
Each role keeps its own continuity memory:
|
|
237
242
|
- `roles/<role>/memory/YYYY-MM-DD.md` (daily log)
|
|
238
243
|
- `roles/<role>/MEMORY.md` (curated long-term memory)
|
|
239
244
|
|
|
240
|
-
These files are what the role agent uses to
|
|
245
|
+
These files are what the role agent uses to "remember" decisions and context across sessions.
|
|
241
246
|
|
|
242
247
|
## Where to write things
|
|
243
248
|
- Ticket = source of truth for a unit of work.
|
|
244
249
|
- `../notes/plan.md` + `../shared-context/priorities.md` are **lead-curated**.
|
|
245
|
-
- `../notes/status.md` is **append-only** and updated after each work session (3
|
|
250
|
+
- `../notes/status.md` is **append-only** and updated after each work session (3-5 bullets).
|
|
246
251
|
|
|
247
252
|
## Outputs / artifacts
|
|
248
253
|
- Role-level raw output (append-only): `roles/<role>/agent-outputs/`
|
|
@@ -250,10 +255,11 @@ templates:
|
|
|
250
255
|
|
|
251
256
|
Guardrail: do **not** create or rely on `roles/<role>/shared-context/**`.
|
|
252
257
|
|
|
253
|
-
## Role work loop contract
|
|
254
|
-
When a role
|
|
258
|
+
## Role work loop contract
|
|
259
|
+
When a role's cron/heartbeat runs:
|
|
255
260
|
- **No-op unless explicit queued work exists** for that role (ticket assigned/owned by role, or workflow run nodes assigned to the role agentId).
|
|
256
|
-
- If work
|
|
261
|
+
- If work exists, **complete the ticket fully**. If the task is too large for one session, complete a meaningful self-contained piece (not a fragment) and update the ticket with what's done and what remains. Always leave work in a clean, consistent state.
|
|
262
|
+
- Write back in this order:
|
|
257
263
|
1) Update the relevant ticket(s) (source of truth).
|
|
258
264
|
2) Append 1–3 bullets to `../notes/status.md` (team roll-up).
|
|
259
265
|
3) Write raw logs/artifacts under `roles/<role>/agent-outputs/` and reference them from the ticket.
|
|
@@ -266,11 +272,11 @@ templates:
|
|
|
266
272
|
After meaningful work:
|
|
267
273
|
1) Update the ticket with what changed + how to verify + rollback.
|
|
268
274
|
2) Add a dated note in the ticket `## Comments`.
|
|
269
|
-
3) Append 3
|
|
275
|
+
3) Append 3-5 bullets to `../notes/status.md`.
|
|
270
276
|
4) Append logs/output to `roles/<role>/agent-outputs/`.
|
|
271
277
|
|
|
272
278
|
tickets: |
|
|
273
|
-
# Tickets
|
|
279
|
+
# Tickets - {{teamId}}
|
|
274
280
|
|
|
275
281
|
## Workflow stages
|
|
276
282
|
- backlog → in-progress → testing → done
|
|
@@ -280,7 +286,7 @@ templates:
|
|
|
280
286
|
- test: verify + record PASS/FAIL
|
|
281
287
|
- lead: creates PR **only after QA PASS**
|
|
282
288
|
|
|
283
|
-
##
|
|
289
|
+
## "Ready for PR" (no extra lane)
|
|
284
290
|
This team does **not** add a separate lane.
|
|
285
291
|
|
|
286
292
|
Instead, a ticket is considered **ready for PR** when:
|
|
@@ -295,7 +301,7 @@ templates:
|
|
|
295
301
|
- Move ticket to `work/testing/`
|
|
296
302
|
- Set `Owner: test`
|
|
297
303
|
- Ensure the ticket contains:
|
|
298
|
-
- verification steps (
|
|
304
|
+
- verification steps ("How to test")
|
|
299
305
|
- links to branch/commit under `## PR-ready`
|
|
300
306
|
|
|
301
307
|
### Test → Lead (QA PASS)
|
|
@@ -324,7 +330,7 @@ templates:
|
|
|
324
330
|
|
|
325
331
|
|
|
326
332
|
sharedContext.qaAccess: |
|
|
327
|
-
# QA Access
|
|
333
|
+
# QA Access - {{teamId}}
|
|
328
334
|
|
|
329
335
|
This file exists to prevent QA tickets being bounced due to missing environment access.
|
|
330
336
|
|
|
@@ -381,9 +387,10 @@ templates:
|
|
|
381
387
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
382
388
|
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
383
389
|
|
|
384
|
-
## Role work loop contract
|
|
390
|
+
## Role work loop contract
|
|
385
391
|
- No-op unless explicit queued work exists for the role.
|
|
386
|
-
- If work
|
|
392
|
+
- If work exists, complete it fully. If too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
393
|
+
- Write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
387
394
|
|
|
388
395
|
sharedContext.priorities: |
|
|
389
396
|
# Priorities (lead-curated)
|
|
@@ -1013,7 +1020,7 @@ templates:
|
|
|
1013
1020
|
After you act:
|
|
1014
1021
|
1) Write back:
|
|
1015
1022
|
- Update tickets with decisions/assignments.
|
|
1016
|
-
- Keep `../notes/status.md` current (3
|
|
1023
|
+
- Keep `../notes/status.md` current (3-5 bullets per active ticket).
|
|
1017
1024
|
|
|
1018
1025
|
## Curator model
|
|
1019
1026
|
|
|
@@ -1032,14 +1039,14 @@ templates:
|
|
|
1032
1039
|
Source of truth is the shared team workspace.
|
|
1033
1040
|
|
|
1034
1041
|
Folders:
|
|
1035
|
-
- `inbox/`
|
|
1036
|
-
- `work/backlog/`
|
|
1037
|
-
- `work/in-progress/`
|
|
1038
|
-
- `work/testing/`
|
|
1039
|
-
- `work/done/`
|
|
1040
|
-
- `../notes/plan.md`
|
|
1041
|
-
- `../notes/status.md`
|
|
1042
|
-
- `shared-context/`
|
|
1042
|
+
- `inbox/` - raw incoming requests (append-only)
|
|
1043
|
+
- `work/backlog/` - normalized tickets, filename-ordered (`0001-...md`)
|
|
1044
|
+
- `work/in-progress/` - tickets currently being executed
|
|
1045
|
+
- `work/testing/` - tickets awaiting QA verification
|
|
1046
|
+
- `work/done/` - completed tickets + completion notes
|
|
1047
|
+
- `../notes/plan.md` - current plan / priorities (curated)
|
|
1048
|
+
- `../notes/status.md` - current status snapshot
|
|
1049
|
+
- `shared-context/` - shared context + append-only outputs
|
|
1043
1050
|
|
|
1044
1051
|
### Ticket numbering (critical)
|
|
1045
1052
|
- Backlog tickets MUST be named `0001-...md`, `0002-...md`, etc.
|
|
@@ -1092,7 +1099,7 @@ templates:
|
|
|
1092
1099
|
- `../notes/plan.md`
|
|
1093
1100
|
- `../notes/status.md`
|
|
1094
1101
|
- `../shared-context/priorities.md`
|
|
1095
|
-
- the current ticket you
|
|
1102
|
+
- the current ticket you're working on
|
|
1096
1103
|
|
|
1097
1104
|
Optional (team knowledge memory, Kitchen UI):
|
|
1098
1105
|
- `shared-context/memory/pinned.jsonl`
|
|
@@ -1104,8 +1111,8 @@ templates:
|
|
|
1104
1111
|
|
|
1105
1112
|
After you finish a work session (even if not done):
|
|
1106
1113
|
1) Write back:
|
|
1107
|
-
- Update the ticket with what you did and what
|
|
1108
|
-
- Add **3
|
|
1114
|
+
- Update the ticket with what you did and what's next.
|
|
1115
|
+
- Add **3-5 bullets** to `../notes/status.md` (what changed / what's blocked).
|
|
1109
1116
|
- Append detailed output to `../shared-context/agent-outputs/` (append-only).
|
|
1110
1117
|
|
|
1111
1118
|
Curator model:
|
|
@@ -1125,7 +1132,7 @@ templates:
|
|
|
1125
1132
|
4) Do the work.
|
|
1126
1133
|
|
|
1127
1134
|
5) Handoff to QA (required):
|
|
1128
|
-
- Ensure the ticket has verification steps (
|
|
1135
|
+
- Ensure the ticket has verification steps ("How to test").
|
|
1129
1136
|
- Add a `## PR-ready` section with repo + branch + commit SHA (if known).
|
|
1130
1137
|
- Move the ticket to `work/testing/`.
|
|
1131
1138
|
- Set `Owner: test`.
|
|
@@ -1159,7 +1166,7 @@ templates:
|
|
|
1159
1166
|
- `../notes/plan.md`
|
|
1160
1167
|
- `../notes/status.md`
|
|
1161
1168
|
- `../shared-context/priorities.md`
|
|
1162
|
-
- the current ticket you
|
|
1169
|
+
- the current ticket you're working on
|
|
1163
1170
|
|
|
1164
1171
|
Optional (team knowledge memory, Kitchen UI):
|
|
1165
1172
|
- `shared-context/memory/pinned.jsonl`
|
|
@@ -1168,7 +1175,7 @@ templates:
|
|
|
1168
1175
|
After you finish a work session:
|
|
1169
1176
|
1) Write back:
|
|
1170
1177
|
- Update the ticket with what you did + verification steps.
|
|
1171
|
-
- Add **3
|
|
1178
|
+
- Add **3-5 bullets** to `../notes/status.md`.
|
|
1172
1179
|
- Append detailed output/logs to `../shared-context/agent-outputs/` (append-only).
|
|
1173
1180
|
|
|
1174
1181
|
Curator model:
|
|
@@ -1300,7 +1307,7 @@ templates:
|
|
|
1300
1307
|
|
|
1301
1308
|
## Core workflow (QA gated)
|
|
1302
1309
|
- You verify work before any PR is created.
|
|
1303
|
-
- If the ticket passes: keep it in `work/testing/` but set `Owner: lead` (this is the
|
|
1310
|
+
- If the ticket passes: keep it in `work/testing/` but set `Owner: lead` (this is the "ready for PR" state).
|
|
1304
1311
|
- If it fails: move it back to `work/in-progress/` and set `Owner: dev`.
|
|
1305
1312
|
|
|
1306
1313
|
## Guardrails (read → act → write)
|
|
@@ -1323,7 +1330,7 @@ templates:
|
|
|
1323
1330
|
After each verification pass:
|
|
1324
1331
|
1) Write back:
|
|
1325
1332
|
- Add a short verification note to the ticket (pass/fail + evidence).
|
|
1326
|
-
- Add **3
|
|
1333
|
+
- Add **3-5 bullets** to `../notes/status.md` (what's verified / what's blocked).
|
|
1327
1334
|
- Append detailed findings to `../shared-context/feedback/` or `../shared-context/agent-outputs/`.
|
|
1328
1335
|
|
|
1329
1336
|
Curator model:
|
|
@@ -1342,7 +1349,7 @@ templates:
|
|
|
1342
1349
|
3) If it passes:
|
|
1343
1350
|
- Add a dated ticket comment: `QA: PASS` + evidence (links, logs, screenshots as applicable).
|
|
1344
1351
|
- Keep the ticket in `work/testing/`.
|
|
1345
|
-
- Set `Owner: lead` (this is the
|
|
1352
|
+
- Set `Owner: lead` (this is the "ready for PR" state).
|
|
1346
1353
|
|
|
1347
1354
|
4) If it fails:
|
|
1348
1355
|
- Add a dated ticket comment: `QA: FAIL` + repro + what to fix.
|
|
@@ -10,86 +10,98 @@ cronJobs:
|
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
12
|
agentId: "{{teamId}}-lead"
|
|
13
|
-
|
|
13
|
+
timeoutSeconds: 1800
|
|
14
|
+
message: "Lead triage loop (Marketing Team): triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing."
|
|
14
15
|
enabledByDefault: false
|
|
15
16
|
|
|
16
|
-
# Safe-idle role loops
|
|
17
|
+
# Safe-idle role loops: roles do not "wake up" unless they have their own cron.
|
|
17
18
|
- id: seo-work-loop
|
|
18
19
|
name: "SEO work loop (safe-idle)"
|
|
19
20
|
schedule: "*/30 7-23 * * 1-5"
|
|
20
21
|
timezone: "America/New_York"
|
|
21
22
|
agentId: "{{teamId}}-seo"
|
|
22
|
-
|
|
23
|
+
timeoutSeconds: 1800
|
|
24
|
+
message: "Work loop: check for SEO-assigned work (tickets/workflows). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/seo/agent-outputs/."
|
|
23
25
|
enabledByDefault: false
|
|
24
26
|
- id: copywriter-work-loop
|
|
25
27
|
name: "Copywriter work loop (safe-idle)"
|
|
26
28
|
schedule: "*/30 7-23 * * 1-5"
|
|
27
29
|
timezone: "America/New_York"
|
|
28
30
|
agentId: "{{teamId}}-copywriter"
|
|
29
|
-
|
|
31
|
+
timeoutSeconds: 1800
|
|
32
|
+
message: "Work loop: check for copywriting-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/copywriter/agent-outputs/."
|
|
30
33
|
enabledByDefault: false
|
|
31
34
|
- id: ads-work-loop
|
|
32
35
|
name: "Ads work loop (safe-idle)"
|
|
33
36
|
schedule: "*/30 7-23 * * 1-5"
|
|
34
37
|
timezone: "America/New_York"
|
|
35
38
|
agentId: "{{teamId}}-ads"
|
|
36
|
-
|
|
39
|
+
timeoutSeconds: 1800
|
|
40
|
+
message: "Work loop: check for ads-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/ads/agent-outputs/."
|
|
37
41
|
enabledByDefault: false
|
|
38
42
|
- id: social-work-loop
|
|
39
43
|
name: "Social work loop (safe-idle)"
|
|
40
44
|
schedule: "*/30 7-23 * * 1-5"
|
|
41
45
|
timezone: "America/New_York"
|
|
42
46
|
agentId: "{{teamId}}-social"
|
|
43
|
-
|
|
47
|
+
timeoutSeconds: 1800
|
|
48
|
+
message: "Work loop: check for social/community-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/social/agent-outputs/."
|
|
44
49
|
enabledByDefault: false
|
|
45
50
|
- id: designer-work-loop
|
|
46
51
|
name: "Designer work loop (safe-idle)"
|
|
47
52
|
schedule: "*/30 7-23 * * 1-5"
|
|
48
53
|
timezone: "America/New_York"
|
|
49
54
|
agentId: "{{teamId}}-designer"
|
|
50
|
-
|
|
55
|
+
timeoutSeconds: 1800
|
|
56
|
+
message: "Work loop: check for creative/design-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/designer/agent-outputs/."
|
|
51
57
|
enabledByDefault: false
|
|
52
58
|
- id: analyst-work-loop
|
|
53
59
|
name: "Analyst work loop (safe-idle)"
|
|
54
60
|
schedule: "*/30 7-23 * * 1-5"
|
|
55
61
|
timezone: "America/New_York"
|
|
56
62
|
agentId: "{{teamId}}-analyst"
|
|
57
|
-
|
|
63
|
+
timeoutSeconds: 1800
|
|
64
|
+
message: "Work loop: check for analytics-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/analyst/agent-outputs/."
|
|
58
65
|
enabledByDefault: false
|
|
59
66
|
- id: video-work-loop
|
|
60
67
|
name: "Video work loop (safe-idle)"
|
|
61
68
|
schedule: "*/30 7-23 * * 1-5"
|
|
62
69
|
timezone: "America/New_York"
|
|
63
70
|
agentId: "{{teamId}}-video"
|
|
64
|
-
|
|
71
|
+
timeoutSeconds: 1800
|
|
72
|
+
message: "Work loop: check for video-assigned work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/video/agent-outputs/."
|
|
65
73
|
enabledByDefault: false
|
|
66
74
|
- id: compliance-work-loop
|
|
67
75
|
name: "Compliance work loop (safe-idle)"
|
|
68
76
|
schedule: "*/30 7-23 * * 1-5"
|
|
69
77
|
timezone: "America/New_York"
|
|
70
78
|
agentId: "{{teamId}}-compliance"
|
|
71
|
-
|
|
79
|
+
timeoutSeconds: 1800
|
|
80
|
+
message: "Work loop: check for compliance/brand-review work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/compliance/agent-outputs/."
|
|
72
81
|
enabledByDefault: false
|
|
73
82
|
- id: offer-work-loop
|
|
74
83
|
name: "Offer work loop (safe-idle)"
|
|
75
84
|
schedule: "*/30 7-23 * * 1-5"
|
|
76
85
|
timezone: "America/New_York"
|
|
77
86
|
agentId: "{{teamId}}-offer"
|
|
78
|
-
|
|
87
|
+
timeoutSeconds: 1800
|
|
88
|
+
message: "Work loop: check for offer/positioning work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/offer/agent-outputs/."
|
|
79
89
|
enabledByDefault: false
|
|
80
90
|
- id: funnel-work-loop
|
|
81
91
|
name: "Funnel work loop (safe-idle)"
|
|
82
92
|
schedule: "*/30 7-23 * * 1-5"
|
|
83
93
|
timezone: "America/New_York"
|
|
84
94
|
agentId: "{{teamId}}-funnel"
|
|
85
|
-
|
|
95
|
+
timeoutSeconds: 1800
|
|
96
|
+
message: "Work loop: check for funnel/landing-page work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/funnel/agent-outputs/."
|
|
86
97
|
enabledByDefault: false
|
|
87
98
|
- id: lifecycle-work-loop
|
|
88
99
|
name: "Lifecycle work loop (safe-idle)"
|
|
89
100
|
schedule: "*/30 7-23 * * 1-5"
|
|
90
101
|
timezone: "America/New_York"
|
|
91
102
|
agentId: "{{teamId}}-lifecycle"
|
|
92
|
-
|
|
103
|
+
timeoutSeconds: 1800
|
|
104
|
+
message: "Work loop: check for lifecycle/email work. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/lifecycle/agent-outputs/."
|
|
93
105
|
enabledByDefault: false
|
|
94
106
|
|
|
95
107
|
# NOTE: Workflow worker crons are NOT defined here.
|
|
@@ -104,7 +116,8 @@ cronJobs:
|
|
|
104
116
|
schedule: "*/30 7-23 * * 1-5"
|
|
105
117
|
timezone: "America/New_York"
|
|
106
118
|
agentId: "{{teamId}}-lead"
|
|
107
|
-
|
|
119
|
+
timeoutSeconds: 1800
|
|
120
|
+
message: "Execution loop (Marketing Team): complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on."
|
|
108
121
|
enabledByDefault: false
|
|
109
122
|
requiredSkills: []
|
|
110
123
|
team:
|
|
@@ -9,13 +9,50 @@ cronJobs:
|
|
|
9
9
|
name: "Lead triage loop"
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
|
-
|
|
12
|
+
agentId: "{{teamId}}-lead"
|
|
13
|
+
timeoutSeconds: 1800
|
|
14
|
+
message: "Lead triage loop (Product Team): triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing."
|
|
15
|
+
enabledByDefault: false
|
|
16
|
+
|
|
17
|
+
- id: pm-work-loop
|
|
18
|
+
name: "Product Manager work loop (safe-idle)"
|
|
19
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
20
|
+
timezone: "America/New_York"
|
|
21
|
+
agentId: "{{teamId}}-pm"
|
|
22
|
+
timeoutSeconds: 1800
|
|
23
|
+
message: "Work loop: check for product-management work (specs, user stories, requirements). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/pm/agent-outputs/."
|
|
24
|
+
enabledByDefault: false
|
|
25
|
+
- id: designer-work-loop
|
|
26
|
+
name: "Designer work loop (safe-idle)"
|
|
27
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
28
|
+
timezone: "America/New_York"
|
|
29
|
+
agentId: "{{teamId}}-designer"
|
|
30
|
+
timeoutSeconds: 1800
|
|
31
|
+
message: "Work loop: check for design-assigned work (mockups, prototypes, design reviews). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/designer/agent-outputs/."
|
|
32
|
+
enabledByDefault: false
|
|
33
|
+
- id: engineer-work-loop
|
|
34
|
+
name: "Engineer work loop (safe-idle)"
|
|
35
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
36
|
+
timezone: "America/New_York"
|
|
37
|
+
agentId: "{{teamId}}-engineer"
|
|
38
|
+
timeoutSeconds: 1800
|
|
39
|
+
message: "Work loop: check for engineering-assigned tickets. If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/engineer/agent-outputs/."
|
|
40
|
+
enabledByDefault: false
|
|
41
|
+
- id: test-work-loop
|
|
42
|
+
name: "Test/QA work loop (safe-idle)"
|
|
43
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
44
|
+
timezone: "America/New_York"
|
|
45
|
+
agentId: "{{teamId}}-test"
|
|
46
|
+
timeoutSeconds: 1800
|
|
47
|
+
message: "Work loop: check for testing-assigned tickets (QA, verification, bug reports). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/test/agent-outputs/."
|
|
13
48
|
enabledByDefault: false
|
|
14
49
|
- id: execution-loop
|
|
15
50
|
name: "Execution loop"
|
|
16
51
|
schedule: "*/30 7-23 * * 1-5"
|
|
17
52
|
timezone: "America/New_York"
|
|
18
|
-
|
|
53
|
+
agentId: "{{teamId}}-lead"
|
|
54
|
+
timeoutSeconds: 1800
|
|
55
|
+
message: "Execution loop (Product Team): complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on."
|
|
19
56
|
enabledByDefault: false
|
|
20
57
|
# pr-watcher omitted (enable only when a real PR integration exists)
|
|
21
58
|
requiredSkills: []
|
|
@@ -112,9 +149,10 @@ templates:
|
|
|
112
149
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
113
150
|
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
114
151
|
|
|
115
|
-
## Role work loop contract
|
|
152
|
+
## Role work loop contract
|
|
116
153
|
- No-op unless explicit queued work exists for the role.
|
|
117
|
-
- If work
|
|
154
|
+
- If work exists, complete it fully. If too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
155
|
+
- Write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
118
156
|
|
|
119
157
|
sharedContext.priorities: |
|
|
120
158
|
# Priorities (lead-curated)
|
|
@@ -9,13 +9,42 @@ cronJobs:
|
|
|
9
9
|
name: "Lead triage loop"
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
|
-
|
|
12
|
+
agentId: "{{teamId}}-lead"
|
|
13
|
+
timeoutSeconds: 1800
|
|
14
|
+
message: "Lead triage loop (Research Team): triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing."
|
|
15
|
+
enabledByDefault: false
|
|
16
|
+
|
|
17
|
+
- id: researcher-work-loop
|
|
18
|
+
name: "Researcher work loop (safe-idle)"
|
|
19
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
20
|
+
timezone: "America/New_York"
|
|
21
|
+
agentId: "{{teamId}}-researcher"
|
|
22
|
+
timeoutSeconds: 1800
|
|
23
|
+
message: "Work loop: check for research-assigned work (investigate, gather data, analyze). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/researcher/agent-outputs/."
|
|
24
|
+
enabledByDefault: false
|
|
25
|
+
- id: fact-checker-work-loop
|
|
26
|
+
name: "Fact Checker work loop (safe-idle)"
|
|
27
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
28
|
+
timezone: "America/New_York"
|
|
29
|
+
agentId: "{{teamId}}-fact-checker"
|
|
30
|
+
timeoutSeconds: 1800
|
|
31
|
+
message: "Work loop: check for fact-checking work (verify claims, check sources, flag issues). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/fact-checker/agent-outputs/."
|
|
32
|
+
enabledByDefault: false
|
|
33
|
+
- id: summarizer-work-loop
|
|
34
|
+
name: "Summarizer work loop (safe-idle)"
|
|
35
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
36
|
+
timezone: "America/New_York"
|
|
37
|
+
agentId: "{{teamId}}-summarizer"
|
|
38
|
+
timeoutSeconds: 1800
|
|
39
|
+
message: "Work loop: check for summarization work (distill findings, create briefs, write reports). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/summarizer/agent-outputs/."
|
|
13
40
|
enabledByDefault: false
|
|
14
41
|
- id: execution-loop
|
|
15
42
|
name: "Execution loop"
|
|
16
43
|
schedule: "*/30 7-23 * * 1-5"
|
|
17
44
|
timezone: "America/New_York"
|
|
18
|
-
|
|
45
|
+
agentId: "{{teamId}}-lead"
|
|
46
|
+
timeoutSeconds: 1800
|
|
47
|
+
message: "Execution loop (Research Team): complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on."
|
|
19
48
|
enabledByDefault: false
|
|
20
49
|
# pr-watcher omitted (enable only when a real PR integration exists)
|
|
21
50
|
requiredSkills: []
|
|
@@ -106,9 +135,10 @@ templates:
|
|
|
106
135
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
107
136
|
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
108
137
|
|
|
109
|
-
## Role work loop contract
|
|
138
|
+
## Role work loop contract
|
|
110
139
|
- No-op unless explicit queued work exists for the role.
|
|
111
|
-
- If work
|
|
140
|
+
- If work exists, complete it fully. If too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
141
|
+
- Write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
112
142
|
|
|
113
143
|
sharedContext.priorities: |
|
|
114
144
|
# Priorities (lead-curated)
|
|
@@ -9,13 +9,114 @@ cronJobs:
|
|
|
9
9
|
name: "Lead triage loop"
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
|
-
|
|
12
|
+
agentId: "{{teamId}}-lead"
|
|
13
|
+
timeoutSeconds: 1800
|
|
14
|
+
message: "Lead triage loop (Social Media Team): triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing."
|
|
15
|
+
enabledByDefault: false
|
|
16
|
+
|
|
17
|
+
- id: research-work-loop
|
|
18
|
+
name: "Research work loop (safe-idle)"
|
|
19
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
20
|
+
timezone: "America/New_York"
|
|
21
|
+
agentId: "{{teamId}}-research"
|
|
22
|
+
timeoutSeconds: 1800
|
|
23
|
+
message: "Work loop: check for research-assigned work (trends, competitor analysis, audience insights). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/research/agent-outputs/."
|
|
24
|
+
enabledByDefault: false
|
|
25
|
+
- id: listening-work-loop
|
|
26
|
+
name: "Social Listening work loop (safe-idle)"
|
|
27
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
28
|
+
timezone: "America/New_York"
|
|
29
|
+
agentId: "{{teamId}}-listening"
|
|
30
|
+
timeoutSeconds: 1800
|
|
31
|
+
message: "Work loop: check for social-listening work (monitor mentions, sentiment, conversations). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/listening/agent-outputs/."
|
|
32
|
+
enabledByDefault: false
|
|
33
|
+
- id: social-seo-work-loop
|
|
34
|
+
name: "Social SEO work loop (safe-idle)"
|
|
35
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
36
|
+
timezone: "America/New_York"
|
|
37
|
+
agentId: "{{teamId}}-social-seo"
|
|
38
|
+
timeoutSeconds: 1800
|
|
39
|
+
message: "Work loop: check for social-SEO work (hashtag research, keyword optimization, discoverability). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/social-seo/agent-outputs/."
|
|
40
|
+
enabledByDefault: false
|
|
41
|
+
- id: editorial-work-loop
|
|
42
|
+
name: "Editorial work loop (safe-idle)"
|
|
43
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
44
|
+
timezone: "America/New_York"
|
|
45
|
+
agentId: "{{teamId}}-editorial"
|
|
46
|
+
timeoutSeconds: 1800
|
|
47
|
+
message: "Work loop: check for editorial work (content calendar, copy review, brand voice). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/editorial/agent-outputs/."
|
|
48
|
+
enabledByDefault: false
|
|
49
|
+
- id: community-work-loop
|
|
50
|
+
name: "Community work loop (safe-idle)"
|
|
51
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
52
|
+
timezone: "America/New_York"
|
|
53
|
+
agentId: "{{teamId}}-community"
|
|
54
|
+
timeoutSeconds: 1800
|
|
55
|
+
message: "Work loop: check for community work (engage, moderate, build relationships). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/community/agent-outputs/."
|
|
56
|
+
enabledByDefault: false
|
|
57
|
+
- id: distributor-work-loop
|
|
58
|
+
name: "Distributor work loop (safe-idle)"
|
|
59
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
60
|
+
timezone: "America/New_York"
|
|
61
|
+
agentId: "{{teamId}}-distributor"
|
|
62
|
+
timeoutSeconds: 1800
|
|
63
|
+
message: "Work loop: check for distribution work (schedule posts, cross-post, syndicate content). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/distributor/agent-outputs/."
|
|
64
|
+
enabledByDefault: false
|
|
65
|
+
- id: tiktok-work-loop
|
|
66
|
+
name: "TikTok work loop (safe-idle)"
|
|
67
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
68
|
+
timezone: "America/New_York"
|
|
69
|
+
agentId: "{{teamId}}-tiktok"
|
|
70
|
+
timeoutSeconds: 1800
|
|
71
|
+
message: "Work loop: check for TikTok-assigned work (create, post, optimize short-form video). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/tiktok/agent-outputs/."
|
|
72
|
+
enabledByDefault: false
|
|
73
|
+
- id: instagram-work-loop
|
|
74
|
+
name: "Instagram work loop (safe-idle)"
|
|
75
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
76
|
+
timezone: "America/New_York"
|
|
77
|
+
agentId: "{{teamId}}-instagram"
|
|
78
|
+
timeoutSeconds: 1800
|
|
79
|
+
message: "Work loop: check for Instagram-assigned work (stories, reels, posts, engagement). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/instagram/agent-outputs/."
|
|
80
|
+
enabledByDefault: false
|
|
81
|
+
- id: youtube-work-loop
|
|
82
|
+
name: "YouTube work loop (safe-idle)"
|
|
83
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
84
|
+
timezone: "America/New_York"
|
|
85
|
+
agentId: "{{teamId}}-youtube"
|
|
86
|
+
timeoutSeconds: 1800
|
|
87
|
+
message: "Work loop: check for YouTube-assigned work (video content, descriptions, community). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/youtube/agent-outputs/."
|
|
88
|
+
enabledByDefault: false
|
|
89
|
+
- id: facebook-work-loop
|
|
90
|
+
name: "Facebook work loop (safe-idle)"
|
|
91
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
92
|
+
timezone: "America/New_York"
|
|
93
|
+
agentId: "{{teamId}}-facebook"
|
|
94
|
+
timeoutSeconds: 1800
|
|
95
|
+
message: "Work loop: check for Facebook-assigned work (posts, groups, ads, engagement). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/facebook/agent-outputs/."
|
|
96
|
+
enabledByDefault: false
|
|
97
|
+
- id: x-work-loop
|
|
98
|
+
name: "X/Twitter work loop (safe-idle)"
|
|
99
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
100
|
+
timezone: "America/New_York"
|
|
101
|
+
agentId: "{{teamId}}-x"
|
|
102
|
+
timeoutSeconds: 1800
|
|
103
|
+
message: "Work loop: check for X/Twitter-assigned work (tweets, threads, engagement, spaces). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/x/agent-outputs/."
|
|
104
|
+
enabledByDefault: false
|
|
105
|
+
- id: linkedin-work-loop
|
|
106
|
+
name: "LinkedIn work loop (safe-idle)"
|
|
107
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
108
|
+
timezone: "America/New_York"
|
|
109
|
+
agentId: "{{teamId}}-linkedin"
|
|
110
|
+
timeoutSeconds: 1800
|
|
111
|
+
message: "Work loop: check for LinkedIn-assigned work (posts, articles, professional engagement). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/linkedin/agent-outputs/."
|
|
13
112
|
enabledByDefault: false
|
|
14
113
|
- id: execution-loop
|
|
15
114
|
name: "Execution loop"
|
|
16
115
|
schedule: "*/30 7-23 * * 1-5"
|
|
17
116
|
timezone: "America/New_York"
|
|
18
|
-
|
|
117
|
+
agentId: "{{teamId}}-lead"
|
|
118
|
+
timeoutSeconds: 1800
|
|
119
|
+
message: "Execution loop (Social Media Team): complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on."
|
|
19
120
|
enabledByDefault: false
|
|
20
121
|
# pr-watcher omitted (enable only when a real PR integration exists)
|
|
21
122
|
requiredSkills: []
|
|
@@ -119,9 +220,10 @@ templates:
|
|
|
119
220
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
120
221
|
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
121
222
|
|
|
122
|
-
## Role work loop contract
|
|
223
|
+
## Role work loop contract
|
|
123
224
|
- No-op unless explicit queued work exists for the role.
|
|
124
|
-
- If work
|
|
225
|
+
- If work exists, complete it fully. If too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
226
|
+
- Write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
125
227
|
|
|
126
228
|
sharedContext.priorities: |
|
|
127
229
|
# Priorities (lead-curated)
|
|
@@ -9,13 +9,42 @@ cronJobs:
|
|
|
9
9
|
name: "Lead triage loop"
|
|
10
10
|
schedule: "*/30 7-23 * * 1-5"
|
|
11
11
|
timezone: "America/New_York"
|
|
12
|
-
|
|
12
|
+
agentId: "{{teamId}}-lead"
|
|
13
|
+
timeoutSeconds: 1800
|
|
14
|
+
message: "Lead triage loop (Writing Team): triage inbox/tickets, assign work, and update notes/status.md. Complete all pending triage before finishing."
|
|
15
|
+
enabledByDefault: false
|
|
16
|
+
|
|
17
|
+
- id: outliner-work-loop
|
|
18
|
+
name: "Outliner work loop (safe-idle)"
|
|
19
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
20
|
+
timezone: "America/New_York"
|
|
21
|
+
agentId: "{{teamId}}-outliner"
|
|
22
|
+
timeoutSeconds: 1800
|
|
23
|
+
message: "Work loop: check for outlining work (research, structure, create outlines). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/outliner/agent-outputs/."
|
|
24
|
+
enabledByDefault: false
|
|
25
|
+
- id: writer-work-loop
|
|
26
|
+
name: "Writer work loop (safe-idle)"
|
|
27
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
28
|
+
timezone: "America/New_York"
|
|
29
|
+
agentId: "{{teamId}}-writer"
|
|
30
|
+
timeoutSeconds: 1800
|
|
31
|
+
message: "Work loop: check for writing-assigned work (draft, revise, polish content). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/writer/agent-outputs/."
|
|
32
|
+
enabledByDefault: false
|
|
33
|
+
- id: editor-work-loop
|
|
34
|
+
name: "Editor work loop (safe-idle)"
|
|
35
|
+
schedule: "*/30 7-23 * * 1-5"
|
|
36
|
+
timezone: "America/New_York"
|
|
37
|
+
agentId: "{{teamId}}-editor"
|
|
38
|
+
timeoutSeconds: 1800
|
|
39
|
+
message: "Work loop: check for editing work (review, proofread, fact-check, ensure quality). If you have work, complete it fully. If the task is too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains. Write outputs under roles/editor/agent-outputs/."
|
|
13
40
|
enabledByDefault: false
|
|
14
41
|
- id: execution-loop
|
|
15
42
|
name: "Execution loop"
|
|
16
43
|
schedule: "*/30 7-23 * * 1-5"
|
|
17
44
|
timezone: "America/New_York"
|
|
18
|
-
|
|
45
|
+
agentId: "{{teamId}}-lead"
|
|
46
|
+
timeoutSeconds: 1800
|
|
47
|
+
message: "Execution loop (Writing Team): complete in-progress tickets and update notes/status.md. Finish each ticket fully before moving on."
|
|
19
48
|
enabledByDefault: false
|
|
20
49
|
# pr-watcher omitted (enable only when a real PR integration exists)
|
|
21
50
|
requiredSkills: []
|
|
@@ -106,9 +135,10 @@ templates:
|
|
|
106
135
|
- `roles/<role>/agent-outputs/` (append-only)
|
|
107
136
|
- `../shared-context/agent-outputs/` (team-level, read/write from role via `../`)
|
|
108
137
|
|
|
109
|
-
## Role work loop contract
|
|
138
|
+
## Role work loop contract
|
|
110
139
|
- No-op unless explicit queued work exists for the role.
|
|
111
|
-
- If work
|
|
140
|
+
- If work exists, complete it fully. If too large for one session, complete a meaningful self-contained piece and update the ticket with what's done and what remains.
|
|
141
|
+
- Write back in order: ticket → `../notes/status.md` → `roles/<role>/agent-outputs/`.
|
|
112
142
|
|
|
113
143
|
sharedContext.priorities: |
|
|
114
144
|
# Priorities (lead-curated)
|
package/src/handlers/cron.ts
CHANGED
|
@@ -64,7 +64,7 @@ type CronReconcileScope =
|
|
|
64
64
|
|
|
65
65
|
function buildCronJobForCreate(
|
|
66
66
|
scope: CronReconcileScope,
|
|
67
|
-
j: { id: string; name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; enabledByDefault?: boolean; delivery?: 'none' | 'announce' },
|
|
67
|
+
j: { id: string; name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; enabledByDefault?: boolean; delivery?: 'none' | 'announce'; timeoutSeconds?: number },
|
|
68
68
|
wantEnabled: boolean
|
|
69
69
|
): Record<string, unknown> {
|
|
70
70
|
const name =
|
|
@@ -90,10 +90,12 @@ function buildCronJobForCreate(
|
|
|
90
90
|
wakeMode: "next-heartbeat",
|
|
91
91
|
sessionTarget,
|
|
92
92
|
schedule: { kind: "cron", expr: j.schedule, ...(j.timezone ? { tz: j.timezone } : {}) },
|
|
93
|
-
payload: effectiveAgentId
|
|
93
|
+
payload: effectiveAgentId
|
|
94
|
+
? { kind: "agentTurn", message: j.message, ...(j.timeoutSeconds ? { timeoutSeconds: j.timeoutSeconds } : {}) }
|
|
95
|
+
: { kind: "systemEvent", text: j.message },
|
|
94
96
|
...(j.delivery === 'none'
|
|
95
97
|
? { delivery: { mode: "none" } }
|
|
96
|
-
: j.channel || j.to
|
|
98
|
+
: j.delivery === 'announce' || j.channel || j.to
|
|
97
99
|
? {
|
|
98
100
|
delivery: {
|
|
99
101
|
mode: "announce",
|
|
@@ -102,12 +104,14 @@ function buildCronJobForCreate(
|
|
|
102
104
|
bestEffort: true,
|
|
103
105
|
},
|
|
104
106
|
}
|
|
105
|
-
:
|
|
107
|
+
: // Default to none — OpenClaw defaults isolated agentTurn crons to
|
|
108
|
+
// "announce" which errors when no channel target is available.
|
|
109
|
+
{ delivery: { mode: "none" } }),
|
|
106
110
|
};
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
function buildCronJobPatch(
|
|
110
|
-
j: { name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; delivery?: 'none' | 'announce' },
|
|
114
|
+
j: { name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; delivery?: 'none' | 'announce'; timeoutSeconds?: number },
|
|
111
115
|
name: string
|
|
112
116
|
): CronJobPatch {
|
|
113
117
|
const effectiveAgentId = typeof j.agentId === "string" && j.agentId.trim() ? j.agentId.trim() : undefined;
|
|
@@ -119,17 +123,23 @@ function buildCronJobPatch(
|
|
|
119
123
|
sessionTarget: effectiveAgentId ? "isolated" : "main",
|
|
120
124
|
wakeMode: "next-heartbeat",
|
|
121
125
|
schedule: { kind: "cron", expr: j.schedule, ...(j.timezone ? { tz: j.timezone } : {}) },
|
|
122
|
-
payload: effectiveAgentId
|
|
126
|
+
payload: effectiveAgentId
|
|
127
|
+
? { kind: "agentTurn", message: j.message, ...(j.timeoutSeconds ? { timeoutSeconds: j.timeoutSeconds } : {}) }
|
|
128
|
+
: { kind: "systemEvent", text: j.message },
|
|
123
129
|
};
|
|
124
130
|
if (j.delivery === 'none') {
|
|
125
131
|
patch.delivery = { mode: "none" };
|
|
126
|
-
} else if (j.channel || j.to) {
|
|
132
|
+
} else if (j.delivery === 'announce' || j.channel || j.to) {
|
|
127
133
|
patch.delivery = {
|
|
128
134
|
mode: "announce",
|
|
129
135
|
...(j.channel ? { channel: j.channel } : {}),
|
|
130
136
|
...(j.to ? { to: j.to } : {}),
|
|
131
137
|
bestEffort: true,
|
|
132
138
|
};
|
|
139
|
+
} else {
|
|
140
|
+
// Default to none — OpenClaw defaults isolated agentTurn crons to
|
|
141
|
+
// "announce" which errors when no channel target is available.
|
|
142
|
+
patch.delivery = { mode: "none" };
|
|
133
143
|
}
|
|
134
144
|
return patch;
|
|
135
145
|
}
|
|
@@ -17,9 +17,10 @@ export async function handleWorkflowsRun(api: OpenClawPluginApi, opts: {
|
|
|
17
17
|
export async function handleWorkflowsRunnerOnce(api: OpenClawPluginApi, opts: {
|
|
18
18
|
teamId: string;
|
|
19
19
|
leaseSeconds?: number;
|
|
20
|
+
runId?: string;
|
|
20
21
|
}) {
|
|
21
22
|
if (!opts.teamId) throw new Error('--team-id is required');
|
|
22
|
-
return runWorkflowRunnerOnce(api, { teamId: opts.teamId, leaseSeconds: opts.leaseSeconds });
|
|
23
|
+
return runWorkflowRunnerOnce(api, { teamId: opts.teamId, leaseSeconds: opts.leaseSeconds, runId: opts.runId });
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
|
|
@@ -13,6 +13,8 @@ export type CronJobSpec = {
|
|
|
13
13
|
enabledByDefault?: boolean;
|
|
14
14
|
/** Delivery mode: "none" suppresses announce; "announce" delivers to chat. Omit to use gateway default. */
|
|
15
15
|
delivery?: 'none' | 'announce';
|
|
16
|
+
/** Agent turn timeout in seconds. Sets how long the agent can run before being killed. */
|
|
17
|
+
timeoutSeconds?: number;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
/** Raw input for a cron job from YAML (supports message/task/prompt for backward compat). */
|
|
@@ -30,6 +32,7 @@ type CronJobInput = {
|
|
|
30
32
|
agentId?: unknown;
|
|
31
33
|
enabledByDefault?: unknown;
|
|
32
34
|
delivery?: unknown;
|
|
35
|
+
timeoutSeconds?: unknown;
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
export type RecipeFrontmatter = {
|
|
@@ -87,6 +90,7 @@ function buildCronJobSpec(j: CronJobInput, id: string): CronJobSpec {
|
|
|
87
90
|
agentId: j.agentId != null ? String(j.agentId) : undefined,
|
|
88
91
|
enabledByDefault: Boolean(j.enabledByDefault ?? false),
|
|
89
92
|
delivery: j.delivery === 'none' || j.delivery === 'announce' ? j.delivery : undefined,
|
|
93
|
+
timeoutSeconds: typeof j.timeoutSeconds === 'number' && j.timeoutSeconds > 0 ? j.timeoutSeconds : undefined,
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
|
|
@@ -124,6 +124,7 @@ export async function enqueueWorkflowRun(api: OpenClawPluginApi, opts: {
|
|
|
124
124
|
export async function runWorkflowRunnerOnce(api: OpenClawPluginApi, opts: {
|
|
125
125
|
teamId: string;
|
|
126
126
|
leaseSeconds?: number;
|
|
127
|
+
runId?: string;
|
|
127
128
|
}) {
|
|
128
129
|
const teamId = String(opts.teamId);
|
|
129
130
|
const teamDir = resolveTeamDir(api, teamId);
|
|
@@ -169,6 +170,14 @@ export async function runWorkflowRunnerOnce(api: OpenClawPluginApi, opts: {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
// If a specific runId was requested, only consider that run.
|
|
174
|
+
const targetRunId = opts.runId?.trim();
|
|
175
|
+
if (targetRunId) {
|
|
176
|
+
const match = candidates.filter((c) => path.basename(path.dirname(c.file)) === targetRunId);
|
|
177
|
+
candidates.length = 0;
|
|
178
|
+
candidates.push(...match);
|
|
179
|
+
}
|
|
180
|
+
|
|
172
181
|
if (!candidates.length) {
|
|
173
182
|
return { ok: true as const, teamId, claimed: 0, message: 'No queued runs available.' };
|
|
174
183
|
}
|
|
@@ -415,6 +415,44 @@ export async function runWorkflowWorkerTick(api: OpenClawPluginApi, opts: {
|
|
|
415
415
|
await fs.writeFile(artifactPath, JSON.stringify({ ok: true, tool: toolName, args: toolArgs, result }, null, 2) + '\n', 'utf8');
|
|
416
416
|
|
|
417
417
|
|
|
418
|
+
} else if (toolName === 'fs.write') {
|
|
419
|
+
const relPathRaw = String(toolArgs.path ?? '').trim();
|
|
420
|
+
const contentRaw = String(toolArgs.content ?? '');
|
|
421
|
+
if (!relPathRaw) throw new Error('fs.write requires args.path');
|
|
422
|
+
|
|
423
|
+
const vars = {
|
|
424
|
+
date: new Date().toISOString(),
|
|
425
|
+
'run.id': runId,
|
|
426
|
+
'run.timestamp': runId,
|
|
427
|
+
'workflow.id': String(workflow.id ?? ''),
|
|
428
|
+
'workflow.name': String(workflow.name ?? workflow.id ?? workflowFile),
|
|
429
|
+
};
|
|
430
|
+
// Also inject node outputs so templates like {{brand_review.output}} resolve
|
|
431
|
+
const { run: runSnap } = await loadRunFile(teamDir, runsDir, task.runId);
|
|
432
|
+
for (const nr of (runSnap.nodeResults ?? [])) {
|
|
433
|
+
const nid = String((nr as Record<string, unknown>).nodeId ?? '');
|
|
434
|
+
const nrOutPath = String((nr as Record<string, unknown>).nodeOutputPath ?? '');
|
|
435
|
+
if (nid && nrOutPath) {
|
|
436
|
+
try {
|
|
437
|
+
const outAbs = path.resolve(teamDir, nrOutPath);
|
|
438
|
+
vars[`${nid}.output`] = await fs.readFile(outAbs, 'utf8');
|
|
439
|
+
} catch { /* node output may not exist */ }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const relPath = templateReplace(relPathRaw, vars);
|
|
443
|
+
const content = templateReplace(contentRaw, vars);
|
|
444
|
+
|
|
445
|
+
const abs = path.resolve(teamDir, relPath);
|
|
446
|
+
if (!abs.startsWith(teamDir + path.sep) && abs !== teamDir) {
|
|
447
|
+
throw new Error('fs.write path must be within the team workspace');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
await ensureDir(path.dirname(abs));
|
|
451
|
+
await fs.writeFile(abs, content, 'utf8');
|
|
452
|
+
|
|
453
|
+
const result = { writtenTo: path.relative(teamDir, abs), bytes: Buffer.byteLength(content, 'utf8') };
|
|
454
|
+
await fs.writeFile(artifactPath, JSON.stringify({ ok: true, tool: toolName, args: toolArgs, result }, null, 2) + '\n', 'utf8');
|
|
455
|
+
|
|
418
456
|
} else if (toolName === 'marketing.post_all') {
|
|
419
457
|
// Disabled by default: do not ship plugins that spawn local processes for posting.
|
|
420
458
|
// Use an approval-gated workflow node that calls a dedicated posting tool/plugin instead.
|
|
@@ -515,8 +553,17 @@ export async function runWorkflowWorkerTick(api: OpenClawPluginApi, opts: {
|
|
|
515
553
|
// by the runner/worker loop itself (they send a message + set awaiting state).
|
|
516
554
|
const nextKind = String(nextNode.kind ?? '');
|
|
517
555
|
if (nextKind === 'human_approval' || nextKind === 'start' || nextKind === 'end') {
|
|
518
|
-
//
|
|
519
|
-
|
|
556
|
+
// Route approval nodes to the correct agent:
|
|
557
|
+
// 1. Explicit agentId on the node config (workflow author override)
|
|
558
|
+
// 2. Team lead (${teamId}-lead) — the natural orchestrator role
|
|
559
|
+
// 3. Fallback to the current agent (backwards compat)
|
|
560
|
+
const nextConfig = (nextNode as unknown as Record<string, unknown>)['config'];
|
|
561
|
+
const nextConfigObj = nextConfig && typeof nextConfig === 'object' && !Array.isArray(nextConfig)
|
|
562
|
+
? (nextConfig as Record<string, unknown>) : {};
|
|
563
|
+
const explicitAgentId = String(nextConfigObj['agentId'] ?? '').trim();
|
|
564
|
+
const approvalAgentId = explicitAgentId || `${teamId}-lead` || agentId;
|
|
565
|
+
|
|
566
|
+
await enqueueTask(teamDir, approvalAgentId, {
|
|
520
567
|
teamId,
|
|
521
568
|
runId: task.runId,
|
|
522
569
|
nodeId: nextNode.id,
|
|
@@ -528,7 +575,7 @@ export async function runWorkflowWorkerTick(api: OpenClawPluginApi, opts: {
|
|
|
528
575
|
updatedAt: new Date().toISOString(),
|
|
529
576
|
status: 'waiting_workers',
|
|
530
577
|
nextNodeIndex: enqueueIdx,
|
|
531
|
-
events: [...cur.events, { ts: new Date().toISOString(), type: 'node.enqueued', nodeId: nextNode.id, agentId }],
|
|
578
|
+
events: [...cur.events, { ts: new Date().toISOString(), type: 'node.enqueued', nodeId: nextNode.id, agentId: approvalAgentId }],
|
|
532
579
|
}));
|
|
533
580
|
|
|
534
581
|
results.push({ taskId: task.id, runId: task.runId, nodeId: task.nodeId, status: 'ok' });
|