@jiggai/recipes 0.4.25 → 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 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
- .action(async (options: { teamId?: string; leaseSeconds?: number }) => {
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
  });
@@ -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.25",
5
+ "version": "0.4.26",
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.25",
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",
@@ -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
 
@@ -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.