Skip to content

Commit b73f70f

Browse files
committed
feat: add FBP integration, Flow Graph UI, and inline trigger enhancements
- Add .agents/skills/fbp/SKILL.md mapping FBP NodeDefinitions to platform functions - Add docs/spec/fbp-integration.md research doc with full mapping spec - Add Flows tab with React Flow: drag-and-drop function nodes, edge creation, localStorage persistence, sidebar palette, and minimap - Enhance FunctionsPanel trigger success message with Invocations tab link - Install @xyflow/react for the flow graph canvas
1 parent 19b6320 commit b73f70f

7 files changed

Lines changed: 3351 additions & 7 deletions

File tree

.agents/skills/fbp/SKILL.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
name: fbp
3+
description: Flow-Based Programming integration for the Constructive Functions platform. Maps FBP NodeDefinitions to platform_function_definitions and handler.json, enabling visual flow graphs where each function is a node with typed input/output ports.
4+
---
5+
6+
# FBP Integration Skill
7+
8+
## When to Apply
9+
10+
Use this skill when:
11+
- Mapping platform functions to FBP node definitions
12+
- Building or modifying the Flow Graph UI
13+
- Connecting function outputs to function inputs via edges
14+
- Working with the `@fbp/spec` or `@fbp/types` packages in this repo
15+
16+
## Core Mapping
17+
18+
Each `platform_function_definition` row (from `constructive_infra_public.platform_function_definitions`) maps to an FBP `NodeDefinition`:
19+
20+
| Platform Field | FBP Field | Notes |
21+
|---|---|---|
22+
| `name` | `NodeDefinition.name` | e.g. `send-email` |
23+
| `task_identifier` | `NodeDefinition.context` | e.g. `email:send_email` — acts as the dispatch key |
24+
| `description` | `NodeDefinition.description` | Human-readable |
25+
| `required_secrets` | `NodeDefinition.props[]` | Each secret becomes a `PropDef` with `required` flag |
26+
| `required_configs` | `NodeDefinition.props[]` | Each config becomes a `PropDef` |
27+
| `scope` | `NodeDefinition.category` | Groups nodes in the palette |
28+
29+
### Port Model
30+
31+
Functions have a single implicit input port (`payload`) and a single implicit output port (`result`):
32+
33+
```typescript
34+
// Every function node has these ports by default
35+
inputs: [{ name: 'payload', type: 'json' }]
36+
outputs: [{ name: 'result', type: 'json' }]
37+
```
38+
39+
An edge from `functionA.result` to `functionB.payload` means: "the output of job A is piped as the input payload of job B."
40+
41+
### handler.json → NodeDefinition
42+
43+
The `handler.json` manifest enriches the definition:
44+
45+
| handler.json Field | FBP Field |
46+
|---|---|
47+
| `name` | `NodeDefinition.name` |
48+
| `version` | stored in `meta` |
49+
| `type` | template type (not FBP type) |
50+
| `port` | stored in `meta` (runtime detail) |
51+
| `taskIdentifier` | `NodeDefinition.context` |
52+
| `description` | `NodeDefinition.description` |
53+
| `dependencies` | not mapped (build-time only) |
54+
55+
## FBP Spec Quick Reference
56+
57+
### Graph Structure
58+
59+
```typescript
60+
interface Graph {
61+
name?: string;
62+
nodes: Node[]; // Function instances
63+
edges: Edge[]; // Data flow connections
64+
definitions?: NodeDefinition[]; // Available function types
65+
}
66+
```
67+
68+
### Edge (connection between functions)
69+
70+
```typescript
71+
interface Edge {
72+
src: { node: string; port: string }; // e.g. { node: 'send-email', port: 'result' }
73+
dst: { node: string; port: string }; // e.g. { node: 'log-result', port: 'payload' }
74+
}
75+
```
76+
77+
### Node (function instance in a flow)
78+
79+
```typescript
80+
interface Node {
81+
name: string; // Instance name (unique within flow)
82+
type: string; // References a NodeDefinition name
83+
meta?: { x?: number; y?: number; description?: string };
84+
props?: Array<{ name: string; value?: any }>;
85+
}
86+
```
87+
88+
## Persistence
89+
90+
Flows are stored as JSON objects conforming to the `Graph` interface. Initial implementation uses `localStorage`; production will use a database table.
91+
92+
## Source Packages
93+
94+
- `@fbp/spec` — Storage types + manipulation API (from `constructive-io/fbp`)
95+
- `@fbp/types` — Core TypeScript type definitions
96+
- `@fbp/graph-editor` — React visual editor component
97+
- `@fbp/evaluator` — Graph execution engine

docs/spec/fbp-integration.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# FBP Integration with Constructive Functions
2+
3+
## Overview
4+
5+
This document describes how [Flow-Based Programming](https://en.wikipedia.org/wiki/Flow-based_programming) (FBP) concepts map onto the Constructive Functions platform, enabling visual flow graphs where each registered platform function becomes a draggable node with typed ports.
6+
7+
## Background
8+
9+
### Platform Function Definitions
10+
11+
Functions are registered in `constructive_infra_public.platform_function_definitions`:
12+
13+
```sql
14+
CREATE TABLE platform_function_definitions (
15+
name TEXT PRIMARY KEY,
16+
task_identifier TEXT NOT NULL, -- dispatch key, e.g. 'email:send_email'
17+
service_url TEXT,
18+
is_invocable BOOLEAN DEFAULT true,
19+
is_built_in BOOLEAN DEFAULT false,
20+
scope TEXT, -- e.g. 'platform', 'app'
21+
description TEXT,
22+
required_secrets composite[], -- (name, required) pairs
23+
required_configs composite[], -- (name, required) pairs
24+
namespace_id UUID REFERENCES platform_namespaces(id),
25+
created_at TIMESTAMPTZ,
26+
updated_at TIMESTAMPTZ
27+
);
28+
```
29+
30+
### handler.json Manifest
31+
32+
Each function's source directory contains a `handler.json`:
33+
34+
```json
35+
{
36+
"name": "send-email",
37+
"version": "1.6.4",
38+
"type": "node-graphql",
39+
"port": 8081,
40+
"taskIdentifier": "email:send_email",
41+
"description": "Sends emails directly from job payload",
42+
"dependencies": { ... }
43+
}
44+
```
45+
46+
### FBP NodeDefinition (from `@fbp/types`)
47+
48+
```typescript
49+
interface NodeDefinition {
50+
context: string; // namespace, e.g. "email"
51+
name: string; // e.g. "send-email"
52+
category?: string; // palette group
53+
inputs?: PortDef[]; // input ports
54+
outputs?: PortDef[]; // output ports
55+
props?: PropDef[]; // configuration properties
56+
description?: string;
57+
icon?: string;
58+
}
59+
```
60+
61+
## Mapping: platform_function_definitions → NodeDefinition
62+
63+
### Direct Field Mapping
64+
65+
| DB Column / handler.json | NodeDefinition Field | Transformation |
66+
|---|---|---|
67+
| `name` | `name` | Direct copy |
68+
| `task_identifier` | `context` | The `task_identifier` doubles as the FBP context (dispatch address) |
69+
| `scope` | `category` | Groups functions in the visual palette |
70+
| `description` | `description` | Direct copy |
71+
| `required_secrets[]` | `props[]` | Each `(secret_name, required)``{ name, type: 'secret', required }` |
72+
| `required_configs[]` | `props[]` | Each `(config_name, required)``{ name, type: 'config', required }` |
73+
74+
### Port Model
75+
76+
Every function has an identical port signature — one JSON input, one JSON output:
77+
78+
```
79+
┌────────────────────────┐
80+
│ send-email │
81+
│ │
82+
payload ──►│ handler(params, ctx) │──► result
83+
│ │
84+
└────────────────────────┘
85+
```
86+
87+
```typescript
88+
const toNodeDefinition = (fn: PlatformFunction): NodeDefinition => ({
89+
context: fn.task_identifier,
90+
name: fn.name,
91+
category: fn.scope || 'default',
92+
description: fn.description,
93+
inputs: [
94+
{ name: 'payload', type: 'json', description: 'Job payload object' }
95+
],
96+
outputs: [
97+
{ name: 'result', type: 'json', description: 'Handler return value' }
98+
],
99+
props: [
100+
...fn.required_secrets.map(s => ({
101+
name: s.name,
102+
type: 'secret',
103+
required: s.required,
104+
description: `Secret: ${s.name}`
105+
})),
106+
...fn.required_configs.map(c => ({
107+
name: c.name,
108+
type: 'config',
109+
required: c.required,
110+
description: `Config: ${c.name}`
111+
}))
112+
]
113+
});
114+
```
115+
116+
### Edge Semantics
117+
118+
An edge connects one function's output to another's input:
119+
120+
```typescript
121+
// "Pipe result of send-email into log-result"
122+
{
123+
src: { node: 'send-email-1', port: 'result' },
124+
dst: { node: 'log-result-1', port: 'payload' }
125+
}
126+
```
127+
128+
At runtime this means: when the job for `send-email-1` completes, its return value is used as the `payload` for a new job dispatched to `log-result-1`.
129+
130+
### Job Payload Flow
131+
132+
```
133+
User triggers flow
134+
135+
136+
Job A inserted into app_jobs.jobs
137+
│ task_identifier = fn_a.task_identifier
138+
│ payload = user-provided or upstream result
139+
140+
141+
Worker picks up Job A → calls fn_a handler
142+
143+
144+
fn_a returns { complete: true, data: {...} }
145+
146+
147+
For each outgoing edge from fn_a:
148+
│ Insert new job with:
149+
│ task_identifier = edge.dst function's task_identifier
150+
│ payload = fn_a's result (or mapped subset)
151+
152+
153+
Job B inserted → worker picks up → fn_b handler runs → ...
154+
```
155+
156+
## Flow Graph (UI Model)
157+
158+
A flow is persisted as an FBP `Graph`:
159+
160+
```typescript
161+
interface Flow {
162+
name: string;
163+
nodes: Array<{
164+
name: string; // instance id, e.g. "send-email-1"
165+
type: string; // function name, e.g. "send-email"
166+
meta?: { x: number; y: number };
167+
props?: Array<{ name: string; value?: any }>;
168+
}>;
169+
edges: Array<{
170+
src: { node: string; port: string };
171+
dst: { node: string; port: string };
172+
}>;
173+
definitions: NodeDefinition[]; // populated from GET /api/functions
174+
}
175+
```
176+
177+
### Persistence Strategy
178+
179+
| Phase | Storage | Notes |
180+
|---|---|---|
181+
| MVP | `localStorage` | Immediate, no backend changes needed |
182+
| Production | DB table `constructive_infra_public.platform_flows` | Content-addressable via merkle hashing (aligned with `@fbp/spec` design) |
183+
184+
## Future Extensions
185+
186+
- **Typed ports**: Parse handler signatures to generate richer port schemas (beyond generic JSON)
187+
- **Conditional routing**: Use FBP channels (`edge.channel`) for error vs. success paths
188+
- **Subnets**: Compose flows of flows using FBP's nested graph model
189+
- **Live execution**: Trigger a flow and watch job status propagate through the graph in real-time
190+
- **Flow evaluation**: Use `@fbp/evaluator` to validate graphs before execution

www/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"dependencies": {
1414
"@xterm/addon-fit": "^0.10.0",
1515
"@xterm/xterm": "^5.5.0",
16+
"@xyflow/react": "^12.11.0",
1617
"commander": "^15.0.0",
1718
"express": "^4.21.0",
1819
"kubernetesjs": "^0.7.7",

0 commit comments

Comments
 (0)