snow-flow 10.0.140 → 10.0.141
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
|
@@ -208,7 +208,9 @@ async function getMaxOrderFromVersion(client: any, flowId: string): Promise<numb
|
|
|
208
208
|
const max = findMaxOrder(data)
|
|
209
209
|
if (max > 0) return max
|
|
210
210
|
}
|
|
211
|
-
} catch (
|
|
211
|
+
} catch (e: any) {
|
|
212
|
+
console.warn("[snow_manage_flow] getMaxOrderFromVersion processflow API failed for flow=" + flowId + ": " + (e.message || ""))
|
|
213
|
+
}
|
|
212
214
|
|
|
213
215
|
// Strategy 2: sys_hub_flow_version.payload (may be stale after rapid mutations)
|
|
214
216
|
try {
|
|
@@ -225,7 +227,9 @@ async function getMaxOrderFromVersion(client: any, flowId: string): Promise<numb
|
|
|
225
227
|
const max = findMaxOrder(parsed)
|
|
226
228
|
if (max > 0) return max
|
|
227
229
|
}
|
|
228
|
-
} catch (
|
|
230
|
+
} catch (e: any) {
|
|
231
|
+
console.warn("[snow_manage_flow] getMaxOrderFromVersion version payload failed for flow=" + flowId + ": " + (e.message || ""))
|
|
232
|
+
}
|
|
229
233
|
|
|
230
234
|
return 0
|
|
231
235
|
}
|
|
@@ -372,8 +376,8 @@ async function releaseFlowEditingLock(
|
|
|
372
376
|
await client.delete("/api/now/table/sys_hub_flow_safe_edit/" + records[r].sys_id)
|
|
373
377
|
}
|
|
374
378
|
debug.safe_edit_records_cleaned = records.length
|
|
375
|
-
} catch (
|
|
376
|
-
|
|
379
|
+
} catch (e: any) {
|
|
380
|
+
console.warn("[snow_manage_flow] releaseFlowEditingLock: REST cleanup failed: " + (e.message || ""))
|
|
377
381
|
}
|
|
378
382
|
|
|
379
383
|
return { success: graphqlOk, compilationError, debug }
|
|
@@ -456,8 +460,8 @@ async function ensureUpdateSetForFlow(
|
|
|
456
460
|
await setUpdateSetForServiceAccount(client, existingSet.sys_id)
|
|
457
461
|
return { updateSetId: existingSet.sys_id, updateSetName: existingSet.name }
|
|
458
462
|
}
|
|
459
|
-
} catch (
|
|
460
|
-
|
|
463
|
+
} catch (e: any) {
|
|
464
|
+
console.warn("[snow_manage_flow] ensureUpdateSetForFlow: lookup failed, falling through to create: " + (e.message || ""))
|
|
461
465
|
}
|
|
462
466
|
|
|
463
467
|
// Create a new update set
|
|
@@ -578,7 +582,8 @@ async function getExistingLabelCachePills(client: any, flowId: string): Promise<
|
|
|
578
582
|
}
|
|
579
583
|
}
|
|
580
584
|
return result
|
|
581
|
-
} catch (
|
|
585
|
+
} catch (e: any) {
|
|
586
|
+
console.warn("[snow_manage_flow] getExistingLabelCachePills failed for flow=" + flowId + ": " + (e.message || ""))
|
|
582
587
|
return {}
|
|
583
588
|
}
|
|
584
589
|
}
|
|
@@ -667,7 +672,9 @@ async function buildActionInputsForInsert(
|
|
|
667
672
|
},
|
|
668
673
|
})
|
|
669
674
|
actionParams = resp.data.result || []
|
|
670
|
-
} catch (
|
|
675
|
+
} catch (e: any) {
|
|
676
|
+
console.warn("[snow_manage_flow] sys_hub_action_input query failed for model=" + actionDefId + ": " + (e.message || ""))
|
|
677
|
+
}
|
|
671
678
|
|
|
672
679
|
// Fuzzy-match user-provided values to actual field names.
|
|
673
680
|
// ServiceNow action parameters often have prefixed element names:
|
|
@@ -834,7 +841,8 @@ async function validateTableExists(client: any, tableName: string): Promise<{ ex
|
|
|
834
841
|
})
|
|
835
842
|
var rec = resp.data.result?.[0]
|
|
836
843
|
return rec ? { exists: true, label: str(rec.label) || tableName } : { exists: false, label: "" }
|
|
837
|
-
} catch (
|
|
844
|
+
} catch (e: any) {
|
|
845
|
+
console.warn("[snow_manage_flow] validateTableExists failed for table=" + tableName + ": " + (e.message || ""))
|
|
838
846
|
return { exists: false, label: "" }
|
|
839
847
|
}
|
|
840
848
|
}
|
|
@@ -873,7 +881,8 @@ async function validateTableExtends(
|
|
|
873
881
|
return str(c.name) === tableName
|
|
874
882
|
})
|
|
875
883
|
return { valid: !!match, validOptions: validOptions, tableLabel: match ? str(match.label) : "" }
|
|
876
|
-
} catch (
|
|
884
|
+
} catch (e: any) {
|
|
885
|
+
console.warn("[snow_manage_flow] validateTableExtends failed for " + tableName + " extends " + parentTable + ": " + (e.message || ""))
|
|
877
886
|
return { valid: false, validOptions: [], tableLabel: "" }
|
|
878
887
|
}
|
|
879
888
|
}
|
|
@@ -3058,6 +3067,9 @@ function isStandardEncodedQuery(condition: string): boolean {
|
|
|
3058
3067
|
if (condition.startsWith("fd_data.")) return false
|
|
3059
3068
|
// Already contains data pill references (already transformed)
|
|
3060
3069
|
if (condition.includes("{{")) return false
|
|
3070
|
+
// Final check: try to parse as encoded query — if it produces valid clauses, it's a standard query
|
|
3071
|
+
var clauses = parseEncodedQuery(condition)
|
|
3072
|
+
if (clauses.length === 0 && condition.length > 0) return false
|
|
3061
3073
|
return true
|
|
3062
3074
|
}
|
|
3063
3075
|
|
|
@@ -3874,11 +3886,11 @@ async function addFlowLogicViaGraphQL(
|
|
|
3874
3886
|
!rawCondition.includes(".") &&
|
|
3875
3887
|
!rawCondition.startsWith("fd_data")
|
|
3876
3888
|
) {
|
|
3877
|
-
var BARE_FIELD_RE = /(\w+)\s*(===?|!==?|>=|<=|>|<|=|LIKE|STARTSWITH|ENDSWITH|NOT LIKE|ISEMPTY|ISNOTEMPTY)\s*/g
|
|
3889
|
+
var BARE_FIELD_RE = /(^|\^(?:OR)?|\^NQ)(\w+)\s*(===?|!==?|>=|<=|>|<|=|LIKE|STARTSWITH|ENDSWITH|NOT LIKE|ISEMPTY|ISNOTEMPTY)\s*/g
|
|
3878
3890
|
if (BARE_FIELD_RE.test(rawCondition)) {
|
|
3879
3891
|
BARE_FIELD_RE.lastIndex = 0
|
|
3880
|
-
rawCondition = rawCondition.replace(BARE_FIELD_RE, function (_m: string, field: string, op: string) {
|
|
3881
|
-
return "trigger.current." + field + op
|
|
3892
|
+
rawCondition = rawCondition.replace(BARE_FIELD_RE, function (_m: string, prefix: string, field: string, op: string) {
|
|
3893
|
+
return prefix + "trigger.current." + field + op
|
|
3882
3894
|
})
|
|
3883
3895
|
steps.bare_field_rewrite = { original: conditionInput?.value?.value, rewritten: rawCondition }
|
|
3884
3896
|
}
|
|
@@ -4633,18 +4645,59 @@ async function createFlowViaProcessFlowAPI(
|
|
|
4633
4645
|
async function resolveFlowId(client: any, flowId: string): Promise<string> {
|
|
4634
4646
|
if (isSysId(flowId)) return flowId
|
|
4635
4647
|
|
|
4648
|
+
// Strategy 1: Exact match on name
|
|
4636
4649
|
var lookup = await client.get("/api/now/table/sys_hub_flow", {
|
|
4637
4650
|
params: {
|
|
4638
4651
|
sysparm_query: "name=" + flowId,
|
|
4639
|
-
sysparm_fields: "sys_id",
|
|
4640
|
-
sysparm_limit:
|
|
4652
|
+
sysparm_fields: "sys_id,name,active",
|
|
4653
|
+
sysparm_limit: 5,
|
|
4641
4654
|
},
|
|
4642
4655
|
})
|
|
4656
|
+
if (lookup.data.result && lookup.data.result.length > 0) {
|
|
4657
|
+
// Prefer active flows when multiple matches
|
|
4658
|
+
var activeMatch = lookup.data.result.find(function (f: any) {
|
|
4659
|
+
return f.active === "true"
|
|
4660
|
+
})
|
|
4661
|
+
return (activeMatch || lookup.data.result[0]).sys_id
|
|
4662
|
+
}
|
|
4643
4663
|
|
|
4644
|
-
|
|
4645
|
-
|
|
4664
|
+
// Strategy 2: Exact match on internal_name
|
|
4665
|
+
var internalName = sanitizeInternalName(flowId)
|
|
4666
|
+
var internalLookup = await client.get("/api/now/table/sys_hub_flow", {
|
|
4667
|
+
params: {
|
|
4668
|
+
sysparm_query: "internal_name=" + internalName,
|
|
4669
|
+
sysparm_fields: "sys_id,name,active",
|
|
4670
|
+
sysparm_limit: 5,
|
|
4671
|
+
},
|
|
4672
|
+
})
|
|
4673
|
+
if (internalLookup.data.result && internalLookup.data.result.length > 0) {
|
|
4674
|
+
var activeInternalMatch = internalLookup.data.result.find(function (f: any) {
|
|
4675
|
+
return f.active === "true"
|
|
4676
|
+
})
|
|
4677
|
+
return (activeInternalMatch || internalLookup.data.result[0]).sys_id
|
|
4646
4678
|
}
|
|
4647
|
-
|
|
4679
|
+
|
|
4680
|
+
// Strategy 3: LIKE fallback on name and internal_name
|
|
4681
|
+
var likeLookup = await client.get("/api/now/table/sys_hub_flow", {
|
|
4682
|
+
params: {
|
|
4683
|
+
sysparm_query: "nameLIKE" + flowId + "^ORinternal_nameLIKE" + internalName,
|
|
4684
|
+
sysparm_fields: "sys_id,name,active",
|
|
4685
|
+
sysparm_limit: 10,
|
|
4686
|
+
},
|
|
4687
|
+
})
|
|
4688
|
+
if (likeLookup.data.result && likeLookup.data.result.length > 0) {
|
|
4689
|
+
var activeLikeMatch = likeLookup.data.result.find(function (f: any) {
|
|
4690
|
+
return f.active === "true"
|
|
4691
|
+
})
|
|
4692
|
+
return (activeLikeMatch || likeLookup.data.result[0]).sys_id
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
throw new SnowFlowError(
|
|
4696
|
+
ErrorType.NOT_FOUND,
|
|
4697
|
+
"Flow not found: '" +
|
|
4698
|
+
flowId +
|
|
4699
|
+
"'. Use the 'list' action to find available flows, or provide a sys_id.",
|
|
4700
|
+
)
|
|
4648
4701
|
}
|
|
4649
4702
|
|
|
4650
4703
|
// ── tool definition ────────────────────────────────────────────────────
|
|
@@ -4652,7 +4705,8 @@ async function resolveFlowId(client: any, flowId: string): Promise<string> {
|
|
|
4652
4705
|
export const toolDefinition: MCPToolDefinition = {
|
|
4653
4706
|
name: "snow_manage_flow",
|
|
4654
4707
|
description:
|
|
4655
|
-
"Complete Flow Designer lifecycle: create flows/subflows, add/update triggers and actions, list, get details, update, activate, deactivate, delete and publish. Use update_trigger to change an existing trigger (e.g. switch from record_created to record_create_or_update) without deleting the flow."
|
|
4708
|
+
"Complete Flow Designer lifecycle: create flows/subflows, add/update triggers and actions, list, get details, update, activate, deactivate, delete and publish. Use update_trigger to change an existing trigger (e.g. switch from record_created to record_create_or_update) without deleting the flow. " +
|
|
4709
|
+
"IMPORTANT: Flow elements (triggers, actions, flow logic, subflows) can ONLY be created/updated/deleted via this tool's GraphQL mutations. Direct Table API operations on sys_hub_action_instance, sys_hub_flow_logic, sys_hub_sub_flow_instance, sys_hub_trigger_instance will NOT work — these tables do not contain individual element records.",
|
|
4656
4710
|
category: "automation",
|
|
4657
4711
|
subcategory: "flow-designer",
|
|
4658
4712
|
use_cases: ["flow-designer", "automation", "flow-management", "subflow"],
|
|
@@ -4946,6 +5000,53 @@ export const toolDefinition: MCPToolDefinition = {
|
|
|
4946
5000
|
export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
|
|
4947
5001
|
var action = args.action
|
|
4948
5002
|
|
|
5003
|
+
// ── Centralized input validation ──
|
|
5004
|
+
var REQUIRED_PARAMS: Record<string, string[]> = {
|
|
5005
|
+
create: ["name"],
|
|
5006
|
+
create_subflow: ["name"],
|
|
5007
|
+
list: [],
|
|
5008
|
+
get: ["flow_id"],
|
|
5009
|
+
update: ["flow_id", "update_fields"],
|
|
5010
|
+
activate: ["flow_id"],
|
|
5011
|
+
publish: ["flow_id"],
|
|
5012
|
+
deactivate: ["flow_id"],
|
|
5013
|
+
delete: ["flow_id"],
|
|
5014
|
+
add_trigger: ["flow_id"],
|
|
5015
|
+
update_trigger: ["flow_id"],
|
|
5016
|
+
delete_trigger: ["flow_id", "element_id"],
|
|
5017
|
+
add_action: ["flow_id"],
|
|
5018
|
+
update_action: ["flow_id", "element_id"],
|
|
5019
|
+
delete_action: ["flow_id", "element_id"],
|
|
5020
|
+
add_flow_logic: ["flow_id", "logic_type"],
|
|
5021
|
+
update_flow_logic: ["flow_id", "element_id"],
|
|
5022
|
+
delete_flow_logic: ["flow_id", "element_id"],
|
|
5023
|
+
add_subflow: ["flow_id", "subflow_id"],
|
|
5024
|
+
update_subflow: ["flow_id", "element_id"],
|
|
5025
|
+
delete_subflow: ["flow_id", "element_id"],
|
|
5026
|
+
open_flow: ["flow_id"],
|
|
5027
|
+
close_flow: ["flow_id"],
|
|
5028
|
+
force_unlock: ["flow_id"],
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
var requiredParams = REQUIRED_PARAMS[action]
|
|
5032
|
+
if (requiredParams && requiredParams.length > 0) {
|
|
5033
|
+
var missingParams = requiredParams.filter(function (p: string) {
|
|
5034
|
+
return args[p] === undefined || args[p] === null || args[p] === ""
|
|
5035
|
+
})
|
|
5036
|
+
if (missingParams.length > 0) {
|
|
5037
|
+
return createErrorResult(
|
|
5038
|
+
new SnowFlowError(
|
|
5039
|
+
ErrorType.VALIDATION_ERROR,
|
|
5040
|
+
"Missing required parameter(s) for '" +
|
|
5041
|
+
action +
|
|
5042
|
+
"': " +
|
|
5043
|
+
missingParams.join(", ") +
|
|
5044
|
+
". Provide these parameters and try again.",
|
|
5045
|
+
),
|
|
5046
|
+
)
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
|
|
4949
5050
|
try {
|
|
4950
5051
|
var client = await getAuthenticatedClient(context)
|
|
4951
5052
|
|
|
@@ -4968,10 +5069,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
4968
5069
|
case "create":
|
|
4969
5070
|
case "create_subflow": {
|
|
4970
5071
|
var flowName = args.name
|
|
4971
|
-
if (!flowName) {
|
|
4972
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "name is required for " + action)
|
|
4973
|
-
}
|
|
4974
|
-
|
|
4975
5072
|
var isSubflow = action === "create_subflow"
|
|
4976
5073
|
var flowDescription = args.description || flowName
|
|
4977
5074
|
var triggerType = isSubflow ? "manual" : args.trigger_type || "manual"
|
|
@@ -5119,7 +5216,9 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5119
5216
|
variable_type: "input",
|
|
5120
5217
|
})
|
|
5121
5218
|
varsCreated++
|
|
5122
|
-
} catch (
|
|
5219
|
+
} catch (e: any) {
|
|
5220
|
+
console.warn("[snow_manage_flow] create: input variable creation failed: " + (e.message || ""))
|
|
5221
|
+
}
|
|
5123
5222
|
}
|
|
5124
5223
|
for (var pfvo = 0; pfvo < outputsArg.length; pfvo++) {
|
|
5125
5224
|
try {
|
|
@@ -5132,7 +5231,9 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5132
5231
|
variable_type: "output",
|
|
5133
5232
|
})
|
|
5134
5233
|
varsCreated++
|
|
5135
|
-
} catch (
|
|
5234
|
+
} catch (e: any) {
|
|
5235
|
+
console.warn("[snow_manage_flow] create: output variable creation failed: " + (e.message || ""))
|
|
5236
|
+
}
|
|
5136
5237
|
}
|
|
5137
5238
|
}
|
|
5138
5239
|
}
|
|
@@ -5279,8 +5380,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5279
5380
|
variable_type: "input",
|
|
5280
5381
|
})
|
|
5281
5382
|
varsCreated++
|
|
5282
|
-
} catch (varError) {
|
|
5283
|
-
|
|
5383
|
+
} catch (varError: any) {
|
|
5384
|
+
console.warn("[snow_manage_flow] create: input variable (table API) failed: " + (varError.message || ""))
|
|
5284
5385
|
}
|
|
5285
5386
|
}
|
|
5286
5387
|
for (var vo = 0; vo < outputsArg.length; vo++) {
|
|
@@ -5294,8 +5395,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5294
5395
|
variable_type: "output",
|
|
5295
5396
|
})
|
|
5296
5397
|
varsCreated++
|
|
5297
|
-
} catch (varError) {
|
|
5298
|
-
|
|
5398
|
+
} catch (varError: any) {
|
|
5399
|
+
console.warn("[snow_manage_flow] create: output variable (table API) failed: " + (varError.message || ""))
|
|
5299
5400
|
}
|
|
5300
5401
|
}
|
|
5301
5402
|
}
|
|
@@ -5470,6 +5571,10 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5470
5571
|
warnings: factoryWarnings.length > 0 ? factoryWarnings : undefined,
|
|
5471
5572
|
diagnostics: diagnostics,
|
|
5472
5573
|
lock_acquired_at: new Date().toISOString(),
|
|
5574
|
+
lock_warning:
|
|
5575
|
+
"IMPORTANT: Editing lock is held. You MUST call close_flow with flow_id='" +
|
|
5576
|
+
flowSysId +
|
|
5577
|
+
"' when done editing.",
|
|
5473
5578
|
next_step:
|
|
5474
5579
|
"Flow is now open for editing. Add actions with add_action, flow logic with add_flow_logic. When DONE, call close_flow with this flow_id to release the editing lock.",
|
|
5475
5580
|
},
|
|
@@ -5497,7 +5602,36 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5497
5602
|
listQuery += (listQuery ? "^" : "") + "active=true"
|
|
5498
5603
|
}
|
|
5499
5604
|
if (filterTable) {
|
|
5500
|
-
|
|
5605
|
+
// Primary: find flows with triggers on the requested table via sys_hub_trigger_instance
|
|
5606
|
+
// This is more reliable than LIKE on flow_definition JSON blob
|
|
5607
|
+
var triggerFlowIds: string[] = []
|
|
5608
|
+
try {
|
|
5609
|
+
var trigLookup = await client.get("/api/now/table/sys_hub_trigger_instance", {
|
|
5610
|
+
params: {
|
|
5611
|
+
sysparm_query: "table=" + filterTable,
|
|
5612
|
+
sysparm_fields: "flow",
|
|
5613
|
+
sysparm_limit: 200,
|
|
5614
|
+
},
|
|
5615
|
+
})
|
|
5616
|
+
var trigResults = trigLookup.data.result || []
|
|
5617
|
+
var seenFlowIds: Record<string, boolean> = {}
|
|
5618
|
+
for (var tfi = 0; tfi < trigResults.length; tfi++) {
|
|
5619
|
+
var fid = typeof trigResults[tfi].flow === "object" ? trigResults[tfi].flow.value : trigResults[tfi].flow
|
|
5620
|
+
if (fid && !seenFlowIds[fid]) {
|
|
5621
|
+
triggerFlowIds.push(fid)
|
|
5622
|
+
seenFlowIds[fid] = true
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
} catch (e: any) {
|
|
5626
|
+
console.warn("[snow_manage_flow] list: trigger table lookup failed, using LIKE fallback: " + (e.message || ""))
|
|
5627
|
+
}
|
|
5628
|
+
|
|
5629
|
+
if (triggerFlowIds.length > 0) {
|
|
5630
|
+
listQuery += (listQuery ? "^" : "") + "sys_idIN" + triggerFlowIds.join(",")
|
|
5631
|
+
} else {
|
|
5632
|
+
// Fallback to LIKE on flow_definition (unreliable but better than nothing)
|
|
5633
|
+
listQuery += (listQuery ? "^" : "") + "flow_definitionLIKE" + filterTable
|
|
5634
|
+
}
|
|
5501
5635
|
}
|
|
5502
5636
|
|
|
5503
5637
|
var listResp = await client.get("/api/now/table/sys_hub_flow", {
|
|
@@ -5548,47 +5682,143 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5548
5682
|
// GET
|
|
5549
5683
|
// ────────────────────────────────────────────────────────────────
|
|
5550
5684
|
case "get": {
|
|
5551
|
-
if (!args.flow_id) {
|
|
5552
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for get action")
|
|
5553
|
-
}
|
|
5554
|
-
|
|
5555
5685
|
var getSysId = await resolveFlowId(client, args.flow_id)
|
|
5556
5686
|
|
|
5557
5687
|
// Fetch flow record
|
|
5558
5688
|
var getResp = await client.get("/api/now/table/sys_hub_flow/" + getSysId)
|
|
5559
5689
|
var flowRecord = getResp.data.result
|
|
5560
5690
|
|
|
5561
|
-
//
|
|
5691
|
+
// ── Primary: processflow API for triggers, actions, flow logic ──
|
|
5692
|
+
// Flow elements do NOT exist as individual records in Table API.
|
|
5693
|
+
// The processflow API returns the real-time state including all elements.
|
|
5562
5694
|
var triggerInstances: any[] = []
|
|
5695
|
+
var actionInstances: any[] = []
|
|
5696
|
+
var flowLogicInstances: any[] = []
|
|
5697
|
+
var dataSource = "table_api"
|
|
5698
|
+
|
|
5563
5699
|
try {
|
|
5564
|
-
var
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5700
|
+
var pfResp = await client.get("/api/now/processflow/flow/" + getSysId)
|
|
5701
|
+
var pfRaw = pfResp.data
|
|
5702
|
+
var pfData: any = null
|
|
5703
|
+
|
|
5704
|
+
if (typeof pfRaw === "string") {
|
|
5705
|
+
// XML response — parse trigger, action and flow logic elements
|
|
5706
|
+
var triggerMatches = pfRaw.match(/<triggerInstance[^>]*>[\s\S]*?<\/triggerInstance>/g) || []
|
|
5707
|
+
for (var pti = 0; pti < triggerMatches.length; pti++) {
|
|
5708
|
+
var tm = triggerMatches[pti]
|
|
5709
|
+
var tidMatch = tm.match(/id="([^"]*)"/) || tm.match(/<id>([^<]*)<\/id>/)
|
|
5710
|
+
var tnameMatch = tm.match(/name="([^"]*)"/) || tm.match(/<name>([^<]*)<\/name>/)
|
|
5711
|
+
var ttypeMatch = tm.match(/typeLabel="([^"]*)"/) || tm.match(/<typeLabel>([^<]*)<\/typeLabel>/)
|
|
5712
|
+
var ttableMatch = tm.match(/table="([^"]*)"/) || tm.match(/<table>([^<]*)<\/table>/)
|
|
5713
|
+
triggerInstances.push({
|
|
5714
|
+
sys_id: tidMatch ? tidMatch[1] : "",
|
|
5715
|
+
name: tnameMatch ? tnameMatch[1] : "",
|
|
5716
|
+
action_type: ttypeMatch ? ttypeMatch[1] : "",
|
|
5717
|
+
table: ttableMatch ? ttableMatch[1] : "",
|
|
5718
|
+
})
|
|
5719
|
+
}
|
|
5720
|
+
// Parse action instances from XML
|
|
5721
|
+
var actionMatches = pfRaw.match(/<actionInstance[^>]*>[\s\S]*?<\/actionInstance>/g) || []
|
|
5722
|
+
for (var pai = 0; pai < actionMatches.length; pai++) {
|
|
5723
|
+
var am = actionMatches[pai]
|
|
5724
|
+
var aidMatch = am.match(/id="([^"]*)"/) || am.match(/<id>([^<]*)<\/id>/)
|
|
5725
|
+
var anameMatch = am.match(/name="([^"]*)"/) || am.match(/<name>([^<]*)<\/name>/)
|
|
5726
|
+
var atypeMatch = am.match(/typeLabel="([^"]*)"/) || am.match(/<typeLabel>([^<]*)<\/typeLabel>/)
|
|
5727
|
+
var aorderMatch = am.match(/order="([^"]*)"/) || am.match(/<order>([^<]*)<\/order>/)
|
|
5728
|
+
actionInstances.push({
|
|
5729
|
+
sys_id: aidMatch ? aidMatch[1] : "",
|
|
5730
|
+
name: anameMatch ? anameMatch[1] : "",
|
|
5731
|
+
action_type: atypeMatch ? atypeMatch[1] : "",
|
|
5732
|
+
order: aorderMatch ? aorderMatch[1] : "",
|
|
5733
|
+
})
|
|
5734
|
+
}
|
|
5735
|
+
// Parse flow logic instances from XML
|
|
5736
|
+
var logicMatches = pfRaw.match(/<flowLogicInstance[^>]*>[\s\S]*?<\/flowLogicInstance>/g) || []
|
|
5737
|
+
for (var pli = 0; pli < logicMatches.length; pli++) {
|
|
5738
|
+
var lm = logicMatches[pli]
|
|
5739
|
+
var lidMatch = lm.match(/id="([^"]*)"/) || lm.match(/<id>([^<]*)<\/id>/)
|
|
5740
|
+
var lnameMatch = lm.match(/name="([^"]*)"/) || lm.match(/<name>([^<]*)<\/name>/)
|
|
5741
|
+
var ltypeMatch = lm.match(/typeLabel="([^"]*)"/) || lm.match(/<typeLabel>([^<]*)<\/typeLabel>/)
|
|
5742
|
+
var lorderMatch = lm.match(/order="([^"]*)"/) || lm.match(/<order>([^<]*)<\/order>/)
|
|
5743
|
+
flowLogicInstances.push({
|
|
5744
|
+
sys_id: lidMatch ? lidMatch[1] : "",
|
|
5745
|
+
name: lnameMatch ? lnameMatch[1] : "",
|
|
5746
|
+
type: ltypeMatch ? ltypeMatch[1] : "",
|
|
5747
|
+
order: lorderMatch ? lorderMatch[1] : "",
|
|
5748
|
+
})
|
|
5749
|
+
}
|
|
5750
|
+
if (triggerInstances.length > 0 || actionInstances.length > 0) dataSource = "processflow_api"
|
|
5751
|
+
} else if (pfRaw && typeof pfRaw === "object") {
|
|
5752
|
+
pfData = pfRaw.result?.data || pfRaw.result || pfRaw.data || pfRaw
|
|
5753
|
+
// JSON response — extract elements from model
|
|
5754
|
+
var model = pfData.model || pfData
|
|
5755
|
+
if (model.triggerInstances && Array.isArray(model.triggerInstances)) {
|
|
5756
|
+
triggerInstances = model.triggerInstances.map(function (t: any) {
|
|
5757
|
+
return {
|
|
5758
|
+
sys_id: t.id || t.sys_id || "",
|
|
5759
|
+
name: t.name || t.typeLabel || "",
|
|
5760
|
+
action_type: t.typeLabel || t.type || "",
|
|
5761
|
+
table: t.table || "",
|
|
5762
|
+
condition: t.condition || "",
|
|
5763
|
+
}
|
|
5764
|
+
})
|
|
5765
|
+
}
|
|
5766
|
+
if (model.actionInstances && Array.isArray(model.actionInstances)) {
|
|
5767
|
+
actionInstances = model.actionInstances.map(function (a: any) {
|
|
5768
|
+
return {
|
|
5769
|
+
sys_id: a.id || a.sys_id || "",
|
|
5770
|
+
name: a.name || a.typeLabel || "",
|
|
5771
|
+
action_type: a.typeLabel || a.type || "",
|
|
5772
|
+
order: a.order || "",
|
|
5773
|
+
}
|
|
5774
|
+
})
|
|
5775
|
+
}
|
|
5776
|
+
if (model.flowLogicInstances && Array.isArray(model.flowLogicInstances)) {
|
|
5777
|
+
flowLogicInstances = model.flowLogicInstances.map(function (l: any) {
|
|
5778
|
+
return {
|
|
5779
|
+
sys_id: l.id || l.sys_id || "",
|
|
5780
|
+
name: l.name || l.typeLabel || "",
|
|
5781
|
+
type: l.typeLabel || l.type || "",
|
|
5782
|
+
order: l.order || "",
|
|
5783
|
+
}
|
|
5784
|
+
})
|
|
5785
|
+
}
|
|
5786
|
+
if (triggerInstances.length > 0 || actionInstances.length > 0) dataSource = "processflow_api"
|
|
5787
|
+
}
|
|
5788
|
+
} catch (e: any) {
|
|
5789
|
+
console.warn("[snow_manage_flow] get: processflow API failed, falling back to Table API: " + (e.message || ""))
|
|
5574
5790
|
}
|
|
5575
5791
|
|
|
5576
|
-
//
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5792
|
+
// ── Fallback: Table API for triggers/actions (may return empty arrays) ──
|
|
5793
|
+
if (dataSource === "table_api") {
|
|
5794
|
+
try {
|
|
5795
|
+
var trigResp = await client.get("/api/now/table/sys_hub_trigger_instance", {
|
|
5796
|
+
params: {
|
|
5797
|
+
sysparm_query: "flow=" + getSysId,
|
|
5798
|
+
sysparm_fields: "sys_id,name,action_type,table,condition,active,order",
|
|
5799
|
+
sysparm_limit: 10,
|
|
5800
|
+
},
|
|
5801
|
+
})
|
|
5802
|
+
triggerInstances = trigResp.data.result || []
|
|
5803
|
+
} catch (e: any) {
|
|
5804
|
+
console.warn("[snow_manage_flow] get: trigger instances query failed: " + (e.message || ""))
|
|
5805
|
+
}
|
|
5806
|
+
|
|
5807
|
+
try {
|
|
5808
|
+
var actResp = await client.get("/api/now/table/sys_hub_action_instance", {
|
|
5809
|
+
params: {
|
|
5810
|
+
sysparm_query: "flow=" + getSysId + "^ORDERBYorder",
|
|
5811
|
+
sysparm_fields: "sys_id,name,action_type,order,active",
|
|
5812
|
+
sysparm_limit: 50,
|
|
5813
|
+
},
|
|
5814
|
+
})
|
|
5815
|
+
actionInstances = actResp.data.result || []
|
|
5816
|
+
} catch (e: any) {
|
|
5817
|
+
console.warn("[snow_manage_flow] get: action instances query failed: " + (e.message || ""))
|
|
5818
|
+
}
|
|
5589
5819
|
}
|
|
5590
5820
|
|
|
5591
|
-
//
|
|
5821
|
+
// Flow variables and executions are always fetched via Table API (they exist as real records)
|
|
5592
5822
|
var flowVars: any[] = []
|
|
5593
5823
|
try {
|
|
5594
5824
|
var varResp = await client.get("/api/now/table/sys_hub_flow_variable", {
|
|
@@ -5599,11 +5829,10 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5599
5829
|
},
|
|
5600
5830
|
})
|
|
5601
5831
|
flowVars = varResp.data.result || []
|
|
5602
|
-
} catch (e) {
|
|
5603
|
-
|
|
5832
|
+
} catch (e: any) {
|
|
5833
|
+
console.warn("[snow_manage_flow] get: flow variables query failed: " + (e.message || ""))
|
|
5604
5834
|
}
|
|
5605
5835
|
|
|
5606
|
-
// Fetch recent executions
|
|
5607
5836
|
var executions: any[] = []
|
|
5608
5837
|
try {
|
|
5609
5838
|
var execResp = await client.get("/api/now/table/sys_hub_flow_run", {
|
|
@@ -5614,8 +5843,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5614
5843
|
},
|
|
5615
5844
|
})
|
|
5616
5845
|
executions = execResp.data.result || []
|
|
5617
|
-
} catch (e) {
|
|
5618
|
-
|
|
5846
|
+
} catch (e: any) {
|
|
5847
|
+
console.warn("[snow_manage_flow] get: executions query failed: " + (e.message || ""))
|
|
5619
5848
|
}
|
|
5620
5849
|
|
|
5621
5850
|
var getSummary = summary()
|
|
@@ -5639,6 +5868,12 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5639
5868
|
getSummary.bullet(actionInstances[aci].name || "action-" + aci)
|
|
5640
5869
|
}
|
|
5641
5870
|
}
|
|
5871
|
+
if (flowLogicInstances.length > 0) {
|
|
5872
|
+
getSummary.blank().line("Flow Logic: " + flowLogicInstances.length)
|
|
5873
|
+
for (var fli = 0; fli < Math.min(flowLogicInstances.length, 10); fli++) {
|
|
5874
|
+
getSummary.bullet((flowLogicInstances[fli].type || "") + " " + (flowLogicInstances[fli].name || "logic-" + fli))
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5642
5877
|
if (flowVars.length > 0) {
|
|
5643
5878
|
getSummary.blank().line("Variables: " + flowVars.length)
|
|
5644
5879
|
}
|
|
@@ -5653,6 +5888,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5653
5888
|
return createSuccessResult(
|
|
5654
5889
|
{
|
|
5655
5890
|
action: "get",
|
|
5891
|
+
data_source: dataSource,
|
|
5656
5892
|
flow: {
|
|
5657
5893
|
sys_id: flowRecord.sys_id,
|
|
5658
5894
|
name: flowRecord.name,
|
|
@@ -5684,6 +5920,14 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5684
5920
|
active: a.active === "true",
|
|
5685
5921
|
}
|
|
5686
5922
|
}),
|
|
5923
|
+
flow_logic: flowLogicInstances.map(function (l: any) {
|
|
5924
|
+
return {
|
|
5925
|
+
sys_id: l.sys_id,
|
|
5926
|
+
name: l.name,
|
|
5927
|
+
type: l.type,
|
|
5928
|
+
order: l.order,
|
|
5929
|
+
}
|
|
5930
|
+
}),
|
|
5687
5931
|
variables: flowVars.map(function (v: any) {
|
|
5688
5932
|
return {
|
|
5689
5933
|
sys_id: v.sys_id,
|
|
@@ -5716,9 +5960,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5716
5960
|
// UPDATE
|
|
5717
5961
|
// ────────────────────────────────────────────────────────────────
|
|
5718
5962
|
case "update": {
|
|
5719
|
-
if (!args.flow_id) {
|
|
5720
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for update action")
|
|
5721
|
-
}
|
|
5722
5963
|
var updateFields = args.update_fields
|
|
5723
5964
|
if (!updateFields || Object.keys(updateFields).length === 0) {
|
|
5724
5965
|
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "update_fields is required for update action")
|
|
@@ -5753,10 +5994,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5753
5994
|
// ────────────────────────────────────────────────────────────────
|
|
5754
5995
|
case "activate":
|
|
5755
5996
|
case "publish": {
|
|
5756
|
-
if (!args.flow_id) {
|
|
5757
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for " + action + " action")
|
|
5758
|
-
}
|
|
5759
|
-
|
|
5760
5997
|
var activateSysId = await resolveFlowId(client, args.flow_id)
|
|
5761
5998
|
var activateSummary = summary()
|
|
5762
5999
|
var publishSuccess = false
|
|
@@ -5810,10 +6047,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5810
6047
|
// DEACTIVATE
|
|
5811
6048
|
// ────────────────────────────────────────────────────────────────
|
|
5812
6049
|
case "deactivate": {
|
|
5813
|
-
if (!args.flow_id) {
|
|
5814
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for deactivate action")
|
|
5815
|
-
}
|
|
5816
|
-
|
|
5817
6050
|
var deactivateSysId = await resolveFlowId(client, args.flow_id)
|
|
5818
6051
|
|
|
5819
6052
|
// Primary: use processflow versioning API with Deactivate type
|
|
@@ -5853,20 +6086,53 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5853
6086
|
// DELETE
|
|
5854
6087
|
// ────────────────────────────────────────────────────────────────
|
|
5855
6088
|
case "delete": {
|
|
5856
|
-
|
|
5857
|
-
|
|
6089
|
+
var deleteSysId = await resolveFlowId(client, args.flow_id)
|
|
6090
|
+
var deleteSteps: any = {}
|
|
6091
|
+
|
|
6092
|
+
// Pre-delete: release any editing lock (flow may be locked)
|
|
6093
|
+
try {
|
|
6094
|
+
await releaseFlowEditingLock(client, deleteSysId)
|
|
6095
|
+
deleteSteps.lock_released = true
|
|
6096
|
+
} catch (e: any) {
|
|
6097
|
+
deleteSteps.lock_release_error = e.message
|
|
6098
|
+
console.warn("[snow_manage_flow] delete: lock release failed: " + (e.message || ""))
|
|
5858
6099
|
}
|
|
5859
6100
|
|
|
5860
|
-
|
|
6101
|
+
// Pre-delete: deactivate the flow (avoids "active flow" errors)
|
|
6102
|
+
try {
|
|
6103
|
+
await client.patch("/api/now/table/sys_hub_flow/" + deleteSysId, { active: false })
|
|
6104
|
+
deleteSteps.deactivated = true
|
|
6105
|
+
} catch (e: any) {
|
|
6106
|
+
deleteSteps.deactivate_error = e.message
|
|
6107
|
+
console.warn("[snow_manage_flow] delete: deactivation failed: " + (e.message || ""))
|
|
6108
|
+
}
|
|
6109
|
+
|
|
6110
|
+
// Delete the flow
|
|
5861
6111
|
await client.delete("/api/now/table/sys_hub_flow/" + deleteSysId)
|
|
5862
6112
|
|
|
6113
|
+
// Post-delete: clean up orphaned safe_edit records
|
|
6114
|
+
try {
|
|
6115
|
+
var orphanResp = await client.get("/api/now/table/sys_hub_flow_safe_edit", {
|
|
6116
|
+
params: { sysparm_query: "document_id=" + deleteSysId, sysparm_fields: "sys_id", sysparm_limit: 10 },
|
|
6117
|
+
})
|
|
6118
|
+
var orphans = orphanResp.data?.result || []
|
|
6119
|
+
for (var oi = 0; oi < orphans.length; oi++) {
|
|
6120
|
+
await client.delete("/api/now/table/sys_hub_flow_safe_edit/" + orphans[oi].sys_id)
|
|
6121
|
+
}
|
|
6122
|
+
if (orphans.length > 0) deleteSteps.orphans_cleaned = orphans.length
|
|
6123
|
+
} catch (e: any) {
|
|
6124
|
+
console.warn("[snow_manage_flow] delete: orphan cleanup failed: " + (e.message || ""))
|
|
6125
|
+
}
|
|
6126
|
+
|
|
5863
6127
|
var deleteSummary = summary().success("Flow deleted").field("sys_id", deleteSysId)
|
|
6128
|
+
if (deleteSteps.orphans_cleaned) deleteSummary.line("Cleaned " + deleteSteps.orphans_cleaned + " orphaned lock record(s)")
|
|
5864
6129
|
|
|
5865
6130
|
return createSuccessResult(
|
|
5866
6131
|
{
|
|
5867
6132
|
action: "delete",
|
|
5868
6133
|
flow_id: deleteSysId,
|
|
5869
6134
|
message: "Flow deleted",
|
|
6135
|
+
steps: deleteSteps,
|
|
5870
6136
|
},
|
|
5871
6137
|
{},
|
|
5872
6138
|
deleteSummary.build(),
|
|
@@ -5877,9 +6143,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5877
6143
|
// ADD_TRIGGER
|
|
5878
6144
|
// ────────────────────────────────────────────────────────────────
|
|
5879
6145
|
case "add_trigger": {
|
|
5880
|
-
if (!args.flow_id) {
|
|
5881
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for add_trigger")
|
|
5882
|
-
}
|
|
5883
6146
|
var addTrigFlowId = await resolveFlowId(client, args.flow_id)
|
|
5884
6147
|
var addTrigType = args.trigger_type || "record_create_or_update"
|
|
5885
6148
|
var addTrigTable = args.table || args.trigger_table || ""
|
|
@@ -5914,9 +6177,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5914
6177
|
// UPDATE_TRIGGER — replace existing trigger(s) with a new one
|
|
5915
6178
|
// ────────────────────────────────────────────────────────────────
|
|
5916
6179
|
case "update_trigger": {
|
|
5917
|
-
if (!args.flow_id) {
|
|
5918
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for update_trigger")
|
|
5919
|
-
}
|
|
5920
6180
|
var updTrigFlowId = await resolveFlowId(client, args.flow_id)
|
|
5921
6181
|
var updTrigType = args.trigger_type || "record_create_or_update"
|
|
5922
6182
|
var updTrigTable = args.table || args.trigger_table || ""
|
|
@@ -5924,6 +6184,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5924
6184
|
var updTrigSteps: any = {}
|
|
5925
6185
|
|
|
5926
6186
|
// Step 1: Find existing trigger instances on this flow
|
|
6187
|
+
var trigInstances: any[] = []
|
|
5927
6188
|
try {
|
|
5928
6189
|
var existingTriggers = await client.get("/api/now/table/sys_hub_trigger_instance", {
|
|
5929
6190
|
params: {
|
|
@@ -5932,16 +6193,35 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5932
6193
|
sysparm_limit: 10,
|
|
5933
6194
|
},
|
|
5934
6195
|
})
|
|
5935
|
-
|
|
5936
|
-
updTrigSteps.existing_triggers = trigInstances.map((t: any)
|
|
5937
|
-
sys_id: t.sys_id,
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
6196
|
+
trigInstances = existingTriggers.data.result || []
|
|
6197
|
+
updTrigSteps.existing_triggers = trigInstances.map(function (t: any) {
|
|
6198
|
+
return { sys_id: t.sys_id, name: t.name, type: t.type }
|
|
6199
|
+
})
|
|
6200
|
+
} catch (e: any) {
|
|
6201
|
+
updTrigSteps.lookup_error = "Could not query existing triggers: " + (e.message || "")
|
|
6202
|
+
}
|
|
6203
|
+
|
|
6204
|
+
// Step 2: Create the NEW trigger FIRST (safe: old triggers still exist if this fails)
|
|
6205
|
+
var updTrigResult = await addTriggerViaGraphQL(
|
|
6206
|
+
client,
|
|
6207
|
+
updTrigFlowId,
|
|
6208
|
+
updTrigType,
|
|
6209
|
+
updTrigTable,
|
|
6210
|
+
updTrigCondition,
|
|
6211
|
+
)
|
|
6212
|
+
updTrigSteps.new_trigger = updTrigResult
|
|
6213
|
+
|
|
6214
|
+
// Step 3: Only delete old triggers if the new one was created successfully
|
|
6215
|
+
if (updTrigResult.success && trigInstances.length > 0) {
|
|
6216
|
+
var newTriggerId = updTrigResult.triggerId || ""
|
|
6217
|
+
var deleteIds = trigInstances
|
|
6218
|
+
.map(function (t: any) {
|
|
6219
|
+
return t.sys_id
|
|
6220
|
+
})
|
|
6221
|
+
.filter(function (id: string) {
|
|
6222
|
+
return id !== newTriggerId
|
|
6223
|
+
})
|
|
6224
|
+
if (deleteIds.length > 0) {
|
|
5945
6225
|
try {
|
|
5946
6226
|
await executeFlowPatchMutation(
|
|
5947
6227
|
client,
|
|
@@ -5954,46 +6234,41 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
5954
6234
|
updTrigSteps.deleted = deleteIds
|
|
5955
6235
|
} catch (e: any) {
|
|
5956
6236
|
updTrigSteps.delete_error = e.message
|
|
6237
|
+
console.warn("[snow_manage_flow] update_trigger: old trigger deletion failed: " + (e.message || ""))
|
|
5957
6238
|
}
|
|
5958
6239
|
}
|
|
5959
|
-
} catch (_) {
|
|
5960
|
-
updTrigSteps.lookup_error = "Could not query existing triggers"
|
|
5961
6240
|
}
|
|
5962
6241
|
|
|
5963
|
-
// Step 3: Add the new trigger
|
|
5964
|
-
var updTrigResult = await addTriggerViaGraphQL(
|
|
5965
|
-
client,
|
|
5966
|
-
updTrigFlowId,
|
|
5967
|
-
updTrigType,
|
|
5968
|
-
updTrigTable,
|
|
5969
|
-
updTrigCondition,
|
|
5970
|
-
)
|
|
5971
|
-
updTrigSteps.new_trigger = updTrigResult
|
|
5972
|
-
|
|
5973
6242
|
var updTrigSummary = summary()
|
|
5974
6243
|
if (updTrigResult.success) {
|
|
5975
6244
|
updTrigSummary
|
|
5976
|
-
.success("Trigger updated via GraphQL")
|
|
6245
|
+
.success("Trigger updated via GraphQL (create-first)")
|
|
5977
6246
|
.field("Flow", updTrigFlowId)
|
|
5978
6247
|
.field("New Type", updTrigType)
|
|
5979
6248
|
.field("Trigger ID", updTrigResult.triggerId || "unknown")
|
|
5980
6249
|
if (updTrigTable) updTrigSummary.field("Table", updTrigTable)
|
|
6250
|
+
if (updTrigSteps.deleted) updTrigSummary.line("Old trigger(s) removed: " + updTrigSteps.deleted.join(", "))
|
|
5981
6251
|
} else {
|
|
5982
|
-
updTrigSummary.error(
|
|
6252
|
+
updTrigSummary.error(
|
|
6253
|
+
"Failed to create new trigger: " +
|
|
6254
|
+
(updTrigResult.error || "unknown") +
|
|
6255
|
+
". Old trigger(s) preserved — flow is not broken.",
|
|
6256
|
+
)
|
|
5983
6257
|
}
|
|
5984
6258
|
|
|
5985
6259
|
return updTrigResult.success
|
|
5986
|
-
? createSuccessResult({ action: "update_trigger", steps: updTrigSteps }, {}, updTrigSummary.build())
|
|
5987
|
-
: createErrorResult(
|
|
6260
|
+
? createSuccessResult({ action: "update_trigger", mutation_method: "graphql", steps: updTrigSteps }, {}, updTrigSummary.build())
|
|
6261
|
+
: createErrorResult(
|
|
6262
|
+
"Failed to update trigger: " +
|
|
6263
|
+
(updTrigResult.error || "unknown") +
|
|
6264
|
+
". Old trigger(s) preserved — the flow still has its original trigger.",
|
|
6265
|
+
)
|
|
5988
6266
|
}
|
|
5989
6267
|
|
|
5990
6268
|
// ────────────────────────────────────────────────────────────────
|
|
5991
6269
|
// ADD_ACTION
|
|
5992
6270
|
// ────────────────────────────────────────────────────────────────
|
|
5993
6271
|
case "add_action": {
|
|
5994
|
-
if (!args.flow_id) {
|
|
5995
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for add_action")
|
|
5996
|
-
}
|
|
5997
6272
|
var addActFlowId = await resolveFlowId(client, args.flow_id)
|
|
5998
6273
|
var addActType = args.action_type || "log"
|
|
5999
6274
|
var addActName = args.action_name || args.name || addActType
|
|
@@ -6052,8 +6327,12 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6052
6327
|
{
|
|
6053
6328
|
action: "add_action",
|
|
6054
6329
|
...addActResult,
|
|
6330
|
+
mutation_method: "graphql",
|
|
6055
6331
|
next_order: addActNextOrder,
|
|
6056
|
-
reminder:
|
|
6332
|
+
reminder:
|
|
6333
|
+
"IMPORTANT: Call close_flow with flow_id='" +
|
|
6334
|
+
addActFlowId +
|
|
6335
|
+
"' when you are done adding elements. Forgetting this will leave the flow locked.",
|
|
6057
6336
|
},
|
|
6058
6337
|
updateSetCtx,
|
|
6059
6338
|
),
|
|
@@ -6068,15 +6347,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6068
6347
|
// ADD_FLOW_LOGIC — add If/Else, For Each, Do Until, Switch blocks
|
|
6069
6348
|
// ────────────────────────────────────────────────────────────────
|
|
6070
6349
|
case "add_flow_logic": {
|
|
6071
|
-
if (!args.flow_id) {
|
|
6072
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for add_flow_logic")
|
|
6073
|
-
}
|
|
6074
|
-
if (!args.logic_type) {
|
|
6075
|
-
throw new SnowFlowError(
|
|
6076
|
-
ErrorType.VALIDATION_ERROR,
|
|
6077
|
-
"logic_type is required for add_flow_logic (e.g. IF, ELSEIF, ELSE, FOR_EACH, DO_UNTIL, PARALLEL, DECISION, TRY, END, TIMER, SET_FLOW_VARIABLES)",
|
|
6078
|
-
)
|
|
6079
|
-
}
|
|
6080
6350
|
var addLogicFlowId = await resolveFlowId(client, args.flow_id)
|
|
6081
6351
|
var addLogicType = args.logic_type
|
|
6082
6352
|
var addLogicInputs =
|
|
@@ -6124,7 +6394,14 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6124
6394
|
// - ELSE/ELSEIF use parent_ui_id = IF's PARENT (same level as IF) + connected_to = IF's logicId
|
|
6125
6395
|
var logicUpperType = addLogicType.toUpperCase().replace(/[^A-Z]/g, "")
|
|
6126
6396
|
var addLogicNextOrder = (addLogicResult.resolvedOrder || 1) + 1
|
|
6127
|
-
var logicHints: any = {
|
|
6397
|
+
var logicHints: any = {
|
|
6398
|
+
mutation_method: "graphql",
|
|
6399
|
+
reminder:
|
|
6400
|
+
"IMPORTANT: Call close_flow with flow_id='" +
|
|
6401
|
+
addLogicFlowId +
|
|
6402
|
+
"' when you are done adding elements. Forgetting this will leave the flow locked.",
|
|
6403
|
+
next_order: addLogicNextOrder,
|
|
6404
|
+
}
|
|
6128
6405
|
if (logicUpperType === "IF" || logicUpperType === "ELSEIF") {
|
|
6129
6406
|
logicHints.important =
|
|
6130
6407
|
"ELSE/ELSEIF must be at the SAME level as this IF (same parent_ui_id), NOT nested inside it. " +
|
|
@@ -6166,15 +6443,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6166
6443
|
// ADD_SUBFLOW — call an existing subflow as a step in the flow
|
|
6167
6444
|
// ────────────────────────────────────────────────────────────────
|
|
6168
6445
|
case "add_subflow": {
|
|
6169
|
-
if (!args.flow_id) {
|
|
6170
|
-
throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for add_subflow")
|
|
6171
|
-
}
|
|
6172
|
-
if (!args.subflow_id) {
|
|
6173
|
-
throw new SnowFlowError(
|
|
6174
|
-
ErrorType.VALIDATION_ERROR,
|
|
6175
|
-
"subflow_id is required for add_subflow (sys_id or name of the subflow to call)",
|
|
6176
|
-
)
|
|
6177
|
-
}
|
|
6178
6446
|
var addSubFlowId = await resolveFlowId(client, args.flow_id)
|
|
6179
6447
|
var addSubSubflowId = args.subflow_id
|
|
6180
6448
|
var addSubInputs =
|
|
@@ -6215,8 +6483,12 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6215
6483
|
{
|
|
6216
6484
|
action: "add_subflow",
|
|
6217
6485
|
...addSubResult,
|
|
6486
|
+
mutation_method: "graphql",
|
|
6218
6487
|
next_order: addSubNextOrder,
|
|
6219
|
-
reminder:
|
|
6488
|
+
reminder:
|
|
6489
|
+
"IMPORTANT: Call close_flow with flow_id='" +
|
|
6490
|
+
addSubFlowId +
|
|
6491
|
+
"' when you are done adding elements. Forgetting this will leave the flow locked.",
|
|
6220
6492
|
},
|
|
6221
6493
|
updateSetCtx,
|
|
6222
6494
|
),
|
|
@@ -6233,12 +6505,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6233
6505
|
case "update_action":
|
|
6234
6506
|
case "update_flow_logic":
|
|
6235
6507
|
case "update_subflow": {
|
|
6236
|
-
if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required")
|
|
6237
|
-
if (!args.element_id)
|
|
6238
|
-
throw new SnowFlowError(
|
|
6239
|
-
ErrorType.VALIDATION_ERROR,
|
|
6240
|
-
"element_id is required (sys_id or uiUniqueIdentifier of the element)",
|
|
6241
|
-
)
|
|
6242
6508
|
var updElemFlowId = await resolveFlowId(client, args.flow_id)
|
|
6243
6509
|
var updElemType =
|
|
6244
6510
|
action === "update_action" ? "action" : action === "update_flow_logic" ? "flowlogic" : "subflow"
|
|
@@ -6294,12 +6560,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6294
6560
|
case "delete_flow_logic":
|
|
6295
6561
|
case "delete_subflow":
|
|
6296
6562
|
case "delete_trigger": {
|
|
6297
|
-
if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required")
|
|
6298
|
-
if (!args.element_id)
|
|
6299
|
-
throw new SnowFlowError(
|
|
6300
|
-
ErrorType.VALIDATION_ERROR,
|
|
6301
|
-
"element_id is required (sys_id(s) to delete, comma-separated for multiple)",
|
|
6302
|
-
)
|
|
6303
6563
|
var delElemFlowId = await resolveFlowId(client, args.flow_id)
|
|
6304
6564
|
var delElemType =
|
|
6305
6565
|
action === "delete_action"
|
|
@@ -6332,7 +6592,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6332
6592
|
// OPEN_FLOW — acquire Flow Designer editing lock (safeEdit create)
|
|
6333
6593
|
// ────────────────────────────────────────────────────────────────
|
|
6334
6594
|
case "open_flow": {
|
|
6335
|
-
if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for open_flow")
|
|
6336
6595
|
var openFlowId = await resolveFlowId(client, args.flow_id)
|
|
6337
6596
|
var openSummary = summary()
|
|
6338
6597
|
|
|
@@ -6355,7 +6614,12 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6355
6614
|
action: "open_flow",
|
|
6356
6615
|
flow_id: openFlowId,
|
|
6357
6616
|
editing_session: true,
|
|
6617
|
+
lock_acquired_at: new Date().toISOString(),
|
|
6358
6618
|
lock_debug: lockResult.debug,
|
|
6619
|
+
lock_warning:
|
|
6620
|
+
"IMPORTANT: Editing lock is held. You MUST call close_flow with flow_id='" +
|
|
6621
|
+
openFlowId +
|
|
6622
|
+
"' when done editing.",
|
|
6359
6623
|
next_step: "Flow is open. Add actions/logic, then call close_flow to release the lock.",
|
|
6360
6624
|
},
|
|
6361
6625
|
{},
|
|
@@ -6378,7 +6642,12 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6378
6642
|
flow_id: openFlowId,
|
|
6379
6643
|
editing_session: true,
|
|
6380
6644
|
ghost_lock_cleared: true,
|
|
6645
|
+
lock_acquired_at: new Date().toISOString(),
|
|
6381
6646
|
lock_debug: retryResult.debug,
|
|
6647
|
+
lock_warning:
|
|
6648
|
+
"IMPORTANT: Editing lock is held. You MUST call close_flow with flow_id='" +
|
|
6649
|
+
openFlowId +
|
|
6650
|
+
"' when done editing.",
|
|
6382
6651
|
next_step: "Flow is open. Add actions/logic, then call close_flow to release the lock.",
|
|
6383
6652
|
},
|
|
6384
6653
|
{},
|
|
@@ -6401,7 +6670,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6401
6670
|
// CLOSE_FLOW — release Flow Designer editing lock (safeEdit)
|
|
6402
6671
|
// ────────────────────────────────────────────────────────────────
|
|
6403
6672
|
case "close_flow": {
|
|
6404
|
-
if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for close_flow")
|
|
6405
6673
|
var closeFlowId = await resolveFlowId(client, args.flow_id)
|
|
6406
6674
|
var closeResult = await releaseFlowEditingLock(client, closeFlowId)
|
|
6407
6675
|
var closeSummary = summary()
|
|
@@ -6427,7 +6695,6 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6427
6695
|
// FORCE_UNLOCK — aggressively clear all locks on a flow (ghost lock recovery)
|
|
6428
6696
|
// ────────────────────────────────────────────────────────────────
|
|
6429
6697
|
case "force_unlock": {
|
|
6430
|
-
if (!args.flow_id) throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for force_unlock")
|
|
6431
6698
|
var unlockFlowId = await resolveFlowId(client, args.flow_id)
|
|
6432
6699
|
var unlockSummary = summary()
|
|
6433
6700
|
var unlockSteps: Record<string, any> = {}
|
|
@@ -6545,6 +6812,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6545
6812
|
var MUTATION_ACTIONS = [
|
|
6546
6813
|
"create",
|
|
6547
6814
|
"create_subflow",
|
|
6815
|
+
"add_trigger",
|
|
6816
|
+
"update_trigger",
|
|
6548
6817
|
"add_action",
|
|
6549
6818
|
"add_flow_logic",
|
|
6550
6819
|
"add_subflow",
|
|
@@ -6556,7 +6825,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
6556
6825
|
"delete_subflow",
|
|
6557
6826
|
"delete_trigger",
|
|
6558
6827
|
]
|
|
6559
|
-
if (client && args.flow_id && MUTATION_ACTIONS.indexOf(action) !== -1
|
|
6828
|
+
if (client && args.flow_id && MUTATION_ACTIONS.indexOf(action) !== -1) {
|
|
6560
6829
|
try {
|
|
6561
6830
|
await releaseFlowEditingLock(client, await resolveFlowId(client, args.flow_id))
|
|
6562
6831
|
} catch (lockReleaseErr: any) {
|