@jiggai/recipes 0.4.21 → 0.4.23

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.
@@ -2,7 +2,7 @@
2
2
  "id": "recipes",
3
3
  "name": "Recipes",
4
4
  "description": "Markdown recipes that scaffold agents and teams (workspace-local).",
5
- "version": "0.4.21",
5
+ "version": "0.4.23",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jiggai/recipes",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
4
4
  "description": "ClawRecipes plugin for OpenClaw (markdown recipes -> scaffold agents/teams)",
5
5
  "main": "index.ts",
6
6
  "type": "commonjs",
@@ -237,8 +237,8 @@ templates:
237
237
  ops.agents: |
238
238
  # AGENTS.md
239
239
 
240
- Team: {teamId}
241
- Shared workspace: {teamDir}
240
+ Team: {{teamId}}
241
+ Shared workspace: {{teamDir}}
242
242
  Role: ops
243
243
 
244
244
  ## Guardrails (read → act → write)
@@ -267,8 +267,8 @@ templates:
267
267
  sales.agents: |
268
268
  # AGENTS.md
269
269
 
270
- Team: {teamId}
271
- Shared workspace: {teamDir}
270
+ Team: {{teamId}}
271
+ Shared workspace: {{teamDir}}
272
272
  Role: sales
273
273
 
274
274
  ## Guardrails (read → act → write)
@@ -297,8 +297,8 @@ templates:
297
297
  marketing.agents: |
298
298
  # AGENTS.md
299
299
 
300
- Team: {teamId}
301
- Shared workspace: {teamDir}
300
+ Team: {{teamId}}
301
+ Shared workspace: {{teamDir}}
302
302
  Role: marketing
303
303
 
304
304
  ## Guardrails (read → act → write)
@@ -327,8 +327,8 @@ templates:
327
327
  finance.agents: |
328
328
  # AGENTS.md
329
329
 
330
- Team: {teamId}
331
- Shared workspace: {teamDir}
330
+ Team: {{teamId}}
331
+ Shared workspace: {{teamDir}}
332
332
  Role: finance
333
333
 
334
334
  ## Guardrails (read → act → write)
@@ -357,7 +357,28 @@ templates:
357
357
  analyst.agents: |
358
358
  # AGENTS.md
359
359
 
360
- Output:
360
+ Team: {{teamId}}
361
+ Shared workspace: {{teamDir}}
362
+ Role: analyst
363
+
364
+ ## Guardrails (read → act → write)
365
+ Before you act:
366
+ 1) Read:
367
+ - `../notes/plan.md`
368
+ - `../notes/status.md`
369
+ - relevant ticket(s) in `work/in-progress/`
370
+ - any relevant shared context under `shared-context/`
371
+
372
+ After you act:
373
+ 1) Write back:
374
+ - Put outputs in the agreed folder (usually `outbox/` or a ticket file).
375
+ - Update the ticket with what you did and where the artifact is.
376
+
377
+ ## Workflow
378
+ - Prefer a pull model: wait for a clear task from the lead, or propose a scoped task.
379
+ - Keep work small and reversible.
380
+
381
+ ## Output locations
361
382
  - Research briefs → outbox/research/
362
383
  - Metrics definitions/dashboards notes → shared-context/metrics/
363
384
 
@@ -491,6 +512,9 @@ files:
491
512
  - path: shared-context/priorities.md
492
513
  template: sharedContext.priorities
493
514
  mode: createOnly
515
+ - path: shared-context/MEMORY_PLAN.md
516
+ template: sharedContext.memoryPlan
517
+ mode: createOnly
494
518
  - path: shared-context/agent-outputs/README.md
495
519
  template: sharedContext.agentOutputsReadme
496
520
  mode: createOnly
@@ -209,8 +209,8 @@ templates:
209
209
  triage.agents: |
210
210
  # AGENTS.md
211
211
 
212
- Team: {teamId}
213
- Shared workspace: {teamDir}
212
+ Team: {{teamId}}
213
+ Shared workspace: {{teamDir}}
214
214
  Role: triage
215
215
 
216
216
  ## Guardrails (read → act → write)
@@ -239,8 +239,8 @@ templates:
239
239
  resolver.agents: |
240
240
  # AGENTS.md
241
241
 
242
- Team: {teamId}
243
- Shared workspace: {teamDir}
242
+ Team: {{teamId}}
243
+ Shared workspace: {{teamDir}}
244
244
  Role: resolver
245
245
 
246
246
  ## Guardrails (read → act → write)
@@ -269,8 +269,8 @@ templates:
269
269
  kb-writer.agents: |
270
270
  # AGENTS.md
271
271
 
272
- Team: {teamId}
273
- Shared workspace: {teamDir}
272
+ Team: {{teamId}}
273
+ Shared workspace: {{teamDir}}
274
274
  Role: kb-writer
275
275
 
276
276
  ## Guardrails (read → act → write)
@@ -380,6 +380,9 @@ files:
380
380
  - path: shared-context/priorities.md
381
381
  template: sharedContext.priorities
382
382
  mode: createOnly
383
+ - path: shared-context/MEMORY_PLAN.md
384
+ template: sharedContext.memoryPlan
385
+ mode: createOnly
383
386
  - path: shared-context/agent-outputs/README.md
384
387
  template: sharedContext.agentOutputsReadme
385
388
  mode: createOnly
@@ -92,6 +92,12 @@ cronJobs:
92
92
  message: "Safe-idle loop: check for lifecycle/email work, make progress, and write outputs under roles/lifecycle/agent-outputs/."
93
93
  enabledByDefault: false
94
94
 
95
+ # NOTE: Workflow worker crons are NOT defined here.
96
+ # ClawKitchen installs them on-demand when the user visits a workflow editor
97
+ # and clicks "Install worker cron(s)" or triggers a run. This avoids duplicate
98
+ # crons between recipe scaffolding and Kitchen's reconciliation.
99
+ # Kitchen stores provenance in workspace-<teamId>/notes/cron-jobs.json.
100
+
95
101
  # Optional team-wide loop (off by default): can be enabled later if you want an extra generic executor.
96
102
  - id: execution-loop
97
103
  name: "Execution loop"
@@ -212,8 +212,8 @@ templates:
212
212
  pm.agents: |
213
213
  # AGENTS.md
214
214
 
215
- Team: {teamId}
216
- Shared workspace: {teamDir}
215
+ Team: {{teamId}}
216
+ Shared workspace: {{teamDir}}
217
217
  Role: pm
218
218
 
219
219
  ## Guardrails (read → act → write)
@@ -242,8 +242,8 @@ templates:
242
242
  designer.agents: |
243
243
  # AGENTS.md
244
244
 
245
- Team: {teamId}
246
- Shared workspace: {teamDir}
245
+ Team: {{teamId}}
246
+ Shared workspace: {{teamDir}}
247
247
  Role: designer
248
248
 
249
249
  ## Guardrails (read → act → write)
@@ -272,8 +272,8 @@ templates:
272
272
  engineer.agents: |
273
273
  # AGENTS.md
274
274
 
275
- Team: {teamId}
276
- Shared workspace: {teamDir}
275
+ Team: {{teamId}}
276
+ Shared workspace: {{teamDir}}
277
277
  Role: engineer
278
278
 
279
279
  ## Guardrails (read → act → write)
@@ -302,8 +302,8 @@ templates:
302
302
  test.agents: |
303
303
  # AGENTS.md
304
304
 
305
- Team: {teamId}
306
- Shared workspace: {teamDir}
305
+ Team: {{teamId}}
306
+ Shared workspace: {{teamDir}}
307
307
  Role: test
308
308
 
309
309
  ## Guardrails (read → act → write)
@@ -428,6 +428,9 @@ files:
428
428
  - path: shared-context/priorities.md
429
429
  template: sharedContext.priorities
430
430
  mode: createOnly
431
+ - path: shared-context/MEMORY_PLAN.md
432
+ template: sharedContext.memoryPlan
433
+ mode: createOnly
431
434
  - path: shared-context/agent-outputs/README.md
432
435
  template: sharedContext.agentOutputsReadme
433
436
  mode: createOnly
@@ -209,8 +209,8 @@ templates:
209
209
  researcher.agents: |
210
210
  # AGENTS.md
211
211
 
212
- Team: {teamId}
213
- Shared workspace: {teamDir}
212
+ Team: {{teamId}}
213
+ Shared workspace: {{teamDir}}
214
214
  Role: researcher
215
215
 
216
216
  ## Guardrails (read → act → write)
@@ -239,8 +239,8 @@ templates:
239
239
  fact-checker.agents: |
240
240
  # AGENTS.md
241
241
 
242
- Team: {teamId}
243
- Shared workspace: {teamDir}
242
+ Team: {{teamId}}
243
+ Shared workspace: {{teamDir}}
244
244
  Role: fact-checker
245
245
 
246
246
  ## Guardrails (read → act → write)
@@ -269,8 +269,8 @@ templates:
269
269
  summarizer.agents: |
270
270
  # AGENTS.md
271
271
 
272
- Team: {teamId}
273
- Shared workspace: {teamDir}
272
+ Team: {{teamId}}
273
+ Shared workspace: {{teamDir}}
274
274
  Role: summarizer
275
275
 
276
276
  ## Guardrails (read → act → write)
@@ -380,6 +380,9 @@ files:
380
380
  - path: shared-context/priorities.md
381
381
  template: sharedContext.priorities
382
382
  mode: createOnly
383
+ - path: shared-context/MEMORY_PLAN.md
384
+ template: sharedContext.memoryPlan
385
+ mode: createOnly
383
386
  - path: shared-context/agent-outputs/README.md
384
387
  template: sharedContext.agentOutputsReadme
385
388
  mode: createOnly
@@ -224,8 +224,8 @@ templates:
224
224
  research.agents: |
225
225
  # AGENTS.md
226
226
 
227
- Team: {teamId}
228
- Shared workspace: {teamDir}
227
+ Team: {{teamId}}
228
+ Shared workspace: {{teamDir}}
229
229
  Role: research
230
230
 
231
231
  ## Guardrails (read → act → write)
@@ -259,8 +259,8 @@ templates:
259
259
  listening.agents: |
260
260
  # AGENTS.md
261
261
 
262
- Team: {teamId}
263
- Shared workspace: {teamDir}
262
+ Team: {{teamId}}
263
+ Shared workspace: {{teamDir}}
264
264
  Role: listening
265
265
 
266
266
  ## Guardrails (read → act → write)
@@ -294,8 +294,8 @@ templates:
294
294
  social-seo.agents: |
295
295
  # AGENTS.md
296
296
 
297
- Team: {teamId}
298
- Shared workspace: {teamDir}
297
+ Team: {{teamId}}
298
+ Shared workspace: {{teamDir}}
299
299
  Role: social-seo
300
300
 
301
301
  ## Guardrails (read → act → write)
@@ -329,8 +329,8 @@ templates:
329
329
  editorial.agents: |
330
330
  # AGENTS.md
331
331
 
332
- Team: {teamId}
333
- Shared workspace: {teamDir}
332
+ Team: {{teamId}}
333
+ Shared workspace: {{teamDir}}
334
334
  Role: editorial
335
335
 
336
336
  ## Guardrails (read → act → write)
@@ -364,8 +364,8 @@ templates:
364
364
  community.agents: |
365
365
  # AGENTS.md
366
366
 
367
- Team: {teamId}
368
- Shared workspace: {teamDir}
367
+ Team: {{teamId}}
368
+ Shared workspace: {{teamDir}}
369
369
  Role: community
370
370
 
371
371
  ## Guardrails (read → act → write)
@@ -399,8 +399,8 @@ templates:
399
399
  distributor.agents: |
400
400
  # AGENTS.md
401
401
 
402
- Team: {teamId}
403
- Shared workspace: {teamDir}
402
+ Team: {{teamId}}
403
+ Shared workspace: {{teamDir}}
404
404
  Role: distributor
405
405
 
406
406
  ## Guardrails (read → act → write)
@@ -434,8 +434,8 @@ templates:
434
434
  tiktok.agents: |
435
435
  # AGENTS.md
436
436
 
437
- Team: {teamId}
438
- Shared workspace: {teamDir}
437
+ Team: {{teamId}}
438
+ Shared workspace: {{teamDir}}
439
439
  Role: tiktok
440
440
 
441
441
  ## Guardrails (read → act → write)
@@ -469,8 +469,8 @@ templates:
469
469
  instagram.agents: |
470
470
  # AGENTS.md
471
471
 
472
- Team: {teamId}
473
- Shared workspace: {teamDir}
472
+ Team: {{teamId}}
473
+ Shared workspace: {{teamDir}}
474
474
  Role: instagram
475
475
 
476
476
  ## Guardrails (read → act → write)
@@ -504,8 +504,8 @@ templates:
504
504
  youtube.agents: |
505
505
  # AGENTS.md
506
506
 
507
- Team: {teamId}
508
- Shared workspace: {teamDir}
507
+ Team: {{teamId}}
508
+ Shared workspace: {{teamDir}}
509
509
  Role: youtube
510
510
 
511
511
  ## Guardrails (read → act → write)
@@ -539,8 +539,8 @@ templates:
539
539
  facebook.agents: |
540
540
  # AGENTS.md
541
541
 
542
- Team: {teamId}
543
- Shared workspace: {teamDir}
542
+ Team: {{teamId}}
543
+ Shared workspace: {{teamDir}}
544
544
  Role: facebook
545
545
 
546
546
  ## Guardrails (read → act → write)
@@ -574,8 +574,8 @@ templates:
574
574
  x.agents: |
575
575
  # AGENTS.md
576
576
 
577
- Team: {teamId}
578
- Shared workspace: {teamDir}
577
+ Team: {{teamId}}
578
+ Shared workspace: {{teamDir}}
579
579
  Role: x
580
580
 
581
581
  ## Guardrails (read → act → write)
@@ -609,8 +609,8 @@ templates:
609
609
  linkedin.agents: |
610
610
  # AGENTS.md
611
611
 
612
- Team: {teamId}
613
- Shared workspace: {teamDir}
612
+ Team: {{teamId}}
613
+ Shared workspace: {{teamDir}}
614
614
  Role: linkedin
615
615
 
616
616
  ## Guardrails (read → act → write)
@@ -856,6 +856,9 @@ files:
856
856
  - path: shared-context/priorities.md
857
857
  template: sharedContext.priorities
858
858
  mode: createOnly
859
+ - path: shared-context/MEMORY_PLAN.md
860
+ template: sharedContext.memoryPlan
861
+ mode: createOnly
859
862
  - path: shared-context/agent-outputs/README.md
860
863
  template: sharedContext.agentOutputsReadme
861
864
  mode: createOnly
@@ -206,8 +206,8 @@ templates:
206
206
  outliner.agents: |
207
207
  # AGENTS.md
208
208
 
209
- Team: {teamId}
210
- Shared workspace: {teamDir}
209
+ Team: {{teamId}}
210
+ Shared workspace: {{teamDir}}
211
211
  Role: outliner
212
212
 
213
213
  ## Guardrails (read → act → write)
@@ -236,8 +236,8 @@ templates:
236
236
  writer.agents: |
237
237
  # AGENTS.md
238
238
 
239
- Team: {teamId}
240
- Shared workspace: {teamDir}
239
+ Team: {{teamId}}
240
+ Shared workspace: {{teamDir}}
241
241
  Role: writer
242
242
 
243
243
  ## Guardrails (read → act → write)
@@ -266,8 +266,8 @@ templates:
266
266
  editor.agents: |
267
267
  # AGENTS.md
268
268
 
269
- Team: {teamId}
270
- Shared workspace: {teamDir}
269
+ Team: {{teamId}}
270
+ Shared workspace: {{teamDir}}
271
271
  Role: editor
272
272
 
273
273
  ## Guardrails (read → act → write)
@@ -377,6 +377,9 @@ files:
377
377
  - path: shared-context/priorities.md
378
378
  template: sharedContext.priorities
379
379
  mode: createOnly
380
+ - path: shared-context/MEMORY_PLAN.md
381
+ template: sharedContext.memoryPlan
382
+ mode: createOnly
380
383
  - path: shared-context/agent-outputs/README.md
381
384
  template: sharedContext.agentOutputsReadme
382
385
  mode: createOnly
@@ -19,7 +19,7 @@ function interpolateTemplate(input: string | undefined, vars: Record<string, str
19
19
 
20
20
  function applyCronJobVars(
21
21
  scope: CronReconcileScope,
22
- j: { id: string; name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; enabledByDefault?: boolean },
22
+ j: { id: string; name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; enabledByDefault?: boolean; delivery?: 'none' | 'announce' },
23
23
  ): typeof j {
24
24
  const vars: Record<string, string> = {
25
25
  recipeId: scope.recipeId,
@@ -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 },
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' },
68
68
  wantEnabled: boolean
69
69
  ): Record<string, unknown> {
70
70
  const name =
@@ -91,21 +91,23 @@ function buildCronJobForCreate(
91
91
  sessionTarget,
92
92
  schedule: { kind: "cron", expr: j.schedule, ...(j.timezone ? { tz: j.timezone } : {}) },
93
93
  payload: effectiveAgentId ? { kind: "agentTurn", message: j.message } : { kind: "systemEvent", text: j.message },
94
- ...(j.channel || j.to
95
- ? {
96
- delivery: {
97
- mode: "announce",
98
- ...(j.channel ? { channel: j.channel } : {}),
99
- ...(j.to ? { to: j.to } : {}),
100
- bestEffort: true,
101
- },
102
- }
103
- : {}),
94
+ ...(j.delivery === 'none'
95
+ ? { delivery: { mode: "none" } }
96
+ : j.channel || j.to
97
+ ? {
98
+ delivery: {
99
+ mode: "announce",
100
+ ...(j.channel ? { channel: j.channel } : {}),
101
+ ...(j.to ? { to: j.to } : {}),
102
+ bestEffort: true,
103
+ },
104
+ }
105
+ : {}),
104
106
  };
105
107
  }
106
108
 
107
109
  function buildCronJobPatch(
108
- j: { name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string },
110
+ j: { name?: string; schedule?: string; timezone?: string; channel?: string; to?: string; agentId?: string; description?: string; message?: string; delivery?: 'none' | 'announce' },
109
111
  name: string
110
112
  ): CronJobPatch {
111
113
  const effectiveAgentId = typeof j.agentId === "string" && j.agentId.trim() ? j.agentId.trim() : undefined;
@@ -119,7 +121,9 @@ function buildCronJobPatch(
119
121
  schedule: { kind: "cron", expr: j.schedule, ...(j.timezone ? { tz: j.timezone } : {}) },
120
122
  payload: effectiveAgentId ? { kind: "agentTurn", message: j.message } : { kind: "systemEvent", text: j.message },
121
123
  };
122
- if (j.channel || j.to) {
124
+ if (j.delivery === 'none') {
125
+ patch.delivery = { mode: "none" };
126
+ } else if (j.channel || j.to) {
123
127
  patch.delivery = {
124
128
  mode: "announce",
125
129
  ...(j.channel ? { channel: j.channel } : {}),
@@ -229,11 +233,16 @@ async function cronAdd(api: OpenClawPluginApi, job: Record<string, unknown>): Pr
229
233
 
230
234
  // delivery
231
235
  const delivery = j.delivery;
232
- if (delivery && typeof delivery === "object" && String(delivery.mode ?? "") === "announce") {
233
- argv.push("--announce");
234
- if (delivery.channel) argv.push("--channel", String(delivery.channel));
235
- if (delivery.to) argv.push("--to", String(delivery.to));
236
- if (delivery.bestEffort) argv.push("--best-effort-deliver");
236
+ if (delivery && typeof delivery === "object") {
237
+ const deliveryMode = String(delivery.mode ?? "");
238
+ if (deliveryMode === "announce") {
239
+ argv.push("--announce");
240
+ if (delivery.channel) argv.push("--channel", String(delivery.channel));
241
+ if (delivery.to) argv.push("--to", String(delivery.to));
242
+ if (delivery.bestEffort) argv.push("--best-effort-deliver");
243
+ } else if (deliveryMode === "none") {
244
+ argv.push("--no-deliver");
245
+ }
237
246
  }
238
247
 
239
248
  const result = await api.runtime.system.runCommandWithTimeout(argv, { timeoutMs: 30_000 });
@@ -140,6 +140,45 @@ Default behavior: **quiet**. If there is nothing to report, do nothing.
140
140
  await writeFileSafely(path.join(teamDir, "TICKETS.md"), ticketsMd, mode);
141
141
  }
142
142
 
143
+ /**
144
+ * Write team-level files from the recipe files[] array.
145
+ *
146
+ * Per-role scaffolding (scaffoldTeamAgents) filters out paths starting with
147
+ * "shared-context/" and "notes/" because those belong to the team workspace root,
148
+ * not individual role directories. This function picks up those filtered paths
149
+ * and writes them to teamDir using the recipe's templates.
150
+ */
151
+ async function writeTeamLevelRecipeFiles(opts: {
152
+ recipe: RecipeFrontmatter;
153
+ teamId: string;
154
+ teamDir: string;
155
+ overwrite: boolean;
156
+ }) {
157
+ const { recipe, teamId, teamDir, overwrite } = opts;
158
+ const files = recipe.files ?? [];
159
+ if (!files.length) return;
160
+ const mode = overwrite ? "overwrite" : "createOnly";
161
+ const templates = (recipe.templates ?? {}) as Record<string, unknown>;
162
+ const vars = { teamId, teamDir };
163
+
164
+ for (const f of files) {
165
+ const filePath = String(f.path ?? "").trim();
166
+ if (!filePath) continue;
167
+ // Only process paths that are team-scoped (filtered out of per-role scaffolding).
168
+ if (!filePath.startsWith("shared-context/") && !filePath.startsWith("notes/")) continue;
169
+
170
+ const templateKey = String(f.template ?? "").trim();
171
+ if (!templateKey) continue;
172
+
173
+ const templateContent = templates[templateKey];
174
+ if (typeof templateContent !== "string") continue;
175
+
176
+ const rendered = renderTemplate(templateContent, vars);
177
+ const target = path.join(teamDir, filePath);
178
+ await writeFileSafely(target, rendered, mode);
179
+ }
180
+ }
181
+
143
182
  async function writeTeamMetadataAndConfig(opts: {
144
183
  api: OpenClawPluginApi;
145
184
  teamId: string;
@@ -392,6 +431,8 @@ export async function handleScaffoldTeam(
392
431
  const { loaded, recipe, cfg, workspaceRoot: baseWorkspace } = validation;
393
432
 
394
433
  // Lint (warn-only) for common team scaffolding pitfalls.
434
+ // NOTE: console.warn/error used throughout src/ for [recipes]-prefixed diagnostics.
435
+ // No plugin SDK logger available; these go to stderr which the host captures.
395
436
  for (const issue of lintRecipe(recipe)) {
396
437
  if (issue.level === "warn") console.warn(`[recipes] WARN ${issue.code}: ${issue.message}`);
397
438
  else console.warn(`[recipes] ${issue.code}: ${issue.message}`);
@@ -439,6 +480,11 @@ export async function handleScaffoldTeam(
439
480
  overwrite,
440
481
  qaChecklist,
441
482
  });
483
+ // Write team-level files from the recipe files[] array.
484
+ // Per-role scaffolding filters out shared-context/ and notes/ paths (those belong to teamDir).
485
+ // We render and write them here so recipe-defined team assets are not silently dropped.
486
+ await writeTeamLevelRecipeFiles({ recipe, teamId, teamDir, overwrite });
487
+
442
488
  const heartbeat = buildHeartbeatCronJobsFromTeamRecipe({
443
489
  teamId,
444
490
  recipe,
@@ -11,6 +11,8 @@ export type CronJobSpec = {
11
11
  to?: string;
12
12
  agentId?: string;
13
13
  enabledByDefault?: boolean;
14
+ /** Delivery mode: "none" suppresses announce; "announce" delivers to chat. Omit to use gateway default. */
15
+ delivery?: 'none' | 'announce';
14
16
  };
15
17
 
16
18
  /** Raw input for a cron job from YAML (supports message/task/prompt for backward compat). */
@@ -27,6 +29,7 @@ type CronJobInput = {
27
29
  to?: unknown;
28
30
  agentId?: unknown;
29
31
  enabledByDefault?: unknown;
32
+ delivery?: unknown;
30
33
  };
31
34
 
32
35
  export type RecipeFrontmatter = {
@@ -83,6 +86,7 @@ function buildCronJobSpec(j: CronJobInput, id: string): CronJobSpec {
83
86
  to: j.to != null ? String(j.to) : undefined,
84
87
  agentId: j.agentId != null ? String(j.agentId) : undefined,
85
88
  enabledByDefault: Boolean(j.enabledByDefault ?? false),
89
+ delivery: j.delivery === 'none' || j.delivery === 'announce' ? j.delivery : undefined,
86
90
  };
87
91
  }
88
92