iit-mcp-autotask 0.35.1 → 0.38.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 (46) hide show
  1. package/README.md +41 -32
  2. package/capabilities.json +10 -44
  3. package/dist/mocks/fixture-ops.d.ts +2 -16
  4. package/dist/mocks/fixture-ops.js +0 -2
  5. package/dist/tools/aggregate.d.ts +15 -3
  6. package/dist/tools/aggregate.js +39 -35
  7. package/dist/tools/capabilities-manifest.js +0 -1
  8. package/dist/tools/catalog-data.d.ts +12 -5
  9. package/dist/tools/catalog-data.js +17 -16
  10. package/dist/tools/context/ops.js +3 -3
  11. package/dist/tools/context/types.d.ts +2 -14
  12. package/dist/tools/dispatch.d.ts +15 -0
  13. package/dist/tools/dispatch.js +90 -16
  14. package/dist/tools/lists.js +38 -169
  15. package/dist/tools/meta.d.ts +0 -2
  16. package/dist/tools/meta.js +105 -82
  17. package/dist/tools/provenance-scrub.d.ts +20 -0
  18. package/dist/tools/provenance-scrub.js +209 -0
  19. package/dist/tools/rank.d.ts +7 -1
  20. package/dist/tools/rank.js +5 -7
  21. package/dist/tools/reasons.d.ts +2 -1
  22. package/dist/tools/reasons.js +10 -0
  23. package/dist/tools/resolves.d.ts +14 -1
  24. package/dist/tools/resolves.js +121 -38
  25. package/dist/tools/results/envelope.d.ts +2 -1
  26. package/dist/tools/results/envelope.js +2 -2
  27. package/dist/tools/results/errors.js +2 -0
  28. package/dist/tools/results/untrusted.d.ts +10 -5
  29. package/dist/tools/results/untrusted.js +9 -5
  30. package/dist/tools/schemas.d.ts +35 -72
  31. package/dist/tools/schemas.js +58 -108
  32. package/dist/tools/scope-guard.js +4 -2
  33. package/dist/tools/server-version.d.ts +1 -1
  34. package/dist/tools/server-version.js +1 -1
  35. package/dist/tools/server.d.ts +2 -2
  36. package/dist/tools/server.js +9 -8
  37. package/dist/tools/surface.js +1 -4
  38. package/dist/tools/write-guidance.d.ts +6 -1
  39. package/dist/tools/write-guidance.js +21 -2
  40. package/package.json +2 -2
  41. package/server.json +2 -2
  42. package/skills/company-360.md +12 -5
  43. package/skills/contract-profitability.md +3 -3
  44. package/skills/filter-semantics.md +5 -5
  45. package/skills/slim-reads.md +3 -3
  46. package/skills/unbilled-work-review.md +5 -5
package/README.md CHANGED
@@ -8,10 +8,10 @@ context into one call, a generic read layer for the long tail, one result envelo
8
8
  packaged playbooks, strong security boundaries, and predictable per-tenant in-process state (pooled
9
9
  client + SDK-owned TTL caches).
10
10
 
11
- > **Package status (2026-10-24):** the runtime dependency `node-autotask` is published to public npm
12
- > at `0.25.0`; the committed `pnpm-lock.yaml` pins that
11
+ > **Package status (2026-10-29):** the runtime dependency `node-autotask` is published to public npm
12
+ > at `0.38.0`; the committed `pnpm-lock.yaml` pins that
13
13
  > release with its integrity hash, so installs resolve deterministically. The dependency is always npm
14
- > (node-autotask pinned exactly at `0.25.0`) — never a `file:`, `git:`, `link:` or workspace reference
14
+ > (node-autotask pinned exactly at `0.38.0`) — never a `file:`, `git:`, `link:` or workspace reference
15
15
  > to a local checkout.
16
16
 
17
17
  > **0.6.0 is a breaking release** (unreleased): tool names, result shapes and configuration all
@@ -19,15 +19,16 @@ client + SDK-owned TTL caches).
19
19
 
20
20
  ## Tool surface
21
21
 
22
- The **core** toolset (12 tools) is always served; `AUTOTASK_TOOLSETS` adds `service`, `records` and
23
- `finance` (or `all`, 19 tools); the operator profile adds two. The whole tool surface (21 tools) is
24
- listed below. Every other operation in the SDK's 1,216-operation registry stays
22
+ The **core** toolset (10 tools) is always served; `AUTOTASK_TOOLSETS` adds `service`, `records` and
23
+ `finance` (or `all`, 17 tools); the operator profile adds two. The whole tool surface (19 tools) is
24
+ listed below. Every other operation in the SDK's 1,281-operation registry stays
25
25
  reachable through the generic layer: `autotask_catalog` finds the key, `autotask_describe` serves
26
- its contract, `autotask_read` runs it.
26
+ its contract (slim by default: transport and retry detail dropped, long text cut, the first two
27
+ examples; `full: true` serves the complete record, `summary: true` about 2 KB), `autotask_read` runs it.
27
28
 
28
29
  | Toolset | Tools |
29
30
  |---------|-------|
30
- | core (always) | `autotask_guide` · `autotask_catalog` · `autotask_describe` · `autotask_read` · `autotask_resolve` · `autotask_query` · `autotask_aggregate` · `autotask_fetch_many` · `autotask_tenant_status` · `autotask_ticket_full_context` · `autotask_company_full_profile` · `autotask_resource_workload` |
31
+ | core (always) | `autotask_guide` · `autotask_catalog` · `autotask_describe` · `autotask_read` · `autotask_resolve` · `autotask_query` · `autotask_tenant_status` · `autotask_ticket_full_context` · `autotask_company_full_profile` · `autotask_resource_workload` |
31
32
  | `service` | `autotask_project_get_context` · `autotask_service_call_full_profile` |
32
33
  | `records` | `autotask_contact_full_profile` · `autotask_configuration_item_profile` · `autotask_contract_full_profile` |
33
34
  | `finance` | `autotask_quote_get` · `autotask_invoice_get` (`format`: `record`, `html`, `xml`, `pdf`) |
@@ -35,12 +36,24 @@ its contract, `autotask_read` runs it.
35
36
 
36
37
  - `autotask_resolve` turns any identifier into a record: an id or name, `by: "ticket_number"`,
37
38
  `"serial"` or `"email"`, in one `resource` or swept across the five workflow resources;
38
- `match: "contains"` for companies, tickets (title) or a sweep. `resource: "resources"` finds staff by
39
+ the default `match: "auto"` tries exact, then contains on a miss for companies, tickets (title) or a
40
+ sweep (one hit: `ok` with `matched: "contains"`; several: `ambiguous`), `match: "exact"` has no
41
+ fallback, `match: "contains"` searches only. `resource: "resources"` finds staff by
39
42
  email, user name or name (active only; API users, which carry their owner's email, are excluded unless
40
43
  `include_api_users: true`; `by: "email"` narrows to the email field; a numeric identifier is looked up
41
- as a Resource id); compact rows carry `apiUser` and the licence label when the tenant's labels tell it, never an email. Every match carries `ref`, its human reference. A sweep returns compact matches (`resource`, `ref`, `id`, the human fields) unless `expand: true` (the full record under `record`); `limit` caps the TOTAL matches across resources (resource order, then the SDK's order) and a contains sweep reports `truncated` and per-resource `counts {returned, found}`.
42
- - `autotask_query` is the bounded filtered list over `resource`: `tickets`, `time_entries`,
43
- `service_calls`, `contracts`, `time_off_requests` (limit default 50, max 2500). The served filter
44
+ as a Resource id); compact rows carry `apiUser` and the licence label when the tenant's labels tell it, never an email. Every match carries `ref`, its human reference, as safe text: control and markup characters (`< > { } [ ] \` " | \\`) dropped, whitespace collapsed, at most 80 characters (a cut ends with `…`), never wrapped; still vendor-authored data (#230). A sweep returns compact matches (`resource`, `ref`, `id`, the human fields) unless `expand: true` (the record under `record`, without its `[REDACTED]`, null and empty fields; `expand: "full"` keeps them); `limit` caps the TOTAL matches across resources (resource order, then the SDK's order) and a contains sweep reports `truncated` and per-resource `counts {returned, found}`.
45
+ - `autotask_query` is the bounded filtered list over `resource`: any resource with a
46
+ `<resource>.query` read (`tickets`, `time_entries`, `service_calls`, `contracts`,
47
+ `time_off_requests`, `companies`, `billing_items`, ...; limit default 50, max 2500). With
48
+ `group_by`, `sum` or `count: true` it answers totals instead of rows (the SDK's
49
+ `operations.aggregate`; `top` 1-100 groups, `limit` up to 2,500 scanned rows; `hasMore` /
50
+ `groupsTruncated` mean partial). Records by id: `filter: [{field: "id", op: "in", value: "1,2,3"}]`
51
+ (`in` / `notIn` take an array or a comma string). Time entries have no `companyID`: the relation
52
+ filter `{field: "ticket.companyID", op: "eq", value: "<id>"}` (also `task.<field>`; `project.<field>`
53
+ where the entity has the FK) scopes them through one parent read of at most 300 ids (more is a typed
54
+ refusal; no parent match is an empty result with a `relationNote`), in rows, counts and totals.
55
+ The separate `autotask_aggregate` and `autotask_fetch_many` tools are removed (#228, 0.37.0); a
56
+ mixed-resource batch by id is `autotask_read` `operations.fetchByIds`. The served filter
44
57
  schema declares each condition as `{field, op, value}` strings, so a client that builds one type
45
58
  per array column writes `"29"`, `"true"`, `"2026-10-22"`; numbers, `in` arrays and and/or groups are
46
59
  still accepted and passed through unchanged.
@@ -51,11 +64,10 @@ its contract, `autotask_read` runs it.
51
64
  fields, because form-filling clients (Copilot Studio) fill every optional input they are shown. A
52
65
  client that cannot send a mixed-type array asks for a time entry window over a resource with
53
66
  `autotask_query` `time_entries` (`resourceID` eq, `dateWorked` gte/lte, every value a string: the
54
- served filter schema types `value` as a string). `autotask_read` and `autotask_fetch_many` answer ONE envelope (the next minor,
67
+ served filter schema types `value` as a string). `autotask_read` answers ONE envelope (the next minor,
55
68
  breaking): `data = { results: Slot[], ok, failed, budget?, budgetWarning? }`, an array in input
56
69
  order even for a single read (`data.results[0].result`). A slot is `{ ok: true, operation, result,
57
- windowNote?, scopeNote? }` or `{ ok: false, operation, error }`; fetch_many slots also echo `resource` and `id`
58
- (`operation` is `<resource>.get`; a miss is `ok: false` with `NOT_FOUND`). A single read that fails
70
+ windowNote?, scopeNote? }` or `{ ok: false, operation, error }`. A single read that fails
59
71
  still answers the whole-call error envelope.
60
72
  - `autotask_guide` serves the packaged playbooks — the server's skill layer (below).
61
73
 
@@ -96,7 +108,7 @@ part miss never fails the call (typed `absent`); only a missing root record fail
96
108
  (`src/tools/reasons.ts`). Vendor free text is wrapped in `<untrusted_content>`,
97
109
  stripped of every control (except newline and tab), format and default-ignorable character (bidi,
98
110
  zero-width, tag characters, variation selectors) and capped at 500 characters
99
- (`truncated: true` on the envelope); the wrapper is the injection control — the angle-bracket
111
+ (`textCapped: true` on the envelope; row cuts are `hasMore`); the wrapper is the injection control — the angle-bracket
100
112
  escape earlier builds added was removed in 0.9.0 (issue #70): it was pure cost for LLM consumers
101
113
  and broke read-modify-write round-trips, so wrapped text carries raw `&`/`<`/`>` and is data,
102
114
  never instructions. User-defined field values get the same treatment; `output_path` files keep the
@@ -336,18 +348,15 @@ decoded bytes for `pdf`) under the same root rules — never a JSON envelope, ne
336
348
  to the exact vendor markup); a `pdf` past the vendor 1 MiB inline limit is truncated and carries no
337
349
  data (and no file).
338
350
 
339
- **`enrich` resolves FK ids to names in the same call** (issue #67): `enrich: ['company',
340
- 'resource', 'assignedResource', 'ticket']` — the keys are per-resource (a key that does not apply to
341
- the resource is refused pre-dispatch, zero wire) — fetches the referenced records per key — a
342
- `fetchByIds` batch of 250 distinct ids, then one authority read per id the batch did not return
343
- (query-blind resources are read one by one) — and lands `<key>Name` in each row (`companyName`, the
344
- joined `firstName middleName lastName`, `ticketNumber`). A key with more than 500 distinct ids is
345
- skipped (its rows are null) and named in the summary; `include_fields` never blinds it — the server
346
- adds the keys' FK ids to the outgoing projection; a miss (an id the vendor does not resolve) is a
347
- `null` on that row only. There is no `contact` key in this release: the SDK's bulk id read refuses
348
- parent-scoped resources (`contacts` is one — its get needs (parentId, id)) before the request,
349
- after the query wire is spent; contact names stay reachable via `autotask_resolve` and the
350
- composite tools, and a scoped contact read is a follow-up.
351
+ **Labels come with the rows** (node-autotask 0.38.0, #227): query rows, `group_by` keys and
352
+ fetched records carry SDK label siblings by default: picklists as `<field>Label` (`statusLabel`),
353
+ ids as `<field>Name` (`companyIDName`, `contractIDName`, `resourceIDName`, `ticketIDName`, ...;
354
+ `assignedResourceName` stays as a deprecated alias for one release). Each name is capped at 200
355
+ characters and wrapped once as untrusted content; since node-autotask 0.39.0 the sibling key is always present, `null` when the id is null, 0 or unresolved (or the picklist value is null); a `names` note says when
356
+ the SDK's cold-read cap left something unlabelled; `names: false` turns labels off. The server-side
357
+ `enrich` parameter is removed (0.37.0): a call that still sends it is refused as an unknown input; read
358
+ the SDK label instead (`companyIDName` for the old `companyName`, `resourceIDName` for `resourceName`,
359
+ `ticketIDName` for `ticketName`).
351
360
 
352
361
  ## Transports
353
362
 
@@ -763,7 +772,7 @@ independent review with its resolutions, [`docs/reviews/0.6.0.md`](docs/reviews/
763
772
 
764
773
  ## SDK dependency
765
774
 
766
- `iit-mcp-autotask` depends on **`node-autotask` from npm at exactly `0.25.0`** (an exact pin —
775
+ `iit-mcp-autotask` depends on **`node-autotask` from npm at exactly `0.38.0`** (an exact pin —
767
776
  no range operator; subpaths `.` / `/capabilities` / `/operations` / `/resources` / `/types` /
768
777
  `/pool`) — the only **vendor** dependency; the other two runtime dependencies are
769
778
  `@modelcontextprotocol/server` (the MCP framework) and `zod` (schema validation). It never
@@ -773,7 +782,7 @@ user — the published tarball ships no lockfile) must resolve to the release th
773
782
  reviewed against, or the plan-hash boot check refuses to start.
774
783
 
775
784
  **Publishing `node-autotask` is the maintainer's action, done manually.** It is published on the
776
- public registry at `0.25.0` (published 2026-10-23); the
785
+ public registry at `0.37.0` (published 2026-10-28 UTC); the
777
786
  exact pin and the committed `pnpm-lock.yaml` (with `pnpm-workspace.yaml`) pin the real published
778
787
  tarball with its integrity hash, so installs resolve to exactly that release — the lockfile
779
788
  freezes the pnpm/git/Docker paths, the pin freezes the npm/npx paths. The docs are written against this
@@ -867,9 +876,9 @@ physical assets (Autotask models them as Configuration Items — covered).
867
876
 
868
877
  | # | Item | Status |
869
878
  |---|------|--------|
870
- | B1 | **Project phase & task lists** (W7 sub-capability): no `project_tasks`/`project_phases` operations in the SDK's 1,216-op registry. W7 ships with project status + context; phase/task lists excluded and documented. Classification (SDK gap vs vendor API-version limit) is decided by recon's spec-side check (A2). | blocked — documented |
879
+ | B1 | **Project phase & task lists** (W7 sub-capability): no `project_tasks`/`project_phases` operations in the SDK's 1,281-op registry. W7 ships with project status + context; phase/task lists excluded and documented. Classification (SDK gap vs vendor API-version limit) is decided by recon's spec-side check (A2). | blocked — documented |
871
880
  | B2 | **Time-entry writes on ww29/australia-nz for API-only users**: vendor 500 (live-verified). Time-entry creation works where the API user is a real resource, through the operator profile (`log-time-entry`); on ww29 the write is classified `failed` (a 500 with a vendor `errors[]` body), never `unknown`. | zone limitation |
872
- | B3 | **`time_entries.getPosted`/`getUnposted`: the zone's TimeEntry entity has no `postedDate`**, so SDK 0.31 and earlier 500ed (and `postedWithin`/`dateWorkedWindow` silently counted every entry unposted). SDK 0.32.0 derives posted state from a `billing_items.timeEntryID` join and returns `{ items, hasMore, window, approvedCount, unapprovedCount }` (optional date-only `dateFrom`/`dateTo`; no dates reads only the first 500 entries). Needs BillingItems read permission (one extra request per call); `approvedCount`/`unapprovedCount` are not posted state. Reachable via the generic layer. | resolved in SDK 0.32.0 (not re-verified live) |
881
+ | B3 | **`time_entries.getPosted`/`getUnposted`: the zone's TimeEntry entity has no `postedDate`**, so SDK 0.31 and earlier 500ed (and `postedWithin`/`dateWorkedWindow` silently counted every entry unposted). SDK 0.32.0 derives posted state from a `billing_items.timeEntryID` join and returns `{ items, hasMore, window, approvedCount, unapprovedCount }` (optional date-only `dateFrom`/`dateTo`; no dates reads only the first 500 entries). Needs BillingItems read permission (one extra request per call); `approvedCount`/`unapprovedCount` are not posted state. SDK 0.37.0 adds `cursor`/`nextCursor` (page past 500 entries), `unconfirmed` (entries whose posted state is unknown, kept out of both counts) and `countOnly: true` on the four posted-state ops. Reachable via the generic layer. | resolved in SDK 0.32.0 (not re-verified live) |
873
882
  | B4 | **CI create on tenants without products** (ww29 product wall: `products.count = 0`): `configuration_items.create` requires an existing `productID`. No dedicated CI-create in v1; the wall is documented on the generic side. | zone limitation |
874
883
  | B5 | **Blind count/query surfaces on ww29** (companies not owned by the caller; tickets): count/query return 0/empty even when data exists. Not fixable client-side; the surface carries the typed blind-surface sentinel — `get(id)`/full-profile are the authorities. | zone limitation |
875
884
  | B6 | **`node-autotask` npm publish**: the Phase-C install gate re-checks the registry; if still 404 the build pauses and escalates (SCOPING-resolved: the user publishes — no git/file dependency fallback). | open — user action |
package/capabilities.json CHANGED
@@ -3,10 +3,10 @@
3
3
  "domain": "autotask",
4
4
  "description": "Machine-readable capability catalogue for iit-mcp-autotask: the tools the server can serve, with the toolset and the lowest profile that serves each. Everything else in the registry is reachable through the generic layer (autotask_catalog / autotask_describe / autotask_read) and documented in docs/capability-matrix.md. Generated from src/tools/surface.ts by scripts/build-capabilities.mjs.",
5
5
  "exposedToolCount": {
6
- "readonlyCore": 12,
7
- "readonlyAll": 19,
8
- "operatorCore": 14,
9
- "operatorAll": 21
6
+ "readonlyCore": 10,
7
+ "readonlyAll": 17,
8
+ "operatorCore": 12,
9
+ "operatorAll": 19
10
10
  },
11
11
  "profiles": {
12
12
  "readonly": {
@@ -24,8 +24,6 @@
24
24
  "autotask_read",
25
25
  "autotask_resolve",
26
26
  "autotask_query",
27
- "autotask_aggregate",
28
- "autotask_fetch_many",
29
27
  "autotask_tenant_status",
30
28
  "autotask_ticket_full_context",
31
29
  "autotask_company_full_profile",
@@ -69,7 +67,7 @@
69
67
  },
70
68
  {
71
69
  "id": "autotask.platform.catalog",
72
- "description": "Find the exact operation key for anything without a dedicated tool: ranked search (q) over the 1,216 registry operations, with effect and whether this deployment serves it. No vendor call.",
70
+ "description": "Find the exact operation key for anything without a dedicated tool: ranked search (q) over the 1,281 registry operations, with effect and whether this deployment serves it. No vendor call.",
73
71
  "mcpPrimitive": "tool",
74
72
  "toolName": "autotask_catalog",
75
73
  "type": "read",
@@ -101,7 +99,7 @@
101
99
  },
102
100
  {
103
101
  "id": "autotask.generic.read",
104
- "description": "Run reads by exact registry key, or up to 10 in one call: unbilled work, profitability and margin, SLA health. *.query: 50 rows; scope by company/contract ids (unscoped = tenant walk); roll-ups: autotask_aggregate; slim: includeFields, noteChars.",
102
+ "description": "Run reads by exact registry key, or up to 10 in one call: unbilled work, profitability and margin, SLA health. *.query: 50 rows; scope by company/contract ids (unscoped = tenant walk); totals: autotask_query group_by; slim: includeFields, noteChars.",
105
103
  "mcpPrimitive": "tool",
106
104
  "toolName": "autotask_read",
107
105
  "type": "read",
@@ -117,7 +115,7 @@
117
115
  },
118
116
  {
119
117
  "id": "autotask.discovery.resolve",
120
- "description": "Find the record behind an identifier: id or name, ticket number, CI serial, contact email (by), staff by email, user name or name (resource resources, include_api_users); one resource or a sweep. Exact by default; contains: companies, tickets, sweep.",
118
+ "description": "Find the record for an identifier: id or name, ticket number, CI serial, contact email (by), staff by email, user name or name (resource resources, include_api_users); one resource or a sweep. Exact, then contains on a miss.",
121
119
  "mcpPrimitive": "tool",
122
120
  "toolName": "autotask_resolve",
123
121
  "type": "read",
@@ -133,45 +131,13 @@
133
131
  },
134
132
  {
135
133
  "id": "autotask.discovery.query",
136
- "description": "List records with a filter: tickets, time entries, service calls, contracts or time-off requests. Scope by company/contract ids (unscoped = tenant walk); roll-ups: autotask_aggregate. default 50, max 2500; latest N. Slim: include_fields, note_chars.",
134
+ "description": "List records or totals with a filter: tickets, time entries, service calls, contracts, time off, more. Totals, no rows: group_by, sum, count (hours logged by ticket, how many match). By id: id in. Scope by company ids (unscoped = tenant walk).",
137
135
  "mcpPrimitive": "tool",
138
136
  "toolName": "autotask_query",
139
137
  "type": "read",
140
138
  "toolset": "core",
141
139
  "profile": "readonly",
142
- "backingOperation": "tickets.query | time_entries.query | service_calls.query | contracts.query | time_off_requests.query",
143
- "annotations": {
144
- "readOnly": true,
145
- "idempotent": true,
146
- "destructive": false,
147
- "openWorld": true
148
- }
149
- },
150
- {
151
- "id": "autotask.core.aggregate",
152
- "description": "Totals, counts and group-by sums (hours by ticket) over any <resource>.query table without reading rows. No groupBy or sum = one count call. hasMore or groupsTruncated = a partial answer, never a tenant total.",
153
- "mcpPrimitive": "tool",
154
- "toolName": "autotask_aggregate",
155
- "type": "read",
156
- "toolset": "core",
157
- "profile": "readonly",
158
- "backingOperation": "operations.aggregate",
159
- "annotations": {
160
- "readOnly": true,
161
- "idempotent": true,
162
- "destructive": false,
163
- "openWorld": true
164
- }
165
- },
166
- {
167
- "id": "autotask.discovery.fetch_many",
168
- "description": "Fetch up to 500 records by numeric id across mixed resources in one call: items: [{resource, id, fields?}]. Answers data.results[{ok, operation, result|error}], ok, failed (same as autotask_read); a miss is a typed NOT_FOUND slot.",
169
- "mcpPrimitive": "tool",
170
- "toolName": "autotask_fetch_many",
171
- "type": "read",
172
- "toolset": "core",
173
- "profile": "readonly",
174
- "backingOperation": "operations.fetchByIds",
140
+ "backingOperation": "tickets.query | time_entries.query | service_calls.query | contracts.query | time_off_requests.query | operations.aggregate",
175
141
  "annotations": {
176
142
  "readOnly": true,
177
143
  "idempotent": true,
@@ -395,7 +361,7 @@
395
361
  "autotask_describe",
396
362
  "autotask_read"
397
363
  ],
398
- "backingRegistryOperations": 1280,
364
+ "backingRegistryOperations": 1281,
399
365
  "note": "autotask_catalog finds the exact operation key (ranked search), autotask_describe serves its contract, autotask_read runs any effect=read key. Writes go through the operator profile (autotask_write then autotask_apply); authenticate.* is never served."
400
366
  },
401
367
  "schemas": {}
@@ -20,7 +20,7 @@ import type { InvokeOptions } from 'node-autotask/operations';
20
20
  import type { HourlyBudget, QueryWalkParams, QueryWalkResult, ToolClient, ToolOps } from '../tools/context.js';
21
21
  /** One recorded ops invocation (the handler-driven vendor-call view). */
22
22
  export interface OpCall {
23
- method: 'invokeRead' | 'invokeWrite' | 'invokeDelete' | 'resolveAny' | 'searchAcrossResources' | 'fetchMany' | 'fetchByIds' | 'relationshipMap' | 'queryWalk' | 'hourlyBudget';
23
+ method: 'invokeRead' | 'invokeWrite' | 'invokeDelete' | 'resolveAny' | 'searchAcrossResources' | 'relationshipMap' | 'queryWalk' | 'hourlyBudget';
24
24
  client: ToolClient;
25
25
  /** The operation key (invoke*) or the helper's primary argument. */
26
26
  target: string;
@@ -35,7 +35,7 @@ export type FixtureBehaviour = {
35
35
  kind: 'error';
36
36
  error: () => unknown;
37
37
  }
38
- /** The helper-shaped outcomes (resolveAny / searchAcrossResources / fetchMany). */
38
+ /** The helper-shaped outcomes (resolveAny / searchAcrossResources). */
39
39
  | {
40
40
  kind: 'helper';
41
41
  value: () => unknown;
@@ -83,20 +83,6 @@ export declare class MockToolOps implements ToolOps {
83
83
  resources?: readonly string[];
84
84
  limit?: number;
85
85
  }, signal?: AbortSignal) => Promise<unknown>;
86
- fetchMany: (client: ToolClient, items: readonly Readonly<{
87
- resource: string;
88
- id: number;
89
- fields?: string[];
90
- }>[], opts?: {
91
- maxItems?: number;
92
- }, signal?: AbortSignal) => Promise<unknown>;
93
- fetchByIds: (client: ToolClient, items: readonly Readonly<{
94
- resource: string;
95
- id: number | string;
96
- fields?: string[];
97
- }>[], opts?: {
98
- maxItems?: number;
99
- }, signal?: AbortSignal) => Promise<unknown>;
100
86
  relationshipMap: (client: ToolClient, resourceKey: string) => Promise<unknown>;
101
87
  /** The `output_path` walk (target = the query operation key). */
102
88
  queryWalk: (client: ToolClient, operation: string, params: QueryWalkParams, maxRows: number, signal?: AbortSignal) => Promise<QueryWalkResult | undefined>;
@@ -108,8 +108,6 @@ export class MockToolOps {
108
108
  invokeDelete = (client, operation, args, opts, signal) => this.answer(client, 'invokeDelete', operation, args, opts, signal);
109
109
  resolveAny = (client, identifier, opts, signal) => this.answer(client, 'resolveAny', 'resolveAny', [identifier, opts?.resources], undefined, signal);
110
110
  searchAcrossResources = (client, query, opts, signal) => this.answer(client, 'searchAcrossResources', 'searchAcrossResources', [query, opts?.resources, opts?.limit], undefined, signal);
111
- fetchMany = (client, items, opts, signal) => this.answer(client, 'fetchMany', 'fetchMany', [items, opts?.maxItems], undefined, signal);
112
- fetchByIds = (client, items, opts, signal) => this.answer(client, 'fetchByIds', 'fetchByIds', [items, opts?.maxItems], undefined, signal);
113
111
  relationshipMap = (client, resourceKey) => this.answer(client, 'relationshipMap', 'relationshipMap', [resourceKey], undefined, undefined);
114
112
  /** The `output_path` walk (target = the query operation key). */
115
113
  queryWalk = (client, operation, params, maxRows, signal) => this.answer(client, 'queryWalk', operation, [params, maxRows], undefined, signal);
@@ -1,4 +1,16 @@
1
1
  import type { ToolContext } from './context.js';
2
- import type { BuiltTool } from './surface.js';
3
- /** `autotask_aggregate`: counts, totals and group-by sums over one `<resource>.query` surface. */
4
- export declare function buildAggregate(ctx: ToolContext): BuiltTool;
2
+ import { type ToolReturn } from './results.js';
3
+ /** The SDK aggregate opts this server passes (the SDK's own names). */
4
+ export interface AggregateOpts {
5
+ groupBy?: string[];
6
+ sum?: string[];
7
+ top?: number;
8
+ limit?: number;
9
+ /** false: no label siblings on the group keys (SDK default on). */
10
+ names?: boolean;
11
+ }
12
+ /**
13
+ * Counts, totals and group-by sums over one `<resource>.query` surface: `autotask_query` with
14
+ * group_by / sum / count (#228). `extra` rides the data.
15
+ */
16
+ export declare function runAggregate(ctx: ToolContext, operation: string, rawFilter: unknown, opts: AggregateOpts, signal: AbortSignal | undefined, extra?: Record<string, unknown>): Promise<ToolReturn>;
@@ -1,21 +1,22 @@
1
1
  /**
2
- * tools/aggregate.ts — `autotask_aggregate` (#197): a thin wrapper over the SDK's
3
- * `operations.aggregate`, served in core so a roll-up question (totals, counts, group-by sums) is one
4
- * call to a tool the model sees, not a ~50 KB discovery trip through catalog, describe and read.
2
+ * tools/aggregate.ts — the totals mode of `autotask_query` (`group_by` / `sum` / `count`; #197, #228):
3
+ * a thin wrapper over the SDK's `operations.aggregate`, so a roll-up question (totals, counts, group-by
4
+ * sums) is one call to a core tool, not a ~50 KB discovery trip through catalog, describe and read.
5
5
  *
6
6
  * Nothing is reimplemented: the SDK validates the operation, the field names and the caps (top 1-100,
7
7
  * limit at most 2,500), folds the rows and reports its own truncation honesty (`hasMore`,
8
- * `groupsTruncated`, `totalScanned`, `blind`); the tool passes the answer through unchanged. What the
9
- * tool adds is the gates every read passes (the deployment policy on both the helper and the target
10
- * surface, the inbound untrusted-wrapper strip, the filter shape) and the untrusted marking of the
11
- * answer by the TARGET resource's field stamps, the same list `autotask_read` of `<resource>.query` uses.
8
+ * `groupsTruncated`, `totalScanned`, `blind`); the answer passes through unchanged. What this adds is
9
+ * the gates every read passes (the deployment policy on both the helper and the target surface, the
10
+ * inbound untrusted-wrapper strip, the filter shape) and the untrusted marking of the answer by the
11
+ * TARGET resource's field stamps, the same list `autotask_read` of `<resource>.query` uses.
12
12
  */
13
13
  import { AutotaskConfigError } from 'node-autotask';
14
14
  import { stripUntrustedDeep } from 'node-autotask/untrusted';
15
- import { AGGREGATE_INPUT, assertFilterShape, closedInput } from './schemas.js';
15
+ import { assertFilterShape } from './schemas.js';
16
16
  import { abortedAnswer } from './dispatch.js';
17
17
  import { markResult, ok } from './results.js';
18
18
  import { assertOperationPermitted } from '../policy.js';
19
+ import { scopedCatalogCall } from './field-pointer.js';
19
20
  /** The server line for an SDK aggregate answer: numbers only, and the partial marker whenever the SDK flags truncation. */
20
21
  function summaryLine(operation, result) {
21
22
  const r = (result ?? {});
@@ -28,32 +29,35 @@ function summaryLine(operation, result) {
28
29
  const blindNote = r.blindSurface !== undefined && r.blindSurface !== false ? ' blindSurface: the surface could not be read, an empty result is not a true zero.' : '';
29
30
  return `${operation}: ${scanned}${groups}.${blindNote}${partial ? ' PARTIAL: the scan or the group list was cut short (hasMore / groupsTruncated); these are not tenant-wide totals, narrow the filter.' : ''}`;
30
31
  }
31
- /** `autotask_aggregate`: counts, totals and group-by sums over one `<resource>.query` surface. */
32
- export function buildAggregate(ctx) {
33
- return {
34
- title: 'Total, count or group-by sum over a query surface',
35
- input: closedInput(AGGREGATE_INPUT),
36
- run: async (raw, signal) => {
37
- const args = raw;
38
- const helper = 'operations.aggregate';
39
- assertOperationPermitted(helper, ctx.record(helper), ctx.config, ctx.log);
40
- const target = ctx.record(args.operation);
41
- if (target === undefined) {
42
- throw new AutotaskConfigError(`aggregate: unknown operation "${args.operation}": it is not in the capability registry (find the exact key with autotask_catalog)`, { operation: helper });
43
- }
44
- assertOperationPermitted(args.operation, target, ctx.config, ctx.log);
45
- assertFilterShape(args.filter, helper);
46
- const filter = stripUntrustedDeep(args.filter);
47
- // The SDK's positional form: [operation, filter] and the opts bag only when a fold or cap was set.
48
- const opts = {};
49
- for (const key of ['groupBy', 'sum', 'top', 'limit'])
50
- if (args[key] !== undefined)
51
- opts[key] = args[key];
52
- const answer = await ctx.ops.invokeRead(ctx.client, helper, Object.keys(opts).length > 0 ? [args.operation, filter, opts] : [args.operation, filter], undefined, signal);
53
- if (answer === undefined)
54
- return abortedAnswer(ctx.correlationId);
55
- return ok(summaryLine(args.operation, answer), { operation: args.operation, result: markResult(answer, ctx.untrustedFields(args.operation)) });
56
- },
57
- };
32
+ /**
33
+ * Counts, totals and group-by sums over one `<resource>.query` surface: `autotask_query` with
34
+ * group_by / sum / count (#228). `extra` rides the data.
35
+ */
36
+ export async function runAggregate(ctx, operation, rawFilter, opts, signal, extra = {}) {
37
+ const helper = 'operations.aggregate';
38
+ assertOperationPermitted(helper, ctx.record(helper), ctx.config, ctx.log);
39
+ const target = ctx.record(operation);
40
+ if (target === undefined) {
41
+ throw new AutotaskConfigError(`aggregate: unknown operation "${operation}": it is not in the capability registry (find the exact key with autotask_catalog)`, { operation: helper });
42
+ }
43
+ assertOperationPermitted(operation, target, ctx.config, ctx.log);
44
+ assertFilterShape(rawFilter, helper);
45
+ // the conditions array (a params bag's own filter when the caller sent the bag form)
46
+ const conditions = Array.isArray(rawFilter) ? rawFilter : (rawFilter.filter ?? []);
47
+ const filter = stripUntrustedDeep(conditions);
48
+ // The SDK's positional form: [operation, filter] and the opts bag only when a fold or cap was set.
49
+ const bag = {};
50
+ for (const key of ['groupBy', 'sum', 'top', 'limit', 'names'])
51
+ if (opts[key] !== undefined)
52
+ bag[key] = opts[key];
53
+ const answer = await ctx.ops.invokeRead(ctx.client, helper, Object.keys(bag).length > 0 ? [operation, filter, bag] : [operation, filter], undefined, signal).catch((err) => {
54
+ // node-autotask #280: a bad filter / groupBy / sum name is a zero-wire CONFIG_ERROR; point at the scoped field catalog
55
+ if (!(err instanceof AutotaskConfigError) || !err.message.includes('is not a field of'))
56
+ throw err;
57
+ throw new AutotaskConfigError(err.message, { operation: helper, suggestedAction: `Use exact vendor field names: ${scopedCatalogCall(operation.slice(0, operation.indexOf('.')))}.` });
58
+ });
59
+ if (answer === undefined)
60
+ return abortedAnswer(ctx.correlationId);
61
+ return ok(summaryLine(operation, answer), { ...extra, operation, result: markResult(answer, ctx.untrustedFields(operation)) });
58
62
  }
59
63
  //# sourceMappingURL=aggregate.js.map
@@ -9,7 +9,6 @@ const CAPABILITY_IDS = {
9
9
  autotask_read: 'autotask.generic.read',
10
10
  autotask_resolve: 'autotask.discovery.resolve',
11
11
  autotask_query: 'autotask.discovery.query',
12
- autotask_fetch_many: 'autotask.discovery.fetch_many',
13
12
  autotask_tenant_status: 'autotask.identity.tenant_status',
14
13
  autotask_ticket_full_context: 'autotask.ticket.full_context',
15
14
  autotask_company_full_profile: 'autotask.company.full_profile',
@@ -19,6 +19,11 @@ export interface CatalogRow {
19
19
  reach: string | null;
20
20
  reachReason: string | null;
21
21
  boundedAlternative: string | null;
22
+ /** node-autotask 0.38.0 (#277): a repeated date-window helper, left out of default listings (still callable by key). */
23
+ hidden?: boolean;
24
+ hiddenReason?: string;
25
+ /** node-autotask 0.38.0: superseded (time_entries.recentTickets). */
26
+ deprecated?: boolean;
22
27
  }
23
28
  export interface MetaToolSpec {
24
29
  name: string;
@@ -56,6 +61,8 @@ export declare function recordById(operation: string): CapabilityRecord | undefi
56
61
  * name the deployment does not serve.
57
62
  */
58
63
  export declare const TOOL_NAME_BY_OP: Readonly<Record<string, string>>;
64
+ /** The tool that serves one registry key: the table above, else `autotask_query` for any `<resource>.query` read (#228). */
65
+ export declare function toolForOp(operation: string): string | undefined;
59
66
  /** The served tool for this row's operation in a deployment serving `served`, or `null`. */
60
67
  export declare function servedToolName(row: CatalogRow, served: ReadonlySet<string>): string | null;
61
68
  /** The registry keys projected onto an MCP tool name (the reverse of {@link TOOL_NAME_BY_OP}); empty when none. */
@@ -65,10 +72,10 @@ export declare const UNTRUSTED_NOTE = "Externally-sourced free text in the resul
65
72
  /** The record's plan-derived untrusted field list (the registry stamp). */
66
73
  export declare function untrustedFieldsOf(rec: CapabilityRecord): string[];
67
74
  /**
68
- * The resources that carry user-defined fields (#183 S8), derived from the registry with zero wire:
69
- * the plan's `udfCapable` entities (the ones whose SDK resource class normalises `userDefinedFields`)
70
- * are exactly the resources whose records stamp `UDF1` in `metadata.untrustedFields`. Not the
71
- * `getUdfInfo` set (every vendor entity has that metadata read, UDFs or not). A test pins the exact set.
75
+ * The resources that carry user-defined fields (#183 S8), read from the registry with zero wire:
76
+ * each record's published `udfCapable` flag (node-autotask 0.37.0 #254; the plan's entities whose SDK
77
+ * resource class normalises `userDefinedFields`). Not the `getUdfInfo` set (every vendor entity has
78
+ * that metadata read, UDFs or not). A test pins the exact set.
72
79
  */
73
80
  export declare const UDF_CAPABLE: ReadonlySet<string>;
74
81
  /** The per-tool description budget (A0 slimming: every served description <= 250 chars). */
@@ -109,7 +116,7 @@ export declare function cacheOptionsForPreset(preset: 'on' | 'off', fieldCacheTt
109
116
  * fails the boot (or the CI test) instead of quietly shipping a tool surface that no longer
110
117
  * describes the SDK behind it.
111
118
  */
112
- export declare const BUILT_AGAINST_PLAN_HASH = "08cf890fa1b2b50574638a1364993fced7e83a5dfd4f630763d80cdc01d5a5f1";
119
+ export declare const BUILT_AGAINST_PLAN_HASH = "9bb87de7bcbda60f6c44955dba52b8e61e9c5547750ea8acacfcb6f261d7734f";
113
120
  /**
114
121
  * Fail fast when the installed SDK's plan has moved out from under this surface.
115
122
  * Called at boot (main.ts) and safe to call repeatedly (pure comparison).
@@ -4,7 +4,7 @@
4
4
  * Two sources, both shipped inside the `node-autotask` package:
5
5
  * - `MCP_TOOL_CATALOG.json` — the machine catalog: one row per registry operation (projected
6
6
  * tool, effect, dry-run/confirm, required args, tier, reach) and the plan hash;
7
- * - the compiled capability registry (`node-autotask/capabilities`) — the 1,216-operation
7
+ * - the compiled capability registry (`node-autotask/capabilities`) — the 1,281-operation
8
8
  * records the SDK's own invoke governor resolves against.
9
9
  *
10
10
  * The JSON file is NOT behind the package's `exports` map, so it is read from the installed
@@ -51,7 +51,6 @@ export const TOOL_NAME_BY_OP = {
51
51
  'threshold_information.get': 'autotask_tenant_status',
52
52
  'operations.resolveAny': 'autotask_resolve',
53
53
  'operations.searchAcrossResources': 'autotask_resolve',
54
- 'operations.fetchByIds': 'autotask_fetch_many',
55
54
  'operations.relationshipMap': 'autotask_describe',
56
55
  'companies.resolve': 'autotask_resolve',
57
56
  'companies.search': 'autotask_resolve',
@@ -67,7 +66,7 @@ export const TOOL_NAME_BY_OP = {
67
66
  'service_calls.query': 'autotask_query',
68
67
  'contracts.query': 'autotask_query',
69
68
  'time_off_requests.query': 'autotask_query',
70
- 'operations.aggregate': 'autotask_aggregate',
69
+ 'operations.aggregate': 'autotask_query',
71
70
  'tickets.fullContext': 'autotask_ticket_full_context',
72
71
  'companies.fullProfile': 'autotask_company_full_profile',
73
72
  'projects.getContext': 'autotask_project_get_context',
@@ -82,9 +81,13 @@ export const TOOL_NAME_BY_OP = {
82
81
  'invoices.markupXml': 'autotask_invoice_get',
83
82
  'invoices.pdf': 'autotask_invoice_get',
84
83
  };
84
+ /** The tool that serves one registry key: the table above, else `autotask_query` for any `<resource>.query` read (#228). */
85
+ export function toolForOp(operation) {
86
+ return TOOL_NAME_BY_OP[operation] ?? (operation.endsWith('.query') && recordById(operation)?.effect === 'read' ? 'autotask_query' : undefined);
87
+ }
85
88
  /** The served tool for this row's operation in a deployment serving `served`, or `null`. */
86
89
  export function servedToolName(row, served) {
87
- const tool = TOOL_NAME_BY_OP[row.operation] ?? (row.effect === 'read' ? undefined : 'autotask_write');
90
+ const tool = toolForOp(row.operation) ?? (row.effect === 'read' ? undefined : 'autotask_write');
88
91
  return tool !== undefined && served.has(tool) ? tool : null;
89
92
  }
90
93
  /** The registry keys projected onto an MCP tool name (the reverse of {@link TOOL_NAME_BY_OP}); empty when none. */
@@ -102,12 +105,12 @@ export function untrustedFieldsOf(rec) {
102
105
  return Array.isArray(list) ? list : [];
103
106
  }
104
107
  /**
105
- * The resources that carry user-defined fields (#183 S8), derived from the registry with zero wire:
106
- * the plan's `udfCapable` entities (the ones whose SDK resource class normalises `userDefinedFields`)
107
- * are exactly the resources whose records stamp `UDF1` in `metadata.untrustedFields`. Not the
108
- * `getUdfInfo` set (every vendor entity has that metadata read, UDFs or not). A test pins the exact set.
108
+ * The resources that carry user-defined fields (#183 S8), read from the registry with zero wire:
109
+ * each record's published `udfCapable` flag (node-autotask 0.37.0 #254; the plan's entities whose SDK
110
+ * resource class normalises `userDefinedFields`). Not the `getUdfInfo` set (every vendor entity has
111
+ * that metadata read, UDFs or not). A test pins the exact set.
109
112
  */
110
- export const UDF_CAPABLE = new Set(RECORDS.filter((r) => untrustedFieldsOf(r).includes('UDF1')).map((r) => r.resource));
113
+ export const UDF_CAPABLE = new Set(RECORDS.filter((r) => r.udfCapable).map((r) => r.resource));
111
114
  /** The per-tool description budget (A0 slimming: every served description <= 250 chars). */
112
115
  export const MAX_DESCRIPTION_CHARS = 250;
113
116
  /**
@@ -117,13 +120,11 @@ export const MAX_DESCRIPTION_CHARS = 250;
117
120
  */
118
121
  export const TOOL_DESCRIPTIONS = {
119
122
  autotask_guide: 'Step-by-step playbooks for multi-step Autotask tasks. No args: the index; {skill} returns one playbook, {skill, section} one section. Static text, no vendor call. Use it first when a task needs several tools.',
120
- autotask_catalog: 'Find the exact operation key for anything without a dedicated tool: ranked search (q) over the 1,216 registry operations, with effect and whether this deployment serves it. No vendor call.',
123
+ autotask_catalog: 'Find the exact operation key for anything without a dedicated tool: ranked search (q) over the 1,281 registry operations, with effect and whether this deployment serves it. No vendor call.',
121
124
  autotask_describe: 'One operation\'s (registry key or tool name) exact input/output contract, examples, errors, policy verdict and playbooks (operation), or one resource\'s relationship map: parent, children, lookup keys (resource). No vendor call.',
122
- autotask_read: 'Run reads by exact registry key, or up to 10 in one call: unbilled work, profitability and margin, SLA health. *.query: 50 rows; scope by company/contract ids (unscoped = tenant walk); roll-ups: autotask_aggregate; slim: includeFields, noteChars.',
123
- autotask_resolve: 'Find the record behind an identifier: id or name, ticket number, CI serial, contact email (by), staff by email, user name or name (resource resources, include_api_users); one resource or a sweep. Exact by default; contains: companies, tickets, sweep.',
124
- autotask_query: 'List records with a filter: tickets, time entries, service calls, contracts or time-off requests. Scope by company/contract ids (unscoped = tenant walk); roll-ups: autotask_aggregate. default 50, max 2500; latest N. Slim: include_fields, note_chars.',
125
- autotask_aggregate: 'Totals, counts and group-by sums (hours by ticket) over any <resource>.query table without reading rows. No groupBy or sum = one count call. hasMore or groupsTruncated = a partial answer, never a tenant total.',
126
- autotask_fetch_many: 'Fetch up to 500 records by numeric id across mixed resources in one call: items: [{resource, id, fields?}]. Answers data.results[{ok, operation, result|error}], ok, failed (same as autotask_read); a miss is a typed NOT_FOUND slot.',
125
+ autotask_read: 'Run reads by exact registry key, or up to 10 in one call: unbilled work, profitability and margin, SLA health. *.query: 50 rows; scope by company/contract ids (unscoped = tenant walk); totals: autotask_query group_by; slim: includeFields, noteChars.',
126
+ autotask_resolve: 'Find the record for an identifier: id or name, ticket number, CI serial, contact email (by), staff by email, user name or name (resource resources, include_api_users); one resource or a sweep. Exact, then contains on a miss.',
127
+ autotask_query: 'List records or totals with a filter: tickets, time entries, service calls, contracts, time off, more. Totals, no rows: group_by, sum, count (hours logged by ticket, how many match). By id: id in. Scope by company ids (unscoped = tenant walk).',
127
128
  autotask_tenant_status: 'Identity (API user + impersonated user), Autotask API version, this server\'s package/SDK version and profile (server), request thresholds, and the time zone of the people asking, with local and UTC time (time). Use it first to check credentials.',
128
129
  autotask_ticket_full_context: 'One ticket with its company, contact, assigned resource, project, notes, time entries and attachment metadata. Missing parts degrade to typed absent; a missing ticket is not found.',
129
130
  autotask_company_full_profile: 'One company with locations, contacts, notes, open tickets and projects, 10 rows per part (capped: true = more exist; raise the limit). fields {part:[names]} trims columns. Open = not tenant-closed. Missing parts degrade to typed absent.',
@@ -173,7 +174,7 @@ export function cacheOptionsForPreset(preset, fieldCacheTtlMs) {
173
174
  * fails the boot (or the CI test) instead of quietly shipping a tool surface that no longer
174
175
  * describes the SDK behind it.
175
176
  */
176
- export const BUILT_AGAINST_PLAN_HASH = '08cf890fa1b2b50574638a1364993fced7e83a5dfd4f630763d80cdc01d5a5f1';
177
+ export const BUILT_AGAINST_PLAN_HASH = '9bb87de7bcbda60f6c44955dba52b8e61e9c5547750ea8acacfcb6f261d7734f';
177
178
  /**
178
179
  * Fail fast when the installed SDK's plan has moved out from under this surface.
179
180
  * Called at boot (main.ts) and safe to call repeatedly (pure comparison).
@@ -1,7 +1,7 @@
1
1
  /** The default tool ops: the bounded query walk and the abort-signal wrapper (issue #91). */
2
2
  import { AutotaskClient, camelKey } from 'node-autotask';
3
3
  import { AutotaskConfigError } from 'node-autotask';
4
- import { fetchByIds, fetchMany, invokeDelete, invokeRead, invokeWrite, relationshipMap, resolveAny, searchAcrossResources } from 'node-autotask/operations';
4
+ import { invokeDelete, invokeRead, invokeWrite, relationshipMap, resolveAny, searchAcrossResources } from 'node-autotask/operations';
5
5
  /** The real SDK bindings (the production default; tests substitute a fixture-backed bundle). */
6
6
  /**
7
7
  * The SDK operations bundle (the production default; tests substitute a fixture-backed bundle).
@@ -17,8 +17,6 @@ export const defaultToolOps = {
17
17
  invokeDelete: (client, operation, args, opts, signal) => withSignal(client, signal, () => invokeDelete(client, operation, args, opts ?? {})),
18
18
  resolveAny: (client, identifier, opts, signal) => withSignal(client, signal, () => resolveAny(client, identifier, opts ?? {})),
19
19
  searchAcrossResources: (client, query, opts, signal) => withSignal(client, signal, () => searchAcrossResources(client, query, opts ?? {})),
20
- fetchMany: (client, items, opts, signal) => withSignal(client, signal, () => fetchMany(client, items, opts ?? {})),
21
- fetchByIds: (client, items, opts, signal) => withSignal(client, signal, () => fetchByIds(client, items, opts ?? {})),
22
20
  relationshipMap: (client, resourceKey) => relationshipMap(client, resourceKey),
23
21
  hourlyBudget: (client) => Promise.resolve(client.hourlyBudget()),
24
22
  queryWalk: (client, operation, params, maxRows, signal) => withSignal(client, signal, () => walkQuery(client, operation, params, maxRows)),
@@ -45,6 +43,8 @@ export async function walkQuery(client, operation, params, maxRows) {
45
43
  bag.includeFields = params.includeFields;
46
44
  if (params.via !== undefined)
47
45
  bag.via = params.via;
46
+ if (params.names !== undefined)
47
+ bag.names = params.names;
48
48
  const items = [];
49
49
  for await (const item of holder.query(bag)) {
50
50
  if (items.length >= maxRows)