n8n-nodes-autotask 2.26.2 → 2.26.3

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.
@@ -47,6 +47,7 @@ const operation_contracts_1 = require("./operation-contracts");
47
47
  const operation_metadata_1 = require("./operation-metadata");
48
48
  const resource_language_1 = require("./resource-language");
49
49
  const operation_dispatch_1 = require("./operation-handlers/operation-dispatch");
50
+ const read_param_descriptions_1 = require("./read-param-descriptions");
50
51
  exports.DESCRIPTION_REFERENCE_PLACEHOLDER = '__REFERENCE_UTC__';
51
52
  const RESOURCE_LANGUAGE_CONFIG = {
52
53
  ticket: {
@@ -146,11 +147,10 @@ function describeFieldsHint(resourceName, mode = '') {
146
147
  return `If field names are uncertain, call autotask_${resourceName} with operation 'describeFields'${modeClause} first.`;
147
148
  }
148
149
  function buildGetDescription(resourceLabel, resourceName) {
149
- return (`Retrieve a single ${resourceLabel} record by numeric ID. ` +
150
- `ONLY call this when you already have a numeric ID — never pass a name or text. ` +
150
+ return (`Retrieve a single ${resourceLabel} record by numeric ID — never pass a name or text. ` +
151
151
  `If you only have a name or description, call autotask_${resourceName} with operation 'getMany' with a filter first, extract the 'id' from results, then call this. ` +
152
- `Optionally use 'fields' to return only selected columns. ` +
153
- `If a record should exist but response is empty, verify API user permissions (including line-of-business access). ` +
152
+ `Use 'fields' to return only selected columns. ` +
153
+ `If a record should exist but the response is empty, verify API user permissions (including line-of-business access). ` +
154
154
  `Do not guess field names. ${describeFieldsHint(resourceName, 'read')}`);
155
155
  }
156
156
  function buildGetManyDescription(resourceLabel, resourceName, readFields, terminalStatusLabel, referenceUtc) {
@@ -224,7 +224,7 @@ function buildCreateDescription(resourceLabel, resourceName, writeFields, refere
224
224
  `Create a new ${resourceLabel} record. ` +
225
225
  `${requiredSummary}${parentHint} ` +
226
226
  `Picklist and reference fields accept human-readable names — auto-resolved to IDs. ` +
227
- `Date-time values must be ISO-8601 and UTC-safe (for example 2026-02-14T03:15:00Z). ` +
227
+ `Date-time values must be ISO-8601 and UTC-safe (e.g. 2026-02-14T03:15:00Z). ` +
228
228
  `Confirm field values with user before executing when acting autonomously. ` +
229
229
  `If picklist values fail validation, call autotask_${resourceName} with operation 'listPicklistValues'.` +
230
230
  (extraHint ? ` ${extraHint}` : ''));
@@ -239,27 +239,25 @@ function buildUpdateDescription(resourceLabel, resourceName, writeFields, refere
239
239
  : '';
240
240
  return (ref +
241
241
  `Update an existing ${resourceLabel} record by numeric ID. ` +
242
- `PREREQUISITE: you need the numeric ID. If you only have a name or text, call autotask_${resourceName} with operation 'getMany' with a filter to find the record and get its 'id' first. ` +
243
- `Only provide fields to change (PATCH-style behaviour). ` +
244
- `Do not assume PUT-style replacement where omitted fields become null. ` +
242
+ `If you only have a name or text, call autotask_${resourceName} with operation 'getMany' with a filter to find the record's 'id' first. ` +
243
+ `Provide only fields to change (PATCH semantics — omitted fields keep existing values, not PUT-style nulling). ` +
245
244
  createFieldsNote +
246
245
  `Picklist and reference fields accept human-readable names — auto-resolved to IDs. ` +
247
- `Date-time values must be ISO-8601 and UTC-safe (for example 2026-02-14T03:15:00Z). ` +
246
+ `Date-time values must be ISO-8601 and UTC-safe (e.g. 2026-02-14T03:15:00Z). ` +
248
247
  `Confirm field values with user before executing when acting autonomously. ` +
249
248
  `${describeFieldsHint(resourceName, 'write')} ` +
250
249
  `Use autotask_${resourceName} with operation 'listPicklistValues' for picklist fields.` +
251
250
  (extraHint ? ` ${extraHint}` : ''));
252
251
  }
253
252
  function buildDeleteDescription(resourceLabel, resourceName) {
254
- return (`Delete a ${resourceLabel} record by numeric ID. ` +
255
- `ONLY on explicit user intent. Do not infer delete intent from context. Confirm ID is correct before proceeding. ` +
256
- `Operational delete responses may be minimal, so treat non-200 outcomes as failures. ` +
257
- `Use autotask_${resourceName} with operation 'getMany' or autotask_${resourceName} with operation 'get' first to confirm the correct ID before deletion.`);
253
+ return (`Delete a ${resourceLabel} record by numeric ID — ONLY on explicit user intent, never inferred from context. ` +
254
+ `Confirm the correct ID first via autotask_${resourceName} with operation 'getMany' or operation 'get'. ` +
255
+ `Delete responses may be minimal; treat non-200 outcomes as failures.`);
258
256
  }
259
257
  function buildWhoAmIDescription(resourceLabel) {
260
258
  return (`Resolve the current authenticated ${resourceLabel} record from API credentials. ` +
261
259
  `Use this to discover the active Autotask user context before running user-scoped actions. ` +
262
- `Optionally use 'fields' to limit returned columns.`);
260
+ `Use 'fields' to limit returned columns.`);
263
261
  }
264
262
  function buildPostedTimeEntriesDescription(resourceName, referenceUtc) {
265
263
  const ref = referenceUtc ? dateTimeReferenceSnippet(referenceUtc) : '';
@@ -279,20 +277,19 @@ function buildUnpostedTimeEntriesDescription(resourceName, referenceUtc) {
279
277
  }
280
278
  function buildCompanySearchByDomainDescription(resourceName) {
281
279
  return ('Search companies by domain using website-style fields. ' +
282
- 'Identifier priority for company resolution: first extract/use domain from any provided email or website, then use company-name contains matching only as fallback. ' +
283
- 'Input can be a bare domain or full URL; the tool normalises it to a domain fragment (for example autotask.net). ' +
284
- 'IMPORTANT: Autotask typically stores company websites as full URLs (for example https://www.autotask.net/), so exact operator matches can fail on bare domain input. ' +
285
- 'To avoid false negatives, eq/like semantics are handled safely for website matching. ' +
286
- 'Always searches both company website fields and contact-email domains (no toggle): when no company website field matches, the tool searches Contact.emailAddress by domain and resolves the canonical company from companyID references. Public email-provider domains (gmail.com, outlook.com, etc.) skip the contact-email fallback to avoid over-matching consumer addresses. ' +
287
- "Use the 'fields' parameter to limit which company fields are returned per result (comma-separated); omit to receive the full company entity. matchedField and matchedValue are always included to indicate which website field matched and its value. " +
280
+ 'Resolution priority: extract/use domain from any provided email or website first; use company-name contains matching only as fallback. ' +
281
+ 'Accepts a bare domain or full URL normalised to a domain fragment (e.g. autotask.net). ' +
282
+ 'Autotask stores company websites as full URLs (e.g. https://www.autotask.net/), so exact operator matches can fail on bare domain input; eq/like semantics are handled safely for website matching to avoid false negatives. ' +
283
+ 'Always searches both company website fields and contact-email domains (no toggle): when no company website field matches, searches Contact.emailAddress by domain and resolves the canonical company from companyID references. Public email-provider domains (gmail.com, outlook.com, etc.) skip the contact-email fallback to avoid over-matching consumer addresses. ' +
284
+ "Use 'fields' (comma-separated) to limit returned company fields per result; omit for the full company entity. matchedField and matchedValue are always included to indicate which website field matched and its value. " +
288
285
  describeFieldsHint(resourceName));
289
286
  }
290
287
  function buildCompanySearchByIdentityDescription(resourceName) {
291
- return ('Preferred AI company resolution operation. Accepts companyName, email, and website/domain, then ranks candidates by confidence. ' +
292
- 'Domain is normalised from website first, then email; domain matching is attempted first (company website fields, then contact-email fallback). ' +
293
- 'If domain signals are weak or absent, companyName contains matching is executed and merged into a confidence-ranked candidate list. ' +
294
- 'Use this operation as first choice for company lookup when identity hints may be incomplete or noisy. ' +
295
- "Use the 'fields' parameter to limit returned company fields; omit for full records. " +
288
+ return ('Preferred AI company resolution operation first choice when identity hints may be incomplete or noisy. ' +
289
+ 'Accepts companyName, email, and website/domain; ranks candidates by confidence. ' +
290
+ 'Domain is normalised from website first, then email; domain matching runs first (company website fields, then contact-email fallback). ' +
291
+ 'If domain signals are weak or absent, companyName contains matching is executed and merged into the confidence-ranked candidate list. ' +
292
+ "Use 'fields' to limit returned company fields; omit for full records. " +
296
293
  describeFieldsHint(resourceName));
297
294
  }
298
295
  function buildTicketSummaryDescription(resourceName) {
@@ -334,7 +331,7 @@ function buildTicketSlaHealthCheckDescription(resourceName) {
334
331
  'Returns first-response, resolution-plan, and resolution milestone timing and status in consistent hours (2 decimal places). ' +
335
332
  "Use 'ticketFields' to limit which ticket fields are returned in the ticket section. " +
336
333
  'Includes wallClockRemainingHours, where negative values indicate overdue milestones. ' +
337
- 'This operation combines data from Ticket and ServiceLevelAgreementResults entities. ' +
334
+ 'Combines data from Ticket and ServiceLevelAgreementResults entities. ' +
338
335
  "For population-level SLA queries (e.g. 'how many breached this week?'), do NOT call slaHealthCheck without an id. Use operation 'count' or 'getMany' with filter_field='serviceLevelAgreementHasBeenMet', filter_op='eq', filter_value=false instead. " +
339
336
  describeFieldsHint(resourceName));
340
337
  }
@@ -344,7 +341,7 @@ function buildTicketTimelineDescription(resourceName) {
344
341
  return ('Chronological merged event stream (notes, time entries, and optionally field-change history) for a single ticket — use for escalation briefs, effort audits, and manager summaries. ' +
345
342
  identifierRule +
346
343
  "Events sorted oldest-first. Each event has a 'type' field: 'note' (communications), 'timeEntry' (work logged), or 'history' (field changes). " +
347
- "Parameters: 'since'/'until' (ISO date — strongly recommended for active tickets to scope results); " +
344
+ "Parameters: 'since'/'until' (ISO date — use on active tickets to scope results); " +
348
345
  "'resourceId' (name or numeric ID — filters note authors, time entry resources, and history actors); " +
349
346
  "'includeHistories' (default false — enable for full field-change audit; can be very large on busy tickets — combine with since/until); " +
350
347
  "'textLimit' (default 500 chars for note/entry text, 0=no limit); " +
@@ -440,26 +437,26 @@ function buildTicketSearchByKeywordDescription(_resource) {
440
437
  "Use 'returnAll' for the full deduplicated result set.");
441
438
  }
442
439
  function buildConfigurationItemMoveConfigurationItemDescription(resourceName) {
443
- return ('Clone a configuration item to a different company because companyID cannot be updated in place. ' +
444
- 'Copies CI core fields and optional UDFs, optional CI attachments, optional notes, and optional note attachments. ' +
445
- 'Always writes audit notes and can deactivate the source CI after safety checks. ' +
446
- 'Optionally provide impersonationResourceId so created records (CI, notes, attachments) are attributed to that resource. ' +
447
- 'Optionally set proceedWithoutImpersonationIfDenied (default on); this only applies when impersonationResourceId is set and retries without impersonation if write permissions are denied for the impersonated resource. ' +
448
- 'This operation does not migrate tickets, tasks, projects, contracts, related items, DNS records, or billing-product associations. ' +
440
+ return ('Clone a configuration item to a different company (companyID cannot be updated in place). ' +
441
+ 'Copies CI core fields plus optional UDFs, CI attachments, notes, and note attachments. ' +
442
+ 'Always writes audit notes; can deactivate the source CI after safety checks. ' +
443
+ 'impersonationResourceId attributes created records (CI, notes, attachments) to that resource. ' +
444
+ 'proceedWithoutImpersonationIfDenied (default on) applies only when impersonationResourceId is set retries without impersonation if write permissions are denied for the impersonated resource. ' +
445
+ 'Does not migrate tickets, tasks, projects, contracts, related items, DNS records, or billing-product associations. ' +
449
446
  `If field names or expected behaviour are uncertain, call autotask_${resourceName} with operation 'describeFields' first.`);
450
447
  }
451
448
  function buildContactMoveToCompanyDescription(resourceName) {
452
449
  return ('Move a contact to another company by cloning the contact record and optional related data. ' +
453
- 'Supports duplicate email safeguards, optional company note and attachment copy, contact group copy, and configurable source/destination audit notes. ' +
454
- 'Supports optional impersonation for write attribution with optional fallback when impersonation is denied. ' +
450
+ 'Includes duplicate-email safeguards, optional company note and attachment copy, contact group copy, and configurable source/destination audit notes. ' +
451
+ 'Supports impersonation for write attribution, with fallback when impersonation is denied. ' +
455
452
  `If field names or expected behaviour are uncertain, call autotask_${resourceName} with operation 'describeFields' first.`);
456
453
  }
457
454
  function buildResourceTransferOwnershipDescription(resourceName) {
458
- return ('Transfer ownership and assignments from a source resource to a receiving resource. ' +
459
- 'Supports companies, opportunities, tickets, tasks, projects, task secondary resources, service call assignments, and appointments. ' +
455
+ return ('Transfer ownership and assignments from a source resource to a receiving resource: ' +
456
+ 'companies, opportunities, tickets, tasks, projects, task secondary resources, service call assignments, and appointments. ' +
460
457
  'Supports due-window filtering, status filtering, and optional audit notes. ' +
461
- "Use dueWindowPreset for convenient date ranges, or dueWindowPreset='custom' with dueBeforeCustom for exact cut-offs. " +
462
- 'By default, only open/active-style work is targeted by excluding terminal statuses. ' +
458
+ "Use dueWindowPreset for date ranges, or dueWindowPreset='custom' with dueBeforeCustom for exact cut-offs. " +
459
+ 'By default targets only open/active work by excluding terminal statuses. ' +
463
460
  `If field names or expected behaviour are uncertain, call autotask_${resourceName} with operation 'describeFields' first.`);
464
461
  }
465
462
  function buildDescribeFieldsDescription(resourceLabel) {
@@ -570,418 +567,408 @@ const DEDUP_NOTES = [
570
567
  'updateFields: field names to compare against the duplicate — values that differ cause an update. Requires errorOnDuplicate=false.',
571
568
  'Returns outcome: created, skipped, updated, or a resource-specific not_found variant.',
572
569
  ];
573
- const LIST_ADVANCED_NOTES = [
574
- 'filtersJson: JSON array of Autotask IFilterCondition objects for 3+ conditions or nested OR. Mutually exclusive with flat filter_field triplets. No label resolution — pass numeric IDs.',
575
- `returnAll=true: fetches ALL matching records via API-native pagination. Without fields param: capped at ${operation_dispatch_1.MAX_RESPONSE_RECORDS} records. With fields param (sparse): no cap all records returned. Use a narrow fields list for bulk ID/lookup patterns.`,
576
- ASCENDING_ID_WARNING,
577
- RECENCY_VS_SINCE_UNTIL_RULE.trim(),
578
- ];
570
+ let _listAdvancedNotesCache;
571
+ /**
572
+ * Built lazily (first call, memoized) rather than as a module-load-time const:
573
+ * MAX_RESPONSE_RECORDS comes from operation-dispatch.ts, which sits in the same
574
+ * circular-import cycle as this module — reading it at module-eval time can yield
575
+ * undefined ("capped at undefined") depending on which module is required first.
576
+ */
577
+ function getListAdvancedNotes() {
578
+ if (_listAdvancedNotesCache)
579
+ return _listAdvancedNotesCache;
580
+ _listAdvancedNotesCache = [
581
+ 'filtersJson: JSON array of Autotask IFilterCondition objects for 3+ conditions or nested OR. Mutually exclusive with flat filter_field triplets. No label resolution — pass numeric IDs.',
582
+ `returnAll=true: fetches ALL matching records via API-native pagination. Without fields param: capped at ${operation_dispatch_1.MAX_RESPONSE_RECORDS} records. With fields param (sparse): no cap — all records returned. Use a narrow fields list for bulk ID/lookup patterns.`,
583
+ ASCENDING_ID_WARNING,
584
+ RECENCY_VS_SINCE_UNTIL_RULE.trim(),
585
+ ];
586
+ return _listAdvancedNotesCache;
587
+ }
579
588
  const SEARCH_BY_KEYWORD_NOTES = [
580
589
  "Use 'limit' (default 10) or 'returnAll=true' to control result count from the merged set.",
581
590
  "Use 'recency', 'since', or 'until' to filter by creation date — applied post-merge, not per-stage.",
582
591
  "Per-stage cap is 200 records. If a stage hits the cap, set includeNotes/includeTimeEntries=false or narrow the keyword.",
583
592
  ];
584
- /** Static parameter map for read and metadata operations */
585
- const READ_OP_PARAMS = {
586
- get: {
587
- required: [{ field: 'id', type: 'number', description: 'Numeric entity ID.' }],
588
- optional: [
589
- { field: 'fields', type: 'string', description: 'Real API field names only (call describeFields for the list). Do not include *_label or *_name fields — auto-added by outputMode=idsAndLabels.' },
590
- ],
591
- },
592
- getMany: {
593
- required: [],
594
- optional: [
595
- { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
596
- {
597
- field: 'filter_op',
598
- type: 'string',
599
- description: 'Operator: eq, noteq, gt, gte, lt, lte, contains, beginsWith, endsWith, exist, notExist, in, notIn.',
600
- },
601
- {
602
- field: 'filter_value',
603
- type: 'string',
604
- description: "Filter value as string. For reference/picklist fields, human-readable names auto-resolve to IDs. For in/notIn: comma-separate names or IDs ('Neil,Andrew' or '123,456'); each resolved independently — unresolved names with multiple candidates return pendingConfirmations; already-resolved in resolvedElements. Booleans: 'true'/'false'.",
605
- },
606
- { field: 'filter_field_2', type: 'string', description: 'Second filter field.' },
607
- { field: 'filter_op_2', type: 'string', description: 'Second filter operator.' },
608
- {
609
- field: 'filter_value_2',
610
- type: 'string',
611
- description: "Second filter value as string. For in/notIn, comma-separate values (e.g. '1,2,3').",
612
- },
613
- { field: 'filter_logic', type: 'string', description: "'and' (default) or 'or'." },
614
- {
615
- field: 'filtersJson',
616
- type: 'string',
617
- description: 'JSON IFilterCondition array (mutually exclusive with flat filter_field triplets). No label resolution.',
618
- },
619
- {
620
- field: 'returnAll',
621
- type: 'boolean',
622
- description: `Fetch ALL matching records via API pagination. Without fields: capped at ${operation_dispatch_1.MAX_RESPONSE_RECORDS}. With fields (sparse fieldset): cap lifted — all records returned.`,
623
- },
624
- { field: 'limit', type: 'number', description: 'Max records (1-500, default 10).' },
625
- { field: 'offset', type: 'number', description: 'Skip first N records (max 499).' },
626
- {
627
- field: 'recency',
628
- type: 'string',
629
- description: 'Preset window (last_7d, last_30d, etc.) or custom last_Nd.',
630
- },
631
- { field: 'since', type: 'string', description: 'Range start ISO-8601 UTC.' },
632
- { field: 'until', type: 'string', description: 'Range end ISO-8601 UTC.' },
633
- { field: 'fields', type: 'string', description: 'Real API field names only (call describeFields for the list). Do not include *_label or *_name fields — auto-added by outputMode=idsAndLabels.' },
634
- { field: 'outputMode', type: 'string', description: "'idsAndLabels' (default): appends label fields automatically (resourceFullName, *_label etc.) — do NOT request these via fields. 'rawIds': numeric IDs only." },
635
- ],
636
- },
637
- count: {
638
- required: [],
639
- optional: [
640
- { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
641
- { field: 'filter_op', type: 'string', description: 'Filter operator.' },
642
- {
643
- field: 'filter_value',
644
- type: 'string',
645
- description: "Filter value as string. For reference/picklist fields, human-readable names auto-resolve to IDs. For in/notIn: comma-separate names or IDs; each resolved independently.",
646
- },
647
- { field: 'filter_field_2', type: 'string', description: 'Second filter field.' },
648
- { field: 'filter_op_2', type: 'string', description: 'Second filter operator.' },
649
- {
650
- field: 'filter_value_2',
651
- type: 'string',
652
- description: "Second filter value as string. For in/notIn, comma-separate values (e.g. '1,2,3').",
653
- },
654
- { field: 'filter_logic', type: 'string', description: "'and' (default) or 'or'." },
655
- { field: 'filtersJson', type: 'string', description: 'JSON IFilterCondition array.' },
656
- { field: 'recency', type: 'string', description: 'Preset window.' },
657
- { field: 'since', type: 'string', description: 'Range start ISO-8601 UTC.' },
658
- { field: 'until', type: 'string', description: 'Range end ISO-8601 UTC.' },
659
- ],
660
- },
661
- delete: {
662
- required: [{ field: 'id', type: 'number', description: 'Numeric entity ID to delete.' }],
663
- optional: [],
664
- },
665
- whoAmI: {
666
- required: [],
667
- optional: [
668
- { field: 'fields', type: 'string', description: 'Real API field names only (call describeFields for the list). Do not include *_label or *_name fields — auto-added by outputMode=idsAndLabels.' },
669
- ],
670
- },
671
- getPosted: {
672
- required: [],
673
- optional: [
674
- { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
675
- { field: 'filter_op', type: 'string', description: 'Filter operator.' },
676
- {
677
- field: 'filter_value',
678
- type: 'string',
679
- description: "Filter value as string. For reference/picklist fields, human-readable names auto-resolve to IDs. For in/notIn: comma-separate names or IDs; each resolved independently.",
680
- },
681
- { field: 'filter_field_2', type: 'string', description: 'Second field to filter on.' },
682
- { field: 'filter_op_2', type: 'string', description: 'Second filter operator.' },
683
- { field: 'filter_value_2', type: 'string', description: 'Second filter value.' },
684
- { field: 'filter_logic', type: 'string', description: "'and' (default) or 'or' — logic between filter pairs." },
685
- { field: 'filtersJson', type: 'string', description: 'JSON IFilterCondition array. Mutually exclusive with filter_field.' },
686
- { field: 'returnAll', type: 'boolean', description: 'Fetch ALL matching records.' },
687
- { field: 'limit', type: 'number', description: 'Max records (1-500, default 10).' },
688
- { field: 'offset', type: 'number', description: 'Client-side offset for pagination (0–499).' },
689
- { field: 'recency', type: 'string', description: 'Preset window.' },
690
- { field: 'since', type: 'string', description: 'Range start ISO-8601 UTC.' },
691
- { field: 'until', type: 'string', description: 'Range end ISO-8601 UTC.' },
692
- { field: 'fields', type: 'string', description: 'Real API field names only (call describeFields for the list). Do not include *_label or *_name fields — auto-added by outputMode=idsAndLabels.' },
693
- { field: 'outputMode', type: 'string', description: "'idsAndLabels' (default): appends label fields automatically (resourceFullName, *_label etc.) — do NOT request these via fields. 'rawIds': numeric IDs only." },
694
- ],
695
- },
696
- getUnposted: {
697
- required: [],
698
- optional: [
699
- { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
700
- { field: 'filter_op', type: 'string', description: 'Filter operator.' },
701
- {
702
- field: 'filter_value',
703
- type: 'string',
704
- description: "Filter value as string. For reference/picklist fields, human-readable names auto-resolve to IDs. For in/notIn: comma-separate names or IDs; each resolved independently.",
705
- },
706
- { field: 'filter_field_2', type: 'string', description: 'Second field to filter on.' },
707
- { field: 'filter_op_2', type: 'string', description: 'Second filter operator.' },
708
- { field: 'filter_value_2', type: 'string', description: 'Second filter value.' },
709
- { field: 'filter_logic', type: 'string', description: "'and' (default) or 'or' — logic between filter pairs." },
710
- { field: 'filtersJson', type: 'string', description: 'JSON IFilterCondition array. Mutually exclusive with filter_field.' },
711
- { field: 'returnAll', type: 'boolean', description: 'Fetch ALL matching records.' },
712
- { field: 'limit', type: 'number', description: 'Max records (1-500, default 10).' },
713
- { field: 'offset', type: 'number', description: 'Client-side offset for pagination (0–499).' },
714
- { field: 'recency', type: 'string', description: 'Preset window.' },
715
- { field: 'since', type: 'string', description: 'Range start ISO-8601 UTC.' },
716
- { field: 'until', type: 'string', description: 'Range end ISO-8601 UTC.' },
717
- { field: 'fields', type: 'string', description: 'Real API field names only (call describeFields for the list). Do not include *_label or *_name fields — auto-added by outputMode=idsAndLabels.' },
718
- { field: 'outputMode', type: 'string', description: "'idsAndLabels' (default): appends label fields automatically (resourceFullName, *_label etc.) — do NOT request these via fields. 'rawIds': numeric IDs only." },
719
- ],
720
- },
721
- searchByDomain: {
722
- required: [],
723
- optional: [
724
- {
725
- field: 'domain',
726
- type: 'string',
727
- description: "Domain to search. Prefer domain extracted from email/website first (e.g. email='user@domain.com' -> 'domain.com').",
728
- },
729
- {
730
- field: 'domainOperator',
731
- type: 'string',
732
- description: "Operator: eq, beginsWith, endsWith, contains (default 'contains'). Do domain matching first; avoid strict exact-name-only matching when a domain exists.",
733
- },
734
- {
735
- field: 'fields',
736
- type: 'string',
737
- description: 'Comma-separated company field names to return.',
738
- },
739
- ],
740
- },
741
- searchByIdentity: {
742
- required: [],
743
- optional: [
744
- {
745
- field: 'companyName',
746
- type: 'string',
747
- description: 'Optional company name signal (contains match).',
748
- },
749
- {
750
- field: 'email',
751
- type: 'string',
752
- description: 'Optional email signal used to infer domain.',
753
- },
754
- {
755
- field: 'website',
756
- type: 'string',
757
- description: 'Optional website/domain signal used for primary match.',
758
- },
759
- {
760
- field: 'limit',
761
- type: 'number',
762
- description: 'Max ranked candidates to return (1-100, default 25).',
763
- },
764
- {
765
- field: 'fields',
766
- type: 'string',
767
- description: 'Comma-separated company field names to return.',
768
- },
769
- ],
770
- },
771
- slaHealthCheck: {
772
- required: [],
773
- optional: [
774
- {
775
- field: 'id',
776
- type: 'number',
777
- description: 'Numeric Ticket ID (required if ticketNumber not provided).',
778
- },
779
- {
780
- field: 'ticketNumber',
781
- type: 'string',
782
- description: 'Ticket number T{date}.{seq} (required if id not provided).',
783
- },
784
- {
785
- field: 'ticketFields',
786
- type: 'string',
787
- description: 'Optional comma-separated ticket fields to return.',
788
- },
789
- ],
790
- },
791
- summary: {
792
- required: [],
793
- optional: [
794
- {
795
- field: 'id',
796
- type: 'number',
797
- description: 'Numeric Ticket ID (required if ticketNumber not provided).',
798
- },
799
- {
800
- field: 'ticketNumber',
801
- type: 'string',
802
- description: 'Ticket number T{date}.{seq} (required if id not provided).',
803
- },
804
- {
805
- field: 'includeRaw',
806
- type: 'boolean',
807
- description: 'Include full enriched pre-alias payload.',
808
- },
809
- {
810
- field: 'summaryTextLimit',
811
- type: 'number',
812
- description: 'Max chars for description/resolution fields (default 500, 0=no limit).',
813
- },
814
- {
815
- field: 'includeChildCounts',
816
- type: 'boolean',
817
- description: 'Include child entity counts (default false, adds API calls).',
818
- },
819
- ],
820
- },
821
- moveConfigurationItem: {
822
- required: [
823
- { field: 'sourceConfigurationItemId', type: 'number', description: 'Source CI ID to clone.' },
824
- { field: 'destinationCompanyId', type: 'number', description: 'Destination company ID.' },
825
- ],
826
- optional: [
827
- {
828
- field: 'destinationCompanyLocationId',
829
- type: 'number',
830
- description: 'Optional destination location ID.',
831
- },
832
- { field: 'copyUdfs', type: 'boolean', description: 'Copy UDFs (default true).' },
833
- {
834
- field: 'copyAttachments',
835
- type: 'boolean',
836
- description: 'Copy CI attachments (default true).',
837
- },
838
- { field: 'copyNotes', type: 'boolean', description: 'Copy notes (default true).' },
839
- {
840
- field: 'deactivateSource',
841
- type: 'boolean',
842
- description: 'Deactivate source CI after safety checks (default true).',
843
- },
844
- {
845
- field: 'impersonationResourceId',
846
- type: 'number | string',
847
- description: 'Resource ID or name for write attribution.',
848
- },
849
- ],
850
- },
851
- moveToCompany: {
852
- required: [
853
- { field: 'sourceContactId', type: 'number', description: 'Source contact ID to move.' },
854
- { field: 'destinationCompanyId', type: 'number', description: 'Destination company ID.' },
855
- ],
856
- optional: [
857
- {
858
- field: 'skipIfDuplicateEmailFound',
859
- type: 'boolean',
860
- description: 'Skip move on duplicate email (default true).',
861
- },
862
- {
863
- field: 'copyContactGroups',
864
- type: 'boolean',
865
- description: 'Copy contact group memberships (default true).',
866
- },
867
- {
868
- field: 'copyCompanyNotes',
869
- type: 'boolean',
870
- description: 'Copy company notes linked to contact (default true).',
871
- },
872
- {
873
- field: 'impersonationResourceId',
874
- type: 'number | string',
875
- description: 'Resource ID or name for write attribution.',
876
- },
877
- ],
878
- },
879
- transferOwnership: {
880
- required: [
881
- { field: 'sourceResourceId', type: 'number', description: 'Source resource ID.' },
882
- {
883
- field: 'destinationResourceId',
884
- type: 'number',
885
- description: 'Receiving resource ID (must be active).',
886
- },
887
- ],
888
- optional: [
889
- { field: 'includeTickets', type: 'boolean', description: 'Include tickets (default false).' },
890
- {
891
- field: 'includeProjects',
892
- type: 'boolean',
893
- description: 'Include projects (default false).',
894
- },
895
- {
896
- field: 'includeCompanies',
897
- type: 'boolean',
898
- description: 'Include companies (default false).',
899
- },
900
- {
901
- field: 'dueWindowPreset',
902
- type: 'string',
903
- description: "Due window (today, tomorrow, plus7Days, etc. or 'custom' with dueBeforeCustom).",
904
- },
905
- {
906
- field: 'impersonationResourceId',
907
- type: 'number | string',
908
- description: 'Resource ID or name for write attribution.',
909
- },
910
- ],
911
- },
912
- getAvailableRoles: {
913
- required: [
914
- { field: 'resourceID', type: 'number | string', description: 'Resource name, email, or numeric ID (auto-resolved). Required.' },
915
- ],
916
- optional: [
917
- { field: 'ticketID', type: 'number', description: 'Ticket ID. If provided, derives queueID and contractID automatically.' },
918
- { field: 'queueID', type: 'number', description: 'Queue ID to filter roles by.' },
919
- { field: 'contractID', type: 'number', description: 'Contract ID to apply exclusion rules.' },
920
- ],
921
- },
922
- getByResource: {
923
- required: [
924
- { field: 'resourceID', type: 'number | string', description: 'Resource name, email, or numeric ID (auto-resolved).' },
925
- ],
926
- optional: [
927
- { field: 'mode', type: 'string', description: "ticket only: 'primary' | 'secondary' | 'both' (default 'both')." },
928
- { field: 'limit', type: 'number', description: 'Max records per branch (1-500, default 10).' },
929
- { field: 'returnAll', type: 'boolean', description: 'Fetch all per branch.' },
930
- { field: 'recency', type: 'string', description: 'Preset window (e.g. last_7d).' },
931
- { field: 'since', type: 'string', description: 'Range start ISO-8601 UTC.' },
932
- { field: 'until', type: 'string', description: 'Range end ISO-8601 UTC.' },
933
- { field: 'excludeTerminalStatuses', type: 'boolean', description: 'Exclude Complete/Cancelled (ticket only, default true).' },
934
- { field: 'fields', type: 'string', description: 'Real API field names only (call describeFields for the list). Do not include *_label or *_name fields auto-added by outputMode=idsAndLabels.' },
935
- ],
936
- },
937
- getByYear: {
938
- required: [
939
- {
940
- field: 'resourceID',
941
- type: 'number | string',
942
- description: 'Resource ID or name (auto-resolved).',
943
- },
944
- { field: 'year', type: 'number', description: 'Calendar year (e.g. 2024).' },
945
- ],
946
- optional: [],
947
- },
948
- approve: {
949
- required: [{ field: 'id', type: 'number', description: 'Numeric entity ID to approve.' }],
950
- optional: [],
951
- },
952
- reject: {
953
- required: [{ field: 'id', type: 'number', description: 'Numeric entity ID to reject.' }],
954
- optional: [
955
- {
956
- field: 'rejectReason',
957
- type: 'string',
958
- description: 'Reason for rejection (recommended for audit trail).',
959
- },
960
- ],
961
- },
962
- describeFields: {
963
- required: [],
964
- optional: [
965
- { field: 'mode', type: 'string', description: "'read' or 'write'. Defaults to 'read'." },
966
- ],
967
- },
968
- listPicklistValues: {
969
- required: [
970
- { field: 'fieldId', type: 'string', description: 'Field ID to list picklist values for.' },
971
- ],
972
- optional: [
973
- { field: 'query', type: 'string', description: 'Optional search term.' },
974
- { field: 'limit', type: 'number', description: 'Max results (default 50).' },
975
- { field: 'page', type: 'number', description: 'Page number (default 1).' },
976
- ],
977
- },
978
- describeOperation: {
979
- required: [
980
- { field: 'targetOperation', type: 'string', description: 'Operation name to document.' },
981
- ],
982
- optional: [],
983
- },
984
- };
593
+ let _readOpParamsCache;
594
+ /**
595
+ * Static parameter map for read and metadata operations. Built lazily (first call, memoized)
596
+ * rather than as a module-load-time const: this module sits in a circular-import cycle
597
+ * (tool-executor.ts -> description-builders.ts -> read-param-descriptions.ts ->
598
+ * operation-dispatch.ts -> tool-executor.ts), and several entries below call
599
+ * fieldsDesc()/filtersJsonDesc()/returnAllDesc() from read-param-descriptions.ts — calling
600
+ * those at module-eval time can race the cycle and throw "X is not a function" depending on
601
+ * which module is required first. Deferring to first actual use avoids the race entirely.
602
+ */
603
+ function getReadOpParams() {
604
+ if (_readOpParamsCache)
605
+ return _readOpParamsCache;
606
+ _readOpParamsCache = {
607
+ get: {
608
+ required: [{ field: 'id', type: 'number', description: 'Numeric entity ID.' }],
609
+ optional: [
610
+ { field: 'fields', type: 'string', description: (0, read_param_descriptions_1.fieldsDesc)() },
611
+ ],
612
+ },
613
+ getMany: {
614
+ required: [],
615
+ optional: [
616
+ { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
617
+ { field: 'filter_op', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op },
618
+ { field: 'filter_value', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value },
619
+ { field: 'filter_field_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_field_2 },
620
+ { field: 'filter_op_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op_2 },
621
+ { field: 'filter_value_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value_2 },
622
+ { field: 'filter_logic', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_logic },
623
+ { field: 'filtersJson', type: 'string', description: (0, read_param_descriptions_1.filtersJsonDesc)() },
624
+ { field: 'returnAll', type: 'boolean', description: (0, read_param_descriptions_1.returnAllDesc)() },
625
+ { field: 'limit', type: 'number', description: read_param_descriptions_1.READ_PARAM_DESC.limit },
626
+ { field: 'offset', type: 'number', description: read_param_descriptions_1.READ_PARAM_DESC.offset },
627
+ {
628
+ field: 'recency',
629
+ type: 'string',
630
+ description: 'Preset window (last_7d, last_30d, etc.) or custom last_Nd.',
631
+ },
632
+ { field: 'since', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.since },
633
+ { field: 'until', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.until },
634
+ { field: 'fields', type: 'string', description: (0, read_param_descriptions_1.fieldsDesc)() },
635
+ { field: 'outputMode', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.outputMode },
636
+ ],
637
+ },
638
+ count: {
639
+ required: [],
640
+ optional: [
641
+ { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
642
+ { field: 'filter_op', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op },
643
+ { field: 'filter_value', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value },
644
+ { field: 'filter_field_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_field_2 },
645
+ { field: 'filter_op_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op_2 },
646
+ { field: 'filter_value_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value_2 },
647
+ { field: 'filter_logic', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_logic },
648
+ { field: 'filtersJson', type: 'string', description: (0, read_param_descriptions_1.filtersJsonDesc)() },
649
+ { field: 'recency', type: 'string', description: 'Preset window.' },
650
+ { field: 'since', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.since },
651
+ { field: 'until', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.until },
652
+ ],
653
+ },
654
+ delete: {
655
+ required: [{ field: 'id', type: 'number', description: 'Numeric entity ID to delete.' }],
656
+ optional: [],
657
+ },
658
+ whoAmI: {
659
+ required: [],
660
+ optional: [
661
+ { field: 'fields', type: 'string', description: (0, read_param_descriptions_1.fieldsDesc)() },
662
+ ],
663
+ },
664
+ getPosted: {
665
+ required: [],
666
+ optional: [
667
+ { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
668
+ { field: 'filter_op', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op },
669
+ { field: 'filter_value', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value },
670
+ { field: 'filter_field_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_field_2 },
671
+ { field: 'filter_op_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op_2 },
672
+ { field: 'filter_value_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value_2 },
673
+ { field: 'filter_logic', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_logic },
674
+ { field: 'filtersJson', type: 'string', description: (0, read_param_descriptions_1.filtersJsonDesc)() },
675
+ { field: 'returnAll', type: 'boolean', description: (0, read_param_descriptions_1.returnAllDesc)() },
676
+ { field: 'limit', type: 'number', description: read_param_descriptions_1.READ_PARAM_DESC.limit },
677
+ { field: 'offset', type: 'number', description: read_param_descriptions_1.READ_PARAM_DESC.offset },
678
+ { field: 'recency', type: 'string', description: 'Preset window.' },
679
+ { field: 'since', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.since },
680
+ { field: 'until', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.until },
681
+ { field: 'fields', type: 'string', description: (0, read_param_descriptions_1.fieldsDesc)() },
682
+ { field: 'outputMode', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.outputMode },
683
+ ],
684
+ },
685
+ getUnposted: {
686
+ required: [],
687
+ optional: [
688
+ { field: 'filter_field', type: 'string', description: 'Field to filter on.' },
689
+ { field: 'filter_op', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op },
690
+ { field: 'filter_value', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value },
691
+ { field: 'filter_field_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_field_2 },
692
+ { field: 'filter_op_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_op_2 },
693
+ { field: 'filter_value_2', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_value_2 },
694
+ { field: 'filter_logic', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.filter_logic },
695
+ { field: 'filtersJson', type: 'string', description: (0, read_param_descriptions_1.filtersJsonDesc)() },
696
+ { field: 'returnAll', type: 'boolean', description: (0, read_param_descriptions_1.returnAllDesc)() },
697
+ { field: 'limit', type: 'number', description: read_param_descriptions_1.READ_PARAM_DESC.limit },
698
+ { field: 'offset', type: 'number', description: read_param_descriptions_1.READ_PARAM_DESC.offset },
699
+ { field: 'recency', type: 'string', description: 'Preset window.' },
700
+ { field: 'since', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.since },
701
+ { field: 'until', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.until },
702
+ { field: 'fields', type: 'string', description: (0, read_param_descriptions_1.fieldsDesc)() },
703
+ { field: 'outputMode', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.outputMode },
704
+ ],
705
+ },
706
+ searchByDomain: {
707
+ required: [],
708
+ optional: [
709
+ {
710
+ field: 'domain',
711
+ type: 'string',
712
+ description: "Domain to search. Prefer domain extracted from email/website first (e.g. email='user@domain.com' -> 'domain.com').",
713
+ },
714
+ {
715
+ field: 'domainOperator',
716
+ type: 'string',
717
+ description: "Operator: eq, beginsWith, endsWith, contains (default 'contains'). Do domain matching first; avoid strict exact-name-only matching when a domain exists.",
718
+ },
719
+ {
720
+ field: 'fields',
721
+ type: 'string',
722
+ description: 'Comma-separated company field names to return.',
723
+ },
724
+ ],
725
+ },
726
+ searchByIdentity: {
727
+ required: [],
728
+ optional: [
729
+ {
730
+ field: 'companyName',
731
+ type: 'string',
732
+ description: 'Company name signal (contains match).',
733
+ },
734
+ {
735
+ field: 'email',
736
+ type: 'string',
737
+ description: 'Email signal used to infer domain.',
738
+ },
739
+ {
740
+ field: 'website',
741
+ type: 'string',
742
+ description: 'Website/domain signal used for primary match.',
743
+ },
744
+ {
745
+ field: 'limit',
746
+ type: 'number',
747
+ description: 'Max ranked candidates to return (1-100, default 25).',
748
+ },
749
+ {
750
+ field: 'fields',
751
+ type: 'string',
752
+ description: 'Comma-separated company field names to return.',
753
+ },
754
+ ],
755
+ },
756
+ slaHealthCheck: {
757
+ required: [],
758
+ optional: [
759
+ {
760
+ field: 'id',
761
+ type: 'number',
762
+ description: 'Numeric Ticket ID (required if ticketNumber not provided).',
763
+ },
764
+ {
765
+ field: 'ticketNumber',
766
+ type: 'string',
767
+ description: 'Ticket number T{date}.{seq} (required if id not provided).',
768
+ },
769
+ {
770
+ field: 'ticketFields',
771
+ type: 'string',
772
+ description: 'Comma-separated ticket fields to return.',
773
+ },
774
+ ],
775
+ },
776
+ summary: {
777
+ required: [],
778
+ optional: [
779
+ {
780
+ field: 'id',
781
+ type: 'number',
782
+ description: 'Numeric Ticket ID (required if ticketNumber not provided).',
783
+ },
784
+ {
785
+ field: 'ticketNumber',
786
+ type: 'string',
787
+ description: 'Ticket number T{date}.{seq} (required if id not provided).',
788
+ },
789
+ {
790
+ field: 'includeRaw',
791
+ type: 'boolean',
792
+ description: 'Include full enriched pre-alias payload.',
793
+ },
794
+ {
795
+ field: 'summaryTextLimit',
796
+ type: 'number',
797
+ description: 'Max chars for description/resolution fields (default 500, 0=no limit).',
798
+ },
799
+ {
800
+ field: 'includeChildCounts',
801
+ type: 'boolean',
802
+ description: 'Include child entity counts (default false, adds API calls).',
803
+ },
804
+ ],
805
+ },
806
+ moveConfigurationItem: {
807
+ required: [
808
+ { field: 'sourceConfigurationItemId', type: 'number', description: 'Source CI ID to clone.' },
809
+ { field: 'destinationCompanyId', type: 'number', description: 'Destination company ID.' },
810
+ ],
811
+ optional: [
812
+ {
813
+ field: 'destinationCompanyLocationId',
814
+ type: 'number',
815
+ description: 'Destination location ID.',
816
+ },
817
+ { field: 'copyUdfs', type: 'boolean', description: 'Copy UDFs (default true).' },
818
+ {
819
+ field: 'copyAttachments',
820
+ type: 'boolean',
821
+ description: 'Copy CI attachments (default true).',
822
+ },
823
+ { field: 'copyNotes', type: 'boolean', description: 'Copy notes (default true).' },
824
+ {
825
+ field: 'deactivateSource',
826
+ type: 'boolean',
827
+ description: 'Deactivate source CI after safety checks (default true).',
828
+ },
829
+ {
830
+ field: 'impersonationResourceId',
831
+ type: 'number | string',
832
+ description: 'Resource ID or name for write attribution.',
833
+ },
834
+ ],
835
+ },
836
+ moveToCompany: {
837
+ required: [
838
+ { field: 'sourceContactId', type: 'number', description: 'Source contact ID to move.' },
839
+ { field: 'destinationCompanyId', type: 'number', description: 'Destination company ID.' },
840
+ ],
841
+ optional: [
842
+ {
843
+ field: 'skipIfDuplicateEmailFound',
844
+ type: 'boolean',
845
+ description: 'Skip move on duplicate email (default true).',
846
+ },
847
+ {
848
+ field: 'copyContactGroups',
849
+ type: 'boolean',
850
+ description: 'Copy contact group memberships (default true).',
851
+ },
852
+ {
853
+ field: 'copyCompanyNotes',
854
+ type: 'boolean',
855
+ description: 'Copy company notes linked to contact (default true).',
856
+ },
857
+ {
858
+ field: 'impersonationResourceId',
859
+ type: 'number | string',
860
+ description: 'Resource ID or name for write attribution.',
861
+ },
862
+ ],
863
+ },
864
+ transferOwnership: {
865
+ required: [
866
+ { field: 'sourceResourceId', type: 'number', description: 'Source resource ID.' },
867
+ {
868
+ field: 'destinationResourceId',
869
+ type: 'number',
870
+ description: 'Receiving resource ID (must be active).',
871
+ },
872
+ ],
873
+ optional: [
874
+ { field: 'includeTickets', type: 'boolean', description: 'Include tickets (default false).' },
875
+ {
876
+ field: 'includeProjects',
877
+ type: 'boolean',
878
+ description: 'Include projects (default false).',
879
+ },
880
+ {
881
+ field: 'includeCompanies',
882
+ type: 'boolean',
883
+ description: 'Include companies (default false).',
884
+ },
885
+ {
886
+ field: 'dueWindowPreset',
887
+ type: 'string',
888
+ description: "Due window (today, tomorrow, plus7Days, etc. or 'custom' with dueBeforeCustom).",
889
+ },
890
+ {
891
+ field: 'impersonationResourceId',
892
+ type: 'number | string',
893
+ description: 'Resource ID or name for write attribution.',
894
+ },
895
+ ],
896
+ },
897
+ getAvailableRoles: {
898
+ required: [
899
+ { field: 'resourceID', type: 'number | string', description: 'Resource name, email, or numeric ID (auto-resolved). Required.' },
900
+ ],
901
+ optional: [
902
+ { field: 'ticketID', type: 'number', description: 'Ticket ID. If provided, derives queueID and contractID automatically.' },
903
+ { field: 'queueID', type: 'number', description: 'Queue ID to filter roles by.' },
904
+ { field: 'contractID', type: 'number', description: 'Contract ID to apply exclusion rules.' },
905
+ ],
906
+ },
907
+ getByResource: {
908
+ required: [
909
+ { field: 'resourceID', type: 'number | string', description: 'Resource name, email, or numeric ID (auto-resolved).' },
910
+ ],
911
+ optional: [
912
+ { field: 'mode', type: 'string', description: "ticket only: 'primary' | 'secondary' | 'both' (default 'both')." },
913
+ { field: 'limit', type: 'number', description: 'Max records per branch (1-500, default 10).' },
914
+ { field: 'returnAll', type: 'boolean', description: 'Fetch all per branch.' },
915
+ { field: 'recency', type: 'string', description: 'Preset window (e.g. last_7d).' },
916
+ { field: 'since', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.since },
917
+ { field: 'until', type: 'string', description: read_param_descriptions_1.READ_PARAM_DESC.until },
918
+ { field: 'excludeTerminalStatuses', type: 'boolean', description: 'Exclude Complete/Cancelled (ticket only, default true).' },
919
+ { field: 'fields', type: 'string', description: (0, read_param_descriptions_1.fieldsDesc)() },
920
+ ],
921
+ },
922
+ getByYear: {
923
+ required: [
924
+ {
925
+ field: 'resourceID',
926
+ type: 'number | string',
927
+ description: 'Resource ID or name (auto-resolved).',
928
+ },
929
+ { field: 'year', type: 'number', description: 'Calendar year (e.g. 2024).' },
930
+ ],
931
+ optional: [],
932
+ },
933
+ approve: {
934
+ required: [{ field: 'id', type: 'number', description: 'Numeric entity ID to approve.' }],
935
+ optional: [],
936
+ },
937
+ reject: {
938
+ required: [{ field: 'id', type: 'number', description: 'Numeric entity ID to reject.' }],
939
+ optional: [
940
+ {
941
+ field: 'rejectReason',
942
+ type: 'string',
943
+ description: "Reason for rejection. Required when rejectReasonPolicy='mandatory'; otherwise include for the audit trail.",
944
+ },
945
+ ],
946
+ },
947
+ describeFields: {
948
+ required: [],
949
+ optional: [
950
+ { field: 'mode', type: 'string', description: "'read' or 'write'. Defaults to 'read'." },
951
+ ],
952
+ },
953
+ listPicklistValues: {
954
+ required: [
955
+ { field: 'fieldId', type: 'string', description: 'Field ID to list picklist values for.' },
956
+ ],
957
+ optional: [
958
+ { field: 'query', type: 'string', description: 'Search term to filter values.' },
959
+ { field: 'limit', type: 'number', description: 'Max results (default 50).' },
960
+ { field: 'page', type: 'number', description: 'Page number (default 1).' },
961
+ ],
962
+ },
963
+ describeOperation: {
964
+ required: [
965
+ { field: 'targetOperation', type: 'string', description: 'Operation name to document.' },
966
+ ],
967
+ optional: [],
968
+ },
969
+ };
970
+ return _readOpParamsCache;
971
+ }
985
972
  function buildWriteParams(writeFields, includeDedup = false) {
986
973
  var _a, _b;
987
974
  const required = [];
@@ -1101,7 +1088,7 @@ function getOperationPurpose(resource, resourceLabel, operation, readFields, wri
1101
1088
  case 'approve':
1102
1089
  return `Approve a pending ${resourceLabel} request by numeric ID.`;
1103
1090
  case 'reject':
1104
- return `Reject a pending ${resourceLabel} request by numeric ID. Provide an optional rejectReason string.`;
1091
+ return `Reject a pending ${resourceLabel} request by numeric ID. Provide rejectReason (required when rejectReasonPolicy='mandatory').`;
1105
1092
  case 'describeFields':
1106
1093
  return buildDescribeFieldsDescription(resourceLabel);
1107
1094
  case 'listPicklistValues':
@@ -1151,11 +1138,10 @@ function getOperationNotes(resource, operation) {
1151
1138
  case 'getUnassigned':
1152
1139
  case 'getBySLAStatus':
1153
1140
  case 'getByResource':
1154
- return [...contractNotes, ...LIST_ADVANCED_NOTES];
1155
1141
  case 'getMany':
1156
1142
  case 'getPosted':
1157
1143
  case 'getUnposted':
1158
- return [...contractNotes, ...LIST_ADVANCED_NOTES];
1144
+ return [...contractNotes, ...getListAdvancedNotes()];
1159
1145
  default:
1160
1146
  return [...contractNotes];
1161
1147
  }
@@ -1174,7 +1160,7 @@ function buildOperationDoc(resource, targetOperation, readFields, writeFields) {
1174
1160
  parameters = buildWriteParams(writeFields, targetOperation === 'createIfNotExists');
1175
1161
  }
1176
1162
  else {
1177
- parameters = (_a = READ_OP_PARAMS[targetOperation]) !== null && _a !== void 0 ? _a : { required: [], optional: [] };
1163
+ parameters = (_a = getReadOpParams()[targetOperation]) !== null && _a !== void 0 ? _a : { required: [], optional: [] };
1178
1164
  }
1179
1165
  const notes = getOperationNotes(resource, targetOperation);
1180
1166
  return { operation: targetOperation, purpose, parameters, notes };