@tuongaz/seeflow 0.1.55 → 0.1.57
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/README.md +6 -6
- package/dist/web/assets/{index-Bn_TS_Fi.js → index-CPlccVLi.js} +1 -1
- package/dist/web/assets/{index.es-DFLQGnz3.js → index.es-CYTTDW0Q.js} +1 -1
- package/dist/web/assets/{jspdf.es.min-DOqDFz38.js → jspdf.es.min-DOaPC0dc.js} +3 -3
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/src/api.ts +24 -51
- package/src/cli-helpers.ts +20 -4
- package/src/cli-manifest.ts +37 -37
- package/src/cli.ts +8 -24
- package/src/mcp.ts +28 -57
- package/src/operations.ts +236 -147
- package/src/registry.ts +9 -0
package/src/api.ts
CHANGED
|
@@ -15,10 +15,9 @@ import type { EventBus } from './events.ts';
|
|
|
15
15
|
import { type LayoutOptions, computeLayout } from './layout.ts';
|
|
16
16
|
import {
|
|
17
17
|
ConnectorPatchBodySchema,
|
|
18
|
-
ConnectorsBulkBodySchema,
|
|
19
18
|
CreateProjectBodySchema,
|
|
19
|
+
FlowBulkBodySchema,
|
|
20
20
|
NodePatchBodySchema,
|
|
21
|
-
NodesBulkBodySchema,
|
|
22
21
|
PositionBodySchema,
|
|
23
22
|
RegisterBodySchema,
|
|
24
23
|
ReorderBodySchema,
|
|
@@ -1053,11 +1052,14 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
1053
1052
|
}
|
|
1054
1053
|
});
|
|
1055
1054
|
|
|
1056
|
-
// Bulk-create up to 100 nodes in one transactional write.
|
|
1057
|
-
// batch lands
|
|
1058
|
-
//
|
|
1059
|
-
//
|
|
1060
|
-
|
|
1055
|
+
// Bulk-create up to 100 nodes + 100 connectors in one transactional write.
|
|
1056
|
+
// Either the whole batch lands (single flow:reload broadcast) or nothing
|
|
1057
|
+
// does — a post-mutation ResolvedFlowSchema reject (e.g. dangling connector
|
|
1058
|
+
// source/target) rolls back both arrays together. Connectors may reference
|
|
1059
|
+
// nodes added in the same call; the parse sees the merged graph as a whole.
|
|
1060
|
+
// Intended for skill/LLM seeding where multiple singular calls would burn
|
|
1061
|
+
// tokens and round-trip latency.
|
|
1062
|
+
api.post('/flows/:id/bulk', async (c) => {
|
|
1061
1063
|
const id = c.req.param('id');
|
|
1062
1064
|
|
|
1063
1065
|
let body: unknown;
|
|
@@ -1066,15 +1068,19 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
1066
1068
|
} catch {
|
|
1067
1069
|
return c.json({ error: 'Body must be valid JSON' }, 400);
|
|
1068
1070
|
}
|
|
1069
|
-
const parsed =
|
|
1071
|
+
const parsed = FlowBulkBodySchema.safeParse(body);
|
|
1070
1072
|
if (!parsed.success) {
|
|
1071
|
-
return c.json({ error: 'Invalid bulk
|
|
1073
|
+
return c.json({ error: 'Invalid bulk body', issues: parsed.error.issues }, 400);
|
|
1072
1074
|
}
|
|
1073
1075
|
|
|
1074
|
-
const result = await ops.
|
|
1076
|
+
const result = await ops.addBulk(id, parsed.data);
|
|
1075
1077
|
switch (result.kind) {
|
|
1076
1078
|
case 'ok':
|
|
1077
|
-
return c.json({
|
|
1079
|
+
return c.json({
|
|
1080
|
+
ok: true,
|
|
1081
|
+
nodes: result.data.nodes,
|
|
1082
|
+
connectors: result.data.connectors,
|
|
1083
|
+
});
|
|
1078
1084
|
case 'flowNotFound':
|
|
1079
1085
|
return c.json({ error: 'unknown demo' }, 404);
|
|
1080
1086
|
case 'fileNotFound':
|
|
@@ -1084,9 +1090,14 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
1084
1090
|
case 'badSchema':
|
|
1085
1091
|
return c.json({ error: 'Flow failed schema validation', issues: result.issues }, 400);
|
|
1086
1092
|
case 'duplicateIdInBatch':
|
|
1087
|
-
return c.json({ error: `Duplicate id in batch: ${result.id}` }, 400);
|
|
1093
|
+
return c.json({ error: `Duplicate ${result.collection} id in batch: ${result.id}` }, 400);
|
|
1088
1094
|
case 'idAlreadyExists':
|
|
1089
|
-
return c.json(
|
|
1095
|
+
return c.json(
|
|
1096
|
+
{
|
|
1097
|
+
error: `${result.collection === 'nodes' ? 'Node' : 'Connector'} id already exists: ${result.id}`,
|
|
1098
|
+
},
|
|
1099
|
+
400,
|
|
1100
|
+
);
|
|
1090
1101
|
case 'writeFailed':
|
|
1091
1102
|
return c.json({ error: `Failed to write demo file: ${result.message}` }, 500);
|
|
1092
1103
|
}
|
|
@@ -1195,44 +1206,6 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
1195
1206
|
}
|
|
1196
1207
|
});
|
|
1197
1208
|
|
|
1198
|
-
// Bulk-create up to 100 connectors in one transactional write. Mirrors the
|
|
1199
|
-
// /nodes/bulk shape. Dangling source/target on any item rolls back the whole
|
|
1200
|
-
// batch via the post-mutation ResolvedFlowSchema parse.
|
|
1201
|
-
api.post('/flows/:id/connectors/bulk', async (c) => {
|
|
1202
|
-
const id = c.req.param('id');
|
|
1203
|
-
|
|
1204
|
-
let body: unknown;
|
|
1205
|
-
try {
|
|
1206
|
-
body = await c.req.json();
|
|
1207
|
-
} catch {
|
|
1208
|
-
return c.json({ error: 'Body must be valid JSON' }, 400);
|
|
1209
|
-
}
|
|
1210
|
-
const parsed = ConnectorsBulkBodySchema.safeParse(body);
|
|
1211
|
-
if (!parsed.success) {
|
|
1212
|
-
return c.json({ error: 'Invalid bulk connectors body', issues: parsed.error.issues }, 400);
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
const result = await ops.addConnectorsBulk(id, parsed.data);
|
|
1216
|
-
switch (result.kind) {
|
|
1217
|
-
case 'ok':
|
|
1218
|
-
return c.json({ ok: true, connectors: result.data.connectors });
|
|
1219
|
-
case 'flowNotFound':
|
|
1220
|
-
return c.json({ error: 'unknown demo' }, 404);
|
|
1221
|
-
case 'fileNotFound':
|
|
1222
|
-
return c.json({ error: `Flow file not found: ${result.path}` }, 404);
|
|
1223
|
-
case 'badJson':
|
|
1224
|
-
return c.json({ error: `Flow file is not valid JSON: ${result.message}` }, 400);
|
|
1225
|
-
case 'badSchema':
|
|
1226
|
-
return c.json({ error: 'Flow failed schema validation', issues: result.issues }, 400);
|
|
1227
|
-
case 'duplicateIdInBatch':
|
|
1228
|
-
return c.json({ error: `Duplicate id in batch: ${result.id}` }, 400);
|
|
1229
|
-
case 'idAlreadyExists':
|
|
1230
|
-
return c.json({ error: `Connector id already exists: ${result.id}` }, 400);
|
|
1231
|
-
case 'writeFailed':
|
|
1232
|
-
return c.json({ error: `Failed to write demo file: ${result.message}` }, 500);
|
|
1233
|
-
}
|
|
1234
|
-
});
|
|
1235
|
-
|
|
1236
1209
|
// DELETE a connector. Just removes the entry from demo.connectors — node
|
|
1237
1210
|
// deletion is what cascades, not connector deletion.
|
|
1238
1211
|
api.delete('/flows/:id/connectors/:connId', async (c) => {
|
package/src/cli-helpers.ts
CHANGED
|
@@ -101,10 +101,26 @@ function describeOutcome(outcome: { kind: string } & Record<string, unknown>): s
|
|
|
101
101
|
return `Flow file is not valid JSON: ${String(outcome.detail ?? outcome.message ?? '')}`;
|
|
102
102
|
case 'badSchema':
|
|
103
103
|
return `Flow failed schema validation: ${JSON.stringify(outcome.issues ?? [])}`;
|
|
104
|
-
case 'duplicateIdInBatch':
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
case 'duplicateIdInBatch': {
|
|
105
|
+
// `collection` is present on the FlowBulk outcome ('nodes' | 'connectors')
|
|
106
|
+
// and absent on legacy singular outcomes — keep both shapes working.
|
|
107
|
+
const collection = outcome.collection;
|
|
108
|
+
const prefix =
|
|
109
|
+
collection === 'nodes' || collection === 'connectors'
|
|
110
|
+
? `Duplicate ${collection} id in batch`
|
|
111
|
+
: 'Duplicate id in batch';
|
|
112
|
+
return `${prefix}: ${String(outcome.id ?? '')}`;
|
|
113
|
+
}
|
|
114
|
+
case 'idAlreadyExists': {
|
|
115
|
+
const collection = outcome.collection;
|
|
116
|
+
const prefix =
|
|
117
|
+
collection === 'nodes'
|
|
118
|
+
? 'Node id already exists'
|
|
119
|
+
: collection === 'connectors'
|
|
120
|
+
? 'Connector id already exists'
|
|
121
|
+
: 'Id already exists';
|
|
122
|
+
return `${prefix}: ${String(outcome.id ?? '')}`;
|
|
123
|
+
}
|
|
108
124
|
case 'writeFailed':
|
|
109
125
|
return `Failed to write demo file: ${String(outcome.message ?? '')}`;
|
|
110
126
|
case 'sdkWriteFailed':
|
package/src/cli-manifest.ts
CHANGED
|
@@ -6,10 +6,9 @@ import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
|
6
6
|
import { EXIT_CODE_BY_KIND, exitCodeForKind } from './cli-helpers.ts';
|
|
7
7
|
import {
|
|
8
8
|
ConnectorPatchBodySchema,
|
|
9
|
-
ConnectorsBulkBodySchema,
|
|
10
9
|
CreateProjectBodySchema,
|
|
10
|
+
FlowBulkBodySchema,
|
|
11
11
|
NodePatchBodySchema,
|
|
12
|
-
NodesBulkBodySchema,
|
|
13
12
|
PositionBodySchema,
|
|
14
13
|
RegisterBodySchema,
|
|
15
14
|
ReorderBodySchema,
|
|
@@ -252,6 +251,40 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
252
251
|
requiresStudio: false,
|
|
253
252
|
examples: ['seeflow flows:layout abc12345'],
|
|
254
253
|
},
|
|
254
|
+
{
|
|
255
|
+
name: 'flow:add-bulk',
|
|
256
|
+
synopsis: 'seeflow flow:add-bulk <flowId> [--json | --file | --stdin]',
|
|
257
|
+
description:
|
|
258
|
+
'Add up to 100 nodes + 100 connectors atomically. Body shape: ' +
|
|
259
|
+
'`{ nodes?: Node[], connectors?: Connector[] }` (at least one non-empty). ' +
|
|
260
|
+
'Connectors may reference nodes added in the same batch; the whole flow ' +
|
|
261
|
+
'is re-validated post-merge so a dangling source/target — or any per-item ' +
|
|
262
|
+
'schema failure — rolls back both arrays together and emits no broadcast.',
|
|
263
|
+
category: 'flows',
|
|
264
|
+
args: [{ name: 'flowId', required: true, description: 'Flow id or slug' }],
|
|
265
|
+
flags: BODY_FLAGS,
|
|
266
|
+
body: { schemaRef: 'FlowBulkBody' },
|
|
267
|
+
outputs: {
|
|
268
|
+
okExample: {
|
|
269
|
+
nodes: [{ id: 'node-a', node: { id: 'node-a' } }],
|
|
270
|
+
connectors: [{ id: 'conn-a' }],
|
|
271
|
+
},
|
|
272
|
+
errorKinds: [
|
|
273
|
+
'flowNotFound',
|
|
274
|
+
'fileNotFound',
|
|
275
|
+
'badJson',
|
|
276
|
+
'badSchema',
|
|
277
|
+
'duplicateIdInBatch',
|
|
278
|
+
'idAlreadyExists',
|
|
279
|
+
'writeFailed',
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
requiresStudio: false,
|
|
283
|
+
examples: [
|
|
284
|
+
'seeflow flow:add-bulk abc12345 --json \'{"nodes":[{"id":"a","type":"shapeNode","data":{"shape":"rectangle"}}],"connectors":[]}\'',
|
|
285
|
+
'seeflow flow:add-bulk abc12345 --file batch.json',
|
|
286
|
+
],
|
|
287
|
+
},
|
|
255
288
|
{
|
|
256
289
|
name: 'flows:play',
|
|
257
290
|
synopsis: 'seeflow flows:play <flowId> <nodeId>',
|
|
@@ -317,25 +350,6 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
317
350
|
'seeflow nodes:add abc12345 --json \'{"type":"shapeNode","data":{"shape":"rectangle"}}\'',
|
|
318
351
|
],
|
|
319
352
|
},
|
|
320
|
-
{
|
|
321
|
-
name: 'nodes:add-bulk',
|
|
322
|
-
synopsis: 'seeflow nodes:add-bulk <flowId> [--json | --file | --stdin]',
|
|
323
|
-
description:
|
|
324
|
-
'Add up to 100 nodes in one transactional write. Body shape: ' +
|
|
325
|
-
'`{ nodes: Node[] }`. Any duplicate id rolls back the whole batch.',
|
|
326
|
-
category: 'nodes',
|
|
327
|
-
args: [{ name: 'flowId', required: true, description: 'Flow id or slug' }],
|
|
328
|
-
flags: BODY_FLAGS,
|
|
329
|
-
body: { schemaRef: 'NodesBulkBody' },
|
|
330
|
-
outputs: {
|
|
331
|
-
okExample: { ids: ['a', 'b'] },
|
|
332
|
-
errorKinds: ['flowNotFound', 'fileNotFound', 'badSchema', 'duplicateIdInBatch'],
|
|
333
|
-
},
|
|
334
|
-
requiresStudio: false,
|
|
335
|
-
examples: [
|
|
336
|
-
'seeflow nodes:add-bulk abc12345 --json \'{"nodes":[{"id":"a","type":"shapeNode","data":{"shape":"rectangle"}}]}\'',
|
|
337
|
-
],
|
|
338
|
-
},
|
|
339
353
|
{
|
|
340
354
|
name: 'nodes:get',
|
|
341
355
|
synopsis: 'seeflow nodes:get <flowId> <nodeId>',
|
|
@@ -451,18 +465,6 @@ export const COMMAND_MANIFEST: CommandManifestEntry[] = [
|
|
|
451
465
|
'seeflow connectors:add abc12345 --json \'{"source":{"nodeId":"a"},"target":{"nodeId":"b"}}\'',
|
|
452
466
|
],
|
|
453
467
|
},
|
|
454
|
-
{
|
|
455
|
-
name: 'connectors:add-bulk',
|
|
456
|
-
synopsis: 'seeflow connectors:add-bulk <flowId> [--json | --file | --stdin]',
|
|
457
|
-
description: 'Add up to 100 connectors transactionally. Body: `{ connectors: Connector[] }`.',
|
|
458
|
-
category: 'connectors',
|
|
459
|
-
args: [{ name: 'flowId', required: true, description: 'Flow id or slug' }],
|
|
460
|
-
flags: BODY_FLAGS,
|
|
461
|
-
body: { schemaRef: 'ConnectorsBulkBody' },
|
|
462
|
-
outputs: { errorKinds: ['flowNotFound', 'badSchema', 'duplicateIdInBatch'] },
|
|
463
|
-
requiresStudio: false,
|
|
464
|
-
examples: ['seeflow connectors:add-bulk abc12345 --file connectors.json'],
|
|
465
|
-
},
|
|
466
468
|
{
|
|
467
469
|
name: 'connectors:patch',
|
|
468
470
|
synopsis: 'seeflow connectors:patch <flowId> <connectorId> [--json | --file | --stdin]',
|
|
@@ -542,10 +544,8 @@ function resolveSchemaRef(ref: string): unknown {
|
|
|
542
544
|
return zodToJsonSchema(NodePatchBodySchema, { $refStrategy: 'none' });
|
|
543
545
|
case 'ConnectorPatchBody':
|
|
544
546
|
return zodToJsonSchema(ConnectorPatchBodySchema, { $refStrategy: 'none' });
|
|
545
|
-
case '
|
|
546
|
-
return zodToJsonSchema(
|
|
547
|
-
case 'ConnectorsBulkBody':
|
|
548
|
-
return zodToJsonSchema(ConnectorsBulkBodySchema, { $refStrategy: 'none' });
|
|
547
|
+
case 'FlowBulkBody':
|
|
548
|
+
return zodToJsonSchema(FlowBulkBodySchema, { $refStrategy: 'none' });
|
|
549
549
|
case 'CreateProjectBody':
|
|
550
550
|
return zodToJsonSchema(CreateProjectBodySchema, { $refStrategy: 'none' });
|
|
551
551
|
case 'RegisterBody':
|
package/src/cli.ts
CHANGED
|
@@ -8,9 +8,8 @@ import { createEventBus } from './events.ts';
|
|
|
8
8
|
import type { LayoutOptions } from './layout.ts';
|
|
9
9
|
import {
|
|
10
10
|
ConnectorPatchBodySchema,
|
|
11
|
-
|
|
11
|
+
FlowBulkBodySchema,
|
|
12
12
|
NodePatchBodySchema,
|
|
13
|
-
NodesBulkBodySchema,
|
|
14
13
|
ReorderBodySchema,
|
|
15
14
|
} from './operations.ts';
|
|
16
15
|
import { seeflowHome } from './paths.ts';
|
|
@@ -134,12 +133,12 @@ if (argv.includes('--version') || argv.includes('-v')) {
|
|
|
134
133
|
await runFlowsDelete();
|
|
135
134
|
} else if (sub === 'flows:layout') {
|
|
136
135
|
await runFlowsLayout();
|
|
136
|
+
} else if (sub === 'flow:add-bulk') {
|
|
137
|
+
await runFlowAddBulk();
|
|
137
138
|
} else if (sub === 'flows:play') {
|
|
138
139
|
await runFlowsPlay();
|
|
139
140
|
} else if (sub === 'nodes:add') {
|
|
140
141
|
await runNodesAdd();
|
|
141
|
-
} else if (sub === 'nodes:add-bulk') {
|
|
142
|
-
await runNodesAddBulk();
|
|
143
142
|
} else if (sub === 'nodes:get') {
|
|
144
143
|
await runNodesGet();
|
|
145
144
|
} else if (sub === 'nodes:patch') {
|
|
@@ -152,8 +151,6 @@ if (argv.includes('--version') || argv.includes('-v')) {
|
|
|
152
151
|
await runNodesDelete();
|
|
153
152
|
} else if (sub === 'connectors:add') {
|
|
154
153
|
await runConnectorsAdd();
|
|
155
|
-
} else if (sub === 'connectors:add-bulk') {
|
|
156
|
-
await runConnectorsAddBulk();
|
|
157
154
|
} else if (sub === 'connectors:patch') {
|
|
158
155
|
await runConnectorsPatch();
|
|
159
156
|
} else if (sub === 'connectors:delete') {
|
|
@@ -188,15 +185,14 @@ Commands (work without a running studio):
|
|
|
188
185
|
flows:graph <id> List nodes + connectors without inlined file content
|
|
189
186
|
flows:delete <id> Unregister a flow
|
|
190
187
|
flows:layout <id> Apply ELK layout, writing style.json (--json/--file/--stdin optional)
|
|
188
|
+
flow:add-bulk <id> Add many nodes + connectors atomically (--json/--file/--stdin; body { nodes?, connectors? })
|
|
191
189
|
nodes:add <id> Add a node (--json/--file/--stdin)
|
|
192
|
-
nodes:add-bulk <id> Add many nodes (--json/--file/--stdin)
|
|
193
190
|
nodes:get <id> <n> Get a node with detail / html content inlined
|
|
194
191
|
nodes:patch <id> <n> Patch a node (--json/--file/--stdin)
|
|
195
192
|
nodes:move <id> <n> Move a node (--x N --y N)
|
|
196
193
|
nodes:reorder <id> <n> Reorder a node (--op forward|backward|toFront|toBack|toIndex [--index N])
|
|
197
194
|
nodes:delete <id> <n> Delete a node
|
|
198
195
|
connectors:add <id> Add a connector (--json/--file/--stdin)
|
|
199
|
-
connectors:add-bulk <id> Add many connectors (--json/--file/--stdin)
|
|
200
196
|
connectors:patch <id> <connId> Patch a connector (--json/--file/--stdin)
|
|
201
197
|
connectors:delete <id> <connId> Delete a connector
|
|
202
198
|
validate Schema-validate a flow.json (--file <file> [--style <file>])
|
|
@@ -661,15 +657,15 @@ async function runNodesAdd() {
|
|
|
661
657
|
printOutcome(result);
|
|
662
658
|
}
|
|
663
659
|
|
|
664
|
-
async function
|
|
660
|
+
async function runFlowAddBulk() {
|
|
665
661
|
const flowId = requireArg(1, '<flowId>');
|
|
666
662
|
const body = await bodyFromFlags();
|
|
667
|
-
const parsed =
|
|
663
|
+
const parsed = FlowBulkBodySchema.safeParse(body);
|
|
668
664
|
if (!parsed.success) {
|
|
669
|
-
printError(`Invalid
|
|
665
|
+
printError(`Invalid flow:add-bulk body: ${JSON.stringify(parsed.error.issues)}`);
|
|
670
666
|
}
|
|
671
667
|
const ops = createCliOperations();
|
|
672
|
-
const result = await ops.
|
|
668
|
+
const result = await ops.addBulk(flowId, parsed.data);
|
|
673
669
|
printOutcome(result);
|
|
674
670
|
}
|
|
675
671
|
|
|
@@ -755,18 +751,6 @@ async function runConnectorsAdd() {
|
|
|
755
751
|
printOutcome(result);
|
|
756
752
|
}
|
|
757
753
|
|
|
758
|
-
async function runConnectorsAddBulk() {
|
|
759
|
-
const flowId = requireArg(1, '<flowId>');
|
|
760
|
-
const body = await bodyFromFlags();
|
|
761
|
-
const parsed = ConnectorsBulkBodySchema.safeParse(body);
|
|
762
|
-
if (!parsed.success) {
|
|
763
|
-
printError(`Invalid connectors:add-bulk body: ${JSON.stringify(parsed.error.issues)}`);
|
|
764
|
-
}
|
|
765
|
-
const ops = createCliOperations();
|
|
766
|
-
const result = await ops.addConnectorsBulk(flowId, parsed.data);
|
|
767
|
-
printOutcome(result);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
754
|
async function runConnectorsPatch() {
|
|
771
755
|
const flowId = requireArg(1, '<flowId>');
|
|
772
756
|
const connId = requireArg(2, '<connectorId>');
|
package/src/mcp.ts
CHANGED
|
@@ -8,15 +8,16 @@ import { type ZodTypeAny, z } from 'zod';
|
|
|
8
8
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
9
9
|
import {
|
|
10
10
|
ConnectorPatchBodySchema,
|
|
11
|
-
ConnectorsBulkBodySchema,
|
|
12
11
|
CreateProjectBodySchema,
|
|
12
|
+
FLOW_BULK_NON_EMPTY_MESSAGE,
|
|
13
|
+
FlowBulkBodyShape,
|
|
13
14
|
NodePatchBodySchema,
|
|
14
|
-
NodesBulkBodySchema,
|
|
15
15
|
type Operations,
|
|
16
16
|
PositionBodySchema,
|
|
17
17
|
RegisterBodySchema,
|
|
18
18
|
ReorderBodySchema,
|
|
19
19
|
createOperations,
|
|
20
|
+
flowBulkNonEmpty,
|
|
20
21
|
} from './operations.ts';
|
|
21
22
|
import type { Registry } from './registry.ts';
|
|
22
23
|
import type { FlowWatcher } from './watcher.ts';
|
|
@@ -103,13 +104,16 @@ const AddNodeInputSchema = z.object({
|
|
|
103
104
|
node: z.record(z.unknown()),
|
|
104
105
|
});
|
|
105
106
|
|
|
106
|
-
//
|
|
107
|
-
// add_node — ResolvedFlowSchema runs once
|
|
108
|
-
//
|
|
109
|
-
// 100-
|
|
110
|
-
|
|
107
|
+
// add_bulk input: { flowId, nodes?: [...], connectors?: [...] }. Same loose
|
|
108
|
+
// per-item shape as add_node / add_connector — ResolvedFlowSchema runs once
|
|
109
|
+
// over the whole merged graph server-side after the batch lands. The
|
|
110
|
+
// 100-per-kind cap and "at least one non-empty" invariant come from
|
|
111
|
+
// FlowBulkBodyShape + flowBulkNonEmpty (the unrefined object + reusable
|
|
112
|
+
// predicate exported by operations.ts) so the JSON Schema the agent
|
|
113
|
+
// introspects stays a clean object — not an intersection.
|
|
114
|
+
const AddBulkInputSchema = FlowBulkBodyShape.extend({
|
|
111
115
|
flowId: z.string().min(1),
|
|
112
|
-
});
|
|
116
|
+
}).refine(flowBulkNonEmpty, { message: FLOW_BULK_NON_EMPTY_MESSAGE });
|
|
113
117
|
|
|
114
118
|
const DeleteNodeInputSchema = FlowNodeIdBaseSchema;
|
|
115
119
|
|
|
@@ -153,11 +157,6 @@ const AddConnectorInputSchema = z.object({
|
|
|
153
157
|
connector: z.record(z.unknown()),
|
|
154
158
|
});
|
|
155
159
|
|
|
156
|
-
// add_connectors input: { flowId, connectors: [...] }. Mirrors add_nodes.
|
|
157
|
-
const AddConnectorsInputSchema = ConnectorsBulkBodySchema.extend({
|
|
158
|
-
flowId: z.string().min(1),
|
|
159
|
-
});
|
|
160
|
-
|
|
161
160
|
// patch_connector input: { flowId, connectorId } merged with the strict
|
|
162
161
|
// ConnectorPatchBodySchema. .extend() preserves strict mode so unknown
|
|
163
162
|
// top-level keys trip the Zod parse before any IO — matching the REST
|
|
@@ -407,20 +406,24 @@ const buildTools = (ops: Operations): McpTool[] => [
|
|
|
407
406
|
},
|
|
408
407
|
},
|
|
409
408
|
{
|
|
410
|
-
name: '
|
|
409
|
+
name: 'seeflow_add_bulk',
|
|
411
410
|
description:
|
|
412
|
-
'Append 1–100 nodes to a flow in a
|
|
413
|
-
inputSchema: inputSchemaFromZod(
|
|
411
|
+
'Append 1–100 nodes and 1–100 connectors to a flow in a SINGLE transactional write. Either every item lands or nothing does — a dangling connector source/target, a duplicate id, or any per-item schema failure rolls back BOTH arrays together (no flow:reload broadcast emitted). Connectors may reference nodes added in the same call. Body: { flowId, nodes?, connectors? } with at least one non-empty. Use this — not multiple seeflow_add_node / seeflow_add_connector round-trips — when seeding a flow. Same per-item shape and externalization rules as the singular tools.',
|
|
412
|
+
inputSchema: inputSchemaFromZod(AddBulkInputSchema),
|
|
414
413
|
handler: async (args) => {
|
|
415
|
-
const parsed =
|
|
414
|
+
const parsed = AddBulkInputSchema.safeParse(args);
|
|
416
415
|
if (!parsed.success) {
|
|
417
|
-
return errorResult(`Invalid
|
|
416
|
+
return errorResult(`Invalid add_bulk arguments: ${JSON.stringify(parsed.error.issues)}`);
|
|
418
417
|
}
|
|
419
|
-
const { flowId, nodes } = parsed.data;
|
|
420
|
-
const result = await ops.
|
|
418
|
+
const { flowId, nodes, connectors } = parsed.data;
|
|
419
|
+
const result = await ops.addBulk(flowId, { nodes, connectors });
|
|
421
420
|
switch (result.kind) {
|
|
422
421
|
case 'ok':
|
|
423
|
-
return okResult({
|
|
422
|
+
return okResult({
|
|
423
|
+
ok: true,
|
|
424
|
+
nodes: result.data.nodes,
|
|
425
|
+
connectors: result.data.connectors,
|
|
426
|
+
});
|
|
424
427
|
case 'flowNotFound':
|
|
425
428
|
return errorResult('unknown demo');
|
|
426
429
|
case 'fileNotFound':
|
|
@@ -430,9 +433,11 @@ const buildTools = (ops: Operations): McpTool[] => [
|
|
|
430
433
|
case 'badSchema':
|
|
431
434
|
return errorResult(`Flow failed schema validation: ${JSON.stringify(result.issues)}`);
|
|
432
435
|
case 'duplicateIdInBatch':
|
|
433
|
-
return errorResult(`Duplicate id in batch: ${result.id}`);
|
|
436
|
+
return errorResult(`Duplicate ${result.collection} id in batch: ${result.id}`);
|
|
434
437
|
case 'idAlreadyExists':
|
|
435
|
-
return errorResult(
|
|
438
|
+
return errorResult(
|
|
439
|
+
`${result.collection === 'nodes' ? 'Node' : 'Connector'} id already exists: ${result.id}`,
|
|
440
|
+
);
|
|
436
441
|
case 'writeFailed':
|
|
437
442
|
return errorResult(`Failed to write demo file: ${result.message}`);
|
|
438
443
|
}
|
|
@@ -592,40 +597,6 @@ const buildTools = (ops: Operations): McpTool[] => [
|
|
|
592
597
|
}
|
|
593
598
|
},
|
|
594
599
|
},
|
|
595
|
-
{
|
|
596
|
-
name: 'seeflow_add_connectors',
|
|
597
|
-
description:
|
|
598
|
-
'Append 1–100 connectors to a flow in a single transactional write. Either every connector lands or nothing does — a dangling source/target on any item rolls back the whole batch. Use after seeflow_add_nodes when seeding a flow.',
|
|
599
|
-
inputSchema: inputSchemaFromZod(AddConnectorsInputSchema),
|
|
600
|
-
handler: async (args) => {
|
|
601
|
-
const parsed = AddConnectorsInputSchema.safeParse(args);
|
|
602
|
-
if (!parsed.success) {
|
|
603
|
-
return errorResult(
|
|
604
|
-
`Invalid add_connectors arguments: ${JSON.stringify(parsed.error.issues)}`,
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
const { flowId, connectors } = parsed.data;
|
|
608
|
-
const result = await ops.addConnectorsBulk(flowId, { connectors });
|
|
609
|
-
switch (result.kind) {
|
|
610
|
-
case 'ok':
|
|
611
|
-
return okResult({ ok: true, connectors: result.data.connectors });
|
|
612
|
-
case 'flowNotFound':
|
|
613
|
-
return errorResult('unknown demo');
|
|
614
|
-
case 'fileNotFound':
|
|
615
|
-
return errorResult(`Flow file not found: ${result.path}`);
|
|
616
|
-
case 'badJson':
|
|
617
|
-
return errorResult(`Flow file is not valid JSON: ${result.message}`);
|
|
618
|
-
case 'badSchema':
|
|
619
|
-
return errorResult(`Flow failed schema validation: ${JSON.stringify(result.issues)}`);
|
|
620
|
-
case 'duplicateIdInBatch':
|
|
621
|
-
return errorResult(`Duplicate id in batch: ${result.id}`);
|
|
622
|
-
case 'idAlreadyExists':
|
|
623
|
-
return errorResult(`Connector id already exists: ${result.id}`);
|
|
624
|
-
case 'writeFailed':
|
|
625
|
-
return errorResult(`Failed to write demo file: ${result.message}`);
|
|
626
|
-
}
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
600
|
{
|
|
630
601
|
name: 'seeflow_patch_connector',
|
|
631
602
|
description:
|