snow-flow 10.0.198 → 10.0.199

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.198",
3
+ "version": "10.0.199",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -1,25 +1,37 @@
1
1
  /**
2
2
  * snow_order_catalog_item - Order catalog item
3
3
  *
4
- * Orders a catalog item programmatically, creating a request (RITM) with specified variable values.
4
+ * Orders a catalog item programmatically, creating a request (RITM) with
5
+ * specified variable values.
6
+ *
7
+ * Operation order (avoids race condition with flows):
8
+ * 1. Fetch variable definitions (item_option_new)
9
+ * 2. Create sc_request
10
+ * 3. Pre-create sc_item_option records in parallel (no RITM needed yet)
11
+ * 4. Short delay to ensure DB commit
12
+ * 5. Create sc_req_item (RITM) — flow triggers here with variables ready
13
+ * 6. Create sc_item_option_mtom links in parallel
14
+ * 7. Two PATCHs to trigger Business Rules that associate the flow context
15
+ *
16
+ * Based on contribution by @frodoyoraul (PR #83).
5
17
  */
6
18
 
7
19
  import { MCPToolDefinition, ServiceNowContext, ToolResult } from "../../shared/types.js"
8
20
  import { getAuthenticatedClient } from "../../shared/auth.js"
9
21
  import { createSuccessResult, createErrorResult } from "../../shared/error-handler.js"
10
22
 
23
+ // Delay between sc_item_option creation and RITM insert to ensure DB commit
24
+ const VARIABLE_COMMIT_DELAY_MS = 1000
25
+
11
26
  export const toolDefinition: MCPToolDefinition = {
12
27
  name: "snow_order_catalog_item",
13
28
  description: "Orders a catalog item programmatically, creating a request (RITM) with specified variable values.",
14
- // Metadata for tool discovery (not sent to LLM)
15
29
  category: "itsm",
16
30
  subcategory: "service-catalog",
17
31
  use_cases: ["ordering", "automation", "ritm"],
18
32
  complexity: "intermediate",
19
33
  frequency: "high",
20
34
 
21
- // Permission enforcement
22
- // Classification: WRITE - Write operation based on name pattern
23
35
  permission: "write",
24
36
  allowedRoles: ["developer", "admin"],
25
37
  inputSchema: {
@@ -36,46 +48,89 @@ export const toolDefinition: MCPToolDefinition = {
36
48
  },
37
49
  }
38
50
 
51
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
52
+
39
53
  export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
40
54
  const { cat_item, requested_for, variables, quantity = 1, delivery_address, special_instructions } = args
41
55
 
42
56
  try {
43
57
  const client = await getAuthenticatedClient(context)
44
58
 
45
- // Create service catalog request
59
+ // 1. Fetch variable definitions before creating the RITM
60
+ const varDefsResponse = await client.get("/api/now/table/item_option_new", {
61
+ params: {
62
+ sysparm_query: "cat_item=" + cat_item,
63
+ sysparm_fields: "sys_id,name",
64
+ sysparm_limit: 100,
65
+ },
66
+ })
67
+ const varDefs = varDefsResponse.data.result || []
68
+ const varMap = new Map<string, string>()
69
+ varDefs.forEach((def: any) => {
70
+ if (def.name) varMap.set(def.name, def.sys_id)
71
+ })
72
+
73
+ // 2. Create sc_request
46
74
  const requestData: any = {
47
75
  requested_for,
48
76
  opened_by: requested_for,
49
77
  }
50
-
51
78
  if (special_instructions) requestData.special_instructions = special_instructions
52
79
 
53
80
  const requestResponse = await client.post("/api/now/table/sc_request", requestData)
54
81
  const requestId = requestResponse.data.result.sys_id
55
82
 
56
- // Create requested item (RITM)
83
+ // 3. Pre-create sc_item_option records in parallel (no RITM needed yet)
84
+ const varEntries = variables ? Object.entries(variables).filter(([name]) => varMap.has(name)) : []
85
+ const skippedVars = variables ? Object.keys(variables).filter((name) => !varMap.has(name)) : []
86
+
87
+ const optionSysIds: Array<{ optionSysId: string }> = await Promise.all(
88
+ varEntries.map(async ([varName, varValue]) => {
89
+ const defSysId = varMap.get(varName)!
90
+ const optResp = await client.post("/api/now/table/sc_item_option", {
91
+ item_option_new: defSysId,
92
+ value: varValue,
93
+ })
94
+ return { optionSysId: optResp.data.result.sys_id }
95
+ }),
96
+ )
97
+
98
+ // 4. Wait for sc_item_option records to be committed in DB
99
+ if (optionSysIds.length > 0) {
100
+ await sleep(VARIABLE_COMMIT_DELAY_MS)
101
+ }
102
+
103
+ // 5. Create sc_req_item (RITM) — flow triggers here with variables already ready
57
104
  const ritmData: any = {
58
105
  request: requestId,
59
106
  cat_item,
60
107
  requested_for,
61
108
  quantity,
62
109
  }
63
-
64
110
  if (delivery_address) ritmData.delivery_address = delivery_address
65
111
 
66
112
  const ritmResponse = await client.post("/api/now/table/sc_req_item", ritmData)
67
113
  const ritmId = ritmResponse.data.result.sys_id
68
114
  const ritmNumber = ritmResponse.data.result.number
69
115
 
70
- // Set variable values if provided
71
- if (variables) {
72
- for (const [varName, varValue] of Object.entries(variables)) {
73
- await client.post("/api/now/table/sc_item_option_mtom", {
116
+ // 6. Create sc_item_option_mtom links in parallel
117
+ await Promise.all(
118
+ optionSysIds.map(({ optionSysId }) =>
119
+ client.post("/api/now/table/sc_item_option_mtom", {
74
120
  request_item: ritmId,
75
- name: varName,
76
- value: varValue,
77
- })
78
- }
121
+ sc_item_option: optionSysId,
122
+ }),
123
+ ),
124
+ )
125
+
126
+ // 7. Two PATCHs to trigger Business Rules that associate the flow context.
127
+ // sys_mod_count is auto-incremented by ServiceNow (the value we send is
128
+ // ignored), but the PATCH itself is a real update that fires BR triggers.
129
+ // Two updates are needed: the first associates the flow, the second
130
+ // ensures the flow context is fully propagated.
131
+ if (optionSysIds.length > 0) {
132
+ await client.patch("/api/now/table/sc_req_item/" + ritmId, { sys_mod_count: "1" })
133
+ await client.patch("/api/now/table/sc_req_item/" + ritmId, { sys_mod_count: "1" })
79
134
  }
80
135
 
81
136
  return createSuccessResult(
@@ -85,6 +140,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
85
140
  ritm_id: ritmId,
86
141
  ritm_number: ritmNumber,
87
142
  quantity,
143
+ variables_processed: varEntries.length,
144
+ variables_skipped: skippedVars.length > 0 ? skippedVars : undefined,
88
145
  },
89
146
  {
90
147
  operation: "order_catalog_item",
@@ -97,5 +154,5 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
97
154
  }
98
155
  }
99
156
 
100
- export const version = "1.0.0"
101
- export const author = "Snow-Flow SDK Migration"
157
+ export const version = "2.0.0"
158
+ export const author = "Snow-Flow"