@unboundcx/sdk 4.13.75 → 4.13.80

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.
package/index.js CHANGED
@@ -38,6 +38,7 @@ import { FaxService } from './services/fax.js';
38
38
  import { DocumentsService } from './services/documents.js';
39
39
  import { EsignService } from './services/esign.js';
40
40
  import { PermissionsService } from './services/permissions.js';
41
+ import { UsersService } from './services/users.js';
41
42
  import { TriggersService } from './services/triggers.js';
42
43
  import { RecentsService } from './services/recents.js';
43
44
  import { SearchService } from './services/search.js';
@@ -45,6 +46,7 @@ import { DirectoryService } from './services/directory.js';
45
46
  import { ChatService } from './services/chat.js';
46
47
  import { DeveloperApisService } from './services/developerApis.js';
47
48
  import { TextService } from './services/text.js';
49
+ import { ReportingService } from './services/reporting.js';
48
50
  import {
49
51
  DataImportService,
50
52
  DataExportService,
@@ -136,6 +138,8 @@ class UnboundSDK extends BaseSDK {
136
138
  this.documents = new DocumentsService(this);
137
139
  this.esign = new EsignService(this);
138
140
  this.permissions = new PermissionsService(this);
141
+ this.users = new UsersService(this);
142
+ this.reporting = new ReportingService(this);
139
143
  this.triggers = new TriggersService(this);
140
144
  this.recents = new RecentsService(this);
141
145
  this.search = new SearchService(this);
@@ -297,6 +301,7 @@ export { MessagingService } from './services/messaging.js';
297
301
  export { VideoService } from './services/video.js';
298
302
  export { VoiceService } from './services/voice.js';
299
303
  export { AIService } from './services/ai.js';
304
+ export { PlaybooksService } from './services/ai/playbooks.js';
300
305
  export { LookupService } from './services/lookup.js';
301
306
  export { LayoutsService } from './services/layouts.js';
302
307
  export { SubscriptionsService } from './services/subscriptions.js';
@@ -339,6 +344,8 @@ export { KnowledgeBaseService } from './services/knowledgeBase.js';
339
344
  export { FaxService } from './services/fax.js';
340
345
  export { EsignService, EsignPublicService } from './services/esign.js';
341
346
  export { PermissionsService } from './services/permissions.js';
347
+ export { UsersService } from './services/users.js';
348
+ export { ReportingService } from './services/reporting.js';
342
349
  export { RecentsService } from './services/recents.js';
343
350
  export { SearchService } from './services/search.js';
344
351
  export { DirectoryService } from './services/directory.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "4.13.75",
3
+ "version": "4.13.80",
4
4
  "description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -186,6 +186,7 @@ export class PlaybooksService {
186
186
  * @param {string} [options.scoreType='boolean'] - Score type ('boolean' or 'scale')
187
187
  * @param {number} [options.weight=0] - Goal weight (0-100)
188
188
  * @param {boolean} [options.requiredForPass=false] - Whether required for pass
189
+ * @param {string} [options.visibility] - 'live' or 'reviewOnly'
189
190
  * @param {string} [options.recordTypeId] - Record type ID
190
191
  * @returns {Promise<Object>} Created goal with id
191
192
  *
@@ -211,6 +212,7 @@ export class PlaybooksService {
211
212
  signal,
212
213
  window,
213
214
  windowTurns,
215
+ visibility,
214
216
  recordTypeId,
215
217
  }) {
216
218
  this.sdk.validateParams(
@@ -240,6 +242,7 @@ export class PlaybooksService {
240
242
  signal: { type: 'string', required: false },
241
243
  window: { type: 'string', required: false },
242
244
  windowTurns: { type: 'number', required: false },
245
+ visibility: { type: 'string', required: false },
243
246
  recordTypeId: { type: 'string', required: false },
244
247
  },
245
248
  );
@@ -257,6 +260,7 @@ export class PlaybooksService {
257
260
  signal,
258
261
  window,
259
262
  windowTurns,
263
+ visibility,
260
264
  recordTypeId,
261
265
  },
262
266
  };
@@ -337,6 +341,7 @@ export class PlaybooksService {
337
341
  * @param {string} [options.scoreType] - Score type ('boolean' or 'scale')
338
342
  * @param {number} [options.weight] - Goal weight (0-100)
339
343
  * @param {boolean} [options.requiredForPass] - Whether required for pass
344
+ * @param {string} [options.visibility] - 'live' or 'reviewOnly'
340
345
  * @param {string} [options.recordTypeId] - Record type ID
341
346
  * @returns {Promise<Object>} Updated goal object
342
347
  *
@@ -360,6 +365,7 @@ export class PlaybooksService {
360
365
  signal,
361
366
  window,
362
367
  windowTurns,
368
+ visibility,
363
369
  recordTypeId,
364
370
  }) {
365
371
  this.sdk.validateParams(
@@ -389,6 +395,7 @@ export class PlaybooksService {
389
395
  signal: { type: 'string', required: false },
390
396
  window: { type: 'string', required: false },
391
397
  windowTurns: { type: 'number', required: false },
398
+ visibility: { type: 'string', required: false },
392
399
  recordTypeId: { type: 'string', required: false },
393
400
  },
394
401
  );
@@ -406,6 +413,7 @@ export class PlaybooksService {
406
413
  signal,
407
414
  window,
408
415
  windowTurns,
416
+ visibility,
409
417
  recordTypeId,
410
418
  },
411
419
  };
@@ -808,6 +816,7 @@ export class PlaybooksService {
808
816
  * @param {string} [options.taskId] - The task ID (used with workerId or userId)
809
817
  * @param {string} [options.workerId] - The worker ID (used with taskId)
810
818
  * @param {string} [options.userId] - The user ID (used with taskId)
819
+ * @param {boolean} [options.includeQa] - Include review-only goals and qaReview
811
820
  * @returns {Promise<Object>} Session object with playbookName and goals array
812
821
  *
813
822
  * @example
@@ -830,7 +839,7 @@ export class PlaybooksService {
830
839
  * userId: 'user_789'
831
840
  * });
832
841
  */
833
- async getSession({ sessionId, taskId, workerId, userId }) {
842
+ async getSession({ sessionId, taskId, workerId, userId, includeQa }) {
834
843
  this.sdk.validateParams(
835
844
  { sessionId, taskId, workerId, userId },
836
845
  {
@@ -838,13 +847,17 @@ export class PlaybooksService {
838
847
  taskId: { type: 'string', required: false },
839
848
  workerId: { type: 'string', required: false },
840
849
  userId: { type: 'string', required: false },
850
+ includeQa: { type: 'boolean', required: false },
841
851
  },
842
852
  );
843
853
 
844
854
  if (sessionId) {
855
+ const query = {};
856
+ if (includeQa) query.includeQa = 1;
845
857
  const result = await internalRequest(this.sdk,
846
858
  `/ai/playbooks/sessions/${sessionId}`,
847
859
  'GET',
860
+ { query },
848
861
  );
849
862
  return result;
850
863
  }
@@ -853,6 +866,7 @@ export class PlaybooksService {
853
866
  if (taskId) query.taskId = taskId;
854
867
  if (workerId) query.workerId = workerId;
855
868
  if (userId) query.userId = userId;
869
+ if (includeQa) query.includeQa = 1;
856
870
 
857
871
  const result = await internalRequest(this.sdk,
858
872
  `/ai/playbooks/sessions`,
@@ -1044,4 +1058,167 @@ export class PlaybooksService {
1044
1058
  );
1045
1059
  return result;
1046
1060
  }
1061
+
1062
+ /**
1063
+ * Submit a human QA review for an AI playbook session (replace-semantics).
1064
+ *
1065
+ * @param {Object} options
1066
+ * @param {string} options.sessionId
1067
+ * @param {Array} options.goals
1068
+ * @returns {Promise<Object>} QA review
1069
+ */
1070
+ async submitQaReview({ sessionId, goals }) {
1071
+ this.sdk.validateParams(
1072
+ { sessionId, goals },
1073
+ {
1074
+ sessionId: { type: 'string', required: true },
1075
+ goals: { type: 'array', required: true },
1076
+ },
1077
+ );
1078
+
1079
+ const result = await internalRequest(
1080
+ this.sdk,
1081
+ `/ai/playbooks/sessions/${sessionId}/qa`,
1082
+ 'PUT',
1083
+ { body: { goals } },
1084
+ );
1085
+ return result;
1086
+ }
1087
+
1088
+ /**
1089
+ * Get the primary QA review for an AI playbook session.
1090
+ *
1091
+ * @param {Object} options
1092
+ * @param {string} options.sessionId
1093
+ * @returns {Promise<Object>} QA review
1094
+ */
1095
+ async getQaReview({ sessionId }) {
1096
+ this.sdk.validateParams(
1097
+ { sessionId },
1098
+ {
1099
+ sessionId: { type: 'string', required: true },
1100
+ },
1101
+ );
1102
+
1103
+ const result = await internalRequest(
1104
+ this.sdk,
1105
+ `/ai/playbooks/sessions/${sessionId}/qa`,
1106
+ 'GET',
1107
+ );
1108
+ return result;
1109
+ }
1110
+
1111
+ /**
1112
+ * List QA disagreements for a playbook goal (keyset on reviewedAt, id).
1113
+ *
1114
+ * @param {Object} options
1115
+ * @param {string} options.playbookGoalId
1116
+ * @param {number} [options.limit]
1117
+ * @param {string} [options.beforeReviewedAt]
1118
+ * @param {string} [options.beforeId]
1119
+ * @returns {Promise<Object>} { results, agreeRate, reviewedCount, disagreeCount, next }
1120
+ */
1121
+ async listQaDisagreements({
1122
+ playbookGoalId,
1123
+ limit,
1124
+ beforeReviewedAt,
1125
+ beforeId,
1126
+ }) {
1127
+ this.sdk.validateParams(
1128
+ { playbookGoalId },
1129
+ {
1130
+ playbookGoalId: { type: 'string', required: true },
1131
+ limit: { type: 'number', required: false },
1132
+ beforeReviewedAt: { type: 'string', required: false },
1133
+ beforeId: { type: 'string', required: false },
1134
+ },
1135
+ );
1136
+
1137
+ const query = {};
1138
+ if (limit != null) query.limit = limit;
1139
+ if (beforeReviewedAt) query.beforeReviewedAt = beforeReviewedAt;
1140
+ if (beforeId) query.beforeId = beforeId;
1141
+
1142
+ const result = await internalRequest(
1143
+ this.sdk,
1144
+ `/ai/playbooks/goals/${playbookGoalId}/disagreements`,
1145
+ 'GET',
1146
+ { query },
1147
+ );
1148
+ return result;
1149
+ }
1150
+
1151
+ /**
1152
+ * Playbook-level QA agree-rate (exact integer match).
1153
+ *
1154
+ * @param {Object} options
1155
+ * @param {string} options.playbookId
1156
+ * @returns {Promise<Object>} agree-rate payload
1157
+ */
1158
+ async getQaAgreeRate({ playbookId }) {
1159
+ this.sdk.validateParams(
1160
+ { playbookId },
1161
+ {
1162
+ playbookId: { type: 'string', required: true },
1163
+ },
1164
+ );
1165
+
1166
+ const result = await internalRequest(
1167
+ this.sdk,
1168
+ `/ai/playbooks/${playbookId}/qa/agree-rate`,
1169
+ 'GET',
1170
+ );
1171
+ return result;
1172
+ }
1173
+
1174
+ /**
1175
+ * Suggest a criteria/window rewrite from QA disagreements. Never auto-applies.
1176
+ *
1177
+ * @param {Object} options
1178
+ * @param {string} options.playbookGoalId
1179
+ * @returns {Promise<Object>} { suggestionId, status, current, proposed, rationale }
1180
+ */
1181
+ async suggestGoalDefinition({ playbookGoalId }) {
1182
+ this.sdk.validateParams(
1183
+ { playbookGoalId },
1184
+ {
1185
+ playbookGoalId: { type: 'string', required: true },
1186
+ },
1187
+ );
1188
+
1189
+ const result = await internalRequest(
1190
+ this.sdk,
1191
+ `/ai/playbooks/goals/${playbookGoalId}/suggest`,
1192
+ 'POST',
1193
+ { body: {} },
1194
+ );
1195
+ return result;
1196
+ }
1197
+
1198
+ /**
1199
+ * Accept, edit, or reject a pending goal-definition suggestion.
1200
+ *
1201
+ * @param {Object} options
1202
+ * @param {string} options.suggestionId
1203
+ * @param {string} options.action - accept | reject | edit
1204
+ * @param {Object} [options.proposed]
1205
+ * @returns {Promise<Object>}
1206
+ */
1207
+ async resolveGoalSuggestion({ suggestionId, action, proposed }) {
1208
+ this.sdk.validateParams(
1209
+ { suggestionId, action },
1210
+ {
1211
+ suggestionId: { type: 'string', required: true },
1212
+ action: { type: 'string', required: true },
1213
+ },
1214
+ );
1215
+
1216
+ const result = await internalRequest(
1217
+ this.sdk,
1218
+ `/ai/playbooks/suggestions/${suggestionId}/resolve`,
1219
+ 'POST',
1220
+ { body: { action, proposed } },
1221
+ );
1222
+ return result;
1223
+ }
1047
1224
  }
@@ -20,24 +20,32 @@ export class PermissionsService {
20
20
  * @param {Object} group - Group configuration
21
21
  * @param {string} group.name - Group name (required)
22
22
  * @param {string} group.description - Group description
23
+ * @param {string} [group.type] - 'access' (default) or 'team'
24
+ * @param {string} [group.managerUserId] - Manager user id, for `team` groups
23
25
  * @returns {Promise<Object>} Created group
24
26
  * @example
25
27
  * await sdk.permissions.createGroup({
26
28
  * name: 'Support Leads',
27
29
  * description: 'Escalation-tier support agents',
30
+ * type: 'team',
31
+ * managerUserId: 'user-456',
28
32
  * });
29
33
  */
30
- async createGroup({ name, description }) {
34
+ async createGroup({ name, description, type, managerUserId }) {
31
35
  this.sdk.validateParams(
32
- { name, description },
36
+ { name, description, type, managerUserId },
33
37
  {
34
38
  name: { type: 'string', required: true },
35
39
  description: { type: 'string', required: false },
40
+ type: { type: 'string', required: false },
41
+ managerUserId: { type: 'string', required: false },
36
42
  },
37
43
  );
38
44
 
39
45
  const groupData = { name };
40
46
  if (description !== undefined) groupData.description = description;
47
+ if (type !== undefined) groupData.type = type;
48
+ if (managerUserId !== undefined) groupData.managerUserId = managerUserId;
41
49
 
42
50
  const params = {
43
51
  body: groupData,
@@ -50,10 +58,11 @@ export class PermissionsService {
50
58
  /**
51
59
  * Update an existing permission group
52
60
  * @param {string} groupId - Group ID to update
53
- * @param {Object} data - Fields to update (e.g. name, description)
61
+ * @param {Object} data - Fields to update (name, description, type,
62
+ * managerUserId)
54
63
  * @returns {Promise<Object>} Updated group
55
64
  * @example
56
- * await sdk.permissions.updateGroup('group-123', { description: 'Updated' });
65
+ * await sdk.permissions.updateGroup('group-123', { type: 'team', managerUserId: 'user-456' });
57
66
  */
58
67
  async updateGroup(groupId, data) {
59
68
  groupId = String(groupId);
@@ -0,0 +1,329 @@
1
+ import { internalRequest } from '../base.js';
2
+ import { ReportingAgentsService } from './reportingAgents.js';
3
+
4
+ // Q2: same queryString helper as reportingAgents.js (kept local -- no
5
+ // shared util module between the two files today).
6
+ function queryString(params = {}) {
7
+ const parts = [];
8
+ for (const [key, value] of Object.entries(params)) {
9
+ if (value === undefined || value === null || value === '') continue;
10
+ const v = Array.isArray(value) ? value.join(',') : value;
11
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(v)}`);
12
+ }
13
+ return parts.length ? `?${parts.join('&')}` : '';
14
+ }
15
+
16
+ // F7 (cc-reporting-foundation-plan.md §8) -- reporting API surface.
17
+ export class ReportingService {
18
+ constructor(sdk) {
19
+ this.sdk = sdk;
20
+ this.views = {
21
+ list: (...args) => this.listViews(...args),
22
+ create: (...args) => this.createView(...args),
23
+ update: (...args) => this.updateView(...args),
24
+ remove: (...args) => this.removeView(...args),
25
+ };
26
+ this.schedules = {
27
+ create: (...args) => this.createSchedule(...args),
28
+ list: (...args) => this.listSchedules(...args),
29
+ update: (...args) => this.updateSchedule(...args),
30
+ remove: (...args) => this.removeSchedule(...args),
31
+ runs: (...args) => this.listScheduleRuns(...args),
32
+ runNow: (...args) => this.runScheduleNow(...args),
33
+ };
34
+ // P3 (agent-reporting-plan.md §7 + §8.1): thin presets over the same
35
+ // registry above -- /reporting/agents/*. Split into its own file/class
36
+ // (house 400-line limit) since this file was already at 215 lines.
37
+ this.agents = new ReportingAgentsService(sdk);
38
+ }
39
+
40
+ /**
41
+ * Metrics + dimensions catalogue.
42
+ * @returns {Promise<Object>} { metrics: [...], dimensions: [...] }
43
+ * @example
44
+ * const { metrics } = await sdk.reporting.definitions();
45
+ */
46
+ async definitions() {
47
+ return internalRequest(this.sdk, '/reporting/definitions', 'GET');
48
+ }
49
+
50
+ /**
51
+ * Run a report over the reporting rollups (+ today stitch).
52
+ * @param {Object} body - { metrics, dimensions, filters, from, to, grain, tz, continuous }
53
+ * @returns {Promise<Object>} { columns, rows, definitions, meta }
54
+ * @example
55
+ * await sdk.reporting.query({ metrics: ['handled','slPct'], dimensions: ['queue','time'], from, to, grain: 'day' });
56
+ */
57
+ async query(body) {
58
+ this.sdk.validateParams(
59
+ { metrics: body?.metrics, from: body?.from, to: body?.to },
60
+ {
61
+ metrics: { type: 'array', required: true },
62
+ from: { type: 'string', required: true },
63
+ to: { type: 'string', required: true },
64
+ },
65
+ );
66
+ return internalRequest(this.sdk, '/reporting/query', 'POST', { body });
67
+ }
68
+
69
+ /**
70
+ * Same as query(), but streams a CSV (forces HTTP transport).
71
+ * @param {Object} body
72
+ * @returns {Promise<Object>} raw CSV response (transport-dependent)
73
+ * @example
74
+ * await sdk.reporting.csv({ metrics: ['handled'], dimensions: ['queue'], from, to });
75
+ */
76
+ async csv(body) {
77
+ return internalRequest(this.sdk, '/reporting/query?format=csv', 'POST', { body, httpOnly: true });
78
+ }
79
+
80
+ /**
81
+ * Drilldown: the raw records underlying one metric cell.
82
+ * @param {Object} body - { metric, cell: { dimensions, bucketFrom, bucketTo }, cursor, limit }
83
+ * @returns {Promise<Object>} { rows, cursor, hasMore }
84
+ * @example
85
+ * await sdk.reporting.detail({ metric: 'handled', cell: { dimensions: { queueId }, bucketFrom, bucketTo } });
86
+ */
87
+ async detail(body) {
88
+ this.sdk.validateParams(
89
+ { metric: body?.metric },
90
+ { metric: { type: 'string', required: true } },
91
+ );
92
+ return internalRequest(this.sdk, '/reporting/detail', 'POST', { body });
93
+ }
94
+
95
+ /**
96
+ * Q2 preset: disposition matrix (code x queue|agent|code), incl. a
97
+ * noDisposition count for groupBy=queue|code.
98
+ * @param {Object} params - { from, to, queueIds, userIds, groupBy: 'queue'|'agent'|'code' }
99
+ * @returns {Promise<Object>} { groupBy, columns, rows, noDisposition, meta }
100
+ * @example
101
+ * await sdk.reporting.dispositions({ from, to, groupBy: 'queue' });
102
+ */
103
+ async dispositions({ from, to, queueIds, userIds, groupBy } = {}) {
104
+ this.sdk.validateParams(
105
+ { from, to },
106
+ { from: { type: 'string', required: true }, to: { type: 'string', required: true } },
107
+ );
108
+ return internalRequest(
109
+ this.sdk,
110
+ `/reporting/dispositions${queryString({ from, to, queueIds, userIds, groupBy })}`,
111
+ 'GET',
112
+ );
113
+ }
114
+
115
+ /**
116
+ * Q2 preset: exec summary (originating-tasks-only KPI tiles + channel mix
117
+ * + trend), with a server-computed delta vs the previous period.
118
+ * @param {Object} params - { from, to, compareTo: 'previous' }
119
+ * @returns {Promise<Object>} { kpis, channelMix, trend, fcr, previousPeriod }
120
+ * @example
121
+ * await sdk.reporting.execSummary({ from, to, compareTo: 'previous' });
122
+ */
123
+ async execSummary({ from, to, compareTo } = {}) {
124
+ this.sdk.validateParams(
125
+ { from, to },
126
+ { from: { type: 'string', required: true }, to: { type: 'string', required: true } },
127
+ );
128
+ return internalRequest(
129
+ this.sdk,
130
+ `/reporting/execSummary${queryString({ from, to, compareTo })}`,
131
+ 'GET',
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Q2 preset: first-contact-resolution / repeat-contact count over
137
+ * accounts.fcrWindowHours.
138
+ * @param {Object} params - { from, to, queueIds }
139
+ * @returns {Promise<Object>} { windowHours, repeatContacts, originatingHandled, fcrPct }
140
+ * @example
141
+ * await sdk.reporting.fcr({ from, to });
142
+ */
143
+ async fcr({ from, to, queueIds } = {}) {
144
+ this.sdk.validateParams(
145
+ { from, to },
146
+ { from: { type: 'string', required: true }, to: { type: 'string', required: true } },
147
+ );
148
+ return internalRequest(
149
+ this.sdk,
150
+ `/reporting/fcr${queryString({ from, to, queueIds })}`,
151
+ 'GET',
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Rollup health: last computed bucket, lag, missing buckets.
157
+ * @returns {Promise<Object>}
158
+ * @example
159
+ * const { lagSeconds } = await sdk.reporting.health();
160
+ */
161
+ async health() {
162
+ return internalRequest(this.sdk, '/reporting/health', 'GET');
163
+ }
164
+
165
+ /**
166
+ * @returns {Promise<Object>} { results: [...] }
167
+ * @example
168
+ * const { results } = await sdk.reporting.views.list();
169
+ */
170
+ async listViews() {
171
+ return internalRequest(this.sdk, '/reporting/views', 'GET');
172
+ }
173
+
174
+ /**
175
+ * @param {Object} view - { name, query, isShared }
176
+ * @returns {Promise<Object>} created view
177
+ * @example
178
+ * await sdk.reporting.views.create({ name: 'Daily SL', query: {...} });
179
+ */
180
+ async createView({ name, query, isShared } = {}) {
181
+ this.sdk.validateParams(
182
+ { name, query },
183
+ { name: { type: 'string', required: true }, query: { type: 'object', required: true } },
184
+ );
185
+ const body = { name, query };
186
+ if (isShared !== undefined) body.isShared = isShared;
187
+ return internalRequest(this.sdk, '/reporting/views', 'POST', { body });
188
+ }
189
+
190
+ /**
191
+ * @param {string} id
192
+ * @param {Object} data - { name, query, isShared }
193
+ * @returns {Promise<Object>} updated view
194
+ * @example
195
+ * await sdk.reporting.views.update('263...', { isShared: true });
196
+ */
197
+ async updateView(id, data) {
198
+ id = String(id);
199
+ this.sdk.validateParams({ id }, { id: { type: 'string', required: true } });
200
+ return internalRequest(this.sdk, `/reporting/views/${id}`, 'PUT', { body: data });
201
+ }
202
+
203
+ /**
204
+ * @param {string} id
205
+ * @returns {Promise<Object>} { id, deleted: true }
206
+ * @example
207
+ * await sdk.reporting.views.remove('263...');
208
+ */
209
+ async removeView(id) {
210
+ id = String(id);
211
+ this.sdk.validateParams({ id }, { id: { type: 'string', required: true } });
212
+ return internalRequest(this.sdk, `/reporting/views/${id}`, 'DELETE');
213
+ }
214
+
215
+ /**
216
+ * @param {string} viewId
217
+ * @param {Object} schedule - { cron, timezone, recipients, format, runAs, isEnabled }
218
+ * @returns {Promise<Object>} created schedule
219
+ * @example
220
+ * await sdk.reporting.schedules.create('263...', { cron: '0 8 * * MON', timezone: 'America/Denver', recipients: ['a@b.com'] });
221
+ */
222
+ async createSchedule(viewId, { cron, timezone, recipients, format, runAs, isEnabled } = {}) {
223
+ viewId = String(viewId);
224
+ this.sdk.validateParams(
225
+ { viewId, cron, timezone, recipients },
226
+ {
227
+ viewId: { type: 'string', required: true },
228
+ cron: { type: 'string', required: true },
229
+ timezone: { type: 'string', required: true },
230
+ recipients: { type: 'array', required: true },
231
+ },
232
+ );
233
+ const body = { cron, timezone, recipients };
234
+ if (format !== undefined) body.format = format;
235
+ if (runAs !== undefined) body.runAs = runAs;
236
+ if (isEnabled !== undefined) body.isEnabled = isEnabled;
237
+ return internalRequest(this.sdk, `/reporting/views/${viewId}/schedule`, 'POST', { body });
238
+ }
239
+
240
+ /**
241
+ * @param {string} viewId
242
+ * @returns {Promise<Object>} { results: [...] }
243
+ * @example
244
+ * await sdk.reporting.schedules.list('263...');
245
+ */
246
+ async listSchedules(viewId) {
247
+ viewId = String(viewId);
248
+ this.sdk.validateParams({ viewId }, { viewId: { type: 'string', required: true } });
249
+ return internalRequest(this.sdk, `/reporting/views/${viewId}/schedule`, 'GET');
250
+ }
251
+
252
+ /**
253
+ * @param {string} viewId
254
+ * @param {string} scheduleId
255
+ * @param {Object} data - partial { cron, timezone, recipients, format, isEnabled }
256
+ * @returns {Promise<Object>} updated schedule
257
+ * @example
258
+ * await sdk.reporting.schedules.update('263...', '264...', { isEnabled: false });
259
+ */
260
+ async updateSchedule(viewId, scheduleId, data = {}) {
261
+ viewId = String(viewId);
262
+ scheduleId = String(scheduleId);
263
+ this.sdk.validateParams(
264
+ { viewId, scheduleId },
265
+ { viewId: { type: 'string', required: true }, scheduleId: { type: 'string', required: true } },
266
+ );
267
+ return internalRequest(
268
+ this.sdk,
269
+ `/reporting/views/${viewId}/schedule/${scheduleId}`,
270
+ 'PUT',
271
+ { body: data },
272
+ );
273
+ }
274
+
275
+ /**
276
+ * @param {string} viewId
277
+ * @param {string} scheduleId
278
+ * @returns {Promise<Object>} { id, deleted: true }
279
+ * @example
280
+ * await sdk.reporting.schedules.remove('263...', '264...');
281
+ */
282
+ async removeSchedule(viewId, scheduleId) {
283
+ viewId = String(viewId);
284
+ scheduleId = String(scheduleId);
285
+ this.sdk.validateParams(
286
+ { viewId, scheduleId },
287
+ { viewId: { type: 'string', required: true }, scheduleId: { type: 'string', required: true } },
288
+ );
289
+ return internalRequest(this.sdk, `/reporting/views/${viewId}/schedule/${scheduleId}`, 'DELETE');
290
+ }
291
+
292
+ /**
293
+ * @returns {Promise<Array>} schedule runs (status, startedAt, finishedAt, error, attempt)
294
+ * @example
295
+ * await sdk.reporting.schedules.runs('263...', '264...');
296
+ */
297
+ async listScheduleRuns(viewId, scheduleId) {
298
+ viewId = String(viewId);
299
+ scheduleId = String(scheduleId);
300
+ this.sdk.validateParams(
301
+ { viewId, scheduleId },
302
+ { viewId: { type: 'string', required: true }, scheduleId: { type: 'string', required: true } },
303
+ );
304
+ return internalRequest(this.sdk, `/reporting/views/${viewId}/schedule/${scheduleId}/runs`, 'GET');
305
+ }
306
+
307
+ /**
308
+ * Manual trigger -- runs the same delivery path as the cron tick, awaits
309
+ * sent/failed instead of just queuing.
310
+ * @param {string} viewId
311
+ * @param {string} scheduleId
312
+ * @returns {Promise<Object>} { id, status, attempt, error? }
313
+ * @example
314
+ * await sdk.reporting.schedules.runNow('263...', '264...');
315
+ */
316
+ async runScheduleNow(viewId, scheduleId) {
317
+ viewId = String(viewId);
318
+ scheduleId = String(scheduleId);
319
+ this.sdk.validateParams(
320
+ { viewId, scheduleId },
321
+ { viewId: { type: 'string', required: true }, scheduleId: { type: 'string', required: true } },
322
+ );
323
+ return internalRequest(
324
+ this.sdk,
325
+ `/reporting/views/${viewId}/schedule/${scheduleId}/run-now`,
326
+ 'POST',
327
+ );
328
+ }
329
+ }
@@ -0,0 +1,184 @@
1
+ import { internalRequest } from '../base.js';
2
+
3
+ function queryString(params = {}) {
4
+ const parts = [];
5
+ for (const [key, value] of Object.entries(params)) {
6
+ if (value === undefined || value === null || value === '') continue;
7
+ const v = Array.isArray(value) ? value.join(',') : value;
8
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(v)}`);
9
+ }
10
+ return parts.length ? `?${parts.join('&')}` : '';
11
+ }
12
+
13
+ // P3 (agent-reporting-plan.md §7 + §8.1) -- /reporting/agents/* thin
14
+ // presets over the Foundation registry. sdk.reporting.agents.*
15
+ export class ReportingAgentsService {
16
+ constructor(sdk) {
17
+ this.sdk = sdk;
18
+ }
19
+
20
+ /**
21
+ * Agent summary rows (§6 metrics), grouped by agent, queue, or team.
22
+ * @param {Object} params - { from, to, userIds, queueIds, teamIds, groupBy }
23
+ * @returns {Promise<Object>} { columns, rows, definitions, meta }
24
+ * @example
25
+ * await sdk.reporting.agents.summary({ from, to, groupBy: 'team' });
26
+ */
27
+ async summary({ from, to, userIds, queueIds, teamIds, groupBy } = {}) {
28
+ this.sdk.validateParams(
29
+ { from, to },
30
+ { from: { type: 'string', required: true }, to: { type: 'string', required: true } },
31
+ );
32
+ return internalRequest(
33
+ this.sdk,
34
+ `/reporting/agents/summary${queryString({ from, to, userIds, queueIds, teamIds, groupBy })}`,
35
+ 'GET',
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Collapsed presence timeline for one agent. userId 'me' resolves to the
41
+ * caller.
42
+ * @param {string} userId
43
+ * @param {Object} params - { from, to }
44
+ * @returns {Promise<Object>} { rows }
45
+ * @example
46
+ * await sdk.reporting.agents.states('me', { from, to });
47
+ */
48
+ async states(userId, { from, to } = {}) {
49
+ userId = String(userId);
50
+ this.sdk.validateParams(
51
+ { userId, from, to },
52
+ {
53
+ userId: { type: 'string', required: true },
54
+ from: { type: 'string', required: true },
55
+ to: { type: 'string', required: true },
56
+ },
57
+ );
58
+ return internalRequest(
59
+ this.sdk,
60
+ `/reporting/agents/${userId}/states${queryString({ from, to })}`,
61
+ 'GET',
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Per-task interaction rows for one agent, paginated.
67
+ * @param {string} userId
68
+ * @param {Object} params - { from, to, cursor, limit }
69
+ * @returns {Promise<Object>} { rows, cursor, hasMore }
70
+ * @example
71
+ * await sdk.reporting.agents.interactions('me', { from, to });
72
+ */
73
+ async interactions(userId, { from, to, cursor, limit } = {}) {
74
+ userId = String(userId);
75
+ this.sdk.validateParams(
76
+ { userId, from, to },
77
+ {
78
+ userId: { type: 'string', required: true },
79
+ from: { type: 'string', required: true },
80
+ to: { type: 'string', required: true },
81
+ },
82
+ );
83
+ return internalRequest(
84
+ this.sdk,
85
+ `/reporting/agents/${userId}/interactions${queryString({ from, to, cursor, limit })}`,
86
+ 'GET',
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Per-agent per-local-day timesheet rows (net paid hours).
92
+ * @param {Object} params - { from, to, userIds, teamIds }
93
+ * @returns {Promise<Object>} { columns, rows }
94
+ * @example
95
+ * await sdk.reporting.agents.timesheet({ from, to, userIds: ['u1'] });
96
+ */
97
+ async timesheet({ from, to, userIds, teamIds } = {}) {
98
+ this.sdk.validateParams(
99
+ { from, to },
100
+ { from: { type: 'string', required: true }, to: { type: 'string', required: true } },
101
+ );
102
+ return internalRequest(
103
+ this.sdk,
104
+ `/reporting/agents/timesheet${queryString({ from, to, userIds, teamIds })}`,
105
+ 'GET',
106
+ );
107
+ }
108
+
109
+ /**
110
+ * CSV export of any of the four views above.
111
+ * @param {Object} params - { view: 'summary'|'states'|'interactions'|'timesheet', from, to, userId, userIds, queueIds, teamIds, groupBy }
112
+ * @returns {Promise<Object>} raw CSV response (transport-dependent)
113
+ * @example
114
+ * await sdk.reporting.agents.export({ view: 'timesheet', from, to });
115
+ */
116
+ async export({ view, from, to, userId, userIds, queueIds, teamIds, groupBy } = {}) {
117
+ this.sdk.validateParams(
118
+ { from, to },
119
+ { from: { type: 'string', required: true }, to: { type: 'string', required: true } },
120
+ );
121
+ const qs = queryString({
122
+ view,
123
+ from,
124
+ to,
125
+ userId,
126
+ userIds,
127
+ queueIds,
128
+ teamIds,
129
+ groupBy,
130
+ format: 'csv',
131
+ });
132
+ return internalRequest(this.sdk, `/reporting/agents/export${qs}`, 'GET', { httpOnly: true });
133
+ }
134
+
135
+ /**
136
+ * §8.1 quality trend: aiScoreAvg/humanScoreAvg + n/N counts, per day.
137
+ * @param {string} userId
138
+ * @param {Object} params - { from, to, grain }
139
+ * @returns {Promise<Object>} { columns, rows, definitions, meta }
140
+ * @example
141
+ * await sdk.reporting.agents.qualityTrend('me', { from, to, grain: 'day' });
142
+ */
143
+ async qualityTrend(userId, { from, to, grain } = {}) {
144
+ userId = String(userId);
145
+ this.sdk.validateParams(
146
+ { userId, from, to },
147
+ {
148
+ userId: { type: 'string', required: true },
149
+ from: { type: 'string', required: true },
150
+ to: { type: 'string', required: true },
151
+ },
152
+ );
153
+ return internalRequest(
154
+ this.sdk,
155
+ `/reporting/agents/${userId}/qualityTrend${queryString({ from, to, grain })}`,
156
+ 'GET',
157
+ );
158
+ }
159
+
160
+ /**
161
+ * §8.1 best/lowest playbook-scored tasks for one agent.
162
+ * @param {string} userId
163
+ * @param {Object} params - { from, to, order: 'best'|'lowest', limit }
164
+ * @returns {Promise<Object>} { rows }
165
+ * @example
166
+ * await sdk.reporting.agents.topTasks('me', { from, to, order: 'lowest', limit: 10 });
167
+ */
168
+ async topTasks(userId, { from, to, order, limit } = {}) {
169
+ userId = String(userId);
170
+ this.sdk.validateParams(
171
+ { userId, from, to },
172
+ {
173
+ userId: { type: 'string', required: true },
174
+ from: { type: 'string', required: true },
175
+ to: { type: 'string', required: true },
176
+ },
177
+ );
178
+ return internalRequest(
179
+ this.sdk,
180
+ `/reporting/agents/${userId}/topTasks${queryString({ from, to, order, limit })}`,
181
+ 'GET',
182
+ );
183
+ }
184
+ }
@@ -0,0 +1,134 @@
1
+ import { internalRequest } from '../base.js';
2
+
3
+ // agent-reporting-plan.md P0 — status reasons (§5.1) + a typed wrapper over
4
+ // the existing status-set path (objects.updateById on the `users` object;
5
+ // see app1-api src/services/objects/functions/customHandlers/update/users.js).
6
+ // No existing "users" service in the SDK to extend — this is a new one.
7
+ export class UsersService {
8
+ constructor(sdk) {
9
+ this.sdk = sdk;
10
+ this.statusReasons = {
11
+ list: (...args) => this.listStatusReasons(...args),
12
+ create: (...args) => this.createStatusReason(...args),
13
+ update: (...args) => this.updateStatusReason(...args),
14
+ remove: (...args) => this.removeStatusReason(...args),
15
+ };
16
+ this.status = {
17
+ set: (...args) => this.setStatus(...args),
18
+ };
19
+ }
20
+
21
+ /**
22
+ * List agent status reasons (includes system rows).
23
+ * @returns {Promise<Object>} Object with results: Array of status reasons
24
+ * @example
25
+ * const { results } = await sdk.users.statusReasons.list();
26
+ */
27
+ async listStatusReasons() {
28
+ return internalRequest(this.sdk, '/userStatusReasons', 'GET');
29
+ }
30
+
31
+ /**
32
+ * Create an admin-defined status reason.
33
+ * @param {Object} reason
34
+ * @param {string} reason.code - Unique code (required)
35
+ * @param {string} reason.label - Display label (required)
36
+ * @param {string} reason.state - 'Away' | 'Busy' | 'Offline' (required)
37
+ * @param {string} [reason.emoji]
38
+ * @param {number} [reason.maxSeconds]
39
+ * @param {boolean} [reason.isPaid]
40
+ * @param {number} [reason.order]
41
+ * @returns {Promise<Object>} Created status reason
42
+ * @example
43
+ * await sdk.users.statusReasons.create({ code: 'standup', label: 'Standup', state: 'Away' });
44
+ */
45
+ async createStatusReason({ code, label, state, emoji, maxSeconds, isPaid, order }) {
46
+ this.sdk.validateParams(
47
+ { code, label, state },
48
+ {
49
+ code: { type: 'string', required: true },
50
+ label: { type: 'string', required: true },
51
+ state: { type: 'string', required: true },
52
+ },
53
+ );
54
+
55
+ const body = { code, label, state };
56
+ if (emoji !== undefined) body.emoji = emoji;
57
+ if (maxSeconds !== undefined) body.maxSeconds = maxSeconds;
58
+ if (isPaid !== undefined) body.isPaid = isPaid;
59
+ if (order !== undefined) body.order = order;
60
+
61
+ return internalRequest(this.sdk, '/userStatusReasons', 'POST', { body });
62
+ }
63
+
64
+ /**
65
+ * Update a status reason (label/emoji/maxSeconds/isPaid/order only —
66
+ * code/state are immutable for every row).
67
+ * @param {string} id
68
+ * @param {Object} data
69
+ * @returns {Promise<Object>} Updated status reason
70
+ * @example
71
+ * await sdk.users.statusReasons.update('usr_break', { maxSeconds: 600 });
72
+ */
73
+ async updateStatusReason(id, data) {
74
+ id = String(id);
75
+ this.sdk.validateParams({ id }, { id: { type: 'string', required: true } });
76
+
77
+ return internalRequest(this.sdk, `/userStatusReasons/${id}`, 'PUT', {
78
+ body: data,
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Delete (soft) an admin-defined status reason. System reasons cannot be
84
+ * deleted.
85
+ * @param {string} id
86
+ * @returns {Promise<Object>} Deletion confirmation
87
+ * @example
88
+ * await sdk.users.statusReasons.remove('usr_standup');
89
+ */
90
+ async removeStatusReason(id) {
91
+ id = String(id);
92
+ this.sdk.validateParams({ id }, { id: { type: 'string', required: true } });
93
+
94
+ return internalRequest(this.sdk, `/userStatusReasons/${id}`, 'DELETE');
95
+ }
96
+
97
+ /**
98
+ * Set the current user's (or another user's, with permission) presence
99
+ * status. Thin typed wrapper over
100
+ * `sdk.objects.updateById({ object: 'users', ... })` — the underlying
101
+ * write path is unchanged, this just gives status-set a discoverable,
102
+ * typed entry point that also carries `reasonId`.
103
+ * @param {string} userId
104
+ * @param {Object} status
105
+ * @param {string} status.state - e.g. 'Available' | 'Away' | 'Busy' | 'Offline'
106
+ * @param {string} [status.reasonId] - id from statusReasons.list()
107
+ * @param {string} [status.label] - custom label, when no reasonId applies
108
+ * @param {string} [status.emoji] - custom emoji, when no reasonId applies
109
+ * @returns {Promise<Object>} Updated user
110
+ * @example
111
+ * await sdk.users.status.set('user-123', { state: 'Away', reasonId: 'usr_break' });
112
+ */
113
+ async setStatus(userId, { state, reasonId, label, emoji } = {}) {
114
+ userId = String(userId);
115
+ this.sdk.validateParams(
116
+ { userId, state },
117
+ {
118
+ userId: { type: 'string', required: true },
119
+ state: { type: 'string', required: true },
120
+ },
121
+ );
122
+
123
+ const status = { state };
124
+ if (reasonId !== undefined) status.reasonId = reasonId;
125
+ if (label !== undefined) status.label = label;
126
+ if (emoji !== undefined) status.emoji = emoji;
127
+
128
+ return this.sdk.objects.updateById({
129
+ object: 'users',
130
+ id: userId,
131
+ update: { status },
132
+ });
133
+ }
134
+ }