snow-flow 10.0.185 → 10.0.186-dev.682

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.
Files changed (62) hide show
  1. package/bin/index.js.map +9 -9
  2. package/bin/worker.js.map +7 -7
  3. package/mcp/servicenow-unified.js +116 -116
  4. package/package.json +1 -1
  5. package/parsers-config.ts +2 -1
  6. package/src/bun/index.ts +10 -9
  7. package/src/cli/cmd/agent.ts +3 -3
  8. package/src/cli/cmd/auth.ts +46 -0
  9. package/src/cli/cmd/import.ts +2 -2
  10. package/src/cli/cmd/session.ts +9 -12
  11. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +2 -1
  12. package/src/cli/cmd/tui/component/prompt/index.tsx +19 -6
  13. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  14. package/src/cli/cmd/tui/context/exit.tsx +1 -1
  15. package/src/cli/cmd/tui/routes/home.tsx +16 -2
  16. package/src/cli/cmd/tui/routes/session/index.tsx +122 -53
  17. package/src/cli/cmd/tui/routes/session/permission.tsx +9 -1
  18. package/src/cli/cmd/tui/routes/session/sidebar.tsx +9 -1
  19. package/src/cli/cmd/tui/thread.ts +4 -1
  20. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +1 -1
  21. package/src/cli/cmd/tui/util/clipboard.ts +3 -3
  22. package/src/cli/cmd/tui/worker.ts +6 -1
  23. package/src/config/config.ts +28 -0
  24. package/src/context/context-db.ts +437 -0
  25. package/src/format/formatter.ts +14 -5
  26. package/src/global/index.ts +3 -4
  27. package/src/mcp/index.ts +7 -2
  28. package/src/mcp/oauth-callback.ts +7 -15
  29. package/src/mcp/oauth-provider.ts +34 -3
  30. package/src/project/project.ts +8 -4
  31. package/src/provider/models.ts +1 -1
  32. package/src/provider/provider.ts +88 -9
  33. package/src/provider/transform.ts +7 -2
  34. package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_capacity_plan.ts +20 -7
  35. package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_retrospective.ts +6 -8
  36. package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_sprint_manage.ts +46 -28
  37. package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_team_manage.ts +53 -41
  38. package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_velocity_report.ts +8 -1
  39. package/src/servicenow/servicenow-mcp-unified/tools/automation/snow_schedule_script_job.ts +388 -243
  40. package/src/session/compaction.ts +126 -23
  41. package/src/session/message-v2.ts +33 -10
  42. package/src/session/processor.ts +29 -17
  43. package/src/session/prompt.ts +34 -6
  44. package/src/share/share-next.ts +2 -2
  45. package/src/shell/shell.ts +2 -1
  46. package/src/tool/edit.ts +15 -1
  47. package/src/tool/registry.ts +9 -1
  48. package/src/tool/truncation.ts +17 -0
  49. package/src/tool/websearch.ts +1 -1
  50. package/src/tool/websearch.txt +2 -2
  51. package/src/tool/write.ts +3 -4
  52. package/src/util/filesystem.ts +36 -7
  53. package/src/util/keybind.ts +1 -1
  54. package/src/util/log.ts +8 -5
  55. package/src/util/token.ts +28 -0
  56. package/test/cli/plugin-auth-picker.test.ts +120 -0
  57. package/test/fixture/fixture.ts +3 -0
  58. package/test/mcp/oauth-auto-connect.test.ts +197 -0
  59. package/test/project/project.test.ts +47 -0
  60. package/test/provider/provider.test.ts +2 -0
  61. package/test/provider/transform.test.ts +32 -0
  62. package/test/tool/edit.test.ts +679 -0
@@ -1,22 +1,11 @@
1
1
  /**
2
- * snow_schedule_script_job - Schedule server-side script execution
2
+ * snow_schedule_script_job - Execute server-side JavaScript on ServiceNow
3
3
  *
4
- * ⚠️ IMPORTANT: This tool does NOT execute scripts directly!
5
- * It creates a Scheduled Script Job (sysauto_script) and attempts to trigger it.
4
+ * Primary: synchronous execution via Scripted REST API (~1-3s)
5
+ * Fallback: scheduled job if endpoint unavailable
6
+ * Auto-deploys the executor endpoint on first use.
6
7
  *
7
- * How it works:
8
- * 1. Creates a scheduled job in sysauto_script table
9
- * 2. Attempts to create a sys_trigger to run it immediately
10
- * 3. Polls for results via sys_properties (max 30 seconds)
11
- * 4. If trigger fails or scheduler is slow, returns executed=false
12
- *
13
- * When executed=false is returned:
14
- * - The script job WAS created successfully
15
- * - You need to manually run it: System Scheduler > Scheduled Jobs
16
- * - Or wait for the scheduler to pick it up
17
- *
18
- * ⚠️ CRITICAL: ALL SCRIPTS MUST BE ES5 ONLY!
19
- * ServiceNow runs on Rhino engine - no const/let/arrow functions/template literals.
8
+ * ES5 only! ServiceNow runs on Rhino engine.
20
9
  */
21
10
 
22
11
  import { MCPToolDefinition, ServiceNowContext, ToolResult } from "../../shared/types.js"
@@ -24,18 +13,88 @@ import { getAuthenticatedClient } from "../../shared/auth.js"
24
13
  import { createSuccessResult, createErrorResult, SnowFlowError, ErrorType } from "../../shared/error-handler.js"
25
14
  import crypto from "crypto"
26
15
 
16
+ const ENDPOINT_SERVICE_ID = "snow_flow_exec"
17
+ const ENDPOINT_PATH = "/execute"
18
+
19
+ const deployed = new Map<string, boolean>()
20
+
21
+ const OPERATION_SCRIPT = `(function process(request, response) {
22
+ var body = request.body.data;
23
+ var script = body.script;
24
+ var id = body.execution_id || gs.generateGUID();
25
+
26
+ if (!script) {
27
+ response.setStatus(400);
28
+ response.setBody({ success: false, error: 'No script provided' });
29
+ return;
30
+ }
31
+
32
+ var output = [];
33
+ var result = null;
34
+ var error = null;
35
+ var startTime = new GlideDateTime();
36
+
37
+ var origPrint = gs.print;
38
+ var origInfo = gs.info;
39
+ var origWarn = gs.warn;
40
+ var origError = gs.error;
41
+
42
+ gs.print = function(msg) {
43
+ var m = String(msg);
44
+ output.push({ level: 'print', message: m });
45
+ origPrint.call(gs, m);
46
+ };
47
+ gs.info = function(msg) {
48
+ var m = String(msg);
49
+ output.push({ level: 'info', message: m });
50
+ origInfo.call(gs, m);
51
+ };
52
+ gs.warn = function(msg) {
53
+ var m = String(msg);
54
+ output.push({ level: 'warn', message: m });
55
+ origWarn.call(gs, m);
56
+ };
57
+ gs.error = function(msg) {
58
+ var m = String(msg);
59
+ output.push({ level: 'error', message: m });
60
+ origError.call(gs, m);
61
+ };
62
+
63
+ try {
64
+ result = GlideEvaluator.evaluateString(script);
65
+ } catch (e) {
66
+ error = e.toString();
67
+ if (e.stack) error = error + '\\nStack: ' + e.stack;
68
+ }
69
+
70
+ gs.print = origPrint;
71
+ gs.info = origInfo;
72
+ gs.warn = origWarn;
73
+ gs.error = origError;
74
+
75
+ var endTime = new GlideDateTime();
76
+ var execMs = Math.abs(GlideDateTime.subtract(startTime, endTime).getNumericValue());
77
+
78
+ response.setStatus(error ? 500 : 200);
79
+ response.setBody({
80
+ execution_id: id,
81
+ success: error === null,
82
+ result: result,
83
+ error: error,
84
+ output: output,
85
+ execution_time_ms: execMs
86
+ });
87
+ })(request, response);`
88
+
27
89
  export const toolDefinition: MCPToolDefinition = {
28
90
  name: "snow_schedule_script_job",
29
91
  description:
30
- "⚠️ UNRELIABLE: Creates a Scheduled Script Job (sysauto_script) but CANNOT guarantee execution. The ServiceNow scheduler decides when/if it runs — this frequently times out or returns executed=false. Do NOT use this to verify, test, or validate anything — results are unreliable. Only use as a last resort when no dedicated tool exists. Prefer dedicated tools (snow_query_table, snow_manage_flow, etc.) for all operations. ES5 only!",
31
- // Metadata for tool discovery (not sent to LLM)
92
+ "Execute server-side JavaScript on ServiceNow. Primary: synchronous execution via Scripted REST API (~1-3s). Fallback: scheduled job if endpoint unavailable. Auto-deploys the executor endpoint on first use. ES5 only (Rhino engine)!",
32
93
  category: "automation",
33
94
  subcategory: "scheduled-jobs",
34
95
  use_cases: ["automation", "scripts", "scheduled-jobs", "debugging", "verification"],
35
96
  complexity: "advanced",
36
97
  frequency: "high",
37
-
38
- // Permission enforcement
39
98
  permission: "write",
40
99
  allowedRoles: ["developer", "admin"],
41
100
  inputSchema: {
@@ -43,7 +102,7 @@ export const toolDefinition: MCPToolDefinition = {
43
102
  properties: {
44
103
  script: {
45
104
  type: "string",
46
- description: "🚨 ES5 ONLY! JavaScript code to execute (no const/let/arrows/templates - Rhino engine)",
105
+ description: "ES5 ONLY! JavaScript code to execute (no const/let/arrows/templates - Rhino engine)",
47
106
  },
48
107
  description: {
49
108
  type: "string",
@@ -57,7 +116,7 @@ export const toolDefinition: MCPToolDefinition = {
57
116
  },
58
117
  timeout: {
59
118
  type: "number",
60
- description: "Timeout in milliseconds for polling execution results",
119
+ description: "Timeout in milliseconds for polling execution results (fallback mode only)",
61
120
  default: 30000,
62
121
  },
63
122
  validate_es5: {
@@ -72,7 +131,7 @@ export const toolDefinition: MCPToolDefinition = {
72
131
  },
73
132
  autoConfirm: {
74
133
  type: "boolean",
75
- description: "⚠️ DANGEROUS: Skip user confirmation even if requireConfirmation would normally be required",
134
+ description: "Skip user confirmation even if requireConfirmation would normally be required",
76
135
  default: false,
77
136
  },
78
137
  allowDataModification: {
@@ -89,55 +148,48 @@ export const toolDefinition: MCPToolDefinition = {
89
148
  },
90
149
  }
91
150
 
92
- export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
93
- const {
94
- script,
95
- description = "Script scheduled via snow_schedule_script_job",
96
- scope = "global",
97
- timeout = 30000,
98
- validate_es5 = true,
99
- requireConfirmation = false,
100
- autoConfirm = false,
101
- allowDataModification = false,
102
- runAsUser,
103
- } = args
104
-
105
- // ES5 validation (warning only, does not block execution)
106
- const es5Warnings: string[] = []
151
+ export async function execute(args: Record<string, unknown>, context: ServiceNowContext): Promise<ToolResult> {
152
+ const script = args.script as string
153
+ const description = (args.description as string) || "Script scheduled via snow_schedule_script_job"
154
+ const timeout = (args.timeout as number) || 30000
155
+ const validate = args.validate_es5 !== false
156
+ const confirmation = args.requireConfirmation === true
157
+ const auto = args.autoConfirm === true
158
+ const modification = args.allowDataModification === true
159
+ const user = args.runAsUser as string | undefined
160
+
161
+ const warnings: string[] = []
107
162
 
108
163
  try {
109
- if (validate_es5) {
110
- const es5Validation = validateES5(script)
111
- if (!es5Validation.valid) {
112
- es5Warnings.push(
113
- `Script contains ES6+ syntax (${es5Validation.violations.map((v: any) => v.type).join(", ")}). This may cause runtime errors in ServiceNow's Rhino engine. Consider using ES5 syntax.`,
164
+ if (validate) {
165
+ const validation = validateES5(script)
166
+ if (!validation.valid) {
167
+ warnings.push(
168
+ `Script contains ES6+ syntax (${validation.violations.map((v) => v.type).join(", ")}). This may cause runtime errors in ServiceNow's Rhino engine. Consider using ES5 syntax.`,
114
169
  )
115
170
  }
116
171
  }
117
172
 
118
- // Security analysis
119
- const securityAnalysis = analyzeScriptSecurity(script)
173
+ const security = analyzeScriptSecurity(script)
120
174
 
121
- // Check if confirmation is needed
122
- if (requireConfirmation && !autoConfirm) {
123
- // Return confirmation request
124
- const confirmationPrompt = generateConfirmationPrompt({
175
+ if (confirmation && !auto) {
176
+ const prompt = generateConfirmationPrompt({
125
177
  script,
126
178
  description,
127
- runAsUser,
128
- allowDataModification,
129
- securityAnalysis,
179
+ runAsUser: user,
180
+ allowDataModification: modification,
181
+ securityAnalysis: security,
130
182
  })
131
183
 
132
184
  return createSuccessResult(
133
185
  {
134
186
  requires_confirmation: true,
135
- confirmation_prompt: confirmationPrompt,
187
+ confirmation_prompt: prompt,
136
188
  script_to_execute: script,
137
189
  execution_context: {
138
- runAsUser: runAsUser || "current",
139
- allowDataModification,
140
- securityLevel: securityAnalysis.riskLevel,
190
+ runAsUser: user || "current",
191
+ allowDataModification: modification,
192
+ securityLevel: security.riskLevel,
141
193
  },
142
194
  next_step: "Call snow_confirm_script_execution with userConfirmed=true to execute",
143
195
  },
@@ -147,105 +199,198 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
147
199
  )
148
200
  }
149
201
 
150
- // Execute the script
151
202
  return await executeScript(
152
- {
153
- script,
154
- description,
155
- timeout,
156
- securityAnalysis,
157
- autoConfirm,
158
- es5Warnings,
159
- },
203
+ { script, description, timeout, securityAnalysis: security, autoConfirm: auto, es5Warnings: warnings },
160
204
  context,
161
205
  )
162
- } catch (error: any) {
206
+ } catch (error: unknown) {
207
+ const err = error as Error
163
208
  return createErrorResult(
164
- error instanceof SnowFlowError
165
- ? error
166
- : new SnowFlowError(ErrorType.UNKNOWN_ERROR, error.message, { originalError: error }),
209
+ err instanceof SnowFlowError
210
+ ? err
211
+ : new SnowFlowError(ErrorType.UNKNOWN_ERROR, err.message, { originalError: err }),
167
212
  )
168
213
  }
169
214
  }
170
215
 
171
- async function executeScript(
216
+ async function ensureEndpoint(context: ServiceNowContext): Promise<boolean> {
217
+ if (deployed.get(context.instanceUrl)) return true
218
+
219
+ const client = await getAuthenticatedClient(context)
220
+
221
+ const check = await client.get("/api/now/table/sys_ws_definition", {
222
+ params: {
223
+ sysparm_query: `service_id=${ENDPOINT_SERVICE_ID}`,
224
+ sysparm_fields: "sys_id",
225
+ sysparm_limit: 1,
226
+ },
227
+ })
228
+
229
+ const existing = check.data?.result?.[0]
230
+ if (!existing) {
231
+ const svc = await client.post("/api/now/table/sys_ws_definition", {
232
+ name: "Snow-Flow Script Executor",
233
+ service_id: ENDPOINT_SERVICE_ID,
234
+ short_description: "Synchronous script execution endpoint for Snow-Flow",
235
+ active: true,
236
+ })
237
+
238
+ const svcId = svc.data?.result?.sys_id
239
+ if (!svcId) return false
240
+
241
+ const res = await client.post("/api/now/table/sys_ws_operation", {
242
+ name: "Execute Script",
243
+ web_service_definition: svcId,
244
+ http_method: "POST",
245
+ relative_path: ENDPOINT_PATH,
246
+ operation_script: OPERATION_SCRIPT,
247
+ active: true,
248
+ })
249
+
250
+ if (!res.data?.result?.sys_id) return false
251
+ }
252
+
253
+ const ping = await client
254
+ .post(`/api/${ENDPOINT_SERVICE_ID}${ENDPOINT_PATH}`, {
255
+ script: "'pong'",
256
+ execution_id: "deploy_verify",
257
+ })
258
+ .catch(() => null)
259
+
260
+ const ok = ping?.data?.result?.success === true
261
+ if (ok) deployed.set(context.instanceUrl, true)
262
+ return ok
263
+ }
264
+
265
+ async function executeViaSyncApi(
266
+ params: {
267
+ script: string
268
+ executionId: string
269
+ description: string
270
+ securityAnalysis: Record<string, unknown>
271
+ autoConfirm: boolean
272
+ es5Warnings: string[]
273
+ },
274
+ context: ServiceNowContext,
275
+ ): Promise<ToolResult | null> {
276
+ const client = await getAuthenticatedClient(context)
277
+
278
+ const response = await client
279
+ .post(`/api/${ENDPOINT_SERVICE_ID}${ENDPOINT_PATH}`, {
280
+ script: params.script,
281
+ execution_id: params.executionId,
282
+ })
283
+ .catch((err: { response?: { status?: number } }) => {
284
+ if (err.response?.status === 404 || err.response?.status === 403) return null
285
+ throw err
286
+ })
287
+
288
+ if (!response) return null
289
+
290
+ const data = response.data?.result
291
+ if (!data) return null
292
+
293
+ const organized = {
294
+ print: (data.output || [])
295
+ .filter((o: { level: string }) => o.level === "print")
296
+ .map((o: { message: string }) => o.message),
297
+ info: (data.output || [])
298
+ .filter((o: { level: string }) => o.level === "info")
299
+ .map((o: { message: string }) => o.message),
300
+ warn: (data.output || [])
301
+ .filter((o: { level: string }) => o.level === "warn")
302
+ .map((o: { message: string }) => o.message),
303
+ error: (data.output || [])
304
+ .filter((o: { level: string }) => o.level === "error")
305
+ .map((o: { message: string }) => o.message),
306
+ success: data.success,
307
+ }
308
+
309
+ const result: Record<string, unknown> = {
310
+ executed: true,
311
+ success: data.success,
312
+ result: data.result,
313
+ error: data.error,
314
+ output: organized,
315
+ raw_output: data.output,
316
+ execution_time_ms: data.execution_time_ms,
317
+ execution_id: params.executionId,
318
+ auto_confirmed: params.autoConfirm,
319
+ security_analysis: params.securityAnalysis,
320
+ }
321
+
322
+ if (params.es5Warnings.length > 0) {
323
+ result.warnings = params.es5Warnings
324
+ }
325
+
326
+ return createSuccessResult(result, {
327
+ script_length: params.script.length,
328
+ method: "sync_rest_api",
329
+ description: params.description,
330
+ })
331
+ }
332
+
333
+ async function executeViaScheduler(
172
334
  params: {
173
335
  script: string
174
336
  description: string
175
337
  timeout: number
176
- securityAnalysis: any
338
+ securityAnalysis: Record<string, unknown>
177
339
  autoConfirm: boolean
178
340
  es5Warnings: string[]
179
341
  },
180
342
  context: ServiceNowContext,
181
343
  ): Promise<ToolResult> {
182
- const { script, description, timeout, securityAnalysis, autoConfirm, es5Warnings } = params
183
-
184
344
  const client = await getAuthenticatedClient(context)
185
345
 
186
- // SECURITY: Proper string escaping - escape backslashes first, then quotes
187
- const escapeForJS = (str: string) =>
346
+ const escape = (str: string) =>
188
347
  str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r")
189
348
 
190
- // Create unique execution ID for tracking
191
349
  const executionId = `exec_${Date.now()}_${crypto.randomBytes(6).toString("hex")}`
192
- const outputMarker = `SNOW_FLOW_EXEC_${executionId}`
350
+ const marker = `SNOW_FLOW_EXEC_${executionId}`
193
351
 
194
- // Wrap script with comprehensive output capture
195
- const wrappedScript = `
196
- // Snow-Flow Script Execution - ID: ${executionId}
197
- // Description: ${escapeForJS(description)}
352
+ const wrapped = `
198
353
  var __sfOutput = [];
199
354
  var __sfStartTime = new GlideDateTime();
200
355
  var __sfResult = null;
201
356
  var __sfError = null;
202
357
 
203
- // Store original gs methods
204
358
  var __sfOrigPrint = gs.print;
205
359
  var __sfOrigInfo = gs.info;
206
360
  var __sfOrigWarn = gs.warn;
207
361
  var __sfOrigError = gs.error;
208
362
 
209
- // Override gs methods to capture output
210
363
  gs.print = function(msg) {
211
364
  var m = String(msg);
212
365
  __sfOutput.push({level: 'print', message: m, timestamp: new GlideDateTime().getDisplayValue()});
213
366
  __sfOrigPrint(m);
214
367
  };
215
-
216
368
  gs.info = function(msg) {
217
369
  var m = String(msg);
218
370
  __sfOutput.push({level: 'info', message: m, timestamp: new GlideDateTime().getDisplayValue()});
219
371
  __sfOrigInfo(m);
220
372
  };
221
-
222
373
  gs.warn = function(msg) {
223
374
  var m = String(msg);
224
375
  __sfOutput.push({level: 'warn', message: m, timestamp: new GlideDateTime().getDisplayValue()});
225
376
  __sfOrigWarn(m);
226
377
  };
227
-
228
378
  gs.error = function(msg) {
229
379
  var m = String(msg);
230
380
  __sfOutput.push({level: 'error', message: m, timestamp: new GlideDateTime().getDisplayValue()});
231
381
  __sfOrigError(m);
232
382
  };
233
383
 
234
- // Execute the user script
235
384
  try {
236
385
  gs.info('=== Snow-Flow Script Execution Started ===');
237
- gs.info('Description: ${escapeForJS(description)}');
238
-
386
+ gs.info('Description: ${escape(params.description)}');
239
387
  __sfResult = (function() {
240
- ${script}
388
+ ${params.script}
241
389
  })();
242
-
243
390
  gs.info('=== Snow-Flow Script Execution Completed ===');
244
-
245
391
  if (__sfResult !== undefined && __sfResult !== null) {
246
392
  gs.info('Script returned: ' + (typeof __sfResult === 'object' ? JSON.stringify(__sfResult) : String(__sfResult)));
247
393
  }
248
-
249
394
  } catch(e) {
250
395
  __sfError = e.toString();
251
396
  gs.error('=== Snow-Flow Script Execution Failed ===');
@@ -255,17 +400,14 @@ try {
255
400
  }
256
401
  }
257
402
 
258
- // Restore original gs methods
259
403
  gs.print = __sfOrigPrint;
260
404
  gs.info = __sfOrigInfo;
261
405
  gs.warn = __sfOrigWarn;
262
406
  gs.error = __sfOrigError;
263
407
 
264
- // Calculate execution time
265
408
  var __sfEndTime = new GlideDateTime();
266
409
  var __sfExecTimeMs = Math.abs(GlideDateTime.subtract(__sfStartTime, __sfEndTime).getNumericValue());
267
410
 
268
- // Build result object
269
411
  var __sfResultObj = {
270
412
  executionId: '${executionId}',
271
413
  success: __sfError === null,
@@ -276,109 +418,89 @@ var __sfResultObj = {
276
418
  completedAt: __sfEndTime.getDisplayValue()
277
419
  };
278
420
 
279
- // Store result in system property for retrieval
280
- gs.setProperty('${outputMarker}', JSON.stringify(__sfResultObj));
281
- gs.info('${outputMarker}:DONE');
421
+ gs.setProperty('${marker}', JSON.stringify(__sfResultObj));
422
+ gs.info('${marker}:DONE');
282
423
  `
283
424
 
284
- // Step 1: Create Scheduled Script Job (sysauto_script)
285
- const jobName = `Snow-Flow Exec - ${executionId}`
425
+ const name = `Snow-Flow Exec - ${executionId}`
286
426
 
287
- const createResponse = await client.post("/api/now/table/sysauto_script", {
288
- name: jobName,
289
- script: wrappedScript,
427
+ const job = await client.post("/api/now/table/sysauto_script", {
428
+ name,
429
+ script: wrapped,
290
430
  active: true,
291
431
  run_type: "on_demand",
292
432
  conditional: false,
293
433
  })
294
434
 
295
- if (!createResponse.data?.result?.sys_id) {
435
+ if (!job.data?.result?.sys_id) {
296
436
  throw new SnowFlowError(ErrorType.SERVICENOW_API_ERROR, "Failed to create scheduled script job", {
297
- details: createResponse.data,
437
+ details: job.data,
298
438
  })
299
439
  }
300
440
 
301
- const jobSysId = createResponse.data.result.sys_id
441
+ const jobId = job.data.result.sys_id
302
442
 
303
- // Step 2: Create sys_trigger to execute immediately
304
443
  const now = new Date()
305
- const triggerTime = new Date(now.getTime() + 2000) // 2 seconds from now
306
- const triggerTimeStr = triggerTime.toISOString().replace("T", " ").substring(0, 19)
307
-
308
- try {
309
- await client.post("/api/now/table/sys_trigger", {
310
- name: jobName,
311
- next_action: triggerTimeStr,
312
- trigger_type: 0, // Run Once
313
- state: 0, // Ready
444
+ const trigger = new Date(now.getTime() + 2000)
445
+ const triggerStr = trigger.toISOString().replace("T", " ").substring(0, 19)
446
+
447
+ await client
448
+ .post("/api/now/table/sys_trigger", {
449
+ name,
450
+ next_action: triggerStr,
451
+ trigger_type: 0,
452
+ state: 0,
314
453
  document: "sysauto_script",
315
- document_key: jobSysId,
454
+ document_key: jobId,
316
455
  claimed_by: "",
317
456
  system_id: "snow-flow",
318
457
  })
319
- } catch (triggerError) {
320
- // If trigger creation fails, job won't auto-execute
321
- // Continue anyway - we'll check results
322
- }
458
+ .catch(() => {})
323
459
 
324
- // Step 3: Poll for execution results
325
- const startTime = Date.now()
326
- let result: any = null
327
- let attempts = 0
328
- const maxAttempts = Math.ceil(timeout / 2000)
460
+ const start = Date.now()
461
+ const max = Math.ceil(params.timeout / 2000)
462
+ let result: Record<string, unknown> | null = null
329
463
 
330
- while (Date.now() - startTime < timeout && attempts < maxAttempts) {
331
- attempts++
464
+ for (let i = 0; i < max && Date.now() - start < params.timeout; i++) {
332
465
  await new Promise((resolve) => setTimeout(resolve, 2000))
333
466
 
334
- try {
335
- // Check sys_properties for output marker
336
- const propResponse = await client.get("/api/now/table/sys_properties", {
467
+ const prop = await client
468
+ .get("/api/now/table/sys_properties", {
337
469
  params: {
338
- sysparm_query: `name=${outputMarker}`,
470
+ sysparm_query: `name=${marker}`,
339
471
  sysparm_fields: "value,sys_id",
340
472
  sysparm_limit: 1,
341
473
  },
342
474
  })
475
+ .catch(() => null)
476
+
477
+ const entry = prop?.data?.result?.[0]
478
+ if (!entry?.value) continue
343
479
 
344
- if (propResponse.data?.result?.[0]?.value) {
345
- try {
346
- result = JSON.parse(propResponse.data.result[0].value)
347
-
348
- // Delete the property after reading
349
- const propSysId = propResponse.data.result[0].sys_id
350
- if (propSysId) {
351
- await client.delete(`/api/now/table/sys_properties/${propSysId}`).catch(() => {})
352
- }
353
- break
354
- } catch (parseErr) {
355
- // Continue polling
356
- }
480
+ try {
481
+ result = JSON.parse(entry.value) as Record<string, unknown>
482
+ if (entry.sys_id) {
483
+ await client.delete(`/api/now/table/sys_properties/${entry.sys_id}`).catch(() => {})
357
484
  }
358
- } catch (pollError) {
359
- // Continue polling
485
+ break
486
+ } catch {
487
+ continue
360
488
  }
361
489
  }
362
490
 
363
- // Step 4: Format and return results
364
491
  if (result) {
365
- // Execution was confirmed - cleanup the job
366
- try {
367
- await client.delete(`/api/now/table/sysauto_script/${jobSysId}`)
368
- } catch (cleanupError) {
369
- // Ignore cleanup errors
370
- }
492
+ await client.delete(`/api/now/table/sysauto_script/${jobId}`).catch(() => {})
371
493
 
372
- // Organize output by level
494
+ const output = (result.output || []) as Array<{ level: string; message: string }>
373
495
  const organized = {
374
- print: result.output.filter((o: any) => o.level === "print").map((o: any) => o.message),
375
- info: result.output.filter((o: any) => o.level === "info").map((o: any) => o.message),
376
- warn: result.output.filter((o: any) => o.level === "warn").map((o: any) => o.message),
377
- error: result.output.filter((o: any) => o.level === "error").map((o: any) => o.message),
496
+ print: output.filter((o) => o.level === "print").map((o) => o.message),
497
+ info: output.filter((o) => o.level === "info").map((o) => o.message),
498
+ warn: output.filter((o) => o.level === "warn").map((o) => o.message),
499
+ error: output.filter((o) => o.level === "error").map((o) => o.message),
378
500
  success: result.success,
379
501
  }
380
502
 
381
- const successData: any = {
503
+ const data: Record<string, unknown> = {
382
504
  executed: true,
383
505
  success: result.success,
384
506
  result: result.result,
@@ -387,51 +509,77 @@ gs.info('${outputMarker}:DONE');
387
509
  raw_output: result.output,
388
510
  execution_time_ms: result.executionTimeMs,
389
511
  execution_id: executionId,
390
- auto_confirmed: autoConfirm,
391
- security_analysis: securityAnalysis,
512
+ auto_confirmed: params.autoConfirm,
513
+ security_analysis: params.securityAnalysis,
514
+ fallback_warning: "Executed via scheduler fallback. The sync REST API endpoint could not be reached.",
392
515
  }
393
516
 
394
- // Add ES5 warnings if any (non-blocking, informational only)
395
- if (es5Warnings.length > 0) {
396
- successData.warnings = es5Warnings
517
+ if (params.es5Warnings.length > 0) {
518
+ data.warnings = params.es5Warnings
397
519
  }
398
520
 
399
- return createSuccessResult(successData, {
521
+ return createSuccessResult(data, {
400
522
  script_length: params.script.length,
401
523
  method: "sysauto_script_with_trigger",
402
- description,
524
+ description: params.description,
403
525
  })
404
- } else {
405
- // Script was saved but execution couldn't be confirmed
406
- // DO NOT delete the job - user may want to trigger it manually
407
- const pendingData: any = {
408
- executed: false,
409
- execution_id: executionId,
410
- scheduled_job_sys_id: jobSysId,
411
- job_name: jobName,
412
- auto_confirmed: autoConfirm,
413
- security_analysis: securityAnalysis,
414
- message:
415
- "Script was saved as scheduled job but automatic execution could not be confirmed. The sys_trigger may not have been created (permissions) or the scheduler has not yet picked it up.",
416
- action_required: `Navigate to System Scheduler > Scheduled Jobs and run: ${jobName}`,
417
- manual_url: `${context.instanceUrl}/sysauto_script.do?sys_id=${jobSysId}`,
418
- }
526
+ }
419
527
 
420
- // Add ES5 warnings if any (non-blocking, informational only)
421
- if (es5Warnings.length > 0) {
422
- pendingData.warnings = es5Warnings
423
- }
528
+ const pending: Record<string, unknown> = {
529
+ executed: false,
530
+ execution_id: executionId,
531
+ scheduled_job_sys_id: jobId,
532
+ job_name: name,
533
+ auto_confirmed: params.autoConfirm,
534
+ security_analysis: params.securityAnalysis,
535
+ message:
536
+ "Script was saved as scheduled job but automatic execution could not be confirmed. The sys_trigger may not have been created (permissions) or the scheduler has not yet picked it up.",
537
+ action_required: `Navigate to System Scheduler > Scheduled Jobs and run: ${name}`,
538
+ manual_url: `${context.instanceUrl}/sysauto_script.do?sys_id=${jobId}`,
539
+ fallback_warning: "Both sync REST API and scheduler fallback failed to confirm execution.",
540
+ }
424
541
 
425
- return createSuccessResult(pendingData, {
426
- script_length: params.script.length,
427
- method: "scheduled_job_pending",
428
- description,
429
- })
542
+ if (params.es5Warnings.length > 0) {
543
+ pending.warnings = params.es5Warnings
430
544
  }
545
+
546
+ return createSuccessResult(pending, {
547
+ script_length: params.script.length,
548
+ method: "scheduled_job_pending",
549
+ description: params.description,
550
+ })
431
551
  }
432
552
 
433
- function validateES5(code: string): { valid: boolean; violations: any[] } {
434
- const violations: any[] = []
553
+ async function executeScript(
554
+ params: {
555
+ script: string
556
+ description: string
557
+ timeout: number
558
+ securityAnalysis: Record<string, unknown>
559
+ autoConfirm: boolean
560
+ es5Warnings: string[]
561
+ },
562
+ context: ServiceNowContext,
563
+ ): Promise<ToolResult> {
564
+ const executionId = `exec_${Date.now()}_${crypto.randomBytes(6).toString("hex")}`
565
+
566
+ const syncResult = await executeViaSyncApi({ ...params, executionId }, context)
567
+ if (syncResult) return syncResult
568
+
569
+ const ok = await ensureEndpoint(context).catch(() => false)
570
+ if (ok) {
571
+ const retry = await executeViaSyncApi({ ...params, executionId }, context)
572
+ if (retry) return retry
573
+ }
574
+
575
+ return executeViaScheduler(params, context)
576
+ }
577
+
578
+ function validateES5(code: string): {
579
+ valid: boolean
580
+ violations: Array<{ type: string; line: number; code: string; fix: string }>
581
+ } {
582
+ const violations: Array<{ type: string; line: number; code: string; fix: string }> = []
435
583
 
436
584
  const patterns = [
437
585
  { regex: /\b(const|let)\s+/g, type: "const/let", fix: "Use 'var'" },
@@ -442,57 +590,55 @@ function validateES5(code: string): { valid: boolean; violations: any[] } {
442
590
  { regex: /class\s+\w+/g, type: "class", fix: "Use function constructor" },
443
591
  ]
444
592
 
445
- patterns.forEach(({ regex, type, fix }) => {
593
+ for (const pattern of patterns) {
446
594
  let match
447
- while ((match = regex.exec(code)) !== null) {
595
+ while ((match = pattern.regex.exec(code)) !== null) {
448
596
  violations.push({
449
- type,
597
+ type: pattern.type,
450
598
  line: code.substring(0, match.index).split("\n").length,
451
599
  code: match[0],
452
- fix,
600
+ fix: pattern.fix,
453
601
  })
454
602
  }
455
- })
603
+ }
456
604
 
457
605
  return { valid: violations.length === 0, violations }
458
606
  }
459
607
 
460
- function analyzeScriptSecurity(script: string): any {
608
+ function analyzeScriptSecurity(script: string): Record<string, unknown> {
461
609
  const analysis = {
462
- riskLevel: "LOW",
610
+ riskLevel: "LOW" as string,
463
611
  warnings: [] as string[],
464
612
  dataOperations: [] as string[],
465
613
  systemAccess: [] as string[],
466
614
  }
467
615
 
468
- const dataModificationPatterns = [/\.insert\(\)/gi, /\.update\(\)/gi, /\.deleteRecord\(\)/gi, /\.setValue\(/gi]
469
-
470
- const systemAccessPatterns = [/gs\.getUser\(\)/gi, /gs\.getUserID\(\)/gi, /gs\.hasRole\(/gi, /gs\.executeNow\(/gi]
616
+ const modification = [/\.insert\(\)/gi, /\.update\(\)/gi, /\.deleteRecord\(\)/gi, /\.setValue\(/gi]
617
+ const system = [/gs\.getUser\(\)/gi, /gs\.getUserID\(\)/gi, /gs\.hasRole\(/gi, /gs\.executeNow\(/gi]
618
+ const dangerous = [/eval\(/gi, /new Function\(/gi, /\.setWorkflow\(/gi]
471
619
 
472
- const dangerousPatterns = [/eval\(/gi, /new Function\(/gi, /\.setWorkflow\(/gi]
473
-
474
- dataModificationPatterns.forEach((pattern) => {
620
+ for (const pattern of modification) {
475
621
  const matches = script.match(pattern)
476
622
  if (matches) {
477
623
  analysis.dataOperations.push(...matches)
478
624
  if (analysis.riskLevel === "LOW") analysis.riskLevel = "MEDIUM"
479
625
  }
480
- })
626
+ }
481
627
 
482
- systemAccessPatterns.forEach((pattern) => {
628
+ for (const pattern of system) {
483
629
  const matches = script.match(pattern)
484
630
  if (matches) {
485
631
  analysis.systemAccess.push(...matches)
486
632
  }
487
- })
633
+ }
488
634
 
489
- dangerousPatterns.forEach((pattern) => {
635
+ for (const pattern of dangerous) {
490
636
  const matches = script.match(pattern)
491
637
  if (matches) {
492
638
  analysis.warnings.push(`Potentially dangerous operation detected: ${matches[0]}`)
493
639
  analysis.riskLevel = "HIGH"
494
640
  }
495
- })
641
+ }
496
642
 
497
643
  if (script.includes("while") && (script.includes(".next()") || script.includes(".hasNext()"))) {
498
644
  analysis.warnings.push("Script contains loops that may process many records")
@@ -502,52 +648,51 @@ function analyzeScriptSecurity(script: string): any {
502
648
  return analysis
503
649
  }
504
650
 
505
- function generateConfirmationPrompt(context: any): string {
506
- const { script, description, runAsUser, allowDataModification, securityAnalysis } = context
507
-
508
- const riskEmoji =
509
- {
510
- LOW: "🟢",
511
- MEDIUM: "🟡",
512
- HIGH: "🔴",
513
- }[securityAnalysis.riskLevel] || ""
651
+ function generateConfirmationPrompt(ctx: {
652
+ script: string
653
+ description: string
654
+ runAsUser: string | undefined
655
+ allowDataModification: boolean
656
+ securityAnalysis: Record<string, unknown>
657
+ }): string {
658
+ const risk = ctx.securityAnalysis.riskLevel as string
659
+ const emoji = risk === "HIGH" ? "RED" : risk === "MEDIUM" ? "YELLOW" : "GREEN"
660
+ const ops = ctx.securityAnalysis.dataOperations as string[]
661
+ const access = ctx.securityAnalysis.systemAccess as string[]
662
+ const warns = ctx.securityAnalysis.warnings as string[]
514
663
 
515
664
  return `
516
- 🚨 SCRIPT EXECUTION REQUEST
665
+ SCRIPT EXECUTION REQUEST
517
666
 
518
- 📋 **Description:** ${description}
667
+ Description: ${ctx.description}
519
668
 
520
- ${riskEmoji} **Security Risk Level:** ${securityAnalysis.riskLevel}
669
+ Security Risk Level: ${emoji} ${risk}
521
670
 
522
- 👤 **Run as User:** ${runAsUser || "Current User"}
523
- 📝 **Data Modification:** ${allowDataModification ? "ALLOWED" : "READ-ONLY"}
671
+ Run as User: ${ctx.runAsUser || "Current User"}
672
+ Data Modification: ${ctx.allowDataModification ? "ALLOWED" : "READ-ONLY"}
524
673
 
525
- 🔍 **Script Analysis:**
526
- ${
527
- securityAnalysis.dataOperations.length > 0
528
- ? `📊 Data Operations Detected: ${securityAnalysis.dataOperations.join(", ")}`
529
- : ""
530
- }
531
- ${securityAnalysis.systemAccess.length > 0 ? `🔧 System Access: ${securityAnalysis.systemAccess.join(", ")}` : ""}
532
- ${securityAnalysis.warnings.length > 0 ? `⚠️ Warnings: ${securityAnalysis.warnings.join(", ")}` : ""}
674
+ Script Analysis:
675
+ ${ops.length > 0 ? `Data Operations Detected: ${ops.join(", ")}` : ""}
676
+ ${access.length > 0 ? `System Access: ${access.join(", ")}` : ""}
677
+ ${warns.length > 0 ? `Warnings: ${warns.join(", ")}` : ""}
533
678
 
534
- 📜 **Script to Execute:**
679
+ Script to Execute:
535
680
  \`\`\`javascript
536
- ${script}
681
+ ${ctx.script}
537
682
  \`\`\`
538
683
 
539
- ⚡ **Impact:** This script will run in ServiceNow's server-side JavaScript context with full API access.
684
+ Impact: This script will run in ServiceNow's server-side JavaScript context with full API access.
540
685
 
541
- ❓ **Do you want to proceed with executing this script?**
686
+ Do you want to proceed with executing this script?
542
687
 
543
688
  Reply with:
544
- - ✅ **YES** - Execute the script
545
- - ❌ **NO** - Cancel execution
546
- - 📝 **MODIFY** - Make changes before execution
689
+ - YES - Execute the script
690
+ - NO - Cancel execution
691
+ - MODIFY - Make changes before execution
547
692
 
548
- ⚠️ Only proceed if you understand what this script does and trust its source!
693
+ Only proceed if you understand what this script does and trust its source!
549
694
  `.trim()
550
695
  }
551
696
 
552
- export const version = "2.0.0"
697
+ export const version = "3.0.0"
553
698
  export const author = "Snow-Flow SDK"