@zibby/workflow-templates 0.3.0 → 0.4.2

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.
Files changed (34) hide show
  1. package/browser-test-automation/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  2. package/browser-test-automation/package.json +1 -0
  3. package/code-analysis/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  4. package/code-analysis/nodes/generate-code-node.js +12 -2
  5. package/index.js +235 -0
  6. package/notify-lark/README.md +88 -0
  7. package/notify-lark/graph.mjs +43 -0
  8. package/notify-lark/icon.png +0 -0
  9. package/notify-lark/nodes/notify-lark-node.js +303 -0
  10. package/notify-lark/package.json +18 -0
  11. package/notify-lark/state.js +85 -0
  12. package/notify-notion/README.md +71 -0
  13. package/notify-notion/graph.mjs +64 -0
  14. package/notify-notion/icon.png +0 -0
  15. package/notify-notion/nodes/notify-notion-node.js +342 -0
  16. package/notify-notion/package.json +19 -0
  17. package/notify-notion/state.js +110 -0
  18. package/notify-slack/README.md +94 -0
  19. package/notify-slack/graph.mjs +51 -0
  20. package/notify-slack/icon.png +0 -0
  21. package/notify-slack/nodes/notify-slack-node.js +268 -0
  22. package/notify-slack/package.json +18 -0
  23. package/notify-slack/state.js +112 -0
  24. package/package.json +17 -3
  25. package/sentry-triage/graph.mjs +81 -0
  26. package/sentry-triage/icon.png +0 -0
  27. package/sentry-triage/nodes/classify-node.js +38 -0
  28. package/sentry-triage/nodes/dispatch-alerts-node.js +191 -0
  29. package/sentry-triage/nodes/fetch-issues-node.js +52 -0
  30. package/sentry-triage/nodes/filter-noise-node.js +112 -0
  31. package/sentry-triage/package.json +18 -0
  32. package/sentry-triage/prompts/classify.md +76 -0
  33. package/sentry-triage/prompts/fetch-issues.md +66 -0
  34. package/sentry-triage/state.js +134 -0
@@ -0,0 +1 @@
1
+ {"version":"4.1.5","results":[[":__tests__/preflight-early-exit.test.mjs",{"duration":6.5747499999999945,"failed":false}]]}
@@ -7,6 +7,7 @@
7
7
  "main": "graph.mjs",
8
8
  "dependencies": {
9
9
  "@zibby/core": "^0.5.1",
10
+ "@zibby/ui-memory": "^1.0.0",
10
11
  "zod": "^3.23.0"
11
12
  }
12
13
  }
@@ -0,0 +1 @@
1
+ {"version":"4.1.5","results":[[":nodes/__tests__/middleware.integration.test.js",{"duration":0,"failed":true}],[":nodes/__tests__/finalizeNode.test.js",{"duration":8.396791000000007,"failed":false}]]}
@@ -5,8 +5,9 @@
5
5
  */
6
6
 
7
7
  import { spawn } from 'child_process';
8
- import { join, resolve } from 'path';
8
+ import { dirname, join, resolve } from 'path';
9
9
  import { existsSync, readFileSync } from 'fs';
10
+ import { fileURLToPath } from 'url';
10
11
  import Handlebars from 'handlebars';
11
12
  import { invokeAgent } from '@zibby/core';
12
13
  import { generatePRMeta } from './services/prMetaService.js';
@@ -14,6 +15,14 @@ import { adfToText } from '@zibby/core/utils/adf-converter.js';
14
15
  import { getRepoPath } from './utils/get-repo-path.js';
15
16
  import { z } from 'zod';
16
17
 
18
+ // Prompts ship inside the workflow bundle at `<template>/prompts/`.
19
+ // state.promptsDir is the runner-injection slot for callers that want
20
+ // to override (legacy analyze-graph CLI set it explicitly); when unset,
21
+ // resolve relative to this node file so the template is self-contained
22
+ // and works for any runner.
23
+ const __nodeDir = dirname(fileURLToPath(import.meta.url));
24
+ const DEFAULT_PROMPTS_DIR = join(__nodeDir, '..', 'prompts');
25
+
17
26
  const CodeImplementationOutputSchema = z.object({
18
27
  success: z.boolean(),
19
28
  codeImplementation: z.object({
@@ -60,7 +69,8 @@ export function createCodeGenerationNode(options = {}) {
60
69
  const mode = commitAndPush ? 'implementing' : 'generating preview of';
61
70
  console.log(`\nšŸ’» ${commitAndPush ? 'Implementing' : 'Generating'} code implementation...`);
62
71
 
63
- const { workspace, ticketContext, repos, promptsDir, model, nodeConfigs = {} } = state;
72
+ const { workspace, ticketContext, repos, model, nodeConfigs = {} } = state;
73
+ const promptsDir = state.promptsDir || DEFAULT_PROMPTS_DIR;
64
74
  const aiModel = model || ticketContext.model || 'auto';
65
75
  const _nodeConfig = nodeConfigs[nodeName] || {};
66
76
  const analysis = state.analyze_ticket?.analysis;
package/index.js CHANGED
@@ -165,6 +165,241 @@ export const TEMPLATES = {
165
165
  'Produce specs that an AI agent can execute end-to-end',
166
166
  ],
167
167
  },
168
+ },
169
+
170
+ // ── notify-slack: reusable notifier child workflow ────────────────
171
+ // Dispatched as a sub-graph from any parent that wants Slack alerts.
172
+ // Single-node graph, no LLM, deterministic API call.
173
+ 'notify-slack': {
174
+ name: 'notify-slack',
175
+ displayName: 'Notify Slack',
176
+ description: 'Reusable child workflow — posts a structured Block Kit alert to a Slack channel. Dispatched by other workflows (Sentry triage, autofix, incident) via sub-graph.',
177
+ path: join(__dirname, 'notify-slack'),
178
+ defaultSlug: 'alert-slack',
179
+ deps: { zod: '^3.23.0' },
180
+ features: [
181
+ 'Single-node, no LLM — deterministic ~500ms post',
182
+ 'Block Kit message with severity-coded color + emoji',
183
+ 'Optional Sentry-flavored fields (users affected, events, release)',
184
+ 'Action buttons + caller-supplied @-mentions',
185
+ 'Returns messageTs so parent can thread follow-ups',
186
+ ],
187
+ marketplace: {
188
+ slug: 'notify-slack',
189
+ tagline: 'Reusable Slack alert worker — dispatch from any workflow.',
190
+ iconPrompt: [
191
+ 'Flat geometric vector illustration with subtle clean gradients, in the spirit of Linear / Notion / Stripe iconography — crisp, no painterly textures.',
192
+ 'Subject: the iconic Slack pinwheel mark — four chunky rounded-rectangle "petals" arranged in a plus / asterisk configuration, colored with Slack\'s signature palette (top-left red #E01E5A, top-right yellow #ECB22E, bottom-right green #2EB67D, bottom-left blue #36C5F0), painted as if it\'s the focal brand mark of the icon. A small bright magenta notification dot floats in the upper-right corner of the pinwheel suggesting an incoming alert/ping.',
193
+ 'Background: deep navy (#0B0F1A) rounded square (1024Ɨ1024) with a faint radial glow centered behind the pinwheel so the colors pop without becoming oversaturated.',
194
+ 'Centered composition with the pinwheel as the dominant focal element, the notification dot as a small secondary accent in the upper-right; plenty of breathing room so the silhouette reads at 64Ɨ64 in the marketplace grid.',
195
+ 'Mood: focused, energetic, signal-not-noise — the canonical Slack-flavored notification worker.',
196
+ 'NO text, NO letters, NO photo-realism, NO sleek 3D render, NO literal Slack wordmark — the colored pinwheel shape is allowed as the brand reference.',
197
+ ].join('\n'),
198
+ category: 'Notifications',
199
+ tags: ['slack', 'notification', 'alert', 'child-workflow'],
200
+ capabilities: [
201
+ 'Severity-coded Block Kit message (low/medium/high/critical)',
202
+ 'Code snippet + action button + caller mentions',
203
+ 'Sub-graph dispatchable from any parent workflow',
204
+ 'Returns messageTs for thread replies',
205
+ ],
206
+ conversationStarters: [
207
+ 'Post a CRITICAL alert to #incidents from this workflow',
208
+ 'Send a daily summary to #dev-updates',
209
+ 'Notify @oncall when a Sentry issue exceeds threshold',
210
+ 'Forward the deploy-fail message to #ops',
211
+ ],
212
+ },
213
+ },
214
+
215
+ // ── notify-lark: same as notify-slack, Lark / Feishu variant ─────
216
+ 'notify-lark': {
217
+ name: 'notify-lark',
218
+ displayName: 'Notify Lark',
219
+ description: 'Reusable child workflow — posts a structured Interactive Card to a Lark / Feishu chat. Dispatched by other workflows via sub-graph.',
220
+ path: join(__dirname, 'notify-lark'),
221
+ defaultSlug: 'alert-lark',
222
+ deps: { zod: '^3.23.0' },
223
+ features: [
224
+ 'Single-node, no LLM',
225
+ 'Lark Interactive Card with severity template (red/orange/yellow/grey)',
226
+ 'Optional Sentry-flavored metadata fields',
227
+ 'Token cache across multiple sends in one task',
228
+ 'Returns messageId for threaded replies',
229
+ ],
230
+ marketplace: {
231
+ slug: 'notify-lark',
232
+ tagline: 'Reusable Lark / Feishu alert worker — dispatch from any workflow.',
233
+ iconPrompt: [
234
+ 'A clean, modern app icon for a Lark / Feishu notification worker.',
235
+ '',
236
+ 'Visual style: flat geometric vector with subtle gradient, complementary to the notify-slack icon (same family).',
237
+ 'Subject: a stylized speech-bubble silhouette in Lark-cyan-to-blue gradient (#00D6B9 → #1664FF) with a checkmark inside. Gentle motion lines behind it.',
238
+ 'Background: deep navy (#0B0F1A) rounded square (1024Ɨ1024).',
239
+ 'Mood: focused, professional, signal-not-noise.',
240
+ 'NO Lark / Feishu logo trademark, NO text, NO photo-realism.',
241
+ ].join('\n'),
242
+ category: 'Notifications',
243
+ tags: ['lark', 'feishu', 'notification', 'alert', 'child-workflow'],
244
+ capabilities: [
245
+ 'Severity-coded Lark Interactive Card',
246
+ 'Auto-detects receive_id_type from id prefix (chat_id / open_id / email)',
247
+ 'Sub-graph dispatchable from any parent workflow',
248
+ 'Per-process token cache for fan-out efficiency',
249
+ ],
250
+ conversationStarters: [
251
+ 'Send a CRITICAL alert to the engineering Lark group',
252
+ 'Notify the on-call group chat when Sentry issue spikes',
253
+ 'Forward deploy notifications to our Lark channel',
254
+ ],
255
+ },
256
+ },
257
+
258
+ // ── notify-notion: same as notify-slack/-lark, Notion variant ────
259
+ // Two write modes: create a new page in a database, or append blocks
260
+ // to an existing page. Rich-mode delegates to
261
+ // @zibby/skills/report's reportToNotionBlocks. See notify-notion/README.md.
262
+ 'notify-notion': {
263
+ name: 'notify-notion',
264
+ displayName: 'Notify Notion',
265
+ description: 'Reusable child workflow — creates a Notion page in a database OR appends blocks to an existing page. Dispatched by other workflows (digest, incident archives, weekly reports) via sub-graph.',
266
+ path: join(__dirname, 'notify-notion'),
267
+ defaultSlug: 'archive-notion',
268
+ deps: { zod: '^3.23.0', '@zibby/skills': '^0.1.25' },
269
+ features: [
270
+ 'Single-node, no LLM — deterministic ~1-2s Notion REST call',
271
+ 'Two write modes: create page in database OR append children to existing page',
272
+ 'Rich-mode renders report objects via @zibby/skills/report (heading, callouts, tables, trend code blocks)',
273
+ 'Legacy-mode renders simple severity / title / body alerts (sentry-triage et al.)',
274
+ 'Severity-mapped page-icon emoji (low/medium/high/critical or rich-mode delta severity)',
275
+ 'Surfaces typed errors for 401 (token revoked), 404 (target not shared with bot), 429 (rate-limited)',
276
+ 'Returns pageId + pageUrl for downstream linkage from notify-slack / notify-lark messages',
277
+ ],
278
+ marketplace: {
279
+ slug: 'notify-notion',
280
+ tagline: 'Reusable Notion archiver — durable record for any workflow.',
281
+ iconPrompt: [
282
+ 'Hand-painted gouache illustration with soft brushwork and gentle painterly texture, in the same family as the sentry-triage and generate-test-cases marketplace icons but with its own distinct character.',
283
+ 'Subject: a friendly anthropomorphic notebook-document mascot — a small rounded notebook character with two big smiling eyes and a rosy blush, its open pages showing three painted horizontal ink-lines and a tiny checkmark in the corner. A soft halo of two or three little pastel page-flutter sparkles dance around it, suggesting a freshly-written entry being archived.',
284
+ 'Background: a pale neutral Notion-flavored off-white gradient — warm cream at the top blending into a soft dove-grey at the base (#F7F3EC → #E8E4DA), with a single faint paper-grain texture and a couple of small floating pastel ink-spot dots for friendliness.',
285
+ 'Centered composition with the notebook character as the focal point in the lower-center, sparkles arcing across the upper third; plenty of breathing room so the silhouette reads at 64Ɨ64 in the marketplace grid.',
286
+ 'Mood is calm, archival, gently studious — the friendly notebook companion that keeps a tidy record, NOT corporate productivity or wall-of-text database.',
287
+ 'Soft rounded square 1024Ɨ1024 canvas with a subtle paper-grain texture.',
288
+ 'NO text, NO logo or trademarked marks, NO photo-realism, NO sleek 3D render, NO literal Notion trademark.',
289
+ ].join('\n'),
290
+ category: 'Operations',
291
+ tags: ['notion', 'docs', 'reporting', 'knowledge-base', 'archive'],
292
+ capabilities: [
293
+ 'Create a new page in a Notion database (POST /v1/pages)',
294
+ 'Append blocks to an existing page (PATCH /v1/blocks/{pageId}/children)',
295
+ 'Renders rich report-objects to native Notion blocks (headings, callouts, tables, code, embeds)',
296
+ 'Severity-mapped page-icon emoji + colored callout backgrounds',
297
+ 'Sub-graph dispatchable from any parent workflow',
298
+ 'Maps 401 / 404 / 429 to typed errors so callers can react cleanly',
299
+ ],
300
+ conversationStarters: [
301
+ 'Archive the weekly digest to our Notion reports database',
302
+ 'Append today\'s incident summary to the on-call runbook page',
303
+ 'Create a Notion page for every new Sentry CRITICAL',
304
+ 'Drop the deploy-summary into our engineering Notion every Friday',
305
+ ],
306
+ },
307
+ },
308
+
309
+ // ── sentry-triage: parent workflow that uses notify-slack/-lark ──
310
+ 'sentry-triage': {
311
+ name: 'sentry-triage',
312
+ displayName: 'Sentry Triage Bot',
313
+ description: 'Hourly Sentry triage — pulls new issues, drops obvious noise with a regex pre-filter, classifies survivors with LLM (CRITICAL/HIGH/MEDIUM/LOW/NOISE), and fans out alerts to a notify-slack OR notify-lark child workflow.',
314
+ path: join(__dirname, 'sentry-triage'),
315
+ defaultSlug: 'sentry-triage',
316
+ deps: { zod: '^3.23.0' },
317
+ features: [
318
+ '4-node graph: fetch → filter_noise → classify → dispatch_alerts',
319
+ 'Regex noise filter before LLM cuts ~80% of classification cost',
320
+ 'LLM severity classifier with explicit rubric (rules 1-5)',
321
+ 'Sub-graph fan-out to notify-slack OR notify-lark (choose at deploy)',
322
+ 'Per-issue failure isolation — one Slack hiccup doesn\'t stall the run',
323
+ 'Configurable severityThreshold (don\'t notify on LOW noise)',
324
+ 'Cron-friendly: hourly schedule, default sinceMinutes=60',
325
+ ],
326
+ marketplace: {
327
+ slug: 'sentry-triage',
328
+ tagline: 'Filter noise, classify severity, ping the right channel — every hour.',
329
+ iconPrompt: [
330
+ 'Hand-painted storybook illustration in a warm gouache style with soft brushwork and gentle painterly texture, featuring the friendly round lighthouse mascot character with two big smiling eyes and a rosy blush on its white-and-coral-striped tower body, perched on a tiny mint-green island and clutching a small glowing purple SHIELD BADGE in front of its body — the badge is a rounded geometric emblem in Sentry\'s signature deep violet (#362D59 / #7553FF) with a stylized white "S"-mark inside it formed from overlapping rounded parallelogram shapes, painted with the same soft gouache brushstrokes as the rest of the scene so it feels integrated rather than corporate.',
331
+ 'The lighthouse lantern emits a soft golden beam that catches one glowing amber alert orb while three faded grey noise specks drift harmlessly past, reinforcing the "filter the signal, calm the noise" idea.',
332
+ 'Background is a soft sunrise gradient of pale peach at the top blending through buttercream into a gentle wash of dusty lavender at the base, tying the warm scene to the violet of the badge; a few small fluffy pastel clouds float in for friendliness.',
333
+ 'Centered composition with the purple shield badge as the immediate focal point in the lower-center, the lighthouse rising behind and slightly above it, beam angled diagonally; plenty of breathing room so the silhouette reads at 64Ɨ64 with the violet badge clearly visible at a glance.',
334
+ 'Mood is warm, reassuring, optimistic — the friendly Sentry-flavored night-watch character, NOT tactical or corporate or alarming.',
335
+ 'Soft rounded square 1024Ɨ1024 canvas with a subtle paper-grain texture.',
336
+ 'NO text, NO letters, NO photo-realism, NO sleek 3D render, NO magnifying glass, NO speech bubbles, NO dark navy or near-black backgrounds, NO bug or insect imagery, NO literal Sentry wordmark.',
337
+ ].join('\n'),
338
+ category: 'Operations',
339
+ tags: ['sentry', 'observability', 'on-call', 'triage', 'alerting'],
340
+ capabilities: [
341
+ 'Hourly scheduled triage of new Sentry issues',
342
+ 'Deterministic regex filter drops Script error / ResizeObserver / extension noise',
343
+ 'LLM severity classifier with auditable rubric',
344
+ 'Dispatches to notify-slack or notify-lark (sub-graph, ~5ms in-process)',
345
+ 'CRITICAL alerts get caller-supplied @-mentions; lower severities don\'t',
346
+ 'Configurable severity threshold per deploy',
347
+ ],
348
+ conversationStarters: [
349
+ 'Triage all new Sentry issues from the last hour',
350
+ 'Notify #sentry-alerts when severity is HIGH or above',
351
+ 'Run hourly and post a summary to our team Slack',
352
+ 'Page on-call when a CRITICAL error appears in checkout',
353
+ ],
354
+ },
355
+ },
356
+
357
+ // ── ai-spend-weekly-digest: cross-silo billing digest ─────────────
358
+ 'ai-spend-weekly-digest': {
359
+ name: 'ai-spend-weekly-digest',
360
+ displayName: 'AI Spend Weekly Digest',
361
+ description: 'Weekly digest of OpenAI / Anthropic / Cursor admin billing — pulls trailing-28d cost+usage across all three providers, detects per-project anomalies vs 3-week baseline, and posts a rich report card to Lark and/or Slack via in-process sub-graph dispatch.',
362
+ path: join(__dirname, 'ai-spend-weekly-digest'),
363
+ defaultSlug: 'ai-spend-weekly-digest',
364
+ deps: { zod: '^3.23.0', '@zibby/skills': '^0.1.25' },
365
+ features: [
366
+ '3-node graph: fetch_spending → analyze (LLM narrative) → dispatch_digest',
367
+ 'Parallel admin-API fetch via Promise.allSettled — one provider down doesn\'t kill the run',
368
+ 'Customer attribution via OpenAI project / Anthropic workspace / Cursor user metadata (no manual mapping required)',
369
+ 'Anomaly detection over per-key σ + ratio fallback — flags spend spikes vs 3-week baseline',
370
+ 'Renders to native Slack Block-Kit / Lark Card via reportToBlockKit / reportToLarkCard — zero chart-image dependency',
371
+ 'Parallel sub-graph dispatch to notify-slack + notify-lark (~5ms in-process overhead)',
372
+ 'Cron-friendly: weekly schedule, default Monday 08:00 local',
373
+ ],
374
+ marketplace: {
375
+ slug: 'ai-spend-weekly-digest',
376
+ tagline: 'Track and explain your OpenAI / Anthropic / Cursor spending — every Monday morning, in Lark or Slack.',
377
+ iconPrompt: [
378
+ 'Hand-painted gouache illustration with soft brushwork and gentle painterly texture, featuring a friendly chubby pastel-pink piggy-bank character with two big smiling eyes and rosy blush, its body marked with a soft glowing dollar-sign sigil; the piggy is gently cradling a small stack of three coloured coins floating just above its back — a sky-blue coin for OpenAI-ish, a warm violet coin for Anthropic-ish, and a soft mint-green coin for Cursor-ish — each coin painted in the same loose gouache style with no logos or text on them, just abstract round chips.',
379
+ 'A thin painterly trend-line ribbon arcs gently upward behind the piggy from lower-left to upper-right, suggesting "money over time", rendered as a soft watercolor ribbon in dusty rose with a tiny gentle peak near the top right.',
380
+ 'Background is a calm sunrise gradient of pale buttercream at the top blending through soft peach into a gentle wash of pale mint at the base, with a few small pastel clouds for friendliness.',
381
+ 'Centered composition with the piggy as the immediate focal point in the lower-center, the floating coins arcing across the upper third, the trend ribbon as background scaffolding; plenty of breathing room so the silhouette reads at 64Ɨ64.',
382
+ 'Mood is warm, optimistic, gently informative — feels like a thoughtful finance friend, NOT corporate spreadsheet, NOT alarmist red.',
383
+ 'Soft rounded square 1024Ɨ1024 canvas with a subtle paper-grain texture.',
384
+ 'NO text, NO letters, NO numbers, NO photo-realism, NO sleek 3D render, NO chart axes or grid lines, NO dark navy or near-black backgrounds, NO literal OpenAI / Anthropic / Cursor logos or wordmarks, NO bar charts.',
385
+ ].join('\n'),
386
+ category: 'Operations',
387
+ tags: ['cost', 'finance', 'reporting', 'openai', 'anthropic', 'cursor', 'digest', 'weekly'],
388
+ capabilities: [
389
+ 'Pulls org-wide cost+usage from OpenAI, Anthropic, and Cursor admin APIs in parallel',
390
+ 'Joins customer attribution from provider-native project / workspace / member metadata',
391
+ 'Detects per-project anomalies (σ + ratio) against a 3-week rolling baseline',
392
+ 'Drafts the leadership-grade narrative with an LLM, falls back to deterministic copy if model is unavailable',
393
+ 'Posts a rich Block-Kit / Lark Card report (trend bars, top spenders table, anomalies, provider breakdown)',
394
+ 'Fan-out to Lark + Slack in parallel — partial-failure resilient',
395
+ ],
396
+ conversationStarters: [
397
+ 'Run a weekly AI spend digest every Monday morning',
398
+ 'Tell me which OpenAI projects spiked spending this week',
399
+ 'Compare Anthropic spend vs the 3-week baseline',
400
+ 'Post the digest to our #leadership Lark group + #eng Slack channel',
401
+ ],
402
+ },
168
403
  }
169
404
  };
170
405
 
@@ -0,0 +1,88 @@
1
+ # notify-lark
2
+
3
+ A reusable **child workflow** that posts an Interactive Card alert to a Lark / Feishu chat.
4
+
5
+ Companion to `notify-slack` — same provider-neutral input shape, so a parent workflow can fan out to BOTH Slack and Lark with the same `input` block (just swap `channel` → `receiveId`).
6
+
7
+ ## What it does
8
+
9
+ - Takes a provider-neutral payload (severity, title, body, optional Sentry-flavored context)
10
+ - Builds a Lark Interactive Card with severity-coded header template + emoji + action buttons
11
+ - Resolves `tenant_access_token` from the app id/secret stored in your Lark integration
12
+ - POSTs to `/open-apis/im/v1/messages` with the right `receive_id_type` inferred from the id prefix
13
+
14
+ No LLM call — single deterministic API request, typically <500ms (or <1s on cold token cache).
15
+
16
+ ## Dispatch shape (parent workflow)
17
+
18
+ ```js
19
+ graph.addNode('alert', {
20
+ workflow: 'notify-lark',
21
+ async: false,
22
+ input: (state) => ({
23
+ severity: 'critical',
24
+ title: 'Checkout: TypeError on session.user.id',
25
+ body: '**12 users** affected in the last 1h. Likely regression from `1.42.0`.',
26
+ receiveId: 'oc_abc123def456...', // chat id, open id, or email
27
+ sentryLink: 'https://sentry.io/.../1234567890/',
28
+ affectedUsers: 12,
29
+ events: 47,
30
+ release: '1.42.0',
31
+ firstSeen: '8 min ago',
32
+ codeSnippet:'src/handlers/checkout.ts:142\nconst userId = session.user.id;',
33
+ mentions: ['<at user_id="ou_oncall_group">@backend-oncall</at>'],
34
+ }),
35
+ output: 'notify_lark.messageId',
36
+ });
37
+ ```
38
+
39
+ ## receiveId formats
40
+
41
+ The `receive_id_type` query param is inferred from the prefix:
42
+
43
+ | Prefix | Type | Description |
44
+ |---|---|---|
45
+ | `oc_` | `chat_id` | Group chat or DM (most common) |
46
+ | `ou_` | `open_id` | Direct message to a specific user |
47
+ | `on_` | `union_id` | Cross-app stable user id |
48
+ | `cli_` | `app_id` | App-to-app message |
49
+ | `<email>@…` | `email` | Send to user by email |
50
+
51
+ If you don't know your chat id, DM the bot `whoami` and it'll respond with the caller's `open_id`. Group chat ids appear in the URL when you open the chat in browser.
52
+
53
+ ## Output
54
+
55
+ ```js
56
+ { delivered: true, receiveId: 'oc_abc...', receiveIdType: 'chat_id', messageId: 'om_xxxxx' }
57
+ ```
58
+
59
+ The `messageId` lets the parent post threaded replies later (e.g. incident-commander progress updates).
60
+
61
+ ## Prerequisites
62
+
63
+ Project must have the **Lark** integration connected with:
64
+ - `im:message` scope (send messages)
65
+ - `im:message.group_msg` scope (send to group chats)
66
+
67
+ ## Severity → header template
68
+
69
+ | Severity | Lark template | Emoji |
70
+ |---|---|---|
71
+ | low | `grey` | ⚪ |
72
+ | medium | `yellow` | 🟔 |
73
+ | high | `orange` | 🟠 |
74
+ | critical | `red` | 🚨 |
75
+
76
+ ## Tests
77
+
78
+ ```bash
79
+ cd packages/workflow-templates/notify-lark
80
+ npm test
81
+ ```
82
+
83
+ Tests cover:
84
+ - Card rendering for each severity
85
+ - Conditional sections (fields/body/code/actions only when input present)
86
+ - receive_id_type inference per prefix
87
+ - Tenant token caching across multiple sends
88
+ - Error mapping (invalid receive_id, scope missing, network blip)
@@ -0,0 +1,43 @@
1
+ /**
2
+ * notify-lark — single-node child workflow.
3
+ *
4
+ * Companion to notify-slack; same provider-neutral input shape so a
5
+ * parent can `dispatchSubgraph('notify-slack')` AND
6
+ * `dispatchSubgraph('notify-lark')` with the same input block, just
7
+ * swapping `channel` → `receiveId`. (See notify-slack/graph.mjs for the
8
+ * Slack-flavored example.)
9
+ *
10
+ * Returns `{ delivered, receiveId, receiveIdType, messageId }`. The
11
+ * `messageId` can be used for thread replies via Lark's reply API in
12
+ * follow-up dispatches.
13
+ */
14
+
15
+ import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
16
+ import { notifyLarkNode } from './nodes/notify-lark-node.js';
17
+ import {
18
+ notifyLarkInputSchema,
19
+ notifyLarkContextSchema,
20
+ } from './state.js';
21
+
22
+ export class NotifyLarkAgent extends WorkflowAgent {
23
+ buildGraph() {
24
+ const graph = new WorkflowGraph();
25
+ graph
26
+ .setInputSchema(notifyLarkInputSchema)
27
+ .setContextSchema(notifyLarkContextSchema);
28
+
29
+ graph.addNode('notify_lark', notifyLarkNode);
30
+ graph.setEntryPoint('notify_lark');
31
+ graph.addEdge('notify_lark', 'END');
32
+
33
+ return graph;
34
+ }
35
+
36
+ async onComplete(result) {
37
+ const delivered = !!result?.state?.notify_lark?.delivered;
38
+ const mid = result?.state?.notify_lark?.messageId || '?';
39
+ console.log(`[notify-lark] ${delivered ? 'delivered' : 'failed'} (messageId=${mid})`);
40
+ }
41
+ }
42
+
43
+ export default NotifyLarkAgent;
Binary file