@zibby/workflow-templates 0.4.1 → 0.7.0

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.js CHANGED
@@ -53,8 +53,7 @@ export const TEMPLATES = {
53
53
  'Composition: character centered, slight forward lean, dynamic motion lines. Mood: cheerful, playful, fast.',
54
54
  'NO text, NO browser UI screenshots, NO outline wireframes.',
55
55
  ].join('\n'),
56
- category: 'Testing',
57
- tags: ['testing', 'playwright', 'e2e', 'browser'],
56
+ tags: ['Testing'],
58
57
  capabilities: [
59
58
  'Preflight LLM extracts assertions from a plain-English spec',
60
59
  'Live Playwright execution with screenshots + DOM at every step',
@@ -106,8 +105,7 @@ export const TEMPLATES = {
106
105
  'Composition: brackets centered, subtle drop shadow on the canvas. Mood: high-end, technical, confident — like the cover of a developer tool launch.',
107
106
  'NO text, NO outline wireframes, NO flat sticker style — this one is DEEP and 3D-rendered.',
108
107
  ].join('\n'),
109
- category: 'Engineering',
110
- tags: ['code-analysis', 'jira', 'github', 'test-generation'],
108
+ tags: ['Code Review', 'Testing'],
111
109
  capabilities: [
112
110
  'Clone repos + snapshot git baseline so changes are diff-able',
113
111
  'LLM gate: skip code-gen when ticket can\'t be implemented as-is',
@@ -150,8 +148,7 @@ export const TEMPLATES = {
150
148
  'Mood: friendly, approachable, slightly handmade. Like a children\'s book illustration applied to a developer tool.',
151
149
  'NO text, NO photo-realism, NO sleek 3D render — this one is hand-drawn and warm.',
152
150
  ].join('\n'),
153
- category: 'Testing',
154
- tags: ['testing', 'test-generation', 'pull-request', 'review'],
151
+ tags: ['Code Review', 'Testing'],
155
152
  capabilities: [
156
153
  'Skips ticket analysis — feed it the diff directly',
157
154
  'LLM explores the codebase to ground test steps in real components',
@@ -195,8 +192,7 @@ export const TEMPLATES = {
195
192
  'Mood: focused, energetic, signal-not-noise — the canonical Slack-flavored notification worker.',
196
193
  '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
194
  ].join('\n'),
198
- category: 'Notifications',
199
- tags: ['slack', 'notification', 'alert', 'child-workflow'],
195
+ tags: ['Notifications', 'child-workflow'],
200
196
  capabilities: [
201
197
  'Severity-coded Block Kit message (low/medium/high/critical)',
202
198
  'Code snippet + action button + caller mentions',
@@ -239,8 +235,7 @@ export const TEMPLATES = {
239
235
  'Mood: focused, professional, signal-not-noise.',
240
236
  'NO Lark / Feishu logo trademark, NO text, NO photo-realism.',
241
237
  ].join('\n'),
242
- category: 'Notifications',
243
- tags: ['lark', 'feishu', 'notification', 'alert', 'child-workflow'],
238
+ tags: ['Notifications', 'child-workflow'],
244
239
  capabilities: [
245
240
  'Severity-coded Lark Interactive Card',
246
241
  'Auto-detects receive_id_type from id prefix (chat_id / open_id / email)',
@@ -255,52 +250,143 @@ export const TEMPLATES = {
255
250
  },
256
251
  },
257
252
 
258
- // ── sentry-triage: parent workflow that uses notify-slack/-lark ──
253
+ // ── notify-notion: same as notify-slack/-lark, Notion variant ────
254
+ // Two write modes: create a new page in a database, or append blocks
255
+ // to an existing page. Rich-mode delegates to
256
+ // @zibby/skills/report's reportToNotionBlocks. See notify-notion/README.md.
257
+ 'notify-notion': {
258
+ name: 'notify-notion',
259
+ displayName: 'Notify Notion',
260
+ 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.',
261
+ path: join(__dirname, 'notify-notion'),
262
+ defaultSlug: 'archive-notion',
263
+ deps: { zod: '^3.23.0', '@zibby/skills': '^0.1.25' },
264
+ features: [
265
+ 'Single-node, no LLM — deterministic ~1-2s Notion REST call',
266
+ 'Two write modes: create page in database OR append children to existing page',
267
+ 'Rich-mode renders report objects via @zibby/skills/report (heading, callouts, tables, trend code blocks)',
268
+ 'Legacy-mode renders simple severity / title / body alerts (sentry-triage et al.)',
269
+ 'Severity-mapped page-icon emoji (low/medium/high/critical or rich-mode delta severity)',
270
+ 'Surfaces typed errors for 401 (token revoked), 404 (target not shared with bot), 429 (rate-limited)',
271
+ 'Returns pageId + pageUrl for downstream linkage from notify-slack / notify-lark messages',
272
+ ],
273
+ marketplace: {
274
+ slug: 'notify-notion',
275
+ tagline: 'Reusable Notion archiver — durable record for any workflow.',
276
+ iconPrompt: [
277
+ 'A premium, hi-fi app icon for "Notify Notion" — a workflow node that publishes reports to a Notion database. The real Notion brand mark will be composited on top in a post-process step; this prompt generates the BACKGROUND ONLY, with a clear empty area for the overlay.',
278
+ 'Visual style: 3D-rendered hero object floating in space, in the style of Apple Vision Pro icons, Linear\'s changelog hero illustrations, or a Stripe product render. Glossy, dimensional, with subtle reflections and a soft rim-light. Same family as the code-analysis marketplace icon.',
279
+ 'Subject: a single 3D-rendered page-document hero object made of glossy frosted glass / brushed silver metal, captured in head-on or near-head-on perspective (NOT three-quarter — keep the page face flat to the camera so the logo overlay sits cleanly). The page surface is COMPLETELY EMPTY — no lines, no text, no icons, no markings of any kind. The right edge curls slightly forward like a fresh page being filed, but the front face stays clean. A tiny cyan-teal glow accent sits in the upper-right corner of the page as a "freshly archived" signal.',
280
+ 'Background: a deep midnight-navy gradient (#0F172A at the top, #1E1B4B at the bottom), with a single soft cyan glow behind the page and a few faint star-like specks scattered across the canvas. Square format, 1024×1024.',
281
+ 'Composition: page centered, page face takes ~60-70% of the canvas and is empty/blank so a logo overlay can sit naturally on its surface. Subtle drop shadow. Mood: high-end, durable, archival — premium devtool aesthetic.',
282
+ 'CRITICAL: the page front face must be COMPLETELY CLEAN AND EMPTY. NO N, NO letters, NO horizontal text lines, NO checkmark, NO icons, NO embossing, NO etching, NO decoration on the page surface. Just blank glossy material. NO Notion logo, NO Notion wordmark, NO trademarked marks. NO text anywhere. NO outline wireframes, NO flat sticker style, NO mascot, NO smiling face, NO cartoon. The page surface MUST be empty so a real logo PNG can be composited on it.',
283
+ ].join('\n'),
284
+ tags: ['Notifications', 'Docs', 'Reports'],
285
+ capabilities: [
286
+ 'Create a new page in a Notion database (POST /v1/pages)',
287
+ 'Append blocks to an existing page (PATCH /v1/blocks/{pageId}/children)',
288
+ 'Renders rich report-objects to native Notion blocks (headings, callouts, tables, code, embeds)',
289
+ 'Severity-mapped page-icon emoji + colored callout backgrounds',
290
+ 'Sub-graph dispatchable from any parent workflow',
291
+ 'Maps 401 / 404 / 429 to typed errors so callers can react cleanly',
292
+ ],
293
+ conversationStarters: [
294
+ 'Archive the weekly digest to our Notion reports database',
295
+ 'Append today\'s incident summary to the on-call runbook page',
296
+ 'Create a Notion page for every new Sentry CRITICAL',
297
+ 'Drop the deploy-summary into our engineering Notion every Friday',
298
+ ],
299
+ },
300
+ },
301
+
302
+ // ── sentry-triage: hourly LLM triage → Slack OR Lark ─────────────
259
303
  'sentry-triage': {
260
304
  name: 'sentry-triage',
261
305
  displayName: 'Sentry Triage Bot',
262
- 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.',
306
+ description: 'Hourly Sentry triage that classifies new issues with an LLM rubric (CRITICAL/HIGH/MEDIUM/LOW/NOISE) and posts above-threshold alerts to your Slack or Lark whichever you have connected.',
263
307
  path: join(__dirname, 'sentry-triage'),
264
308
  defaultSlug: 'sentry-triage',
265
- deps: { zod: '^3.23.0' },
309
+ deps: { zod: '^3.23.0', '@zibby/skills': '^0.1.25' },
266
310
  features: [
267
- '4-node graph: fetchfilter_noise → classify → dispatch_alerts',
268
- 'Regex noise filter before LLM cuts ~80% of classification cost',
269
- 'LLM severity classifier with explicit rubric (rules 1-5)',
270
- 'Sub-graph fan-out to notify-slack OR notify-lark (choose at deploy)',
271
- 'Per-issue failure isolation one Slack hiccup doesn\'t stall the run',
272
- 'Configurable severityThreshold (don\'t notify on LOW noise)',
311
+ '3-node LLM graph: fetch_issues → classify → dispatch_alerts',
312
+ 'Severity rubric with auditable reasoning per issue',
313
+ 'Posts to Slack OR Lark (whichever the project has connected — chat_notify OR-group)',
314
+ 'LLM dispatcher batches related issues into one message, dedupes near-duplicates',
315
+ 'CRITICAL alerts get optional @-mentions; lower severities don\'t',
273
316
  'Cron-friendly: hourly schedule, default sinceMinutes=60',
274
317
  ],
275
318
  marketplace: {
276
319
  slug: 'sentry-triage',
277
- tagline: 'Filter noise, classify severity, ping the right channel every hour.',
320
+ tagline: 'Triage Sentry, ping your teamhourly.',
278
321
  iconPrompt: [
279
- '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.',
322
+ '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.',
280
323
  '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.',
281
324
  '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.',
282
- '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.',
283
- 'Mood is warm, reassuring, optimistic — the friendly Sentry-flavored night-watch character, NOT tactical or corporate or alarming.',
325
+ '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.',
326
+ 'Mood is warm, reassuring, optimistic — friendly night-watch character, NOT tactical or corporate or alarming.',
284
327
  'Soft rounded square 1024×1024 canvas with a subtle paper-grain texture.',
285
- '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.',
328
+ '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 / Slack / Lark wordmark.',
286
329
  ].join('\n'),
287
- category: 'Operations',
288
- tags: ['sentry', 'observability', 'on-call', 'triage', 'alerting'],
330
+ tags: ['On-call', 'Bug Triage', 'Notifications'],
289
331
  capabilities: [
290
332
  'Hourly scheduled triage of new Sentry issues',
291
- 'Deterministic regex filter drops Script error / ResizeObserver / extension noise',
292
333
  'LLM severity classifier with auditable rubric',
293
- 'Dispatches to notify-slack or notify-lark (sub-graph, ~5ms in-process)',
294
- 'CRITICAL alerts get caller-supplied @-mentions; lower severities don\'t',
334
+ 'Posts to Slack or Lark whichever your project has connected',
335
+ 'Batches related issues; CRITICAL-only @-mentions for on-call',
295
336
  'Configurable severity threshold per deploy',
296
337
  ],
297
338
  conversationStarters: [
298
339
  'Triage all new Sentry issues from the last hour',
299
- 'Notify #sentry-alerts when severity is HIGH or above',
300
- 'Run hourly and post a summary to our team Slack',
340
+ 'Notify the on-call channel when severity is HIGH or above',
301
341
  'Page on-call when a CRITICAL error appears in checkout',
302
342
  ],
303
343
  },
344
+ },
345
+
346
+ // ── ai-spend-weekly-digest: cross-silo billing digest ─────────────
347
+ 'ai-spend-weekly-digest': {
348
+ name: 'ai-spend-weekly-digest',
349
+ displayName: 'AI Spend Weekly Digest',
350
+ 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.',
351
+ path: join(__dirname, 'ai-spend-weekly-digest'),
352
+ defaultSlug: 'ai-spend-weekly-digest',
353
+ deps: { zod: '^3.23.0', '@zibby/skills': '^0.1.25' },
354
+ features: [
355
+ '3-node graph: fetch_spending → analyze (LLM narrative) → dispatch_digest',
356
+ 'Parallel admin-API fetch via Promise.allSettled — one provider down doesn\'t kill the run',
357
+ 'Customer attribution via OpenAI project / Anthropic workspace / Cursor user metadata (no manual mapping required)',
358
+ 'Anomaly detection over per-key σ + ratio fallback — flags spend spikes vs 3-week baseline',
359
+ 'Renders to native Slack Block-Kit / Lark Card via reportToBlockKit / reportToLarkCard — zero chart-image dependency',
360
+ 'Parallel sub-graph dispatch to notify-slack + notify-lark (~5ms in-process overhead)',
361
+ 'Cron-friendly: weekly schedule, default Monday 08:00 local',
362
+ ],
363
+ marketplace: {
364
+ slug: 'ai-spend-weekly-digest',
365
+ tagline: 'Track and explain your OpenAI / Anthropic / Cursor spending — every Monday morning, in Lark or Slack.',
366
+ iconPrompt: [
367
+ 'A premium, hi-fi app icon for "AI Spend Weekly Digest" — a workflow that reports cross-vendor LLM spend to engineering leaders.',
368
+ 'Visual style: 3D-rendered hero object floating in space, in the style of Apple Vision Pro icons, Linear\'s changelog hero illustrations, or a Stripe product render. Glossy, dimensional, with subtle reflections and a soft rim-light.',
369
+ 'Subject: a 3D-rendered stack of three glossy disc-shaped layered chips — like a tiny ascending bar-chart-of-coins — slightly rotated in three-quarter perspective. Each disc is a different premium accent color: top disc warm gold, middle disc cool platinum-silver, bottom disc deep iridescent purple. A single thin trending-up glowing line traces from lower-left to upper-right behind the stack, rendered as a soft neon ribbon (#7553FF violet glow), suggesting cost-over-time at a glance without literal axes.',
370
+ 'Background: a deep midnight-navy gradient (#0F172A at the top, #1E1B4B at the bottom), with a single soft violet glow behind the stack and a few faint star-like specks scattered across the canvas. Square format, 1024×1024.',
371
+ 'Composition: stack centered, subtle drop shadow on the canvas. Mood: high-end, executive, confident — like the cover image of a finance-ops product launch.',
372
+ 'NO text, NO numbers, NO axes or grid lines, NO outline wireframes, NO flat sticker style, NO mascot, NO piggy bank, NO cartoon faces, NO trademarked OpenAI / Anthropic / Cursor logos — this one is DEEP and 3D-rendered.',
373
+ ].join('\n'),
374
+ tags: ['AI Spend', 'Cost Tracking', 'Reports'],
375
+ capabilities: [
376
+ 'Pulls org-wide cost+usage from OpenAI, Anthropic, and Cursor admin APIs in parallel',
377
+ 'Joins customer attribution from provider-native project / workspace / member metadata',
378
+ 'Detects per-project anomalies (σ + ratio) against a 3-week rolling baseline',
379
+ 'Drafts the leadership-grade narrative with an LLM, falls back to deterministic copy if model is unavailable',
380
+ 'Posts a rich Block-Kit / Lark Card report (trend bars, top spenders table, anomalies, provider breakdown)',
381
+ 'Fan-out to Lark + Slack in parallel — partial-failure resilient',
382
+ ],
383
+ conversationStarters: [
384
+ 'Run a weekly AI spend digest every Monday morning',
385
+ 'Tell me which OpenAI projects spiked spending this week',
386
+ 'Compare Anthropic spend vs the 3-week baseline',
387
+ 'Post the digest to our #leadership Lark group + #eng Slack channel',
388
+ ],
389
+ },
304
390
  }
305
391
  };
306
392
 
@@ -31,6 +31,9 @@
31
31
  import { z } from 'zod';
32
32
  import { SKILLS } from '@zibby/core';
33
33
  import { resolveIntegrationToken } from '@zibby/core/backend-client.js';
34
+ // Universal renderer — see notify-slack-node.js for the design note;
35
+ // when state.report is present the legacy buildLarkCard path is bypassed.
36
+ import { reportToLarkCard } from '@zibby/skills/report';
34
37
 
35
38
  const SEVERITY_TEMPLATE = Object.freeze({
36
39
  low: 'grey',
@@ -268,10 +271,20 @@ export const notifyLarkNode = {
268
271
  if (!receiveId) {
269
272
  throw new Error('notify-lark: input.receiveId is required');
270
273
  }
274
+ if (!state.report && !state.title) {
275
+ throw new Error('notify-lark: input.title is required when input.report is absent');
276
+ }
271
277
  const receiveIdType = inferReceiveIdType(receiveId);
272
278
 
273
279
  const { token, host } = await getTenantAccessToken();
274
- const card = buildLarkCard(state);
280
+ // Two rendering paths (symmetric with notify-slack):
281
+ // 1. state.report present → reportToLarkCard renders the full
282
+ // structured digest. Legacy fields are ignored.
283
+ // 2. Legacy severity/title/body shape → buildLarkCard renders
284
+ // the simple alert (sentry-triage et al.).
285
+ const card = (state.report && typeof state.report === 'object')
286
+ ? reportToLarkCard(state.report)
287
+ : buildLarkCard(state);
275
288
  const { messageId } = await postToLark({
276
289
  token,
277
290
  host,
@@ -27,8 +27,11 @@ export const notifyLarkInputSchema = z.object({
27
27
  .default('medium')
28
28
  .describe('Alert severity. Drives header color + mention strategy.'),
29
29
 
30
- title: z.string().min(1).max(300)
31
- .describe('Card header text (Lark caps at ~100 chars; we truncate to 150).'),
30
+ // Required in legacy-mode; rich-mode (when `report` is set) sources
31
+ // the title from `report.title` instead. See notify-slack/state.js
32
+ // for the symmetric design.
33
+ title: z.string().min(1).max(300).optional()
34
+ .describe('Card header text. Required when `report` is absent (rich-mode sources from report.title).'),
32
35
 
33
36
  body: z.string().max(3000).optional()
34
37
  .describe('Body text. Supports Lark lark_md (e.g. **bold**, `code`, [text](url)).'),
@@ -60,6 +63,13 @@ export const notifyLarkInputSchema = z.object({
60
63
  .describe('Lark @-mentions, e.g. [\'<at user_id="ou_alice">@Alice</at>\'].'),
61
64
 
62
65
  idempotencyKey: z.string().max(128).optional(),
66
+
67
+ // ── Rich-report mode ──────────────────────────────────────────────
68
+ // When `report` is present, the node renders the report-object via
69
+ // reportToLarkCard() and IGNORES the legacy severity/title/body
70
+ // fields. See notify-slack/state.js for the symmetric design.
71
+ report: z.record(z.any()).optional()
72
+ .describe('Rich report-object (see @zibby/skills/report). When set, supersedes severity/title/body — the node renders a full Lark Card.'),
63
73
  });
64
74
 
65
75
  export const notifyLarkContextSchema = z.object({
@@ -0,0 +1,71 @@
1
+ # notify-notion
2
+
3
+ A reusable **child workflow** that posts a structured page (or appends blocks) to a Notion workspace.
4
+
5
+ Designed to be dispatched via sub-graph from any parent workflow — most commonly digest workflows (`ai-spend-weekly-digest`) that want to archive a weekly report to a Notion database, or alert workflows (`sentry-triage`) that want a durable record of each incident.
6
+
7
+ ## What it does
8
+
9
+ - Takes a provider-neutral payload (severity, title, body, target, optional Sentry-flavored fields)
10
+ - Builds a Notion page (or block-children array) — including rich-report mode that delegates to `@zibby/skills/report`'s `reportToNotionBlocks`
11
+ - Posts via `POST /v1/pages` (when `databaseId` is supplied) or `PATCH /v1/blocks/{pageId}/children` (when `pageId` is supplied)
12
+ - Returns the page id + URL so the parent can link to it from downstream notifications
13
+
14
+ No LLM call — single deterministic API request, typically ~1-2s depending on block count.
15
+
16
+ ## Dispatch shape (parent workflow)
17
+
18
+ ```js
19
+ import { WorkflowGraph, SKILLS } from '@zibby/core';
20
+
21
+ const g = new WorkflowGraph();
22
+ g.addNode('archive_to_notion', {
23
+ workflow: 'notify-notion',
24
+ input: (state) => ({
25
+ // Either databaseId OR pageId — not both:
26
+ databaseId: process.env.NOTION_INCIDENT_DB,
27
+ severity: 'critical',
28
+ title: 'Checkout: TypeError on session.user.id',
29
+ body: '12 users affected in the last 1h.',
30
+ sentryLink: 'https://sentry.io/.../1234567890/',
31
+ affectedUsers: 12,
32
+ events: 47,
33
+ release: '1.42.0',
34
+ }),
35
+ });
36
+ ```
37
+
38
+ For digest workflows, pass a `report` object instead of severity/title/body — the node renders the full structured page via `reportToNotionBlocks`:
39
+
40
+ ```js
41
+ g.addNode('archive_digest', {
42
+ workflow: 'notify-notion',
43
+ input: (state) => ({
44
+ databaseId: process.env.NOTION_REPORTS_DB,
45
+ report: state.analyze.report, // produced by an upstream digest node
46
+ }),
47
+ });
48
+ ```
49
+
50
+ ## Output
51
+
52
+ ```ts
53
+ {
54
+ delivered: boolean,
55
+ pageId: string,
56
+ pageUrl?: string, // only set on the create branch (Notion returns it)
57
+ blocksCount?: number // diagnostic — how many blocks were posted
58
+ }
59
+ ```
60
+
61
+ ## Auth
62
+
63
+ The bot's OAuth token is resolved via `resolveIntegrationToken('notion')`. Connect your workspace in **Settings → Integrations → Notion** in the Zibby dashboard before running.
64
+
65
+ ## Failure modes
66
+
67
+ - **401** — Notion rejected the token (revoked, or wrong workspace).
68
+ - **404** — Page/database not found, OR the Notion integration isn't added to the target page (Notion's quirk — share the page with the integration explicitly).
69
+ - **429** — Rate-limited. The parent should backoff + retry.
70
+
71
+ All three surface as typed errors with `.status` set; the parent's `onComplete` sees a failed execution row and can re-dispatch.
@@ -0,0 +1,4 @@
1
+ <svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z" fill="#fff"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z" fill="#000"/>
4
+ </svg>
@@ -0,0 +1,64 @@
1
+ /**
2
+ * notify-notion — single-node child workflow.
3
+ *
4
+ * Companion to notify-slack / notify-lark; same provider-neutral input
5
+ * shape so a parent can dispatch the same payload to all three (just
6
+ * swap the destination field: `channel` → `receiveId` → `databaseId`
7
+ * or `pageId`).
8
+ *
9
+ * Two write modes:
10
+ * - Create a NEW page in a database (`input.databaseId` set)
11
+ * - APPEND blocks to an existing page (`input.pageId` set)
12
+ * Exactly one must be supplied — the node throws clearly if neither
13
+ * or both are present.
14
+ *
15
+ * Returns `{ delivered, pageId, pageUrl, blocksCount }`. The `pageUrl`
16
+ * is only populated on the create-page branch (Notion returns the URL
17
+ * in the response); appended pages reuse the caller-supplied pageId.
18
+ *
19
+ * Example dispatch shape (parent workflow):
20
+ *
21
+ * g.addNode('notify', { workflow: 'notify-notion',
22
+ * input: (state) => ({
23
+ * severity: 'critical',
24
+ * title: 'Checkout: TypeError',
25
+ * body: '12 users affected in last 1h',
26
+ * databaseId: 'abc123...', // create new page in this DB
27
+ * sentryLink: 'https://sentry.io/.../1234567890/',
28
+ * }),
29
+ * });
30
+ *
31
+ * For digest / report workflows, drop the legacy severity/title/body
32
+ * fields and pass a `report` object instead — the node delegates to
33
+ * @zibby/skills's reportToNotionBlocks() for the full structured page.
34
+ */
35
+
36
+ import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
37
+ import { notifyNotionNode } from './nodes/notify-notion-node.js';
38
+ import {
39
+ notifyNotionInputSchema,
40
+ notifyNotionContextSchema,
41
+ } from './state.js';
42
+
43
+ export class NotifyNotionAgent extends WorkflowAgent {
44
+ buildGraph() {
45
+ const graph = new WorkflowGraph();
46
+ graph
47
+ .setInputSchema(notifyNotionInputSchema)
48
+ .setContextSchema(notifyNotionContextSchema);
49
+
50
+ graph.addNode('notify_notion', notifyNotionNode);
51
+ graph.setEntryPoint('notify_notion');
52
+ graph.addEdge('notify_notion', 'END');
53
+
54
+ return graph;
55
+ }
56
+
57
+ async onComplete(result) {
58
+ const delivered = !!result?.state?.notify_notion?.delivered;
59
+ const pageId = result?.state?.notify_notion?.pageId || '?';
60
+ console.log(`[notify-notion] ${delivered ? 'delivered' : 'failed'} (pageId=${pageId})`);
61
+ }
62
+ }
63
+
64
+ export default NotifyNotionAgent;
Binary file