nowaikit 4.12.1 → 4.15.0

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 (51) hide show
  1. package/README.md +12 -5
  2. package/dist/cli/auth.d.ts +13 -0
  3. package/dist/cli/auth.d.ts.map +1 -1
  4. package/dist/cli/auth.js +237 -50
  5. package/dist/cli/auth.js.map +1 -1
  6. package/dist/cli/detect-clients.d.ts.map +1 -1
  7. package/dist/cli/detect-clients.js +78 -2
  8. package/dist/cli/detect-clients.js.map +1 -1
  9. package/dist/cli/setup.d.ts.map +1 -1
  10. package/dist/cli/setup.js +218 -9
  11. package/dist/cli/setup.js.map +1 -1
  12. package/dist/servicenow/client.d.ts +49 -1
  13. package/dist/servicenow/client.d.ts.map +1 -1
  14. package/dist/servicenow/client.js +346 -40
  15. package/dist/servicenow/client.js.map +1 -1
  16. package/dist/servicenow/instances.d.ts.map +1 -1
  17. package/dist/servicenow/instances.js +56 -4
  18. package/dist/servicenow/instances.js.map +1 -1
  19. package/dist/servicenow/types.d.ts +17 -0
  20. package/dist/servicenow/types.d.ts.map +1 -1
  21. package/dist/tools/app-studio.d.ts.map +1 -1
  22. package/dist/tools/app-studio.js +80 -9
  23. package/dist/tools/app-studio.js.map +1 -1
  24. package/dist/tools/change.d.ts.map +1 -1
  25. package/dist/tools/change.js +3 -1
  26. package/dist/tools/change.js.map +1 -1
  27. package/dist/tools/core.d.ts.map +1 -1
  28. package/dist/tools/core.js +28 -2
  29. package/dist/tools/core.js.map +1 -1
  30. package/dist/tools/incident.d.ts.map +1 -1
  31. package/dist/tools/incident.js +5 -3
  32. package/dist/tools/incident.js.map +1 -1
  33. package/dist/tools/index.d.ts +159 -0
  34. package/dist/tools/index.d.ts.map +1 -1
  35. package/dist/tools/index.js +2 -2
  36. package/dist/tools/index.js.map +1 -1
  37. package/dist/tools/knowledge.d.ts.map +1 -1
  38. package/dist/tools/knowledge.js +12 -1
  39. package/dist/tools/knowledge.js.map +1 -1
  40. package/dist/tools/notification.d.ts +169 -0
  41. package/dist/tools/notification.d.ts.map +1 -1
  42. package/dist/tools/notification.js +88 -9
  43. package/dist/tools/notification.js.map +1 -1
  44. package/dist/tools/problem.d.ts.map +1 -1
  45. package/dist/tools/problem.js +3 -1
  46. package/dist/tools/problem.js.map +1 -1
  47. package/dist/tools/updateset.d.ts.map +1 -1
  48. package/dist/tools/updateset.js +62 -38
  49. package/dist/tools/updateset.js.map +1 -1
  50. package/dist/tools-manifest.json +82 -11
  51. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/servicenow/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EAGpB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAKpB;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAc5C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAoBzE;AAuBD,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAmBnD;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,WAAW,CAAC,CAA4B;IAChD,OAAO,CAAC,WAAW,CAAC,CAA4B;IAChD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,gBAAgB,CAAS;IAEjC,sEAAsE;IACtE,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,yEAAyE;IACzE,OAAO,CAAC,kBAAkB,CAAC,CAAS;IAEpC,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAS;gBAEjB,MAAM,EAAE,gBAAgB;IAapC;;;OAGG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,gBAAgB;IAgCnG;;OAEG;YACW,YAAY;IA0E1B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IA4BrB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAO5B;;OAEG;YACW,OAAO;IAuHrB;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAuE7E;;;;;OAKG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAO3H;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAwCrD;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4BzF;;OAEG;IACG,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoChE;;OAEG;IACG,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmClE;;OAEG;IACG,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA+BrF;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI5E;;OAEG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IA0BtD;;OAEG;IACG,sBAAsB,CAAC,UAAU,GAAE,OAAe,GAAG,OAAO,CAAC,GAAG,CAAC;IA0BvE;;OAEG;IACG,cAAc,CAAC,UAAU,GAAE,OAAe,GAAG,OAAO,CAAC,GAAG,CAAC;IA0B/D;;OAEG;IACG,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA+BzF;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,GAAG,CAAC;IAmDzC;;OAEG;IACG,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IA8B/D;;OAEG;IACG,mBAAmB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAyBjE;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8BvF;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwCtG;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC/D;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAmBjF;;;;OAIG;IACG,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAOxD;;;;OAIG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAQnE;;;;;OAKG;IACG,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAC1K,OAAO,CAAC,GAAG,EAAE,CAAC;IAmBjB;;OAEG;IACG,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAE,MAAgB,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAmBnH;;OAEG;IACG,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,GAAG,CAAC;IAc5E;;;OAGG;IACG,gBAAgB,CACpB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,GAAG,CAAC;IA+Cf;;;OAGG;IACG,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IA2D5G;;;OAGG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAsDjE;;OAEG;IACG,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;CAUhF"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/servicenow/client.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,30 @@ export function forbiddenDiagnostic(authMethod) {
43
43
  return lines.join('\n');
44
44
  }
45
45
  // ─── Input validation helpers ────────────────────────────────────────────────
46
+ /** Best-effort MIME type from a file extension, for attachments fetched without a content type. */
47
+ function guessContentType(fileName) {
48
+ const ext = (fileName.split('.').pop() || '').toLowerCase();
49
+ const map = {
50
+ pdf: 'application/pdf',
51
+ doc: 'application/msword',
52
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
53
+ xls: 'application/vnd.ms-excel',
54
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
55
+ ppt: 'application/vnd.ms-powerpoint',
56
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
57
+ csv: 'text/csv',
58
+ txt: 'text/plain',
59
+ json: 'application/json',
60
+ xml: 'application/xml',
61
+ html: 'text/html',
62
+ png: 'image/png',
63
+ jpg: 'image/jpeg',
64
+ jpeg: 'image/jpeg',
65
+ gif: 'image/gif',
66
+ zip: 'application/zip',
67
+ };
68
+ return map[ext] || 'application/octet-stream';
69
+ }
46
70
  /** Validate and sanitize ServiceNow table names (alphanumeric + underscores only) */
47
71
  function validateTableName(table) {
48
72
  if (!table || !/^[a-zA-Z][a-zA-Z0-9_]*$/.test(table)) {
@@ -91,6 +115,10 @@ export class ServiceNowClient {
91
115
  impersonateUserSysId;
92
116
  /** For per-user mode: pre-loaded token overrides service-account auth */
93
117
  perUserBearerToken;
118
+ /** Local per-user OAuth only: refresh token + expiry + persist callback (unset for the gateway). */
119
+ perUserRefreshToken;
120
+ perUserTokenExpiry;
121
+ onTokenRefreshed;
94
122
  accessToken;
95
123
  tokenExpiry;
96
124
  constructor(config) {
@@ -104,6 +132,49 @@ export class ServiceNowClient {
104
132
  this.requestTimeoutMs = config.requestTimeoutMs || 30000;
105
133
  this.impersonateUserSysId = config.impersonateUserSysId;
106
134
  this.perUserBearerToken = config.perUserBearerToken;
135
+ this.perUserRefreshToken = config.perUserRefreshToken;
136
+ this.perUserTokenExpiry = config.perUserTokenExpiry;
137
+ this.onTokenRefreshed = config.onTokenRefreshed;
138
+ }
139
+ /**
140
+ * True only for LOCAL per-user OAuth that owns a refresh token + client id (i.e. can renew its
141
+ * own token). The multi-tenant gateway's per-user mode has no refresh token, so this is false
142
+ * there and the injected token is used as-is — the gateway path is unchanged.
143
+ */
144
+ hasPerUserRefresh() {
145
+ return this.authMode === 'per-user' && !!this.perUserRefreshToken && !!this.oauthConfig?.clientId;
146
+ }
147
+ /** Renew the per-user access token using the stored refresh token (refresh_token grant). */
148
+ async refreshPerUserToken() {
149
+ if (!this.perUserRefreshToken || !this.oauthConfig?.clientId) {
150
+ throw new ServiceNowError('Cannot refresh: no refresh token or OAuth client configured', 'AUTHENTICATION_FAILED');
151
+ }
152
+ const body = new URLSearchParams({
153
+ grant_type: 'refresh_token',
154
+ client_id: this.oauthConfig.clientId,
155
+ refresh_token: this.perUserRefreshToken,
156
+ });
157
+ // Confidential clients send the secret; public (PKCE) clients omit it.
158
+ if (this.oauthConfig.clientSecret)
159
+ body.set('client_secret', this.oauthConfig.clientSecret);
160
+ const resp = await fetch(`${this.baseUrl}/oauth_token.do`, {
161
+ method: 'POST',
162
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
163
+ body: body.toString(),
164
+ });
165
+ if (!resp.ok) {
166
+ throw new ServiceNowError(`OAuth token refresh failed: ${resp.status} ${resp.statusText}`, 'AUTHENTICATION_FAILED');
167
+ }
168
+ const data = await resp.json();
169
+ this.perUserBearerToken = data.access_token;
170
+ if (data.refresh_token)
171
+ this.perUserRefreshToken = data.refresh_token; // honour rotation
172
+ const expiresAt = Date.now() + (data.expires_in ? data.expires_in * 1000 * 0.9 : 25 * 60 * 1000);
173
+ this.perUserTokenExpiry = expiresAt;
174
+ try {
175
+ this.onTokenRefreshed?.({ accessToken: this.perUserBearerToken, refreshToken: this.perUserRefreshToken, expiresAt });
176
+ }
177
+ catch { /* persistence is best-effort; never fail the request over it */ }
107
178
  }
108
179
  /**
109
180
  * The ServiceNow instance host this client targets (e.g. "acme.service-now.com").
@@ -117,6 +188,56 @@ export class ServiceNowClient {
117
188
  return this.baseUrl.toLowerCase();
118
189
  }
119
190
  }
191
+ /** Public accessor for the resolved instance base URL (no trailing slash), for building UI links. */
192
+ get instanceUrl() {
193
+ return this.baseUrl;
194
+ }
195
+ /** Human-facing record URL (opens the form in ServiceNow), not the /api/now REST link. */
196
+ recordUrl(table, sysId) {
197
+ return `${this.baseUrl}/${encodeURIComponent(table)}.do?sys_id=${encodeURIComponent(sysId)}`;
198
+ }
199
+ /**
200
+ * Return the fields that ServiceNow Data Policies make mandatory on a table, so an agent can
201
+ * collect them BEFORE a create/update and avoid the "Data Policy Exception: mandatory fields"
202
+ * error. Reads sys_data_policy_rule (the mandatory rules) and sys_data_policy2 (the condition
203
+ * under which each applies). All server-side, read-only.
204
+ */
205
+ async getMandatoryFields(table) {
206
+ const t = validateTableName(table);
207
+ const rules = await this.queryRecords({
208
+ table: 'sys_data_policy_rule',
209
+ query: `table=${t}^mandatory=true`,
210
+ fields: 'field,sys_data_policy',
211
+ limit: 100,
212
+ });
213
+ const rows = rules.records ?? rules ?? [];
214
+ // Resolve the parent policy conditions once.
215
+ const policyIds = Array.from(new Set(rows.map((r) => (typeof r.sys_data_policy === 'object' ? r.sys_data_policy?.value : r.sys_data_policy)).filter(Boolean)));
216
+ const condByPolicy = new Map();
217
+ if (policyIds.length) {
218
+ const pol = await this.queryRecords({
219
+ table: 'sys_data_policy2',
220
+ query: `sys_idIN${policyIds.join(',')}`,
221
+ fields: 'sys_id,short_description,conditions,active',
222
+ limit: 100,
223
+ });
224
+ for (const p of (pol.records ?? pol ?? [])) {
225
+ condByPolicy.set(p.sys_id, { desc: p.short_description, conditions: p.conditions, active: p.active });
226
+ }
227
+ }
228
+ const fields = rows.map((r) => {
229
+ const pid = typeof r.sys_data_policy === 'object' ? r.sys_data_policy?.value : r.sys_data_policy;
230
+ const meta = condByPolicy.get(pid) || {};
231
+ return { field: r.field, applies_when: meta.conditions || 'always', policy: meta.desc, active: meta.active };
232
+ }).filter((f) => f.active !== 'false');
233
+ return {
234
+ table: t,
235
+ mandatory_fields: fields,
236
+ summary: fields.length
237
+ ? `On ${t}, these fields are made mandatory by data policy: ${fields.map((f) => f.field).join(', ')}. Collect them before creating or updating.`
238
+ : `No data-policy mandatory fields found on ${t} (dictionary-level mandatory fields may still apply).`,
239
+ };
240
+ }
120
241
  /**
121
242
  * Return a copy of this client configured to run as a specific user.
122
243
  * Used for per-request user context switching without mutating the shared client.
@@ -154,6 +275,11 @@ export class ServiceNowClient {
154
275
  if (options.bearerToken) {
155
276
  copy.authMode = 'per-user';
156
277
  copy.perUserBearerToken = options.bearerToken;
278
+ // A per-request injected token (the gateway path) is not ours to refresh — never carry over
279
+ // any refresh state from the base client. Keeps hasPerUserRefresh() false for the gateway.
280
+ copy.perUserRefreshToken = undefined;
281
+ copy.perUserTokenExpiry = undefined;
282
+ copy.onTokenRefreshed = undefined;
157
283
  }
158
284
  return copy;
159
285
  }
@@ -164,6 +290,11 @@ export class ServiceNowClient {
164
290
  // Per-user mode carries the user's own bearer token (delegated auth) —
165
291
  // no service-account token acquisition needed.
166
292
  if (this.authMode === 'per-user' && this.perUserBearerToken) {
293
+ // Local stored-session OAuth: proactively refresh a token that is expired or within 60s of it.
294
+ // The gateway path has no refresh token (hasPerUserRefresh() === false), so it is untouched.
295
+ if (this.hasPerUserRefresh() && this.perUserTokenExpiry && Date.now() >= this.perUserTokenExpiry - 60_000) {
296
+ await this.refreshPerUserToken();
297
+ }
167
298
  return;
168
299
  }
169
300
  if (this.authMethod === 'basic') {
@@ -308,6 +439,17 @@ export class ServiceNowClient {
308
439
  catch { /* fall through to the throw below */ }
309
440
  continue;
310
441
  }
442
+ // Local per-user OAuth that owns a refresh token: renew once and retry. The multi-tenant
443
+ // gateway's injected-token per-user mode has NO refresh token, so hasPerUserRefresh() is
444
+ // false there and this is skipped — the live Copilot path is unchanged.
445
+ if (this.hasPerUserRefresh() && !reauthed) {
446
+ reauthed = true;
447
+ try {
448
+ await this.refreshPerUserToken();
449
+ }
450
+ catch { /* fall through to the throw below */ }
451
+ continue;
452
+ }
311
453
  // Attach the actionable checklist so a bare 401 isn't a dead end.
312
454
  if (this.authMethod === 'basic') {
313
455
  errorMessage = `${errorMessage}\n\n${basicAuthDiagnostic()}`;
@@ -375,6 +517,11 @@ export class ServiceNowClient {
375
517
  if (params.fields) {
376
518
  queryParams.set('sysparm_fields', params.fields);
377
519
  }
520
+ // Return reference fields as readable names, not raw sys_ids. 'all' keeps the sys_id too
521
+ // (as {display_value, value}) so the agent can still act on the record.
522
+ if (params.displayValue) {
523
+ queryParams.set('sysparm_display_value', params.displayValue === 'all' ? 'all' : 'true');
524
+ }
378
525
  if (params.limit !== undefined && params.limit > 0) {
379
526
  queryParams.set('sysparm_limit', Math.min(params.limit, 1000).toString());
380
527
  }
@@ -465,7 +612,7 @@ export class ServiceNowClient {
465
612
  /**
466
613
  * Get a single record by sys_id
467
614
  */
468
- async getRecord(table, sysId, fields) {
615
+ async getRecord(table, sysId, fields, displayValue) {
469
616
  validateTableName(table);
470
617
  validateSysId(sysId);
471
618
  await this.authenticate();
@@ -473,6 +620,9 @@ export class ServiceNowClient {
473
620
  if (fields) {
474
621
  queryParams.set('sysparm_fields', fields);
475
622
  }
623
+ if (displayValue) {
624
+ queryParams.set('sysparm_display_value', displayValue === 'all' ? 'all' : 'true');
625
+ }
476
626
  const url = `${this.baseUrl}/api/now/table/${table}/${sysId}${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
477
627
  logger.info(`Getting record from ${table}: ${sysId}`);
478
628
  try {
@@ -983,12 +1133,73 @@ export class ServiceNowClient {
983
1133
  * Accepts base64-encoded content and uploads it as a multipart form.
984
1134
  */
985
1135
  async uploadAttachment(table, recordSysId, fileName, contentType, contentBase64) {
1136
+ // Decode base64 to binary, then hand off to the shared binary uploader.
1137
+ const binary = Buffer.from(contentBase64, 'base64');
1138
+ return this.uploadAttachmentBuffer(table, recordSysId, fileName, contentType, binary);
1139
+ }
1140
+ /**
1141
+ * Attach a file to a record by fetching its bytes server-side from a URL, so the file content
1142
+ * never has to travel through the LLM/tool call as a base64 argument. This is the reliable path
1143
+ * for anything larger than a few hundred KB. `sourceHeaders` lets the caller pass auth (e.g. a
1144
+ * bearer/basic credential) for any protected URL. Content type is taken from the caller, then
1145
+ * the response's Content-Type, then guessed from the file extension.
1146
+ */
1147
+ async uploadAttachmentFromUrl(table, recordSysId, fileName, sourceUrl, contentType, sourceHeaders) {
1148
+ const MAX_FETCH_BYTES = 50 * 1024 * 1024; // 50MB safety cap on server-side fetch
1149
+ let src;
1150
+ try {
1151
+ src = new URL(sourceUrl);
1152
+ }
1153
+ catch {
1154
+ throw new ServiceNowError(`source_url is not a valid URL: ${sourceUrl}`, 'INVALID_REQUEST');
1155
+ }
1156
+ if (src.protocol !== 'https:' && src.protocol !== 'http:') {
1157
+ throw new ServiceNowError(`source_url must be http(s), got ${src.protocol}`, 'INVALID_REQUEST');
1158
+ }
1159
+ logger.info(`Fetching attachment "${fileName}" from source URL for ${table}:${recordSysId}`);
1160
+ let fetched;
1161
+ try {
1162
+ fetched = await fetch(src, { headers: sourceHeaders ?? {} });
1163
+ }
1164
+ catch (error) {
1165
+ throw new ServiceNowError(`Failed to fetch source_url: ${error instanceof Error ? error.message : 'Unknown error'}`, 'ATTACHMENT_SOURCE_FETCH_FAILED');
1166
+ }
1167
+ if (!fetched.ok) {
1168
+ throw new ServiceNowError(`source_url returned HTTP ${fetched.status} ${fetched.statusText}`, 'ATTACHMENT_SOURCE_FETCH_FAILED');
1169
+ }
1170
+ const arrayBuf = await fetched.arrayBuffer();
1171
+ const binary = Buffer.from(arrayBuf);
1172
+ if (binary.length > MAX_FETCH_BYTES) {
1173
+ throw new ServiceNowError(`Fetched file is ${(binary.length / 1024 / 1024).toFixed(1)}MB, over the ${MAX_FETCH_BYTES / 1024 / 1024}MB limit`, 'ATTACHMENT_TOO_LARGE');
1174
+ }
1175
+ const responseType = fetched.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() || '';
1176
+ const resolvedType = contentType || responseType || guessContentType(fileName);
1177
+ // Phantom-attachment guard: a protected/expired link (e.g. SharePoint) often returns a tiny
1178
+ // HTML/JSON error page with HTTP 200. Uploading that as "document.pdf" creates an empty, broken
1179
+ // attachment that looks successful. Reject the obvious cases instead of attaching junk.
1180
+ const expectedType = (contentType || guessContentType(fileName)).toLowerCase();
1181
+ const expectsBinary = !expectedType.startsWith('text/') && !/json|xml|csv|html/.test(expectedType);
1182
+ const looksLikeErrorPage = /text\/html|application\/json|application\/xml/.test(responseType);
1183
+ const head = binary.subarray(0, 5).toString('latin1');
1184
+ const magicOk = !expectedType.includes('pdf') ? true : head.startsWith('%PDF'); // PDFs must start with %PDF
1185
+ if (binary.length < 100 && expectsBinary) {
1186
+ throw new ServiceNowError(`source_url returned only ${binary.length} bytes for "${fileName}" — this is almost certainly an error/redirect page, not the file. ` +
1187
+ 'The link is likely protected or expired (for SharePoint, use the pre-authorised @microsoft.graph.downloadUrl). Nothing was attached.', 'ATTACHMENT_SOURCE_NOT_A_FILE');
1188
+ }
1189
+ if (expectsBinary && looksLikeErrorPage) {
1190
+ throw new ServiceNowError(`source_url returned ${responseType} but "${fileName}" was expected to be a binary file — this looks like an error page, not the document. Nothing was attached.`, 'ATTACHMENT_SOURCE_NOT_A_FILE');
1191
+ }
1192
+ if (!magicOk) {
1193
+ throw new ServiceNowError(`source_url content is not a valid PDF (missing %PDF header) for "${fileName}". The link likely returned an error page. Nothing was attached.`, 'ATTACHMENT_SOURCE_NOT_A_FILE');
1194
+ }
1195
+ return this.uploadAttachmentBuffer(table, recordSysId, fileName, resolvedType, binary);
1196
+ }
1197
+ /** Shared core: POST raw bytes to ServiceNow's native binary attachment endpoint. */
1198
+ async uploadAttachmentBuffer(table, recordSysId, fileName, contentType, binary) {
986
1199
  await this.authenticate();
987
1200
  const url = `${this.baseUrl}/api/now/attachment/file?table_name=${encodeURIComponent(table)}&table_sys_id=${encodeURIComponent(recordSysId)}&file_name=${encodeURIComponent(fileName)}`;
988
- logger.info(`Uploading attachment "${fileName}" to ${table}:${recordSysId}`);
1201
+ logger.info(`Uploading attachment "${fileName}" (${binary.length} bytes) to ${table}:${recordSysId}`);
989
1202
  try {
990
- // Decode base64 to binary
991
- const binary = Buffer.from(contentBase64, 'base64');
992
1203
  const response = await fetch(url, {
993
1204
  method: 'POST',
994
1205
  headers: {
@@ -1023,6 +1234,76 @@ export class ServiceNowClient {
1023
1234
  throw new ServiceNowError(`Failed to upload attachment: ${error instanceof Error ? error.message : 'Unknown error'}`, 'ATTACHMENT_UPLOAD_FAILED');
1024
1235
  }
1025
1236
  }
1237
+ /** Fetch an existing attachment's metadata + raw bytes, server-side (no LLM involvement). */
1238
+ async fetchAttachment(attachmentSysId) {
1239
+ await this.authenticate();
1240
+ const id = validateSysId(attachmentSysId);
1241
+ // Metadata (file_name, content_type, size) via the attachment record.
1242
+ const metaRes = await fetch(`${this.baseUrl}/api/now/attachment/${encodeURIComponent(id)}`, {
1243
+ headers: { Authorization: this.getAuthHeader(), Accept: 'application/json' },
1244
+ });
1245
+ if (!metaRes.ok) {
1246
+ throw new ServiceNowError(metaRes.status === 404 ? `Attachment not found: ${id}` : `HTTP ${metaRes.status}: ${metaRes.statusText}`, metaRes.status === 404 ? 'NOT_FOUND' : 'ATTACHMENT_READ_FAILED');
1247
+ }
1248
+ const meta = (await metaRes.json()).result ?? {};
1249
+ // Raw bytes via the binary file endpoint.
1250
+ const fileRes = await fetch(`${this.baseUrl}/api/now/attachment/${encodeURIComponent(id)}/file`, {
1251
+ headers: { Authorization: this.getAuthHeader() },
1252
+ });
1253
+ if (!fileRes.ok) {
1254
+ throw new ServiceNowError(`HTTP ${fileRes.status}: ${fileRes.statusText}`, 'ATTACHMENT_READ_FAILED');
1255
+ }
1256
+ const bytes = Buffer.from(await fileRes.arrayBuffer());
1257
+ return { meta, bytes };
1258
+ }
1259
+ /**
1260
+ * Copy an existing ServiceNow attachment onto another record, entirely server-side. The bytes
1261
+ * are fetched from the source attachment and re-posted to the target, so nothing passes through
1262
+ * the LLM/tool call. Great for "attach the same document from the RITM onto the KB".
1263
+ */
1264
+ async copyAttachment(attachmentSysId, targetTable, targetRecordSysId, newFileName) {
1265
+ const { meta, bytes } = await this.fetchAttachment(attachmentSysId);
1266
+ const fileName = newFileName || meta.file_name || `attachment-${attachmentSysId}`;
1267
+ const contentType = meta.content_type || guessContentType(fileName);
1268
+ logger.info(`Copying attachment ${attachmentSysId} (${bytes.length} bytes) to ${targetTable}:${targetRecordSysId}`);
1269
+ return this.uploadAttachmentBuffer(targetTable, targetRecordSysId, fileName, contentType, bytes);
1270
+ }
1271
+ /**
1272
+ * Read an existing ServiceNow attachment and return its text content when the file is text-based
1273
+ * (txt/csv/json/xml/html/md). For binary formats (PDF/DOCX/images) it returns metadata plus a note
1274
+ * rather than dumping bytes, since those need a dedicated extractor. All server-side.
1275
+ */
1276
+ async readAttachment(attachmentSysId, maxChars = 200_000) {
1277
+ const { meta, bytes } = await this.fetchAttachment(attachmentSysId);
1278
+ const ct = String(meta.content_type || guessContentType(meta.file_name || '')).toLowerCase();
1279
+ const name = String(meta.file_name || '').toLowerCase();
1280
+ const isTextual = ct.startsWith('text/') ||
1281
+ /json|xml|csv|html|yaml|markdown|javascript|x-www-form-urlencoded/.test(ct) ||
1282
+ /\.(txt|csv|json|xml|html?|md|log|yaml|yml|tsv)$/.test(name);
1283
+ const base = {
1284
+ attachment_sys_id: attachmentSysId,
1285
+ file_name: meta.file_name,
1286
+ content_type: meta.content_type,
1287
+ size_bytes: bytes.length,
1288
+ };
1289
+ if (isTextual) {
1290
+ const text = bytes.toString('utf8');
1291
+ const truncated = text.length > maxChars;
1292
+ return {
1293
+ ...base,
1294
+ text: truncated ? text.slice(0, maxChars) : text,
1295
+ truncated,
1296
+ summary: `Read ${bytes.length} bytes of text from "${meta.file_name}"${truncated ? ` (truncated to ${maxChars} chars)` : ''}`,
1297
+ };
1298
+ }
1299
+ return {
1300
+ ...base,
1301
+ text: null,
1302
+ extractable: false,
1303
+ note: `"${meta.file_name}" is a binary format (${meta.content_type}) that needs a dedicated text extractor. ` +
1304
+ 'Use copy_attachment to move it between records, or process the file where it was uploaded.',
1305
+ };
1306
+ }
1026
1307
  /**
1027
1308
  * Execute multiple REST API operations in a single HTTP call (Batch API).
1028
1309
  * Uses /api/now/v1/batch endpoint. Up to 50 operations per batch.
@@ -1085,50 +1366,75 @@ export class ServiceNowClient {
1085
1366
  */
1086
1367
  async executeScript(script, scope) {
1087
1368
  await this.authenticate();
1088
- logger.info('Executing server-side script');
1089
- try {
1090
- // Use the standard script execution endpoint
1091
- const response = await this.request(`${this.baseUrl}/api/now/v1/batch`, {
1369
+ // ServiceNow has NO supported REST endpoint for running arbitrary background scripts. The only
1370
+ // reliable server-side path is an optional helper the customer installs and reviews: a scoped,
1371
+ // secured Scripted REST API. Point us at it with SCRIPT_EXEC_ENDPOINT (e.g. "/api/x_nowaikit/exec")
1372
+ // and this executes through it; otherwise we fail with a clear, actionable message rather than
1373
+ // silently hitting a non-existent endpoint (the previous implementation targeted a bogus
1374
+ // sys_script_execution table wrapped in a malformed Batch request and never worked).
1375
+ // 1) Optional customer-installed helper (a scoped, secured Scripted REST API) if configured.
1376
+ const endpoint = process.env.SCRIPT_EXEC_ENDPOINT;
1377
+ if (endpoint) {
1378
+ logger.info('Executing server-side script via SCRIPT_EXEC_ENDPOINT');
1379
+ const url = `${this.baseUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;
1380
+ const response = await this.request(url, {
1092
1381
  method: 'POST',
1093
- body: JSON.stringify({
1094
- batch_request_id: `script_${Date.now()}`,
1095
- rest_requests: [{
1096
- id: 'script_exec',
1097
- method: 'POST',
1098
- url: '/api/now/table/sys_script_execution',
1099
- headers: [
1100
- { name: 'Content-Type', value: 'application/json' },
1101
- { name: 'Accept', value: 'application/json' },
1102
- ],
1103
- body: JSON.stringify({
1104
- script,
1105
- scope: scope || 'global',
1106
- }),
1107
- }],
1108
- }),
1382
+ body: JSON.stringify({ script, scope: scope || 'global' }),
1383
+ });
1384
+ return { status: 200, output: (response && response.result !== undefined) ? response.result : response, scope: scope || 'global' };
1385
+ }
1386
+ // 2) Default (no install): run via a one-time, self-terminating scheduled job. ServiceNow has no
1387
+ // REST endpoint to run background scripts, but the SCHEDULER is a separate path — and the job record
1388
+ // is created with the Table API, which works. The wrapper captures the result to a temp property,
1389
+ // then deactivates and deletes the job.
1390
+ return await this.executeViaScheduledJob(script);
1391
+ }
1392
+ async executeViaScheduledJob(script) {
1393
+ const runId = `nwk_${Date.now().toString(36)}_${Math.floor(Math.random() * 1e9).toString(36)}`;
1394
+ const jobName = `NowAIKit exec ${runId}`;
1395
+ const prop = `nowaikit.exec.${runId}`;
1396
+ const wrapper = `var __r, __e = null;\n` +
1397
+ `try { __r = (function(){\n${script}\n})(); } catch (e) { __e = '' + e; }\n` +
1398
+ `try { gs.setProperty(${JSON.stringify(prop)}, JSON.stringify({ ok: __e === null, result: (__r === undefined ? null : __r), error: __e })); } catch (ig) {}\n` +
1399
+ `try { var __j = new GlideRecord('sysauto_script'); if (__j.get('name', ${JSON.stringify(jobName)})) { __j.active = false; __j.deleteRecord(); } } catch (ig) {}\n`;
1400
+ const nowGmt = new Date().toISOString().replace('T', ' ').slice(0, 19);
1401
+ let jobSysId;
1402
+ try {
1403
+ const job = await this.createRecord('sysauto_script', {
1404
+ name: jobName, script: wrapper, active: 'true', run_type: 'once', run_start: nowGmt, next_action: nowGmt,
1109
1405
  });
1110
- const results = response.serviced_requests || [];
1111
- if (results.length > 0) {
1112
- let body;
1406
+ jobSysId = job?.sys_id;
1407
+ }
1408
+ catch (e) {
1409
+ throw new ServiceNowError(`Server-side script execution needs to create a one-time scheduled job (sysauto_script), but that ` +
1410
+ `write was rejected: ${e instanceof Error ? e.message : String(e)}. ` +
1411
+ `Alternatives: use the Table API tools for data changes, run it in ServiceNow (Scripts - Background), ` +
1412
+ `or install the optional script-exec helper and set SCRIPT_EXEC_ENDPOINT.`, 'SCRIPT_EXEC_UNAVAILABLE');
1413
+ }
1414
+ // Poll for the result property (scheduler latency is typically a few to ~60s).
1415
+ const deadline = Date.now() + 90_000;
1416
+ while (Date.now() < deadline) {
1417
+ await new Promise((r) => setTimeout(r, 3000));
1418
+ const res = await this.queryRecords({ table: 'sys_properties', query: `name=${prop}`, fields: 'sys_id,value', limit: 1 });
1419
+ if (res.count > 0) {
1420
+ const rec = res.records[0];
1421
+ try {
1422
+ await this.deleteRecord('sys_properties', rec.sys_id);
1423
+ }
1424
+ catch { /* leave it */ }
1425
+ let parsed;
1113
1426
  try {
1114
- body = typeof results[0].body === 'string' ? JSON.parse(results[0].body) : results[0].body;
1427
+ parsed = JSON.parse(rec.value);
1115
1428
  }
1116
1429
  catch {
1117
- body = results[0].body;
1430
+ parsed = { ok: true, result: rec.value };
1118
1431
  }
1119
- return {
1120
- status: results[0].status_code,
1121
- output: body,
1122
- scope: scope || 'global',
1123
- };
1432
+ return { status: 200, ...parsed, via: 'scheduled-job' };
1124
1433
  }
1125
- return { status: 200, output: 'Script executed (no output captured)', scope: scope || 'global' };
1126
- }
1127
- catch (error) {
1128
- if (error instanceof ServiceNowError)
1129
- throw error;
1130
- throw new ServiceNowError(`Script execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'SCRIPT_FAILED');
1131
1434
  }
1435
+ throw new ServiceNowError(`Script was scheduled (sysauto_script "${jobName}"${jobSysId ? ' ' + jobSysId : ''}) but did not report a ` +
1436
+ `result within 90s — the scheduler may be backed up or the script was blocked by instance hardening. ` +
1437
+ `Check the job and its output on the instance.`, 'SCRIPT_EXEC_TIMEOUT');
1132
1438
  }
1133
1439
  /**
1134
1440
  * Natural language update (simplified implementation)