@tenonhq/dovetail-servicenow 0.0.21 → 0.0.23

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.
@@ -26,6 +26,7 @@ const createFlow_1 = require("../flowDesigner/createFlow");
26
26
  const editFlow_1 = require("../flowDesigner/editFlow");
27
27
  const testFlow_1 = require("../flowDesigner/testFlow");
28
28
  const table_1 = require("../table");
29
+ const hostAssets_1 = require("../hostAssets");
29
30
  const schemas_1 = require("./schemas");
30
31
  exports.TOOL_NAMES = [
31
32
  "create_view",
@@ -40,7 +41,9 @@ exports.TOOL_NAMES = [
40
41
  "flow_create",
41
42
  "flow_test",
42
43
  "flow_edit",
43
- "create_table"
44
+ "create_table",
45
+ "add_column",
46
+ "host_assets"
44
47
  ];
45
48
  // Annotation presets (READ_ONLY / WRITE_ADDITIVE_IDEMPOTENT / WRITE_CREATE /
46
49
  // WRITE_OVERWRITE / WRITE_EXECUTE) come from @tenonhq/dovetail-mcp-kit.
@@ -263,6 +266,53 @@ function buildDescriptors(deps = {}) {
263
266
  dryRun: p.dryRun
264
267
  });
265
268
  }
269
+ },
270
+ {
271
+ name: "add_column",
272
+ annotations: dovetail_mcp_kit_1.WRITE_CREATE,
273
+ description: "Add ONE column to an EXISTING ServiceNow table, headless and faithfully. Creating a column is a "
274
+ + "sys_dictionary insert; a REST/createRecord insert reliably 500s for a scoped-app column. This "
275
+ + "replays the Studio table-form save (POST /sys_db_object.do) against the existing table record, "
276
+ + "embedding the new column as list-edit XML, then READS THE COLUMN BACK from sys_dictionary to prove "
277
+ + "it landed (a 302 that did not create the field is reported failed, not created). table is the table "
278
+ + "name or its sys_db_object sys_id; column is { label, type, name?, max_length?, reference? } with "
279
+ + "friendly types mapped to internal types (string -> string_full_utf8); element is derived from label "
280
+ + "unless column.name is given. dryRun:true returns the plan + column XML with no session and no writes. "
281
+ + "NOTE: the live write path is pending a validated-live spike — prefer dryRun until confirmed, and "
282
+ + "always verify the sys_update_xml landed in the intended update set.",
283
+ shape: schemas_1.addColumnSchema.shape,
284
+ handler: async function (args) {
285
+ var p = schemas_1.addColumnSchema.parse(args);
286
+ return (0, table_1.addColumn)({
287
+ client: client(),
288
+ table: p.table,
289
+ column: p.column,
290
+ scope: p.scope,
291
+ updateSetSysId: p.updateSetSysId,
292
+ saveActionSysId: p.saveActionSysId,
293
+ columnsRelId: p.columnsRelId,
294
+ dryRun: p.dryRun,
295
+ debug: p.debug
296
+ });
297
+ }
298
+ },
299
+ {
300
+ name: "host_assets",
301
+ annotations: dovetail_mcp_kit_1.WRITE_OVERWRITE,
302
+ description: "Deploy a pre-built front-end dist/ bundle to ServiceNow. For each chunk (index.html + "
303
+ + "assets/*.{js,css}) upserts a carrier sys_ui_script named app_shell_asset:<vite-relative-path> "
304
+ + "(the rotating hash is part of the name on purpose — the Scripted REST serving resource resolves "
305
+ + "an asset by this exact name), stores the chunk bytes as a sys_attachment (the script field caps "
306
+ + "at 65 KB), and wires an x_cadso_app_shell_m2m_app_script row (application, script, chunk_role, "
307
+ + "order). PRUNES carriers + m2m rows for chunks no longer in the build (hashes rotate per build). "
308
+ + "Idempotent — identical bytes (by SHA-256) are left in place. Fails fast on any chunk at/over the "
309
+ + "~5 MB serve cap (glide.scriptable.excel.max_file_size) unless allowOversize. app is the application "
310
+ + "record sys_id; dir is a local dist path on the server running this tool; script + m2m writes are "
311
+ + "captured in the update set; dryRun previews without writing.",
312
+ shape: schemas_1.hostAssetsSchema.shape,
313
+ handler: async function (args) {
314
+ return (0, hostAssets_1.hostAssets)(client(), schemas_1.hostAssetsSchema.parse(args));
315
+ }
266
316
  }
267
317
  ];
268
318
  }
@@ -382,6 +382,31 @@ export declare var editFlowSchema: z.ZodObject<{
382
382
  updateSetSysId?: string | undefined;
383
383
  apply?: boolean | undefined;
384
384
  }>;
385
+ export declare var hostAssetsSchema: z.ZodObject<{
386
+ dir: z.ZodString;
387
+ app: z.ZodString;
388
+ scope: z.ZodString;
389
+ updateSetSysId: z.ZodOptional<z.ZodString>;
390
+ maxBytes: z.ZodOptional<z.ZodNumber>;
391
+ allowOversize: z.ZodOptional<z.ZodBoolean>;
392
+ dryRun: z.ZodOptional<z.ZodBoolean>;
393
+ }, "strip", z.ZodTypeAny, {
394
+ dir: string;
395
+ scope: string;
396
+ app: string;
397
+ dryRun?: boolean | undefined;
398
+ updateSetSysId?: string | undefined;
399
+ maxBytes?: number | undefined;
400
+ allowOversize?: boolean | undefined;
401
+ }, {
402
+ dir: string;
403
+ scope: string;
404
+ app: string;
405
+ dryRun?: boolean | undefined;
406
+ updateSetSysId?: string | undefined;
407
+ maxBytes?: number | undefined;
408
+ allowOversize?: boolean | undefined;
409
+ }>;
385
410
  export declare var columnSpecSchema: z.ZodObject<{
386
411
  label: z.ZodString;
387
412
  type: z.ZodString;
@@ -480,3 +505,61 @@ export declare var createTableSchema: z.ZodObject<{
480
505
  saveActionSysId?: string | undefined;
481
506
  columnsRelId?: string | undefined;
482
507
  }>;
508
+ export declare var addColumnSchema: z.ZodObject<{
509
+ table: z.ZodString;
510
+ column: z.ZodObject<{
511
+ label: z.ZodString;
512
+ type: z.ZodString;
513
+ name: z.ZodOptional<z.ZodString>;
514
+ max_length: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
515
+ reference: z.ZodOptional<z.ZodString>;
516
+ }, "strip", z.ZodTypeAny, {
517
+ type: string;
518
+ label: string;
519
+ name?: string | undefined;
520
+ max_length?: string | number | undefined;
521
+ reference?: string | undefined;
522
+ }, {
523
+ type: string;
524
+ label: string;
525
+ name?: string | undefined;
526
+ max_length?: string | number | undefined;
527
+ reference?: string | undefined;
528
+ }>;
529
+ scope: z.ZodOptional<z.ZodString>;
530
+ updateSetSysId: z.ZodOptional<z.ZodString>;
531
+ saveActionSysId: z.ZodOptional<z.ZodString>;
532
+ columnsRelId: z.ZodOptional<z.ZodString>;
533
+ dryRun: z.ZodOptional<z.ZodBoolean>;
534
+ debug: z.ZodOptional<z.ZodBoolean>;
535
+ }, "strip", z.ZodTypeAny, {
536
+ table: string;
537
+ column: {
538
+ type: string;
539
+ label: string;
540
+ name?: string | undefined;
541
+ max_length?: string | number | undefined;
542
+ reference?: string | undefined;
543
+ };
544
+ dryRun?: boolean | undefined;
545
+ debug?: boolean | undefined;
546
+ scope?: string | undefined;
547
+ updateSetSysId?: string | undefined;
548
+ saveActionSysId?: string | undefined;
549
+ columnsRelId?: string | undefined;
550
+ }, {
551
+ table: string;
552
+ column: {
553
+ type: string;
554
+ label: string;
555
+ name?: string | undefined;
556
+ max_length?: string | number | undefined;
557
+ reference?: string | undefined;
558
+ };
559
+ dryRun?: boolean | undefined;
560
+ debug?: boolean | undefined;
561
+ scope?: string | undefined;
562
+ updateSetSysId?: string | undefined;
563
+ saveActionSysId?: string | undefined;
564
+ columnsRelId?: string | undefined;
565
+ }>;
@@ -4,7 +4,7 @@
4
4
  * own file so registry.ts stays focused on wiring.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.createTableSchema = exports.columnSpecSchema = exports.editFlowSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
7
+ exports.addColumnSchema = exports.createTableSchema = exports.columnSpecSchema = exports.hostAssetsSchema = exports.editFlowSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
8
8
  const zod_1 = require("zod");
9
9
  exports.createViewSchema = zod_1.z.object({
10
10
  name: zod_1.z.string().min(1),
@@ -113,6 +113,15 @@ exports.editFlowSchema = zod_1.z.object({
113
113
  scopeSysId: zod_1.z.string().optional(),
114
114
  updateSetSysId: zod_1.z.string().optional()
115
115
  });
116
+ exports.hostAssetsSchema = zod_1.z.object({
117
+ dir: zod_1.z.string().min(1),
118
+ app: zod_1.z.string().min(1),
119
+ scope: zod_1.z.string().min(1),
120
+ updateSetSysId: zod_1.z.string().min(1).optional(),
121
+ maxBytes: zod_1.z.number().int().positive().optional(),
122
+ allowOversize: zod_1.z.boolean().optional(),
123
+ dryRun: zod_1.z.boolean().optional()
124
+ });
116
125
  exports.columnSpecSchema = zod_1.z.object({
117
126
  label: zod_1.z.string().min(1),
118
127
  type: zod_1.z.string().min(1),
@@ -137,3 +146,13 @@ exports.createTableSchema = zod_1.z.object({
137
146
  dryRun: zod_1.z.boolean().optional(),
138
147
  debug: zod_1.z.boolean().optional()
139
148
  });
149
+ exports.addColumnSchema = zod_1.z.object({
150
+ table: zod_1.z.string().min(1),
151
+ column: exports.columnSpecSchema,
152
+ scope: zod_1.z.string().optional(),
153
+ updateSetSysId: zod_1.z.string().optional(),
154
+ saveActionSysId: zod_1.z.string().optional(),
155
+ columnsRelId: zod_1.z.string().optional(),
156
+ dryRun: zod_1.z.boolean().optional(),
157
+ debug: zod_1.z.boolean().optional()
158
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Add ONE column to an EXISTING ServiceNow table — headless, the faithful way.
3
+ * Creating a column is inserting a `sys_dictionary` record; a REST / Dovetail
4
+ * createRecord insert into sys_dictionary reliably 500s for a scoped-app column
5
+ * (scope-policy + the dictionary engine being wired to the form transaction, not
6
+ * the REST insert). The faithful path is the one the Studio UI drives: a single
7
+ * `POST /sys_db_object.do` against the EXISTING table record whose body embeds the
8
+ * new column as the same list-edit XML blob create-table uses.
9
+ *
10
+ * This REUSES the create-table machinery wholesale (buildColumnXml, formSession,
11
+ * the list-edit key + type normalization) and differs in exactly two ways:
12
+ * 1. It GETs the EXISTING table form (sys_id=<tableSysId>), which renders the
13
+ * "Columns" related list — so the real listEditKey is harvested, not faked.
14
+ * 2. It overlays ONLY the column XML + the save machinery, preserving every
15
+ * existing table field from the harvest (name/label/scope/ACLs untouched).
16
+ * Then it READS THE COLUMN BACK from sys_dictionary to prove it landed — a write
17
+ * that 302s but didn't create the field is reported as failed, never "created".
18
+ *
19
+ * Validated live 2026-06-19 on tenonworkstudio (a smoke-test scoped table): the
20
+ * form save creates the column (HTTP 200 re-render, not a 302), the before/after
21
+ * sys_dictionary diff confirms it, and a scoped insert into the new column
22
+ * round-trips — proving the physical column, not just the dictionary row. ES6
23
+ * only, no optional chaining, no `any`.
24
+ */
25
+ import type { ServiceNowClient } from "../client";
26
+ import { ColumnSpec } from "./buildTableSave";
27
+ export interface AddColumnParams {
28
+ /** REST client for table/scope resolution + the read-back verify. */
29
+ client: ServiceNowClient;
30
+ /** Existing table — its name ("x_cadso_journey") OR its sys_db_object sys_id. */
31
+ table: string;
32
+ /** The single column to add. `name` (the element) is optional; derived from label when omitted. */
33
+ column: ColumnSpec;
34
+ /** Scope name or sys_scope sys_id to run the form session in. Defaults to the table's own scope. */
35
+ scope?: string;
36
+ /** Update set sys_id to pin the writes to. Strongly recommended. */
37
+ updateSetSysId?: string;
38
+ /** Override the Save UI-action sys_id (defaults to the well-known global Save). */
39
+ saveActionSysId?: string;
40
+ /** Override the "Table Columns" relationship sys_id (fallback only — the existing form normally yields it). */
41
+ columnsRelId?: string;
42
+ /** Emit diagnostic detail in the result note. */
43
+ debug?: boolean;
44
+ /** Instance/creds for the form session (default: env, same precedence as the client). */
45
+ instance?: string;
46
+ user?: string;
47
+ password?: string;
48
+ /** Plan only — no session, no writes. Pure + deterministic. */
49
+ dryRun?: boolean;
50
+ }
51
+ export interface AddColumnResult {
52
+ status: "created" | "dry-run" | "failed";
53
+ /** The table's name (resolved; echoes the input on dry-run). */
54
+ table: string;
55
+ /** sys_db_object sys_id ("" on dry-run / when unresolved). */
56
+ tableSysId: string;
57
+ /** The dictionary element (column name) — derived from label or the explicit `name`. */
58
+ element: string;
59
+ /** The column's display label. */
60
+ label: string;
61
+ /** Resolved ServiceNow internal_type (friendly -> internal). */
62
+ internalType: string;
63
+ /** Client-generated sys_id sent for the new sys_dictionary row. */
64
+ columnSysId: string;
65
+ /** Update set the write was pinned to ("" if none). */
66
+ updateSetSysId: string;
67
+ httpStatus: number;
68
+ /** 302 Location on a successful POST. */
69
+ location: string;
70
+ /** The embedded column XML (always populated — it is pure). */
71
+ columnXml: string;
72
+ /** True only when the column was READ BACK from sys_dictionary after the POST. */
73
+ verified: boolean;
74
+ /** Human-readable note (success summary, the read-back result, or the failure body). */
75
+ note: string;
76
+ }
77
+ /**
78
+ * Derive the dictionary element (column name) from a label the way Studio does for
79
+ * a scoped table: lower-case, every run of non-alphanumerics -> a single
80
+ * underscore, trimmed. Scoped custom columns are NOT u_-prefixed (that is a
81
+ * global-scope-on-OOB-table behaviour), so "URL" -> "url". Pass `column.name`
82
+ * explicitly for anything non-trivial.
83
+ */
84
+ export declare function deriveElement(label: string, explicit?: string): string;
85
+ /**
86
+ * Overlay ONLY the new-column list-edit XML and the form-save machinery onto the
87
+ * fields harvested from the EXISTING table form. Returns a NEW object; every
88
+ * existing table field (name, label, scope, access, super_class, …) is preserved
89
+ * from `base` untouched — an add-column must not re-stamp the table. Mirrors the
90
+ * harvested-defaults-preserved contract of applyTableSaveOverlay.
91
+ */
92
+ export declare function applyAddColumnOverlay(base: Record<string, string>, o: {
93
+ tableSysId: string;
94
+ saveActionSysId: string;
95
+ listEditKey: string;
96
+ columnXml: string;
97
+ }): Record<string, string>;
98
+ export declare function addColumn(params: AddColumnParams): Promise<AddColumnResult>;