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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "10.0.140",
3
+ "version": "10.0.141",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -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
- /* best-effort */
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
- // Fall through to create
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: 1,
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
- if (!lookup.data.result || lookup.data.result.length === 0) {
4645
- throw new SnowFlowError(ErrorType.NOT_FOUND, "Flow not found: " + flowId)
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
- return lookup.data.result[0].sys_id
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
- /* best-effort */
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
- /* best-effort */
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
- listQuery += (listQuery ? "^" : "") + "flow_definitionLIKE" + filterTable
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
- // Fetch trigger instances
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 trigResp = await client.get("/api/now/table/sys_hub_trigger_instance", {
5565
- params: {
5566
- sysparm_query: "flow=" + getSysId,
5567
- sysparm_fields: "sys_id,name,action_type,table,condition,active,order",
5568
- sysparm_limit: 10,
5569
- },
5570
- })
5571
- triggerInstances = trigResp.data.result || []
5572
- } catch (e) {
5573
- /* best-effort */
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
- // Fetch action instances
5577
- var actionInstances: any[] = []
5578
- try {
5579
- var actResp = await client.get("/api/now/table/sys_hub_action_instance", {
5580
- params: {
5581
- sysparm_query: "flow=" + getSysId + "^ORDERBYorder",
5582
- sysparm_fields: "sys_id,name,action_type,order,active",
5583
- sysparm_limit: 50,
5584
- },
5585
- })
5586
- actionInstances = actResp.data.result || []
5587
- } catch (e) {
5588
- /* best-effort */
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
- // Fetch flow variables
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
- /* best-effort */
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
- /* best-effort */
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
- if (!args.flow_id) {
5857
- throw new SnowFlowError(ErrorType.VALIDATION_ERROR, "flow_id is required for delete action")
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
- var deleteSysId = await resolveFlowId(client, args.flow_id)
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
- var trigInstances = existingTriggers.data.result || []
5936
- updTrigSteps.existing_triggers = trigInstances.map((t: any) => ({
5937
- sys_id: t.sys_id,
5938
- name: t.name,
5939
- type: t.type,
5940
- }))
5941
-
5942
- // Step 2: Delete existing triggers via GraphQL
5943
- if (trigInstances.length > 0) {
5944
- var deleteIds = trigInstances.map((t: any) => t.sys_id)
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("Failed to update trigger: " + (updTrigResult.error || "unknown"))
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(updTrigResult.error || "Failed to update trigger")
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: "Call close_flow when all steps are added.",
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 = { reminder: "Call close_flow when all steps are added.", next_order: addLogicNextOrder }
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: "Call close_flow when all steps are added.",
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 && !(error instanceof SnowFlowError)) {
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) {