@jiggai/recipes 0.4.71 → 0.4.72

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.71",
5
+ "version": "0.4.72",
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.71",
3
+ "version": "0.4.72",
4
4
  "description": "ClawRecipes plugin for OpenClaw (markdown recipes -> scaffold agents/teams)",
5
5
  "main": "index.ts",
6
6
  "type": "commonjs",
@@ -305,14 +305,12 @@ export async function executeWorkflowNodes(opts: {
305
305
  `- openclaw recipes workflows resume --team-id ${teamId} --run-id ${runId}`,
306
306
  ].join('\n');
307
307
 
308
- // Deliver the approval prompt. Wrap in try/catch + Telegram bot-API
309
- // fallback because OpenClaw 2026.4.26's gateway has been observed to
310
- // return "Tool not available: message" from /tools/invoke even though
311
- // the channel itself is healthy. The approval.json file is durable
312
- // either way, so we never fail the run on delivery error — operators
313
- // can still approve via the kitchen UI or `openclaw recipes workflows
314
- // approve` CLI when this falls through.
315
- let approvalDelivered = false;
308
+ // Deliver the approval prompt via the OpenClaw `message` tool. The tool
309
+ // requires the calling agent to have `group:messaging` (or `message`)
310
+ // in its tools.allow policy see openclaw/openclaw#74780. If delivery
311
+ // fails (misconfigured policy, channel adapter down, etc.) we log and
312
+ // continue; approval.json is durable, so operators can still approve
313
+ // via the kitchen UI or `openclaw recipes workflows approve` CLI.
316
314
  try {
317
315
  await toolsInvoke<ToolTextResult>(api, {
318
316
  tool: 'message',
@@ -324,39 +322,10 @@ export async function executeWorkflowNodes(opts: {
324
322
  message: msg,
325
323
  },
326
324
  });
327
- approvalDelivered = true;
328
325
  } catch (err) {
329
326
  const errMsg = err instanceof Error ? err.message : String(err);
330
327
  console.warn(`[workflow] tools.invoke('message') failed for run ${runId} on node ${node.id}: ${errMsg}`);
331
- if (channel === 'telegram') {
332
- try {
333
- const cfg = await loadOpenClawConfig(api);
334
- const tgToken = (cfg as { channels?: { telegram?: { botToken?: string } } })
335
- .channels?.telegram?.botToken;
336
- if (tgToken) {
337
- const tgRes = await fetch(`https://api.telegram.org/bot${tgToken}/sendMessage`, {
338
- method: 'POST',
339
- headers: { 'content-type': 'application/json' },
340
- body: JSON.stringify({ chat_id: target, text: msg }),
341
- });
342
- if (tgRes.ok) {
343
- approvalDelivered = true;
344
- console.log(`[workflow] approval delivered via direct telegram bot API for run ${runId}`);
345
- } else {
346
- const tgBody = await tgRes.text().catch(() => '');
347
- console.error(`[workflow] telegram fallback failed (${tgRes.status}) for run ${runId}: ${tgBody}`);
348
- }
349
- } else {
350
- console.error(`[workflow] telegram fallback skipped for run ${runId}: missing channels.telegram.botToken in openclaw config`);
351
- }
352
- } catch (fbErr) {
353
- const fbMsg = fbErr instanceof Error ? fbErr.message : String(fbErr);
354
- console.error(`[workflow] telegram fallback threw for run ${runId}: ${fbMsg}`);
355
- }
356
- }
357
- if (!approvalDelivered) {
358
- console.warn(`[workflow] approval message not delivered for run ${runId}; approve via kitchen UI or CLI`);
359
- }
328
+ console.warn(`[workflow] approval message not delivered for run ${runId}; approve via kitchen UI or CLI`);
360
329
  }
361
330
 
362
331
  const waitingTs = new Date().toISOString();
@@ -12,7 +12,7 @@ import type { WorkflowLane, WorkflowNode, RunLog } from './workflow-types';
12
12
  import { dequeueNextTask, enqueueTask, hasPendingTaskFor, releaseTaskClaim, compactQueue } from './workflow-queue';
13
13
  import { currentLockOwner, isLockHolderDead } from './lock-liveness';
14
14
  import { loadPriorLlmInput, loadProposedPostTextFromPriorNode } from './workflow-node-output-readers';
15
- import { readTextFile } from './workflow-runner-io';
15
+ import { readTextFile, readJsonFile } from './workflow-runner-io';
16
16
  import { resolveApprovalBindingTarget } from './workflow-node-executor';
17
17
  import { buildKitchenWorkflowReviewUrl } from './kitchen-review-url';
18
18
  import {
@@ -45,7 +45,7 @@ async function buildMemoryContext(teamDir: string): Promise<string> {
45
45
  // Read pinned items first (highest priority)
46
46
  const pinnedPath = path.join(memoryDir, 'pinned.jsonl');
47
47
  if (await fileExists(pinnedPath)) {
48
- const pinnedContent = await fs.readFile(pinnedPath, 'utf8');
48
+ const pinnedContent = await readTextFile(pinnedPath);
49
49
  const pinnedItems = pinnedContent.trim().split('\n').filter(Boolean);
50
50
 
51
51
  if (pinnedItems.length > 0) {
@@ -75,7 +75,7 @@ async function buildMemoryContext(teamDir: string): Promise<string> {
75
75
  if (currentTokens > maxTokens * 0.8) break; // Leave room for recent items
76
76
 
77
77
  const filePath = path.join(memoryDir, filename);
78
- const content = await fs.readFile(filePath, 'utf8');
78
+ const content = await readTextFile(filePath);
79
79
  const items = content.trim().split('\n').filter(Boolean);
80
80
 
81
81
  if (items.length > 0) {
@@ -151,7 +151,7 @@ async function buildTemplateVars(
151
151
  if (nid && nrOutPath) {
152
152
  try {
153
153
  const outAbs = path.resolve(teamDir, nrOutPath);
154
- const outputContent = await fs.readFile(outAbs, 'utf8');
154
+ const outputContent = await readTextFile(outAbs);
155
155
  vars[`${nid}.output`] = outputContent;
156
156
 
157
157
  try {
@@ -314,7 +314,7 @@ async function checkWaitingHandoffs(api: OpenClawPluginApi, teamId: string, team
314
314
  const runPath = path.join(runDir, 'run.json');
315
315
  let run: RunLog;
316
316
  try {
317
- const raw = await fs.readFile(runPath, 'utf8');
317
+ const raw = await readTextFile(runPath);
318
318
  run = JSON.parse(raw) as RunLog;
319
319
  } catch { continue; }
320
320
 
@@ -335,7 +335,7 @@ async function checkWaitingHandoffs(api: OpenClawPluginApi, teamId: string, team
335
335
  nodeOutputRel: string;
336
336
  };
337
337
  try {
338
- marker = JSON.parse(await fs.readFile(waitPath, 'utf8'));
338
+ marker = await readJsonFile<typeof marker>(waitPath);
339
339
  } catch { continue; }
340
340
 
341
341
  // Check timeout
@@ -416,7 +416,7 @@ async function checkWaitingHandoffs(api: OpenClawPluginApi, teamId: string, team
416
416
  const workflowsDir = path.join(teamDir, 'shared-context', 'workflows');
417
417
  let workflow;
418
418
  try {
419
- const wfRaw = await fs.readFile(path.join(workflowsDir, run.workflow.file), 'utf8');
419
+ const wfRaw = await readTextFile(path.join(workflowsDir, run.workflow.file));
420
420
  workflow = normalizeWorkflow(JSON.parse(wfRaw));
421
421
  } catch { workflow = null; }
422
422
 
@@ -1505,7 +1505,7 @@ export async function runWorkflowWorkerTick(api: OpenClawPluginApi, opts: {
1505
1505
  if (!wf.endsWith('.json')) continue;
1506
1506
  try {
1507
1507
  const wfPath = path.join(targetWorkflowsDir, wf);
1508
- const wfRaw = await fs.readFile(wfPath, 'utf8');
1508
+ const wfRaw = await readTextFile(wfPath);
1509
1509
  const wfParsed = JSON.parse(wfRaw);
1510
1510
  if (String(wfParsed.id ?? '') === targetWorkflowId || String(wfParsed.name ?? '') === targetWorkflowId) {
1511
1511
  targetWorkflowFile = wf;