npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kupola/pivot-flow

v0.3.23

Published

AI-native frontend business flow orchestration layer for Kupola PIVOT.

Readme

@kupola/pivot-flow

AI-native frontend business flow orchestration for Kupola PIVOT.

@kupola/pivot-flow is the product layer above @kupola/pivot. It helps web apps define, manage, preview, and run frontend business flows that are triggered by user intent.

Natural language
  -> Intent mapper
  -> Flow definition
  -> PIVOT plan
  -> PIVOT runtime preview
  -> user confirmation
  -> PIVOT runtime execute
  -> result and audit

Install

npm install @kupola/pivot-flow @kupola/pivot @kupola/kupola
import {
  createFlow,
  createLocalIntentMapper,
  createLocalStorageFlowStore,
  createFlowRunner,
  createFlowVariableSources,
  createIntentClarificationPlan,
  canConnectFlowNodes,
  analyzeIntentConfig,
  explainIntentMatches,
  analyzeFlowDataDependencies,
  getFlowRunSummary,
  registerFlowFrontendCapabilities,
  renderIntentClarificationPlanToHTML,
  renderFlowDataDependenciesToHTML,
  renderIntentMatchExplanationToHTML,
  renderIntentPatternEditorToHTML,
  renderVariableMapperToHTML,
  renderFlowRunSummaryToHTML,
  flowToPlan,
  FlowManager,
  FlowAssistantDrawer
} from '@kupola/pivot-flow';
import '@kupola/pivot-flow/css';

10 Minute Designer Setup

Use the UI entry when you want the default designer and panels:

import { createPivotRuntime } from '@kupola/pivot';
import { createMemoryFlowStore, createFlowFromTemplate, registerFlowFrontendCapabilities } from '@kupola/pivot-flow';
import { createPivotFlowApp } from '@kupola/pivot-flow/ui';
import '@kupola/pivot-flow/css';

const runtime = createPivotRuntime();

runtime.registerCapability({
  name: 'user.query',
  resource: 'users',
  action: 'query',
  risk: 'low',
  paramsSchema: {
    filters: { type: 'array', required: true },
    limit: { type: 'number', default: 20 }
  },
  execute: async ({ params }) => api.users.query(params)
});

registerFlowFrontendCapabilities(runtime, {
  showMessage: ({ message }) => app.message.info(message),
  selectRecord: ({ source, title }) => app.tablePicker.open({ title, rows: source }),
  displayData: ({ data, renderer, title }) => app.renderer.show({ data, renderer, title })
});

const flowStore = createMemoryFlowStore([
  createFlowFromTemplate('user.query-by-name')
]);

createPivotFlowApp({
  target: '#flow-designer',
  runtime,
  flowStore,
  resourceSchemas: {
    users: {
      fields: {
        name: { type: 'string', label: 'Name' },
        departmentName: { type: 'string', label: 'Department' },
        phone: { type: 'string', label: 'Phone' }
      }
    }
  }
});

@kupola/pivot-flow is the headless API plus the default UI exports. @kupola/pivot-flow/ui is the UI-only subpath for FlowManager, FlowDesigner, panels, and one-call helpers. @kupola/pivot-flow/css provides the default styles. The UI layer is built on Kupola UI classes and tokens; projects should replace business renderers through adapters, not by forking base Table/Form/Modal/Drawer primitives.

Core Example

import { createPivotRuntime } from '@kupola/pivot';
import { createFlow, createLocalIntentMapper, flowToPlan, registerFlowFrontendCapabilities } from '@kupola/pivot-flow';

const runtime = createPivotRuntime();
registerFlowFrontendCapabilities(runtime, {
  showMessage: ({ message }) => console.info(message),
  refreshTable: ({ target }) => console.info(`refresh ${target}`)
});

// Built-in frontend node types infer their capability automatically:
// message.show -> capability "message.show"
// table.refresh -> capability "table.refresh"

runtime.registerCapability({
  name: 'org.create',
  resource: 'organization',
  action: 'create',
  risk: 'medium',
  permissions: ['system:org:create'],
  paramsSchema: {
    name: { type: 'string', required: true },
    parentId: { type: 'string', required: true }
  },
  execute: ({ params }) => ({ id: 'org-1', ...params })
});

const flow = createFlow({
  id: 'org-create',
  name: 'Create organization',
  status: 'published',
  intent: {
    examples: ['在集团下增加分机构 C'],
    keywords: ['增加', '分机构'],
    slots: [
      { name: 'organizationName', type: 'string', required: true, pattern: '分机构\\s*(?<organizationName>\\S+)' },
      { name: 'parentId', type: 'string', fallback: 'group-root' }
    ]
  },
  nodes: [
    {
      id: 'create-org',
      type: 'capability.run',
      label: 'Create organization',
      capability: 'org.create',
      params: {
        name: '{{intent.organizationName}}',
        parentId: '{{intent.parentId}}'
      }
    }
  ]
});

const mapper = createLocalIntentMapper();
const match = mapper.match('在集团下增加分机构 C', [flow]).best;
const plan = flowToPlan(match.flow, { prompt: match.prompt, slots: match.slots });
const preview = await runtime.previewPlan(plan, { actor: { permissions: ['system:org:create'] } });

Headless Runner

Use createFlowRunner() when you need the intent-to-execution pipeline without using the bundled drawer UI.

const runner = createFlowRunner({
  runtime,
  flowStore,
  intentMapper: createLocalIntentMapper(),
  contextProvider: () => ({
    actor: {
      id: 'admin',
      permissions: ['system:org:create']
    }
  })
});

const preview = await runner.preview('在集团下增加分机构 C');
const result = await runner.execute('在集团下增加分机构 C');

When a matched flow has required slots that cannot be extracted from the prompt, preview() returns stage: 'slots' with missingSlots. Pass the collected values back into the runner:

const preview = await runner.preview('创建');

if (preview.stage === 'slots') {
  const confirmedPreview = await runner.preview('创建', {
    match: preview.match,
    slots: {
      name: '张三'
    }
  });
}

FlowAssistantDrawer uses the same mechanism and renders parameter inputs for missing required slots before preview or execution.

For sensitive values, prefer manual slots instead of asking users to put secrets in the natural-language prompt. Mark the slot as sensitive so the drawer renders a password input:

slots: [
  {
    name: 'password',
    label: 'Initial password',
    source: 'manual',
    required: true,
    sensitive: true,
    inputType: 'password'
  }
]

Intent Match Explanation

Local intent matching is rule-based and explainable. Use explainIntentMatches() to inspect why a prompt matched a Flow, including examples, keywords, patterns, extracted slots, missing required slots, confidence, and draft eligibility.

const explanation = explainIntentMatches('在集团下增加分机构 C', flows, {
  includeIneligible: true
});

document.querySelector('#intentExplain').innerHTML = renderIntentMatchExplanationToHTML(explanation);

const clarification = createIntentClarificationPlan(explanation);
document.querySelector('#intentClarify').innerHTML = renderIntentClarificationPlanToHTML(clarification);

This helps operators understand whether a command matched because of a keyword, a regex pattern, a similar example, or extracted parameters. It is still only the matching layer; preview, confirmation, PIVOT policies, and backend authorization remain required before execution.

createIntentClarificationPlan() returns a structured next step when the prompt has no strong match, multiple close matches, or missing required slots. Applications can render the default HTML or turn the returned questions into their own multi-step form.

FlowAssistantDrawer and the FlowManager test panel render clarification hints by default when a command is ambiguous or missing required parameters.

analyzeIntentConfig() and renderIntentPatternEditorToHTML() review rule quality for examples, keywords, regex patterns, slots, required extraction sources, and sensitive manual input. The default designer renders this as the Intent patterns side panel.

Data Dependencies

Use analyzeFlowDataDependencies() to inspect node-to-node data references before a Flow is published or executed. It detects template references such as {{query-parent.data.id}} and structured references such as { "$from": "query-parent", "path": "data.id" }.

const report = analyzeFlowDataDependencies(flow);
document.querySelector('#dependencies').innerHTML = renderFlowDataDependenciesToHTML(report);

The report identifies upstream dependencies, external intent/context references, missing nodes, self references, downstream references, and unconnected references that should be connected with edges. FlowManager renders the dependency report by default.

This is a frontend modeling aid. Backend APIs must still enforce transaction boundaries, data integrity, authorization, and business invariants.

Variable Mapper

createFlowVariableSources() and renderVariableMapperToHTML() help administrators insert safe parameter references without memorizing template syntax. The default designer shows intent slots, common runtime context values, and upstream node outputs for the selected node.

const sources = createFlowVariableSources(flow, 'create-child');
document.querySelector('#mapper').innerHTML = renderVariableMapperToHTML({
  flow,
  selectedNodeId: 'create-child'
});

FlowManager handles the default Insert action by adding {{reference}} to the selected node params with a generated key.

Run Diagnostics

Use getFlowRunSummary() when a page needs a compact business-facing summary of a preview or execution result. The summary normalizes node status, failed nodes, node duration, result messages, result codes, and recommended checks.

const execution = await runner.execute('删除角色 管理员');
const summary = getFlowRunSummary(execution.result, flow);

document.querySelector('#runSummary').innerHTML = renderFlowRunSummaryToHTML(summary);

FlowRunPanel renders this summary by default before the underlying PIVOT result and timeline. FlowRunHistory can render records returned by flowStore.listRuns(), with keyword, success/failed, and time range filters. FlowManager shows the selected flow history by default and records direct manager executions when the store implements recordRun(). The recommendations are UI guidance only. Server APIs must still return proper 401, 403, 409, and 422 responses for unauthorized or invalid operations.

FlowRunner records summarized run data by default when flowStore.recordRun() exists. The summary redacts common sensitive keys, limits deep objects, and turns large arrays into counts. Use createFlowRunRecord(), summarizeFlowRunResult(), and sanitizeFlowRunValue() when a project needs the same behavior outside the bundled runner. Raw results are only included when runRecord.includeRawResult is explicitly enabled.

Flow Stores

Use an in-memory or localStorage store for prototypes. Use createHttpFlowStore() when the app needs server-backed persistence and backend authorization.

import { createHttpFlowStore } from '@kupola/pivot-flow';

const flowStore = createHttpFlowStore({
  baseUrl: '/api/pivot-flows',
  runsUrl: '/api/pivot-flow-runs',
  headers: () => ({
    'X-CSRF-Token': getCsrfToken()
  })
});

Expected HTTP endpoints:

  • GET /api/pivot-flows
  • POST /api/pivot-flows
  • GET /api/pivot-flows/:id
  • PUT /api/pivot-flows/:id
  • DELETE /api/pivot-flows/:id
  • POST /api/pivot-flows/:id/publish
  • POST /api/pivot-flows/:id/disable
  • GET /api/pivot-flow-runs
  • POST /api/pivot-flow-runs

Import And Export

Use the import/export helpers when a project needs Flow backup, migration between environments, or administrator-reviewed configuration uploads.

import {
  createFlowImportReport,
  exportFlowsToJSON,
  importFlowsToStore,
  renderFlowImportReportToHTML
} from '@kupola/pivot-flow';

const json = exportFlowsToJSON(await flowStore.list());

const report = createFlowImportReport(json, {
  importedFrom: 'production-backup.json',
  existingFlows: await flowStore.list(),
  runtime
});

document.querySelector('#importReport').innerHTML = renderFlowImportReportToHTML(report);

if (report.ok) {
  await importFlowsToStore(report, flowStore);
}

Imported flows are prepared as draft by default and publishedAt is cleared. If an imported ID already exists, the default behavior is to generate a new Flow ID and keep the original ID in metadata.originalId. The report surfaces schema errors, missing registered capabilities, status downgrades, and ID conflicts before anything is saved.

Importing a Flow never publishes, executes, or registers backend capabilities. It is only a configuration preparation step. Server APIs must still enforce authentication, authorization, schema validation, data permissions, and conflict checks when the imported configuration is saved or later published.

Snapshots And Change Review

Use snapshots when administrators need a restore point before editing or publishing a Flow. Restoring a snapshot creates a draft by default and clears publishedAt.

import {
  createFlowChangeReport,
  createFlowSnapshot,
  renderFlowChangeReportToHTML,
  restoreFlowSnapshot
} from '@kupola/pivot-flow';

const snapshot = createFlowSnapshot(currentFlow, {
  label: 'Before role deletion update',
  reason: 'publish review',
  createdBy: actor.id
});

const restoredDraft = restoreFlowSnapshot(snapshot);

const changeReport = createFlowChangeReport(currentFlow, editedFlow, { runtime });
document.querySelector('#changeReport').innerHTML = renderFlowChangeReportToHTML(changeReport);

Change reports classify edits across intent rules, nodes, edges, permissions, lifecycle fields, and metadata. Capability, permission, risk, confirmation, and condition changes are marked as high-impact so they are visible before publish. The report validates the target Flow and blocks invalid definitions, but backend publish APIs must still enforce authorization, capability allowlists, data rules, and audit.

For custom management pages, createFlowEditSession() keeps a stable baseline and draft copy so UI code can show dirty state, reset unsaved edits, create snapshots, and render a reliable change report.

import { createFlowEditSession, renderFlowChangeReportToHTML } from '@kupola/pivot-flow';

const session = createFlowEditSession(currentFlow, { runtime });

session.mutate((draft) => {
  draft.intent.keywords.push('删除角色');
  draft.nodes[0].requiresConfirmation = true;
});

if (session.dirty) {
  document.querySelector('#changes').innerHTML = renderFlowChangeReportToHTML(session.report);
}

const snapshot = session.snapshot({ label: 'Before publish review' });
session.reset();
session.restore(snapshot);
session.commit();

The edit session is headless. It does not save to a server by itself; call your FlowStore.update() only after review and backend authorization.

Use createVersionedFlowStore() when you want store operations to create snapshots automatically before destructive or lifecycle changes.

import { createHttpFlowStore, createVersionedFlowStore } from '@kupola/pivot-flow';

const flowStore = createVersionedFlowStore(createHttpFlowStore({
  baseUrl: '/api/pivot-flows'
}), {
  createdBy: actor.id
});

await flowStore.update(flow.id, editedFlow);       // snapshots the previous flow first
await flowStore.publish(flow.id);                  // snapshots before publish

const snapshots = await flowStore.listSnapshots(flow.id);
await flowStore.restoreSnapshot(snapshots[0].id);  // restores as a draft by default

The default snapshot store is in-memory. Production apps should pass a snapshotStore backed by their own API if snapshots must survive refreshes, devices, or deployments.

Advanced Flow Modules

The library also includes headless modules for production-grade Flow management:

  • createFlowApprovalRequest(), reviewFlowApproval(), createFlowPublishGate(), and applyApprovedPublish() model approval-first publishing.
  • createHybridIntentRouter() combines local explainable matching with an optional AI structured intent router.
  • createFlowCanvasState(), moveFlowCanvasNode(), addFlowCanvasNode(), and connectFlowCanvasNodes() provide safe canvas editing primitives for draggable UIs.
  • simulateFlowPermissions() compares multiple actors against Flow permission hints for role testing.
  • createFlowApiContract() and validateFlowApiResponse() document and validate expected backend API response boundaries.

These modules are frontend orchestration helpers. Backend APIs must still own final authorization, validation, transactions, data-scope checks, and audit.

Flow Templates

Built-in templates provide common starting points for application flows. Templates create draft flows and still require project-specific capability registration, preview, publish checks, and backend authorization.

import { createFlowFromTemplate, listFlowTemplates } from '@kupola/pivot-flow';

const templates = listFlowTemplates({ group: 'organization' });
const draftFlow = createFlowFromTemplate('organization.create-under-parent', {
  name: '在集团下新增分机构'
});

await flowStore.create(draftFlow);

FlowManager renders built-in templates by default. Pass templates to replace them with project-specific templates.

The user.query-by-name template is the official "查询张三的信息" example. It uses generic nodes instead of a dedicated user-query node:

data.query
  -> message.show when total == 0
  -> ui.display when total == 1
  -> human.select when total > 1
  -> ui.display selected record

Register a project-specific user.query capability and wire human.select / ui.display through registerFlowFrontendCapabilities(). The default UI renderer hints are table and detail, and projects can replace them through the frontend adapter.

Conditions And Transforms

evaluateFlowCondition() and applyFlowTransform() provide a small controlled DSL for custom runners, tests, and preview tooling. They do not evaluate arbitrary JavaScript.

const ok = evaluateFlowCondition({
  left: '{{intent.quantity}}',
  operator: 'gt',
  right: 0
}, { slots: { quantity: 3 } });

const payload = applyFlowTransform({
  name: '{{intent.name}}',
  actorId: '{{context.actor.id}}'
}, { slots: { name: '张三' } }, { actor: { id: 'admin' } });

Publish Safety Report

createFlowSafetyReport() reviews a Flow before publish. It checks structure, data dependencies, registered capabilities, high-risk confirmation, permission hints, sensitive slots, and backend authorization reminders.

import {
  createFlowBatchSafetyReport,
  createFlowSafetyReport,
  renderFlowBatchSafetyReportToHTML,
  renderFlowSafetyReportToHTML
} from '@kupola/pivot-flow';

const report = createFlowSafetyReport(flow, runtime);
document.querySelector('#safety').innerHTML = renderFlowSafetyReportToHTML(report);

const batchReport = createFlowBatchSafetyReport(filteredFlows, runtime);
document.querySelector('#batchSafety').innerHTML = renderFlowBatchSafetyReportToHTML(batchReport);

if (!report.ok) {
  throw new Error(report.blockingIssues.join('; '));
}

FlowManager renders single-flow and filtered-flow batch reports by default. Batch reports include risk counts, highest risk, check summaries, blocked flows, review flows, and detailed issues. It blocks publish when the report has blocking issues. A review report can still be published from the frontend, but backend publish APIs must continue to enforce authorization and business rules.

Access Hints

createFlowAccessReport() compares Flow-level and capability-level permission hints with the current actor permissions. FlowManager renders this report by default and blocks publish/execute interactions only when actor permissions are known and declared frontend permission hints are missing. FlowAssistantDrawer renders the same hints after a flow is matched and blocks execution when the current actor is known to be missing declared frontend permission hints.

import {
  createFlowAccessReport,
  renderFlowAccessReportToHTML
} from '@kupola/pivot-flow';

const access = createFlowAccessReport(flow, runtime, {
  context: {
    actor: {
      id: 'admin',
      permissions: ['system:org:create']
    }
  }
});

document.querySelector('#access').innerHTML = renderFlowAccessReportToHTML(access);

This is still a frontend safeguard only. Backend APIs must continue to enforce authentication, role permissions, data permissions, validation, transactions, and audit.

UI Example

FlowManager({
  target: '#flowManager',
  runtime,
  flowStore
});

FlowAssistantDrawer({
  trigger: '#pivotFlowBtn',
  runtime,
  flowStore,
  intentMapper: createLocalIntentMapper(),
  contextProvider: () => ({
    actor: {
      id: 'admin',
      permissions: ['system:org:create']
    }
  })
});

FlowManager provides the first configurable management surface:

  • create blank or sample flows
  • create draft flows from built-in or custom templates grouped by business domain
  • duplicate an existing flow as a draft copy before changing it
  • search, filter, group, publish, and disable filtered flows in the manager sidebar
  • edit flow name, description, status, risk, examples, keywords, patterns, and slots
  • review unsaved local edits with the default change report and reset the selected flow back to its saved baseline
  • list, create, and restore Flow snapshots when the configured store supports versioning
  • add built-in nodes from the palette
  • edit node label, type, capability, risk, confirmation, and JSON params
  • inspect a layered flow canvas generated from nodes and edges
  • search canvas nodes, locate a node from a compact selector, and highlight nodes related to the current selection
  • group canvas nodes by type, risk, or resource, then collapse groups while inspecting large flows
  • adjust canvas zoom, switch between comfortable and compact density, and show a minimap for large flows
  • inspect selected node upstream/downstream context with getFlowNodeNeighborhood() and renderFlowNodeNeighborhoodToHTML()
  • inspect canvas execution diagnostics including failed node summary, node duration, result message snippets, and cross-group edge counts
  • configure condition node JSON and transform node schemas
  • move or delete selected nodes
  • add, edit, and delete edges between nodes
  • validate edge ids, endpoints, and conditions before publishing
  • test match, preview, and execute from the management page with prompt and slots JSON
  • save, publish, disable, or delete flows through the configured FlowStore
  • preview and execute the selected flow through the configured PIVOT runtime
  • inspect capability dependencies, risk levels, confirmation requirements, and registered permissions for the selected flow
  • inspect execution paths in the canvas, including executed nodes, skipped nodes, failed nodes, and active edges
  • automatically focus the first failed node after execution and expose a failed-node jump action so the operator can inspect the broken step
  • prevent invalid canvas connections with canConnectFlowNodes(), including unknown nodes, self links, duplicate edges, and cycles

The designer uses a structured layered canvas rather than a freeform drag canvas. This keeps the API stable while making dependencies, edge direction, risk, confirmation, and execution state easier to inspect.

AI Flow Builder Safety Primitives

pivot-flow does not let AI execute or publish flows directly. The library exposes helper APIs for future AI builders:

import {
  createAIFlowBuilderContext,
  createAIFlowProvider,
  createAIFlowProviderMessages,
  createAIFlowProviderRequest,
  createAIFlowDraft,
  createAIFlowDraftRepairPlan,
  applyAIFlowDraftRepairPlan,
  createCapabilityManifestSummary,
  diffAIFlowDraft,
  generateAIFlowDraft,
  getMissingFlowCapabilities,
  parseAIFlowProviderOutput,
  recommendFlowCapabilities,
  renderAIFlowDraftReviewToHTML,
  renderAIFlowBuilderPanelToHTML,
  renderAIFlowDraftPreviewToHTML,
  AIFlowDraftReviewer,
  AIFlowBuilderPanel,
  validateAIFlowDraft
} from '@kupola/pivot-flow';

const context = createAIFlowBuilderContext(runtime);
const manifest = createCapabilityManifestSummary(runtime);
const recommendations = recommendFlowCapabilities('删除耗材 TEST-001', runtime);
const draft = createAIFlowDraft(aiStructuredOutput, { runtime });
const validation = validateAIFlowDraft(draft.flow, { runtime });
const missing = getMissingFlowCapabilities(draft.flow, runtime);
const repairPlan = createAIFlowDraftRepairPlan(draft, runtime);
const repaired = applyAIFlowDraftRepairPlan(draft, runtime);
const diff = diffAIFlowDraft(aiStructuredOutput.flow, draft.flow);
const previewHTML = renderAIFlowDraftPreviewToHTML(draft, { showDiff: true });

const providerPayload = createAIFlowProviderMessages('删除耗材 TEST-001', runtime);

const provider = createAIFlowProvider(async (request) => {
  const response = await fetch('/api/ai/flow-builder', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      prompt: request.prompt,
      safetyRules: request.safetyRules,
      flowShape: request.flowShape,
      capabilitySummary: request.capabilitySummary
    })
  });
  return await response.json();
}, { name: 'app-ai-flow-builder' });

const generated = await generateAIFlowDraft('删除耗材 TEST-001', {
  runtime,
  provider
});

AIFlowDraftReviewer({
  target: '#review',
  draftResult: generated,
  onSaveDraft: (flow) => flowStore.create(flow)
});

AIFlowBuilderPanel({
  target: '#builder',
  runtime,
  provider,
  onSaveDraft: (flow) => flowStore.create(flow)
});
  • createAIFlowBuilderContext() returns model-facing instructions, safety rules, expected Flow shape, and a sanitized capability summary.
  • createAIFlowProviderRequest() returns the canonical prompt, safety rules, response contract, and sanitized capability summary for a provider.
  • createAIFlowProviderMessages() returns generic chat-style messages plus a json_object response format hint for backend AI proxy implementations.
  • createAIFlowProvider() normalizes a project-owned AI adapter. The project can call any model API, but the provider must return structured JSON.
  • generateAIFlowDraft() sends a controlled builder request to the provider, parses the structured response, normalizes it as a draft, and validates it.
  • parseAIFlowProviderOutput() accepts common JSON response shapes, including raw JSON text, fenced JSON, { flow }, output_text, and chat choices[0].message.content.
  • createAIFlowDraft() converts structured AI output into a normalized draft Flow and validates it immediately.
  • createAIFlowDraftRepairPlan() turns missing capabilities into actionable review items: replace with a registered capability or register a new backend-backed capability first.
  • applyAIFlowDraftRepairPlan() applies reviewed replace-capability recommendations to a draft, then re-normalizes and re-validates it. It does not register capabilities, publish flows, or execute anything.
  • createCapabilityManifestSummary() returns a capability summary without execute functions.
  • getMissingFlowCapabilities() reports draft nodes that reference unavailable capabilities and suggests close registered capabilities.
  • diffAIFlowDraft() shows how the raw AI output changed during normalization, such as published becoming draft or high-risk confirmation being added.
  • recommendFlowCapabilities() ranks registered capabilities for a natural-language prompt.
  • renderAIFlowDraftPreviewToHTML() renders a safe draft preview with validation errors, nodes, risk, and confirmation state.
  • renderAIFlowDraftReviewToHTML() and AIFlowDraftReviewer() add a human review step before a draft is saved, including an optional apply-repair action for reviewed replacement suggestions.
  • renderAIFlowBuilderPanelToHTML() and AIFlowBuilderPanel() provide a default prompt-to-draft UI that recommends capabilities, generates a draft, can apply reviewed repair suggestions, renders review output, and saves only through the project-provided onSaveDraft.
  • validateAIFlowDraft() checks that AI output stays as a draft, only references registered capabilities, and requires confirmation for high-risk or delete operations.

The provider layer is intentionally generic. pivot-flow does not include OpenAI, Tongyi, Claude, or any other model SDK. Applications should call AI APIs through their own backend when secrets, tenant data, or audit requirements are involved. The backend should redact sensitive context, apply rate limits, and return only structured Flow JSON to the browser.

When AI references an unavailable capability, do not auto-create it. Use the repair plan as an operator/developer handoff:

const generated = await generateAIFlowDraft('归档发票 INV-001', {
  runtime,
  provider
});

if (!generated.ok && generated.repairPlan.missingCount > 0) {
  console.table(generated.repairPlan.registrationChecklist);
}

The checklist is only a planning artifact. Developers still need to implement the runtime capability, backend API, backend authorization, validation, and audit behavior before the Flow can be published safely.

Backend proxy shape:

app.post('/api/ai/flow-builder', requireAdmin, async (req, res) => {
  const { prompt, safetyRules, flowShape, capabilitySummary } = req.body;
  const modelResult = await ai.chat({
    messages: [
      {
        role: 'system',
        content: 'Return JSON only: { "prompt": string, "flow": FlowDefinition }. Never execute or publish.'
      },
      {
        role: 'user',
        content: JSON.stringify({
          prompt,
          safetyRules,
          flowShape,
          capabilitySummary
        })
      }
    ],
    response_format: { type: 'json_object' }
  });

  res.json(modelResult);
});

Security Boundary

Frontend PIVOT Flow permissions are only interaction hints and client-side safeguards. They do not replace backend authorization.

Backend APIs must still validate:

  • authentication
  • role permissions
  • data permissions
  • field-level constraints
  • business invariants
  • high-risk operations

If the frontend is bypassed, backend APIs must return 401, 403, 409, or 422 as appropriate.

AI-generated flows must remain drafts until reviewed and published by an authorized administrator.