snow-flow 10.0.1-dev.362 → 10.0.1-dev.389

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "10.0.1-dev.362",
3
+ "version": "10.0.1-dev.389",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -82,6 +82,7 @@ import * as integrationTools from '../tools/integration/index.js';
82
82
  import * as platformTools from '../tools/platform/index.js';
83
83
  import * as devopsTools from '../tools/devops/index.js';
84
84
  import * as virtualAgentTools from '../tools/virtual-agent/index.js';
85
+ import * as flowDesignerTools from '../tools/flow-designer/index.js';
85
86
 
86
87
  // ES Module compatible __dirname
87
88
  const __filename = fileURLToPath(import.meta.url);
@@ -148,6 +149,7 @@ const STATIC_TOOL_MODULES: Record<string, any> = {
148
149
  'platform': platformTools,
149
150
  'devops': devopsTools,
150
151
  'virtual-agent': virtualAgentTools,
152
+ 'flow-designer': flowDesignerTools,
151
153
  };
152
154
 
153
155
  export class ToolRegistry {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Flow Designer Tools - Export all flow designer tool modules
3
+ */
4
+
5
+ export { toolDefinition as snow_manage_flow_def, execute as snow_manage_flow_exec } from './snow_manage_flow.js';
@@ -0,0 +1,809 @@
1
+ /**
2
+ * snow_manage_flow - Complete Flow Designer lifecycle management
3
+ *
4
+ * Create, list, get, update, activate, deactivate, delete and publish
5
+ * Flow Designer flows and subflows via Table API.
6
+ *
7
+ * ServiceNow does NOT expose a public Flow Designer creation API.
8
+ * This tool uses the Table API (sys_hub_flow) to manage flows directly.
9
+ */
10
+
11
+ import { MCPToolDefinition, ServiceNowContext, ToolResult } from '../../shared/types.js';
12
+ import { getAuthenticatedClient } from '../../shared/auth.js';
13
+ import { createSuccessResult, createErrorResult, SnowFlowError, ErrorType } from '../../shared/error-handler.js';
14
+ import { summary } from '../../shared/output-formatter.js';
15
+
16
+ // ── helpers ────────────────────────────────────────────────────────────
17
+
18
+ function sanitizeInternalName(name: string): string {
19
+ return name
20
+ .toLowerCase()
21
+ .replace(/[^a-z0-9_]/g, '_')
22
+ .replace(/_+/g, '_')
23
+ .replace(/^_|_$/g, '');
24
+ }
25
+
26
+ function isSysId(value: string): boolean {
27
+ return /^[a-f0-9]{32}$/.test(value);
28
+ }
29
+
30
+ /**
31
+ * Resolve a flow name to its sys_id. If the value is already a 32-char hex
32
+ * string it is returned as-is.
33
+ */
34
+ async function resolveFlowId(client: any, flowId: string): Promise<string> {
35
+ if (isSysId(flowId)) return flowId;
36
+
37
+ var lookup = await client.get('/api/now/table/sys_hub_flow', {
38
+ params: {
39
+ sysparm_query: 'name=' + flowId,
40
+ sysparm_fields: 'sys_id',
41
+ sysparm_limit: 1
42
+ }
43
+ });
44
+
45
+ if (!lookup.data.result || lookup.data.result.length === 0) {
46
+ throw new SnowFlowError(ErrorType.NOT_FOUND, 'Flow not found: ' + flowId);
47
+ }
48
+ return lookup.data.result[0].sys_id;
49
+ }
50
+
51
+ // ── tool definition ────────────────────────────────────────────────────
52
+
53
+ export const toolDefinition: MCPToolDefinition = {
54
+ name: 'snow_manage_flow',
55
+ description: 'Complete Flow Designer lifecycle: create flows/subflows, list, get details, update, activate, deactivate, delete and publish',
56
+ category: 'automation',
57
+ subcategory: 'flow-designer',
58
+ use_cases: ['flow-designer', 'automation', 'flow-management', 'subflow'],
59
+ complexity: 'advanced',
60
+ frequency: 'high',
61
+ permission: 'write',
62
+ allowedRoles: ['developer', 'admin'],
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ action: {
67
+ type: 'string',
68
+ enum: ['create', 'create_subflow', 'list', 'get', 'update', 'activate', 'deactivate', 'delete', 'publish'],
69
+ description: 'Action to perform'
70
+ },
71
+
72
+ // ── shared identifiers ──
73
+ flow_id: {
74
+ type: 'string',
75
+ description: 'Flow sys_id or name (required for get, update, activate, deactivate, delete, publish)'
76
+ },
77
+
78
+ // ── create / create_subflow params ──
79
+ name: {
80
+ type: 'string',
81
+ description: 'Flow name (required for create / create_subflow)'
82
+ },
83
+ description: {
84
+ type: 'string',
85
+ description: 'Flow description'
86
+ },
87
+ trigger_type: {
88
+ type: 'string',
89
+ enum: ['record_created', 'record_updated', 'scheduled', 'manual'],
90
+ description: 'Trigger type (create only, default: manual)',
91
+ default: 'manual'
92
+ },
93
+ table: {
94
+ type: 'string',
95
+ description: 'Table for record-based triggers (e.g. "incident") or list filter'
96
+ },
97
+ trigger_condition: {
98
+ type: 'string',
99
+ description: 'Encoded query condition for the trigger'
100
+ },
101
+ category: {
102
+ type: 'string',
103
+ description: 'Flow category',
104
+ default: 'custom'
105
+ },
106
+ run_as: {
107
+ type: 'string',
108
+ enum: ['user', 'system'],
109
+ description: 'Run-as context (default: user)',
110
+ default: 'user'
111
+ },
112
+ activities: {
113
+ type: 'array',
114
+ description: 'Flow action steps',
115
+ items: {
116
+ type: 'object',
117
+ properties: {
118
+ name: { type: 'string', description: 'Step name' },
119
+ type: {
120
+ type: 'string',
121
+ enum: ['notification', 'field_update', 'create_record', 'script', 'log', 'wait', 'approval'],
122
+ description: 'Action type'
123
+ },
124
+ inputs: { type: 'object', description: 'Step-specific input values' }
125
+ }
126
+ }
127
+ },
128
+ inputs: {
129
+ type: 'array',
130
+ description: 'Flow input variables',
131
+ items: {
132
+ type: 'object',
133
+ properties: {
134
+ name: { type: 'string' },
135
+ label: { type: 'string' },
136
+ type: { type: 'string', enum: ['string', 'integer', 'boolean', 'reference', 'object', 'array'] },
137
+ mandatory: { type: 'boolean' },
138
+ default_value: { type: 'string' }
139
+ }
140
+ }
141
+ },
142
+ outputs: {
143
+ type: 'array',
144
+ description: 'Flow output variables',
145
+ items: {
146
+ type: 'object',
147
+ properties: {
148
+ name: { type: 'string' },
149
+ label: { type: 'string' },
150
+ type: { type: 'string', enum: ['string', 'integer', 'boolean', 'reference', 'object', 'array'] }
151
+ }
152
+ }
153
+ },
154
+ activate: {
155
+ type: 'boolean',
156
+ description: 'Activate flow after creation (default: true)',
157
+ default: true
158
+ },
159
+
160
+ // ── list params ──
161
+ type: {
162
+ type: 'string',
163
+ enum: ['flow', 'subflow', 'all'],
164
+ description: 'Filter by type (list only, default: all)',
165
+ default: 'all'
166
+ },
167
+ active_only: {
168
+ type: 'boolean',
169
+ description: 'Only list active flows (default: true)',
170
+ default: true
171
+ },
172
+ limit: {
173
+ type: 'number',
174
+ description: 'Max results for list',
175
+ default: 50
176
+ },
177
+
178
+ // ── update params ──
179
+ update_fields: {
180
+ type: 'object',
181
+ description: 'Fields to update (for update action)',
182
+ properties: {
183
+ name: { type: 'string' },
184
+ description: { type: 'string' },
185
+ category: { type: 'string' },
186
+ run_as: { type: 'string' }
187
+ }
188
+ }
189
+ },
190
+ required: ['action']
191
+ }
192
+ };
193
+
194
+ // ── execute ────────────────────────────────────────────────────────────
195
+
196
+ export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
197
+ var action = args.action;
198
+
199
+ try {
200
+ var client = await getAuthenticatedClient(context);
201
+
202
+ switch (action) {
203
+
204
+ // ────────────────────────────────────────────────────────────────
205
+ // CREATE
206
+ // ────────────────────────────────────────────────────────────────
207
+ case 'create':
208
+ case 'create_subflow': {
209
+ var flowName = args.name;
210
+ if (!flowName) {
211
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'name is required for ' + action);
212
+ }
213
+
214
+ var isSubflow = action === 'create_subflow';
215
+ var flowDescription = args.description || flowName;
216
+ var triggerType = isSubflow ? 'manual' : (args.trigger_type || 'manual');
217
+ var flowTable = args.table || '';
218
+ var triggerCondition = args.trigger_condition || '';
219
+ var flowCategory = args.category || 'custom';
220
+ var flowRunAs = isSubflow ? 'user_who_calls' : (args.run_as || 'user');
221
+ var activitiesArg = args.activities || [];
222
+ var inputsArg = args.inputs || [];
223
+ var outputsArg = args.outputs || [];
224
+ var shouldActivate = args.activate !== false;
225
+
226
+ // Build flow_definition JSON
227
+ var flowDefinition: any = {
228
+ name: flowName,
229
+ description: flowDescription,
230
+ trigger: {
231
+ type: triggerType,
232
+ table: flowTable,
233
+ condition: triggerCondition
234
+ },
235
+ activities: activitiesArg.map(function (act: any, idx: number) {
236
+ return {
237
+ name: act.name,
238
+ label: act.name,
239
+ type: act.type || 'script',
240
+ inputs: act.inputs || {},
241
+ order: (idx + 1) * 100,
242
+ active: true
243
+ };
244
+ }),
245
+ inputs: inputsArg.map(function (inp: any) {
246
+ return {
247
+ name: inp.name,
248
+ label: inp.label || inp.name,
249
+ type: inp.type || 'string',
250
+ mandatory: inp.mandatory || false,
251
+ default_value: inp.default_value || ''
252
+ };
253
+ }),
254
+ outputs: outputsArg.map(function (out: any) {
255
+ return {
256
+ name: out.name,
257
+ label: out.label || out.name,
258
+ type: out.type || 'string'
259
+ };
260
+ }),
261
+ version: '1.0'
262
+ };
263
+
264
+ // Remove trigger block for subflows
265
+ if (isSubflow) {
266
+ delete flowDefinition.trigger;
267
+ }
268
+
269
+ var flowData: any = {
270
+ name: flowName,
271
+ description: flowDescription,
272
+ active: shouldActivate,
273
+ internal_name: sanitizeInternalName(flowName),
274
+ category: flowCategory,
275
+ run_as: flowRunAs,
276
+ status: shouldActivate ? 'published' : 'draft',
277
+ validated: true,
278
+ type: isSubflow ? 'subflow' : 'flow',
279
+ flow_definition: JSON.stringify(flowDefinition),
280
+ latest_snapshot: JSON.stringify(flowDefinition)
281
+ };
282
+
283
+ // 1. Create flow record via Table API
284
+ var flowResponse = await client.post('/api/now/table/sys_hub_flow', flowData);
285
+
286
+ var createdFlow = flowResponse.data.result;
287
+ var flowSysId = createdFlow.sys_id;
288
+
289
+ // 2. Create trigger instance (non-manual flows only)
290
+ var triggerCreated = false;
291
+ if (!isSubflow && triggerType !== 'manual') {
292
+ try {
293
+ // Lookup trigger action type
294
+ var triggerTypeLookup: Record<string, string> = {
295
+ 'record_created': 'sn_fd.trigger.record_created',
296
+ 'record_updated': 'sn_fd.trigger.record_updated',
297
+ 'scheduled': 'sn_fd.trigger.scheduled'
298
+ };
299
+ var triggerInternalName = triggerTypeLookup[triggerType] || '';
300
+
301
+ if (triggerInternalName) {
302
+ var triggerDefResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
303
+ params: {
304
+ sysparm_query: 'internal_name=' + triggerInternalName,
305
+ sysparm_fields: 'sys_id',
306
+ sysparm_limit: 1
307
+ }
308
+ });
309
+
310
+ var triggerDefId = triggerDefResp.data.result?.[0]?.sys_id;
311
+ if (triggerDefId) {
312
+ var triggerData: any = {
313
+ flow: flowSysId,
314
+ action_type: triggerDefId,
315
+ name: triggerType,
316
+ order: 0,
317
+ active: true
318
+ };
319
+ if (flowTable) {
320
+ triggerData.table = flowTable;
321
+ }
322
+ if (triggerCondition) {
323
+ triggerData.condition = triggerCondition;
324
+ }
325
+
326
+ await client.post('/api/now/table/sys_hub_trigger_instance', triggerData);
327
+ triggerCreated = true;
328
+ }
329
+ }
330
+ } catch (triggerError) {
331
+ // Trigger creation is best-effort
332
+ }
333
+ }
334
+
335
+ // 3. Create action instances
336
+ var actionsCreated = 0;
337
+ for (var ai = 0; ai < activitiesArg.length; ai++) {
338
+ var activity = activitiesArg[ai];
339
+ try {
340
+ // Lookup action type definition
341
+ var actionTypeName = activity.type || 'script';
342
+ var actionTypeQuery = 'internal_nameLIKE' + actionTypeName + '^ORnameLIKE' + actionTypeName;
343
+
344
+ var actionDefResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
345
+ params: {
346
+ sysparm_query: actionTypeQuery,
347
+ sysparm_fields: 'sys_id,name,internal_name',
348
+ sysparm_limit: 1
349
+ }
350
+ });
351
+
352
+ var actionDefId = actionDefResp.data.result?.[0]?.sys_id;
353
+ var instanceData: any = {
354
+ flow: flowSysId,
355
+ name: activity.name,
356
+ order: (ai + 1) * 100,
357
+ active: true
358
+ };
359
+ if (actionDefId) {
360
+ instanceData.action_type = actionDefId;
361
+ }
362
+
363
+ await client.post('/api/now/table/sys_hub_action_instance', instanceData);
364
+ actionsCreated++;
365
+ } catch (actError) {
366
+ // Action creation is best-effort
367
+ }
368
+ }
369
+
370
+ // 4. Create flow variables (for subflows with inputs/outputs)
371
+ var varsCreated = 0;
372
+ if (isSubflow) {
373
+ for (var vi = 0; vi < inputsArg.length; vi++) {
374
+ var inp = inputsArg[vi];
375
+ try {
376
+ await client.post('/api/now/table/sys_hub_flow_variable', {
377
+ flow: flowSysId,
378
+ name: inp.name,
379
+ label: inp.label || inp.name,
380
+ type: inp.type || 'string',
381
+ mandatory: inp.mandatory || false,
382
+ default_value: inp.default_value || '',
383
+ variable_type: 'input'
384
+ });
385
+ varsCreated++;
386
+ } catch (varError) {
387
+ // Best-effort
388
+ }
389
+ }
390
+ for (var vo = 0; vo < outputsArg.length; vo++) {
391
+ var out = outputsArg[vo];
392
+ try {
393
+ await client.post('/api/now/table/sys_hub_flow_variable', {
394
+ flow: flowSysId,
395
+ name: out.name,
396
+ label: out.label || out.name,
397
+ type: out.type || 'string',
398
+ variable_type: 'output'
399
+ });
400
+ varsCreated++;
401
+ } catch (varError) {
402
+ // Best-effort
403
+ }
404
+ }
405
+ }
406
+
407
+ // 5. Best-effort snapshot
408
+ try {
409
+ await client.post('/api/sn_flow_designer/flow/snapshot', {
410
+ flow_id: flowSysId
411
+ });
412
+ } catch (snapError) {
413
+ // Snapshot API may not exist in all instances
414
+ }
415
+
416
+ // Build summary
417
+ var createSummary = summary()
418
+ .success('Created ' + (isSubflow ? 'subflow' : 'flow') + ': ' + flowName)
419
+ .field('sys_id', flowSysId)
420
+ .field('Type', isSubflow ? 'Subflow' : 'Flow')
421
+ .field('Category', flowCategory)
422
+ .field('Status', shouldActivate ? 'Published (active)' : 'Draft')
423
+ .field('Method', 'Table API');
424
+
425
+ if (!isSubflow && triggerType !== 'manual') {
426
+ createSummary.field('Trigger', triggerType + (triggerCreated ? ' (created)' : ' (best-effort)'));
427
+ if (flowTable) createSummary.field('Table', flowTable);
428
+ }
429
+ if (activitiesArg.length > 0) {
430
+ createSummary.field('Actions', actionsCreated + '/' + activitiesArg.length + ' created');
431
+ }
432
+ if (varsCreated > 0) {
433
+ createSummary.field('Variables', varsCreated + ' created');
434
+ }
435
+
436
+ return createSuccessResult({
437
+ created: true,
438
+ method: 'table_api',
439
+ flow: {
440
+ sys_id: flowSysId,
441
+ name: flowName,
442
+ type: isSubflow ? 'subflow' : 'flow',
443
+ category: flowCategory,
444
+ active: shouldActivate,
445
+ status: shouldActivate ? 'published' : 'draft'
446
+ },
447
+ trigger: !isSubflow && triggerType !== 'manual' ? {
448
+ type: triggerType,
449
+ table: flowTable,
450
+ condition: triggerCondition,
451
+ created: triggerCreated
452
+ } : null,
453
+ activities_created: actionsCreated,
454
+ activities_requested: activitiesArg.length,
455
+ variables_created: varsCreated
456
+ }, {}, createSummary.build());
457
+ }
458
+
459
+ // ────────────────────────────────────────────────────────────────
460
+ // LIST
461
+ // ────────────────────────────────────────────────────────────────
462
+ case 'list': {
463
+ var listQuery = '';
464
+ var filterType = args.type || 'all';
465
+ var activeOnly = args.active_only !== false;
466
+ var listLimit = args.limit || 50;
467
+ var filterTable = args.table || '';
468
+
469
+ if (filterType !== 'all') {
470
+ listQuery = 'type=' + filterType;
471
+ }
472
+ if (activeOnly) {
473
+ listQuery += (listQuery ? '^' : '') + 'active=true';
474
+ }
475
+ if (filterTable) {
476
+ listQuery += (listQuery ? '^' : '') + 'flow_definitionLIKE' + filterTable;
477
+ }
478
+
479
+ var listResp = await client.get('/api/now/table/sys_hub_flow', {
480
+ params: {
481
+ sysparm_query: listQuery || undefined,
482
+ sysparm_fields: 'sys_id,name,description,type,category,active,status,run_as,sys_created_on,sys_updated_on',
483
+ sysparm_limit: listLimit
484
+ }
485
+ });
486
+
487
+ var flows = (listResp.data.result || []).map(function (f: any) {
488
+ return {
489
+ sys_id: f.sys_id,
490
+ name: f.name,
491
+ description: f.description,
492
+ type: f.type,
493
+ category: f.category,
494
+ active: f.active === 'true',
495
+ status: f.status,
496
+ run_as: f.run_as,
497
+ created: f.sys_created_on,
498
+ updated: f.sys_updated_on
499
+ };
500
+ });
501
+
502
+ var listSummary = summary()
503
+ .success('Found ' + flows.length + ' flow' + (flows.length === 1 ? '' : 's'));
504
+
505
+ for (var li = 0; li < Math.min(flows.length, 15); li++) {
506
+ var lf = flows[li];
507
+ listSummary.bullet(
508
+ lf.name +
509
+ ' [' + (lf.type || 'flow') + ']' +
510
+ (lf.active ? '' : ' (inactive)')
511
+ );
512
+ }
513
+ if (flows.length > 15) {
514
+ listSummary.indented('... and ' + (flows.length - 15) + ' more');
515
+ }
516
+
517
+ return createSuccessResult({
518
+ action: 'list',
519
+ count: flows.length,
520
+ flows: flows
521
+ }, {}, listSummary.build());
522
+ }
523
+
524
+ // ────────────────────────────────────────────────────────────────
525
+ // GET
526
+ // ────────────────────────────────────────────────────────────────
527
+ case 'get': {
528
+ if (!args.flow_id) {
529
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for get action');
530
+ }
531
+
532
+ var getSysId = await resolveFlowId(client, args.flow_id);
533
+
534
+ // Fetch flow record
535
+ var getResp = await client.get('/api/now/table/sys_hub_flow/' + getSysId);
536
+ var flowRecord = getResp.data.result;
537
+
538
+ // Fetch trigger instances
539
+ var triggerInstances: any[] = [];
540
+ try {
541
+ var trigResp = await client.get('/api/now/table/sys_hub_trigger_instance', {
542
+ params: {
543
+ sysparm_query: 'flow=' + getSysId,
544
+ sysparm_fields: 'sys_id,name,action_type,table,condition,active,order',
545
+ sysparm_limit: 10
546
+ }
547
+ });
548
+ triggerInstances = trigResp.data.result || [];
549
+ } catch (e) { /* best-effort */ }
550
+
551
+ // Fetch action instances
552
+ var actionInstances: any[] = [];
553
+ try {
554
+ var actResp = await client.get('/api/now/table/sys_hub_action_instance', {
555
+ params: {
556
+ sysparm_query: 'flow=' + getSysId + '^ORDERBYorder',
557
+ sysparm_fields: 'sys_id,name,action_type,order,active',
558
+ sysparm_limit: 50
559
+ }
560
+ });
561
+ actionInstances = actResp.data.result || [];
562
+ } catch (e) { /* best-effort */ }
563
+
564
+ // Fetch flow variables
565
+ var flowVars: any[] = [];
566
+ try {
567
+ var varResp = await client.get('/api/now/table/sys_hub_flow_variable', {
568
+ params: {
569
+ sysparm_query: 'flow=' + getSysId,
570
+ sysparm_fields: 'sys_id,name,label,type,mandatory,variable_type,default_value',
571
+ sysparm_limit: 50
572
+ }
573
+ });
574
+ flowVars = varResp.data.result || [];
575
+ } catch (e) { /* best-effort */ }
576
+
577
+ // Fetch recent executions
578
+ var executions: any[] = [];
579
+ try {
580
+ var execResp = await client.get('/api/now/table/sys_hub_flow_run', {
581
+ params: {
582
+ sysparm_query: 'flow=' + getSysId + '^ORDERBYDESCsys_created_on',
583
+ sysparm_fields: 'sys_id,state,started,ended,duration,trigger_record_table,trigger_record_id',
584
+ sysparm_limit: 10
585
+ }
586
+ });
587
+ executions = execResp.data.result || [];
588
+ } catch (e) { /* best-effort */ }
589
+
590
+ var getSummary = summary()
591
+ .success('Flow: ' + (flowRecord.name || args.flow_id))
592
+ .field('sys_id', flowRecord.sys_id)
593
+ .field('Type', flowRecord.type)
594
+ .field('Category', flowRecord.category)
595
+ .field('Status', flowRecord.active === 'true' ? 'Active' : 'Inactive')
596
+ .field('Run as', flowRecord.run_as)
597
+ .field('Description', flowRecord.description);
598
+
599
+ if (triggerInstances.length > 0) {
600
+ getSummary.blank().line('Triggers: ' + triggerInstances.length);
601
+ for (var ti = 0; ti < triggerInstances.length; ti++) {
602
+ getSummary.bullet(triggerInstances[ti].name || 'trigger-' + ti);
603
+ }
604
+ }
605
+ if (actionInstances.length > 0) {
606
+ getSummary.blank().line('Actions: ' + actionInstances.length);
607
+ for (var aci = 0; aci < Math.min(actionInstances.length, 10); aci++) {
608
+ getSummary.bullet(actionInstances[aci].name || 'action-' + aci);
609
+ }
610
+ }
611
+ if (flowVars.length > 0) {
612
+ getSummary.blank().line('Variables: ' + flowVars.length);
613
+ }
614
+ if (executions.length > 0) {
615
+ getSummary.blank().line('Recent executions: ' + executions.length);
616
+ for (var ei = 0; ei < Math.min(executions.length, 5); ei++) {
617
+ var ex = executions[ei];
618
+ getSummary.bullet((ex.state || 'unknown') + ' - ' + (ex.started || 'pending'));
619
+ }
620
+ }
621
+
622
+ return createSuccessResult({
623
+ action: 'get',
624
+ flow: {
625
+ sys_id: flowRecord.sys_id,
626
+ name: flowRecord.name,
627
+ description: flowRecord.description,
628
+ type: flowRecord.type,
629
+ category: flowRecord.category,
630
+ active: flowRecord.active === 'true',
631
+ status: flowRecord.status,
632
+ run_as: flowRecord.run_as,
633
+ created: flowRecord.sys_created_on,
634
+ updated: flowRecord.sys_updated_on
635
+ },
636
+ triggers: triggerInstances.map(function (t: any) {
637
+ return {
638
+ sys_id: t.sys_id,
639
+ name: t.name,
640
+ action_type: typeof t.action_type === 'object' ? t.action_type.display_value : t.action_type,
641
+ table: t.table,
642
+ condition: t.condition,
643
+ active: t.active === 'true'
644
+ };
645
+ }),
646
+ actions: actionInstances.map(function (a: any) {
647
+ return {
648
+ sys_id: a.sys_id,
649
+ name: a.name,
650
+ action_type: typeof a.action_type === 'object' ? a.action_type.display_value : a.action_type,
651
+ order: a.order,
652
+ active: a.active === 'true'
653
+ };
654
+ }),
655
+ variables: flowVars.map(function (v: any) {
656
+ return {
657
+ sys_id: v.sys_id,
658
+ name: v.name,
659
+ label: v.label,
660
+ type: v.type,
661
+ mandatory: v.mandatory === 'true',
662
+ variable_type: v.variable_type,
663
+ default_value: v.default_value
664
+ };
665
+ }),
666
+ recent_executions: executions.map(function (e: any) {
667
+ return {
668
+ sys_id: e.sys_id,
669
+ state: e.state,
670
+ started: e.started,
671
+ ended: e.ended,
672
+ duration: e.duration,
673
+ trigger_table: e.trigger_record_table,
674
+ trigger_record: e.trigger_record_id
675
+ };
676
+ })
677
+ }, {}, getSummary.build());
678
+ }
679
+
680
+ // ────────────────────────────────────────────────────────────────
681
+ // UPDATE
682
+ // ────────────────────────────────────────────────────────────────
683
+ case 'update': {
684
+ if (!args.flow_id) {
685
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for update action');
686
+ }
687
+ var updateFields = args.update_fields;
688
+ if (!updateFields || Object.keys(updateFields).length === 0) {
689
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'update_fields is required for update action');
690
+ }
691
+
692
+ var updateSysId = await resolveFlowId(client, args.flow_id);
693
+ await client.patch('/api/now/table/sys_hub_flow/' + updateSysId, updateFields);
694
+
695
+ var updateSummary = summary()
696
+ .success('Updated flow: ' + args.flow_id)
697
+ .field('sys_id', updateSysId);
698
+
699
+ var fieldNames = Object.keys(updateFields);
700
+ for (var fi = 0; fi < fieldNames.length; fi++) {
701
+ updateSummary.field(fieldNames[fi], updateFields[fieldNames[fi]]);
702
+ }
703
+
704
+ return createSuccessResult({
705
+ action: 'update',
706
+ flow_id: updateSysId,
707
+ updated_fields: fieldNames,
708
+ message: 'Flow updated successfully'
709
+ }, {}, updateSummary.build());
710
+ }
711
+
712
+ // ────────────────────────────────────────────────────────────────
713
+ // ACTIVATE / PUBLISH
714
+ // ────────────────────────────────────────────────────────────────
715
+ case 'activate':
716
+ case 'publish': {
717
+ if (!args.flow_id) {
718
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for ' + action + ' action');
719
+ }
720
+
721
+ var activateSysId = await resolveFlowId(client, args.flow_id);
722
+ await client.patch('/api/now/table/sys_hub_flow/' + activateSysId, {
723
+ active: true,
724
+ status: 'published',
725
+ validated: true
726
+ });
727
+
728
+ var activateSummary = summary()
729
+ .success('Flow activated and published')
730
+ .field('sys_id', activateSysId);
731
+
732
+ return createSuccessResult({
733
+ action: action,
734
+ flow_id: activateSysId,
735
+ active: true,
736
+ status: 'published',
737
+ message: 'Flow activated and published'
738
+ }, {}, activateSummary.build());
739
+ }
740
+
741
+ // ────────────────────────────────────────────────────────────────
742
+ // DEACTIVATE
743
+ // ────────────────────────────────────────────────────────────────
744
+ case 'deactivate': {
745
+ if (!args.flow_id) {
746
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for deactivate action');
747
+ }
748
+
749
+ var deactivateSysId = await resolveFlowId(client, args.flow_id);
750
+ await client.patch('/api/now/table/sys_hub_flow/' + deactivateSysId, {
751
+ active: false
752
+ });
753
+
754
+ var deactivateSummary = summary()
755
+ .success('Flow deactivated')
756
+ .field('sys_id', deactivateSysId);
757
+
758
+ return createSuccessResult({
759
+ action: 'deactivate',
760
+ flow_id: deactivateSysId,
761
+ active: false,
762
+ message: 'Flow deactivated'
763
+ }, {}, deactivateSummary.build());
764
+ }
765
+
766
+ // ────────────────────────────────────────────────────────────────
767
+ // DELETE
768
+ // ────────────────────────────────────────────────────────────────
769
+ case 'delete': {
770
+ if (!args.flow_id) {
771
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'flow_id is required for delete action');
772
+ }
773
+
774
+ var deleteSysId = await resolveFlowId(client, args.flow_id);
775
+ await client.delete('/api/now/table/sys_hub_flow/' + deleteSysId);
776
+
777
+ var deleteSummary = summary()
778
+ .success('Flow deleted')
779
+ .field('sys_id', deleteSysId);
780
+
781
+ return createSuccessResult({
782
+ action: 'delete',
783
+ flow_id: deleteSysId,
784
+ message: 'Flow deleted'
785
+ }, {}, deleteSummary.build());
786
+ }
787
+
788
+ default:
789
+ throw new SnowFlowError(ErrorType.VALIDATION_ERROR, 'Unknown action: ' + action);
790
+ }
791
+
792
+ } catch (error: any) {
793
+ if (error instanceof SnowFlowError) {
794
+ return createErrorResult(error);
795
+ }
796
+ if (error.response?.status === 403) {
797
+ return createErrorResult(
798
+ 'Permission denied (403): Your ServiceNow user lacks Flow Designer permissions. ' +
799
+ 'Required roles: "flow_designer" or "admin". Contact your ServiceNow administrator.'
800
+ );
801
+ }
802
+ return createErrorResult(
803
+ new SnowFlowError(ErrorType.UNKNOWN_ERROR, error.message, { originalError: error })
804
+ );
805
+ }
806
+ }
807
+
808
+ export const version = '1.0.0';
809
+ export const author = 'Snow-Flow Team';
@@ -31,16 +31,12 @@ const LEGACY_WARNING = `
31
31
  ServiceNow Workflow (wf_workflow) is deprecated in favor of Flow Designer.
32
32
  Flow Designer provides a modern, visual interface for building automations.
33
33
 
34
- However, Flow Designer is NOT programmable via Snow-Flow at this time.
34
+ Use snow_manage_flow to create and manage Flow Designer flows programmatically.
35
35
 
36
36
  RECOMMENDATIONS:
37
- 1. For NEW automations → Consider building manually in Flow Designer
37
+ 1. For NEW automations → Use snow_manage_flow (action: create)
38
38
  2. For EXISTING workflows → This tool can manage legacy workflows
39
- 3. Need Flow Designer specs? Ask Snow-Flow to generate a Flow Designer
40
- specification document with the required triggers, actions, and logic.
41
-
42
- To generate a Flow Designer specification, ask:
43
- "Generate a Flow Designer specification for [describe your automation]"
39
+ 3. For MIGRATIONUse snow_manage_flow to recreate workflows as flows
44
40
  `;
45
41
 
46
42
  export const toolDefinition: MCPToolDefinition = {