@agentuity/core 1.0.29 → 1.0.31
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/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/email.d.ts +507 -8
- package/dist/services/email.d.ts.map +1 -1
- package/dist/services/email.js +325 -1
- package/dist/services/email.js.map +1 -1
- package/dist/services/keyvalue.d.ts +5 -2
- package/dist/services/keyvalue.d.ts.map +1 -1
- package/dist/services/keyvalue.js +44 -1
- package/dist/services/keyvalue.js.map +1 -1
- package/dist/services/queue.d.ts +98 -0
- package/dist/services/queue.d.ts.map +1 -1
- package/dist/services/queue.js +100 -0
- package/dist/services/queue.js.map +1 -1
- package/dist/services/schedule.d.ts +413 -0
- package/dist/services/schedule.d.ts.map +1 -1
- package/dist/services/schedule.js +221 -0
- package/dist/services/schedule.js.map +1 -1
- package/dist/services/task.d.ts +1103 -11
- package/dist/services/task.d.ts.map +1 -1
- package/dist/services/task.js +589 -1
- package/dist/services/task.js.map +1 -1
- package/dist/services/webhook.d.ts +352 -0
- package/dist/services/webhook.d.ts.map +1 -1
- package/dist/services/webhook.js +254 -0
- package/dist/services/webhook.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +8 -0
- package/src/services/email.ts +560 -9
- package/src/services/keyvalue.ts +51 -3
- package/src/services/queue.ts +215 -0
- package/src/services/schedule.ts +442 -0
- package/src/services/task.ts +1329 -23
- package/src/services/webhook.ts +412 -0
package/src/services/task.ts
CHANGED
|
@@ -3,316 +3,1108 @@ import { buildUrl, toServiceException } from './_util.ts';
|
|
|
3
3
|
import { StructuredError } from '../error.ts';
|
|
4
4
|
import { safeStringify } from '../json.ts';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Priority level for a task, from highest (`'high'`) to no priority (`'none'`).
|
|
8
|
+
*/
|
|
7
9
|
export type TaskPriority = 'high' | 'medium' | 'low' | 'none';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The classification of a task.
|
|
13
|
+
*
|
|
14
|
+
* - `'epic'` — Large initiatives that span multiple features or tasks.
|
|
15
|
+
* - `'feature'` — New capabilities to be built.
|
|
16
|
+
* - `'enhancement'` — Improvements to existing features.
|
|
17
|
+
* - `'bug'` — Defects to be fixed.
|
|
18
|
+
* - `'task'` — General work items.
|
|
19
|
+
*/
|
|
8
20
|
export type TaskType = 'epic' | 'feature' | 'enhancement' | 'bug' | 'task';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The lifecycle status of a task.
|
|
24
|
+
*
|
|
25
|
+
* - `'open'` — Created, not yet started.
|
|
26
|
+
* - `'in_progress'` — Actively being worked on.
|
|
27
|
+
* - `'done'` — Work completed.
|
|
28
|
+
* - `'closed'` — Resolved and closed.
|
|
29
|
+
* - `'cancelled'` — Abandoned.
|
|
30
|
+
*/
|
|
9
31
|
export type TaskStatus = 'open' | 'in_progress' | 'closed' | 'done' | 'cancelled';
|
|
10
32
|
|
|
11
|
-
|
|
33
|
+
/**
|
|
34
|
+
* A lightweight reference to a user or project entity, containing just the ID
|
|
35
|
+
* and display name. Used for creator, assignee, closer, and project associations.
|
|
36
|
+
*/
|
|
12
37
|
export interface EntityRef {
|
|
38
|
+
/** Unique identifier of the referenced entity. */
|
|
13
39
|
id: string;
|
|
40
|
+
|
|
41
|
+
/** Human-readable display name of the entity. */
|
|
14
42
|
name: string;
|
|
15
43
|
}
|
|
16
44
|
|
|
17
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The type of user entity.
|
|
47
|
+
*
|
|
48
|
+
* - `'human'` — A human user.
|
|
49
|
+
* - `'agent'` — An AI agent.
|
|
50
|
+
*/
|
|
51
|
+
export type UserType = 'human' | 'agent';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A reference to a user entity with type discrimination.
|
|
55
|
+
* Extends {@link EntityRef} with a {@link UserEntityRef.type | type} field
|
|
56
|
+
* to distinguish between human users and AI agents.
|
|
57
|
+
*/
|
|
58
|
+
export interface UserEntityRef extends EntityRef {
|
|
59
|
+
/**
|
|
60
|
+
* The type of user. Defaults to `'human'` if not specified.
|
|
61
|
+
*
|
|
62
|
+
* @default 'human'
|
|
63
|
+
*/
|
|
64
|
+
type?: UserType;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A work item in the task management system.
|
|
69
|
+
*
|
|
70
|
+
* Tasks can represent epics, features, bugs, enhancements, or generic tasks.
|
|
71
|
+
* They support hierarchical organization via {@link Task.parent_id | parent_id},
|
|
72
|
+
* assignment tracking, and lifecycle management through status transitions.
|
|
73
|
+
*
|
|
74
|
+
* @remarks
|
|
75
|
+
* Status transitions are tracked automatically — when a task moves to a new status,
|
|
76
|
+
* the corresponding date field (e.g., {@link Task.open_date | open_date},
|
|
77
|
+
* {@link Task.in_progress_date | in_progress_date}) is set by the server.
|
|
78
|
+
*/
|
|
18
79
|
export interface Task {
|
|
80
|
+
/** Unique identifier for the task. */
|
|
19
81
|
id: string;
|
|
82
|
+
|
|
83
|
+
/** ISO 8601 timestamp when the task was created. */
|
|
20
84
|
created_at: string;
|
|
85
|
+
|
|
86
|
+
/** ISO 8601 timestamp when the task was last modified. */
|
|
21
87
|
updated_at: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The task title.
|
|
91
|
+
*
|
|
92
|
+
* @remarks Must be non-empty and at most 1024 characters.
|
|
93
|
+
*/
|
|
22
94
|
title: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Detailed description of the task.
|
|
98
|
+
*
|
|
99
|
+
* @remarks Maximum 65,536 characters.
|
|
100
|
+
*/
|
|
23
101
|
description?: string;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Arbitrary key-value metadata attached to the task.
|
|
105
|
+
* Can be used for custom fields, integrations, or filtering.
|
|
106
|
+
*/
|
|
24
107
|
metadata?: Record<string, unknown>;
|
|
108
|
+
|
|
109
|
+
/** The priority level of the task. */
|
|
25
110
|
priority: TaskPriority;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* ID of the parent task, enabling hierarchical task organization
|
|
114
|
+
* (e.g., an epic containing features).
|
|
115
|
+
*/
|
|
26
116
|
parent_id?: string;
|
|
117
|
+
|
|
118
|
+
/** The classification of this task. */
|
|
27
119
|
type: TaskType;
|
|
120
|
+
|
|
121
|
+
/** The current lifecycle status of the task. */
|
|
28
122
|
status: TaskStatus;
|
|
123
|
+
|
|
124
|
+
/** ISO 8601 timestamp when the task was moved to `'open'` status. */
|
|
29
125
|
open_date?: string;
|
|
126
|
+
|
|
127
|
+
/** ISO 8601 timestamp when the task was moved to `'in_progress'` status. */
|
|
30
128
|
in_progress_date?: string;
|
|
129
|
+
|
|
130
|
+
/** ISO 8601 timestamp when the task was closed. */
|
|
31
131
|
closed_date?: string;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* ID of the user who created the task.
|
|
135
|
+
*
|
|
136
|
+
* @remarks Legacy field; prefer {@link Task.creator | creator}.
|
|
137
|
+
*/
|
|
32
138
|
created_id: string;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* ID of the user the task is assigned to.
|
|
142
|
+
*
|
|
143
|
+
* @remarks Legacy field; prefer {@link Task.assignee | assignee}.
|
|
144
|
+
*/
|
|
33
145
|
assigned_id?: string;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* ID of the user who closed the task.
|
|
149
|
+
*
|
|
150
|
+
* @remarks Legacy field; prefer {@link Task.closer | closer}.
|
|
151
|
+
*/
|
|
34
152
|
closed_id?: string;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
153
|
+
|
|
154
|
+
/** Reference to the user who created the task. */
|
|
155
|
+
creator?: UserEntityRef;
|
|
156
|
+
|
|
157
|
+
/** Reference to the user the task is assigned to. */
|
|
158
|
+
assignee?: UserEntityRef;
|
|
159
|
+
|
|
160
|
+
/** Reference to the user who closed the task. */
|
|
161
|
+
closer?: UserEntityRef;
|
|
162
|
+
|
|
163
|
+
/** Reference to the project this task belongs to. */
|
|
38
164
|
project?: EntityRef;
|
|
165
|
+
|
|
166
|
+
/** ISO 8601 timestamp when the task was cancelled. */
|
|
39
167
|
cancelled_date?: string;
|
|
40
|
-
|
|
168
|
+
|
|
169
|
+
/** Array of tags associated with this task. */
|
|
41
170
|
tags?: Tag[];
|
|
171
|
+
|
|
172
|
+
/** Array of comments on this task. */
|
|
42
173
|
comments?: Comment[];
|
|
43
174
|
}
|
|
44
175
|
|
|
45
|
-
|
|
176
|
+
/**
|
|
177
|
+
* A comment on a task, supporting threaded discussion.
|
|
178
|
+
*/
|
|
46
179
|
export interface Comment {
|
|
180
|
+
/** Unique identifier for the comment. */
|
|
47
181
|
id: string;
|
|
182
|
+
|
|
183
|
+
/** ISO 8601 timestamp when the comment was created. */
|
|
48
184
|
created_at: string;
|
|
185
|
+
|
|
186
|
+
/** ISO 8601 timestamp when the comment was last edited. */
|
|
49
187
|
updated_at: string;
|
|
188
|
+
|
|
189
|
+
/** ID of the task this comment belongs to. */
|
|
50
190
|
task_id: string;
|
|
191
|
+
|
|
192
|
+
/** ID of the user who authored the comment. */
|
|
51
193
|
user_id: string;
|
|
52
|
-
|
|
194
|
+
|
|
195
|
+
/** Reference to the comment author with display name. */
|
|
196
|
+
author?: UserEntityRef;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The comment text content.
|
|
200
|
+
*
|
|
201
|
+
* @remarks Must be non-empty.
|
|
202
|
+
*/
|
|
53
203
|
body: string;
|
|
54
204
|
}
|
|
55
205
|
|
|
56
|
-
|
|
206
|
+
/**
|
|
207
|
+
* A label that can be applied to tasks for categorization and filtering.
|
|
208
|
+
*/
|
|
57
209
|
export interface Tag {
|
|
210
|
+
/** Unique identifier for the tag. */
|
|
58
211
|
id: string;
|
|
212
|
+
|
|
213
|
+
/** ISO 8601 timestamp when the tag was created. */
|
|
59
214
|
created_at: string;
|
|
215
|
+
|
|
216
|
+
/** Display name of the tag. */
|
|
60
217
|
name: string;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Optional hex color code for the tag.
|
|
221
|
+
*
|
|
222
|
+
* @example '#ff0000'
|
|
223
|
+
*/
|
|
61
224
|
color?: string;
|
|
62
225
|
}
|
|
63
226
|
|
|
64
|
-
|
|
227
|
+
/**
|
|
228
|
+
* A record of a single field change on a task, providing an audit trail.
|
|
229
|
+
*/
|
|
65
230
|
export interface TaskChangelogEntry {
|
|
231
|
+
/** Unique identifier for the changelog entry. */
|
|
66
232
|
id: string;
|
|
233
|
+
|
|
234
|
+
/** ISO 8601 timestamp when the change occurred. */
|
|
67
235
|
created_at: string;
|
|
236
|
+
|
|
237
|
+
/** ID of the task that was changed. */
|
|
68
238
|
task_id: string;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Name of the field that was changed.
|
|
242
|
+
*
|
|
243
|
+
* @example 'status'
|
|
244
|
+
* @example 'priority'
|
|
245
|
+
* @example 'assigned_id'
|
|
246
|
+
*/
|
|
69
247
|
field: string;
|
|
248
|
+
|
|
249
|
+
/** The previous value of the field (as a string), or `undefined` if the field was newly set. */
|
|
70
250
|
old_value?: string;
|
|
251
|
+
|
|
252
|
+
/** The new value of the field (as a string), or `undefined` if the field was cleared. */
|
|
71
253
|
new_value?: string;
|
|
72
254
|
}
|
|
73
255
|
|
|
74
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Parameters for creating a new task.
|
|
258
|
+
*/
|
|
75
259
|
export interface CreateTaskParams {
|
|
260
|
+
/**
|
|
261
|
+
* The task title (required).
|
|
262
|
+
*
|
|
263
|
+
* @remarks Must be non-empty and at most 1024 characters.
|
|
264
|
+
*/
|
|
76
265
|
title: string;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Detailed description of the task.
|
|
269
|
+
*
|
|
270
|
+
* @remarks Maximum 65,536 characters.
|
|
271
|
+
*/
|
|
77
272
|
description?: string;
|
|
273
|
+
|
|
274
|
+
/** Arbitrary key-value metadata. */
|
|
78
275
|
metadata?: Record<string, unknown>;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Priority level. Defaults to `'none'` if not provided.
|
|
279
|
+
*
|
|
280
|
+
* @default 'none'
|
|
281
|
+
*/
|
|
79
282
|
priority?: TaskPriority;
|
|
283
|
+
|
|
284
|
+
/** ID of the parent task for hierarchical organization. */
|
|
80
285
|
parent_id?: string;
|
|
286
|
+
|
|
287
|
+
/** The task classification (required). */
|
|
81
288
|
type: TaskType;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Initial status. Defaults to `'open'` if not provided.
|
|
292
|
+
*
|
|
293
|
+
* @default 'open'
|
|
294
|
+
*/
|
|
82
295
|
status?: TaskStatus;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* ID of the creator.
|
|
299
|
+
*
|
|
300
|
+
* @remarks Legacy field; prefer {@link CreateTaskParams.creator | creator}.
|
|
301
|
+
*/
|
|
83
302
|
created_id: string;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* ID of the assigned user.
|
|
306
|
+
*
|
|
307
|
+
* @remarks Legacy field; prefer {@link CreateTaskParams.assignee | assignee}.
|
|
308
|
+
*/
|
|
84
309
|
assigned_id?: string;
|
|
85
|
-
|
|
86
|
-
|
|
310
|
+
|
|
311
|
+
/** Reference to the user creating the task (id, name, and optional type). */
|
|
312
|
+
creator?: UserEntityRef;
|
|
313
|
+
|
|
314
|
+
/** Reference to the user being assigned the task. */
|
|
315
|
+
assignee?: UserEntityRef;
|
|
316
|
+
|
|
317
|
+
/** Reference to the project this task belongs to. */
|
|
87
318
|
project?: EntityRef;
|
|
319
|
+
|
|
320
|
+
/** Array of tag IDs to associate with the task at creation. */
|
|
88
321
|
tag_ids?: string[];
|
|
89
322
|
}
|
|
90
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Parameters for partially updating an existing task.
|
|
326
|
+
*
|
|
327
|
+
* @remarks Only provided fields are modified; omitted fields remain unchanged.
|
|
328
|
+
*/
|
|
91
329
|
export interface UpdateTaskParams {
|
|
330
|
+
/**
|
|
331
|
+
* Updated task title.
|
|
332
|
+
*
|
|
333
|
+
* @remarks Must be non-empty and at most 1024 characters if provided.
|
|
334
|
+
*/
|
|
92
335
|
title?: string;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Updated description.
|
|
339
|
+
*
|
|
340
|
+
* @remarks Maximum 65,536 characters.
|
|
341
|
+
*/
|
|
93
342
|
description?: string;
|
|
343
|
+
|
|
344
|
+
/** Updated key-value metadata. */
|
|
94
345
|
metadata?: Record<string, unknown>;
|
|
346
|
+
|
|
347
|
+
/** Updated priority level. */
|
|
95
348
|
priority?: TaskPriority;
|
|
349
|
+
|
|
350
|
+
/** Updated parent task ID. */
|
|
96
351
|
parent_id?: string;
|
|
352
|
+
|
|
353
|
+
/** Updated task classification. */
|
|
97
354
|
type?: TaskType;
|
|
355
|
+
|
|
356
|
+
/** Updated lifecycle status. */
|
|
98
357
|
status?: TaskStatus;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Updated assigned user ID.
|
|
361
|
+
*
|
|
362
|
+
* @remarks Legacy field; prefer {@link UpdateTaskParams.assignee | assignee}.
|
|
363
|
+
*/
|
|
99
364
|
assigned_id?: string;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* ID of the user closing the task.
|
|
368
|
+
*
|
|
369
|
+
* @remarks Legacy field; prefer {@link UpdateTaskParams.closer | closer}.
|
|
370
|
+
*/
|
|
100
371
|
closed_id?: string;
|
|
101
|
-
|
|
102
|
-
|
|
372
|
+
|
|
373
|
+
/** Reference to the user being assigned the task. */
|
|
374
|
+
assignee?: UserEntityRef;
|
|
375
|
+
|
|
376
|
+
/** Reference to the user closing the task. */
|
|
377
|
+
closer?: UserEntityRef;
|
|
378
|
+
|
|
379
|
+
/** Reference to the project this task belongs to. */
|
|
103
380
|
project?: EntityRef;
|
|
104
381
|
}
|
|
105
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Parameters for filtering and paginating the task list.
|
|
385
|
+
*/
|
|
106
386
|
export interface ListTasksParams {
|
|
387
|
+
/** Filter by task status. */
|
|
107
388
|
status?: TaskStatus;
|
|
389
|
+
|
|
390
|
+
/** Filter by task type. */
|
|
108
391
|
type?: TaskType;
|
|
392
|
+
|
|
393
|
+
/** Filter by priority level. */
|
|
109
394
|
priority?: TaskPriority;
|
|
395
|
+
|
|
396
|
+
/** Filter by assigned user ID. */
|
|
110
397
|
assigned_id?: string;
|
|
398
|
+
|
|
399
|
+
/** Filter by parent task ID (get subtasks). */
|
|
111
400
|
parent_id?: string;
|
|
401
|
+
|
|
402
|
+
/** Filter by project ID. */
|
|
112
403
|
project_id?: string;
|
|
404
|
+
|
|
405
|
+
/** Filter by tag ID. */
|
|
113
406
|
tag_id?: string;
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Filter for soft-deleted tasks.
|
|
410
|
+
*
|
|
411
|
+
* @default false
|
|
412
|
+
*/
|
|
114
413
|
deleted?: boolean;
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Sort field. Prefix with `-` for descending order.
|
|
417
|
+
*
|
|
418
|
+
* @remarks Supported values: `'created_at'`, `'updated_at'`, `'priority'`.
|
|
419
|
+
* Prefix with `-` for descending (e.g., `'-created_at'`).
|
|
420
|
+
*/
|
|
115
421
|
sort?: string;
|
|
422
|
+
|
|
423
|
+
/** Sort direction: `'asc'` or `'desc'`. */
|
|
116
424
|
order?: 'asc' | 'desc';
|
|
425
|
+
|
|
426
|
+
/** Maximum number of results to return. */
|
|
117
427
|
limit?: number;
|
|
428
|
+
|
|
429
|
+
/** Number of results to skip for pagination. */
|
|
118
430
|
offset?: number;
|
|
119
431
|
}
|
|
120
432
|
|
|
433
|
+
/**
|
|
434
|
+
* Paginated list of tasks with total count.
|
|
435
|
+
*/
|
|
121
436
|
export interface ListTasksResult {
|
|
437
|
+
/** Array of tasks matching the query. */
|
|
122
438
|
tasks: Task[];
|
|
439
|
+
|
|
440
|
+
/** Total number of tasks matching the filters (before pagination). */
|
|
123
441
|
total: number;
|
|
442
|
+
|
|
443
|
+
/** The limit that was applied. */
|
|
124
444
|
limit: number;
|
|
445
|
+
|
|
446
|
+
/** The offset that was applied. */
|
|
125
447
|
offset: number;
|
|
126
448
|
}
|
|
127
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Parameters for batch-deleting tasks by filter.
|
|
452
|
+
* At least one filter must be provided.
|
|
453
|
+
*/
|
|
454
|
+
export interface BatchDeleteTasksParams {
|
|
455
|
+
/** Filter by task status. */
|
|
456
|
+
status?: TaskStatus;
|
|
457
|
+
|
|
458
|
+
/** Filter by task type. */
|
|
459
|
+
type?: TaskType;
|
|
460
|
+
|
|
461
|
+
/** Filter by priority level. */
|
|
462
|
+
priority?: TaskPriority;
|
|
463
|
+
|
|
464
|
+
/** Filter by parent task ID (delete subtasks). */
|
|
465
|
+
parent_id?: string;
|
|
466
|
+
|
|
467
|
+
/** Filter by creator ID. */
|
|
468
|
+
created_id?: string;
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Delete tasks older than this duration.
|
|
472
|
+
* Accepts Go-style duration strings: `'30m'`, `'24h'`, `'7d'`, `'2w'`.
|
|
473
|
+
*/
|
|
474
|
+
older_than?: string;
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Maximum number of tasks to delete.
|
|
478
|
+
* @default 50
|
|
479
|
+
* @maximum 200
|
|
480
|
+
*/
|
|
481
|
+
limit?: number;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* A single task that was deleted in a batch operation.
|
|
486
|
+
*/
|
|
487
|
+
export interface BatchDeletedTask {
|
|
488
|
+
/** The ID of the deleted task. */
|
|
489
|
+
id: string;
|
|
490
|
+
|
|
491
|
+
/** The title of the deleted task. */
|
|
492
|
+
title: string;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Result of a batch delete operation.
|
|
497
|
+
*/
|
|
498
|
+
export interface BatchDeleteTasksResult {
|
|
499
|
+
/** Array of tasks that were deleted. */
|
|
500
|
+
deleted: BatchDeletedTask[];
|
|
501
|
+
|
|
502
|
+
/** Total number of tasks deleted. */
|
|
503
|
+
count: number;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Paginated list of changelog entries for a task.
|
|
508
|
+
*/
|
|
128
509
|
export interface TaskChangelogResult {
|
|
510
|
+
/** Array of change records. */
|
|
129
511
|
changelog: TaskChangelogEntry[];
|
|
512
|
+
|
|
513
|
+
/** Total number of changelog entries. */
|
|
130
514
|
total: number;
|
|
515
|
+
|
|
516
|
+
/** Applied limit. */
|
|
131
517
|
limit: number;
|
|
518
|
+
|
|
519
|
+
/** Applied offset. */
|
|
132
520
|
offset: number;
|
|
133
521
|
}
|
|
134
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Paginated list of comments on a task.
|
|
525
|
+
*/
|
|
135
526
|
export interface ListCommentsResult {
|
|
527
|
+
/** Array of comments. */
|
|
136
528
|
comments: Comment[];
|
|
529
|
+
|
|
530
|
+
/** Total number of comments. */
|
|
137
531
|
total: number;
|
|
532
|
+
|
|
533
|
+
/** Applied limit. */
|
|
138
534
|
limit: number;
|
|
535
|
+
|
|
536
|
+
/** Applied offset. */
|
|
139
537
|
offset: number;
|
|
140
538
|
}
|
|
141
539
|
|
|
540
|
+
/**
|
|
541
|
+
* List of all tags in the organization.
|
|
542
|
+
*/
|
|
142
543
|
export interface ListTagsResult {
|
|
544
|
+
/** Array of tags. */
|
|
143
545
|
tags: Tag[];
|
|
144
546
|
}
|
|
145
547
|
|
|
146
|
-
|
|
548
|
+
/**
|
|
549
|
+
* A file attachment on a task. Attachments are stored in S3 and accessed via presigned URLs.
|
|
550
|
+
*/
|
|
147
551
|
export interface Attachment {
|
|
552
|
+
/** Unique identifier for the attachment. */
|
|
148
553
|
id: string;
|
|
554
|
+
|
|
555
|
+
/** ISO 8601 timestamp when the attachment was uploaded. */
|
|
149
556
|
created_at: string;
|
|
557
|
+
|
|
558
|
+
/** ID of the task this attachment belongs to. */
|
|
150
559
|
task_id: string;
|
|
560
|
+
|
|
561
|
+
/** ID of the user who uploaded the attachment. */
|
|
151
562
|
user_id: string;
|
|
152
|
-
|
|
563
|
+
|
|
564
|
+
/** Reference to the uploader with display name. */
|
|
565
|
+
author?: UserEntityRef;
|
|
566
|
+
|
|
567
|
+
/** Original filename of the uploaded file. */
|
|
153
568
|
filename: string;
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* MIME type of the file.
|
|
572
|
+
*
|
|
573
|
+
* @example 'application/pdf'
|
|
574
|
+
*/
|
|
154
575
|
content_type?: string;
|
|
576
|
+
|
|
577
|
+
/** File size in bytes. */
|
|
155
578
|
size?: number;
|
|
156
579
|
}
|
|
157
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Parameters for initiating a file upload to a task.
|
|
583
|
+
*/
|
|
158
584
|
export interface CreateAttachmentParams {
|
|
585
|
+
/** The filename for the attachment (required). */
|
|
159
586
|
filename: string;
|
|
587
|
+
|
|
588
|
+
/** MIME type of the file. */
|
|
160
589
|
content_type?: string;
|
|
590
|
+
|
|
591
|
+
/** File size in bytes. */
|
|
161
592
|
size?: number;
|
|
162
593
|
}
|
|
163
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Response from initiating an attachment upload. Contains a presigned S3 URL for direct upload.
|
|
597
|
+
*/
|
|
164
598
|
export interface PresignUploadResponse {
|
|
599
|
+
/** The created attachment record. */
|
|
165
600
|
attachment: Attachment;
|
|
601
|
+
|
|
602
|
+
/** A presigned S3 URL to upload the file content via HTTP PUT. */
|
|
166
603
|
presigned_url: string;
|
|
604
|
+
|
|
605
|
+
/** Number of seconds until the presigned URL expires. */
|
|
167
606
|
expiry_seconds: number;
|
|
168
607
|
}
|
|
169
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Response containing a presigned S3 URL for downloading an attachment.
|
|
611
|
+
*/
|
|
170
612
|
export interface PresignDownloadResponse {
|
|
613
|
+
/** A presigned S3 URL to download the file via HTTP GET. */
|
|
171
614
|
presigned_url: string;
|
|
615
|
+
|
|
616
|
+
/** Number of seconds until the presigned URL expires. */
|
|
172
617
|
expiry_seconds: number;
|
|
173
618
|
}
|
|
174
619
|
|
|
620
|
+
/**
|
|
621
|
+
* List of attachments on a task.
|
|
622
|
+
*/
|
|
175
623
|
export interface ListAttachmentsResult {
|
|
624
|
+
/** Array of attachment records. */
|
|
176
625
|
attachments: Attachment[];
|
|
626
|
+
|
|
627
|
+
/** Total number of attachments. */
|
|
177
628
|
total: number;
|
|
178
629
|
}
|
|
179
630
|
|
|
631
|
+
/**
|
|
632
|
+
* List of all users who have been referenced in tasks (as creators, assignees, or closers).
|
|
633
|
+
*/
|
|
180
634
|
export interface ListUsersResult {
|
|
181
|
-
|
|
635
|
+
/** Array of user entity references with type information. */
|
|
636
|
+
users: UserEntityRef[];
|
|
182
637
|
}
|
|
183
638
|
|
|
639
|
+
/**
|
|
640
|
+
* List of all projects that have been referenced in tasks.
|
|
641
|
+
*/
|
|
184
642
|
export interface ListProjectsResult {
|
|
643
|
+
/** Array of project entity references. */
|
|
185
644
|
projects: EntityRef[];
|
|
186
645
|
}
|
|
187
646
|
|
|
647
|
+
/**
|
|
648
|
+
* Parameters for querying task activity time-series data.
|
|
649
|
+
*/
|
|
188
650
|
export interface TaskActivityParams {
|
|
189
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Number of days of activity to retrieve.
|
|
653
|
+
*
|
|
654
|
+
* @remarks Minimum 7, maximum 365.
|
|
655
|
+
* @default 90
|
|
656
|
+
*/
|
|
657
|
+
days?: number;
|
|
190
658
|
}
|
|
191
659
|
|
|
660
|
+
/**
|
|
661
|
+
* A single day's snapshot of task counts by status.
|
|
662
|
+
*/
|
|
192
663
|
export interface TaskActivityDataPoint {
|
|
193
|
-
|
|
664
|
+
/**
|
|
665
|
+
* The date in `YYYY-MM-DD` format.
|
|
666
|
+
*
|
|
667
|
+
* @example '2026-02-28'
|
|
668
|
+
*/
|
|
669
|
+
date: string;
|
|
670
|
+
|
|
671
|
+
/** Number of tasks in `'open'` status on this date. */
|
|
194
672
|
open: number;
|
|
673
|
+
|
|
674
|
+
/** Number of tasks in `'in_progress'` status on this date. */
|
|
195
675
|
inProgress: number;
|
|
676
|
+
|
|
677
|
+
/** Number of tasks in `'done'` status on this date. */
|
|
196
678
|
done: number;
|
|
679
|
+
|
|
680
|
+
/** Number of tasks in `'closed'` status on this date. */
|
|
197
681
|
closed: number;
|
|
682
|
+
|
|
683
|
+
/** Number of tasks in `'cancelled'` status on this date. */
|
|
198
684
|
cancelled: number;
|
|
199
685
|
}
|
|
200
686
|
|
|
687
|
+
/**
|
|
688
|
+
* Task activity time-series data.
|
|
689
|
+
*/
|
|
201
690
|
export interface TaskActivityResult {
|
|
691
|
+
/** Array of daily activity snapshots, ordered chronologically. */
|
|
202
692
|
activity: TaskActivityDataPoint[];
|
|
693
|
+
|
|
694
|
+
/** The number of days of data returned. */
|
|
203
695
|
days: number;
|
|
204
696
|
}
|
|
205
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Interface defining the contract for task storage operations.
|
|
700
|
+
*
|
|
701
|
+
* Implemented by {@link TaskStorageService}.
|
|
702
|
+
*/
|
|
206
703
|
export interface TaskStorage {
|
|
704
|
+
/**
|
|
705
|
+
* Create a new task.
|
|
706
|
+
*
|
|
707
|
+
* @param params - The task creation parameters
|
|
708
|
+
* @returns The newly created task
|
|
709
|
+
*/
|
|
207
710
|
create(params: CreateTaskParams): Promise<Task>;
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Get a task by its ID.
|
|
714
|
+
*
|
|
715
|
+
* @param id - The unique task identifier
|
|
716
|
+
* @returns The task if found, or `null` if not found
|
|
717
|
+
*/
|
|
208
718
|
get(id: string): Promise<Task | null>;
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* List tasks with optional filtering and pagination.
|
|
722
|
+
*
|
|
723
|
+
* @param params - Optional filter and pagination parameters
|
|
724
|
+
* @returns Paginated list of matching tasks
|
|
725
|
+
*/
|
|
209
726
|
list(params?: ListTasksParams): Promise<ListTasksResult>;
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Partially update an existing task.
|
|
730
|
+
*
|
|
731
|
+
* @param id - The unique task identifier
|
|
732
|
+
* @param params - Fields to update (only provided fields are changed)
|
|
733
|
+
* @returns The updated task
|
|
734
|
+
*/
|
|
210
735
|
update(id: string, params: UpdateTaskParams): Promise<Task>;
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Close a task by setting its status to closed.
|
|
739
|
+
*
|
|
740
|
+
* @param id - The unique task identifier
|
|
741
|
+
* @returns The closed task
|
|
742
|
+
*/
|
|
211
743
|
close(id: string): Promise<Task>;
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Soft-delete a task, marking it as deleted without permanent removal.
|
|
747
|
+
*
|
|
748
|
+
* @param id - The unique task identifier
|
|
749
|
+
* @returns The soft-deleted task
|
|
750
|
+
*/
|
|
212
751
|
softDelete(id: string): Promise<Task>;
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Batch soft-delete tasks matching the given filters.
|
|
755
|
+
* At least one filter must be provided.
|
|
756
|
+
*
|
|
757
|
+
* @param params - Filters to select which tasks to delete
|
|
758
|
+
* @returns The list of deleted tasks and count
|
|
759
|
+
*/
|
|
760
|
+
batchDelete(params: BatchDeleteTasksParams): Promise<BatchDeleteTasksResult>;
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Get the changelog (audit trail) for a task.
|
|
764
|
+
*
|
|
765
|
+
* @param id - The unique task identifier
|
|
766
|
+
* @param params - Optional pagination parameters
|
|
767
|
+
* @returns Paginated list of changelog entries
|
|
768
|
+
*/
|
|
213
769
|
changelog(
|
|
214
770
|
id: string,
|
|
215
771
|
params?: { limit?: number; offset?: number }
|
|
216
772
|
): Promise<TaskChangelogResult>;
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Create a comment on a task.
|
|
776
|
+
*
|
|
777
|
+
* @param taskId - The ID of the task to comment on
|
|
778
|
+
* @param body - The comment text content
|
|
779
|
+
* @param userId - The ID of the user authoring the comment
|
|
780
|
+
* @param author - Optional entity reference with display name
|
|
781
|
+
* @returns The newly created comment
|
|
782
|
+
*/
|
|
217
783
|
createComment(
|
|
218
784
|
taskId: string,
|
|
219
785
|
body: string,
|
|
220
786
|
userId: string,
|
|
221
787
|
author?: EntityRef
|
|
222
788
|
): Promise<Comment>;
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Get a comment by its ID.
|
|
792
|
+
*
|
|
793
|
+
* @param commentId - The unique comment identifier
|
|
794
|
+
* @returns The comment
|
|
795
|
+
*/
|
|
223
796
|
getComment(commentId: string): Promise<Comment>;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Update a comment's body text.
|
|
800
|
+
*
|
|
801
|
+
* @param commentId - The unique comment identifier
|
|
802
|
+
* @param body - The new comment text
|
|
803
|
+
* @returns The updated comment
|
|
804
|
+
*/
|
|
224
805
|
updateComment(commentId: string, body: string): Promise<Comment>;
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Delete a comment.
|
|
809
|
+
*
|
|
810
|
+
* @param commentId - The unique comment identifier
|
|
811
|
+
*/
|
|
225
812
|
deleteComment(commentId: string): Promise<void>;
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* List comments on a task with optional pagination.
|
|
816
|
+
*
|
|
817
|
+
* @param taskId - The ID of the task
|
|
818
|
+
* @param params - Optional pagination parameters
|
|
819
|
+
* @returns Paginated list of comments
|
|
820
|
+
*/
|
|
226
821
|
listComments(
|
|
227
822
|
taskId: string,
|
|
228
823
|
params?: { limit?: number; offset?: number }
|
|
229
824
|
): Promise<ListCommentsResult>;
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Create a new tag.
|
|
828
|
+
*
|
|
829
|
+
* @param name - The tag display name
|
|
830
|
+
* @param color - Optional hex color code (e.g., `'#ff0000'`)
|
|
831
|
+
* @returns The newly created tag
|
|
832
|
+
*/
|
|
230
833
|
createTag(name: string, color?: string): Promise<Tag>;
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Get a tag by its ID.
|
|
837
|
+
*
|
|
838
|
+
* @param tagId - The unique tag identifier
|
|
839
|
+
* @returns The tag
|
|
840
|
+
*/
|
|
231
841
|
getTag(tagId: string): Promise<Tag>;
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Update a tag's name and optionally its color.
|
|
845
|
+
*
|
|
846
|
+
* @param tagId - The unique tag identifier
|
|
847
|
+
* @param name - The new tag name
|
|
848
|
+
* @param color - Optional new hex color code
|
|
849
|
+
* @returns The updated tag
|
|
850
|
+
*/
|
|
232
851
|
updateTag(tagId: string, name: string, color?: string): Promise<Tag>;
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Delete a tag.
|
|
855
|
+
*
|
|
856
|
+
* @param tagId - The unique tag identifier
|
|
857
|
+
*/
|
|
233
858
|
deleteTag(tagId: string): Promise<void>;
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* List all tags in the organization.
|
|
862
|
+
*
|
|
863
|
+
* @returns List of all tags
|
|
864
|
+
*/
|
|
234
865
|
listTags(): Promise<ListTagsResult>;
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Associate a tag with a task.
|
|
869
|
+
*
|
|
870
|
+
* @param taskId - The ID of the task
|
|
871
|
+
* @param tagId - The ID of the tag to add
|
|
872
|
+
*/
|
|
235
873
|
addTagToTask(taskId: string, tagId: string): Promise<void>;
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Remove a tag association from a task.
|
|
877
|
+
*
|
|
878
|
+
* @param taskId - The ID of the task
|
|
879
|
+
* @param tagId - The ID of the tag to remove
|
|
880
|
+
*/
|
|
236
881
|
removeTagFromTask(taskId: string, tagId: string): Promise<void>;
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* List all tags associated with a specific task.
|
|
885
|
+
*
|
|
886
|
+
* @param taskId - The ID of the task
|
|
887
|
+
* @returns Array of tags on the task
|
|
888
|
+
*/
|
|
237
889
|
listTagsForTask(taskId: string): Promise<Tag[]>;
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Initiate a file upload to a task. Returns a presigned S3 URL for direct upload.
|
|
893
|
+
*
|
|
894
|
+
* @param taskId - The ID of the task to attach the file to
|
|
895
|
+
* @param params - Attachment metadata (filename, content type, size)
|
|
896
|
+
* @returns The attachment record and a presigned upload URL
|
|
897
|
+
*/
|
|
238
898
|
uploadAttachment(taskId: string, params: CreateAttachmentParams): Promise<PresignUploadResponse>;
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Confirm that a file upload has completed successfully.
|
|
902
|
+
*
|
|
903
|
+
* @param attachmentId - The unique attachment identifier
|
|
904
|
+
* @returns The confirmed attachment record
|
|
905
|
+
*/
|
|
239
906
|
confirmAttachment(attachmentId: string): Promise<Attachment>;
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Get a presigned S3 URL for downloading an attachment.
|
|
910
|
+
*
|
|
911
|
+
* @param attachmentId - The unique attachment identifier
|
|
912
|
+
* @returns A presigned download URL
|
|
913
|
+
*/
|
|
240
914
|
downloadAttachment(attachmentId: string): Promise<PresignDownloadResponse>;
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* List all attachments on a task.
|
|
918
|
+
*
|
|
919
|
+
* @param taskId - The ID of the task
|
|
920
|
+
* @returns List of attachments with total count
|
|
921
|
+
*/
|
|
241
922
|
listAttachments(taskId: string): Promise<ListAttachmentsResult>;
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Delete an attachment.
|
|
926
|
+
*
|
|
927
|
+
* @param attachmentId - The unique attachment identifier
|
|
928
|
+
*/
|
|
242
929
|
deleteAttachment(attachmentId: string): Promise<void>;
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* List all users who have been referenced in tasks.
|
|
933
|
+
*
|
|
934
|
+
* @returns List of user entity references
|
|
935
|
+
*/
|
|
243
936
|
listUsers(): Promise<ListUsersResult>;
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* List all projects that have been referenced in tasks.
|
|
940
|
+
*
|
|
941
|
+
* @returns List of project entity references
|
|
942
|
+
*/
|
|
244
943
|
listProjects(): Promise<ListProjectsResult>;
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Get task activity time-series data showing daily status counts.
|
|
947
|
+
*
|
|
948
|
+
* @param params - Optional parameters controlling the number of days to retrieve
|
|
949
|
+
* @returns Time-series activity data
|
|
950
|
+
*/
|
|
245
951
|
getActivity(params?: TaskActivityParams): Promise<TaskActivityResult>;
|
|
246
952
|
}
|
|
247
953
|
|
|
954
|
+
/** API version string used for task CRUD, comment, tag, and attachment endpoints. */
|
|
248
955
|
const TASK_API_VERSION = '2026-02-24';
|
|
956
|
+
|
|
957
|
+
/** Maximum number of tasks that can be deleted in a single batch request. */
|
|
958
|
+
const MAX_BATCH_DELETE_LIMIT = 200;
|
|
959
|
+
|
|
960
|
+
/** API version string used for the task activity analytics endpoint. */
|
|
249
961
|
const TASK_ACTIVITY_API_VERSION = '2026-02-28';
|
|
250
962
|
|
|
963
|
+
/** Thrown when a task ID parameter is empty or not a string. */
|
|
251
964
|
const TaskIdRequiredError = StructuredError(
|
|
252
965
|
'TaskIdRequiredError',
|
|
253
966
|
'Task ID is required and must be a non-empty string'
|
|
254
967
|
);
|
|
255
968
|
|
|
969
|
+
/** Thrown when a task title is empty or not a string. */
|
|
256
970
|
const TaskTitleRequiredError = StructuredError(
|
|
257
971
|
'TaskTitleRequiredError',
|
|
258
972
|
'Task title is required and must be a non-empty string'
|
|
259
973
|
);
|
|
260
974
|
|
|
975
|
+
/** Thrown when a comment ID parameter is empty or not a string. */
|
|
261
976
|
const CommentIdRequiredError = StructuredError(
|
|
262
977
|
'CommentIdRequiredError',
|
|
263
978
|
'Comment ID is required and must be a non-empty string'
|
|
264
979
|
);
|
|
265
980
|
|
|
981
|
+
/** Thrown when a comment body is empty or not a string. */
|
|
266
982
|
const CommentBodyRequiredError = StructuredError(
|
|
267
983
|
'CommentBodyRequiredError',
|
|
268
984
|
'Comment body is required and must be a non-empty string'
|
|
269
985
|
);
|
|
270
986
|
|
|
987
|
+
/** Thrown when a tag ID parameter is empty or not a string. */
|
|
271
988
|
const TagIdRequiredError = StructuredError(
|
|
272
989
|
'TagIdRequiredError',
|
|
273
990
|
'Tag ID is required and must be a non-empty string'
|
|
274
991
|
);
|
|
275
992
|
|
|
993
|
+
/** Thrown when a tag name is empty or not a string. */
|
|
276
994
|
const TagNameRequiredError = StructuredError(
|
|
277
995
|
'TagNameRequiredError',
|
|
278
996
|
'Tag name is required and must be a non-empty string'
|
|
279
997
|
);
|
|
280
998
|
|
|
999
|
+
/** Thrown when an attachment ID parameter is empty or not a string. */
|
|
281
1000
|
const AttachmentIdRequiredError = StructuredError(
|
|
282
1001
|
'AttachmentIdRequiredError',
|
|
283
1002
|
'Attachment ID is required and must be a non-empty string'
|
|
284
1003
|
);
|
|
285
1004
|
|
|
1005
|
+
/** Thrown when a user ID parameter is empty or not a string. */
|
|
286
1006
|
const UserIdRequiredError = StructuredError(
|
|
287
1007
|
'UserIdRequiredError',
|
|
288
1008
|
'User ID is required and must be a non-empty string'
|
|
289
1009
|
);
|
|
290
1010
|
|
|
1011
|
+
/**
|
|
1012
|
+
* Thrown when the API returns a success HTTP status but the response body indicates failure.
|
|
1013
|
+
*/
|
|
291
1014
|
const TaskStorageResponseError = StructuredError('TaskStorageResponseError')<{
|
|
292
1015
|
status: number;
|
|
293
1016
|
}>();
|
|
294
1017
|
|
|
1018
|
+
/**
|
|
1019
|
+
* Internal API success response envelope for task operations.
|
|
1020
|
+
*/
|
|
295
1021
|
interface TaskSuccessResponse<T> {
|
|
296
1022
|
success: true;
|
|
297
1023
|
data: T;
|
|
298
1024
|
}
|
|
299
1025
|
|
|
1026
|
+
/**
|
|
1027
|
+
* Internal API error response envelope for task operations.
|
|
1028
|
+
*/
|
|
300
1029
|
interface TaskErrorResponse {
|
|
301
1030
|
success: false;
|
|
302
1031
|
message: string;
|
|
303
1032
|
}
|
|
304
1033
|
|
|
1034
|
+
/**
|
|
1035
|
+
* Discriminated union of API success and error responses for task operations.
|
|
1036
|
+
*/
|
|
305
1037
|
type TaskResponse<T> = TaskSuccessResponse<T> | TaskErrorResponse;
|
|
306
1038
|
|
|
1039
|
+
/**
|
|
1040
|
+
* Client for the Agentuity Task management service.
|
|
1041
|
+
*
|
|
1042
|
+
* Provides a full-featured project management API including task CRUD, hierarchical
|
|
1043
|
+
* organization (epics → features → tasks), comments, tags, file attachments via
|
|
1044
|
+
* presigned S3 URLs, changelog tracking, and activity analytics.
|
|
1045
|
+
*
|
|
1046
|
+
* Tasks support lifecycle management through status transitions (`open` → `in_progress`
|
|
1047
|
+
* → `done`/`closed`/`cancelled`) with automatic date tracking for each transition.
|
|
1048
|
+
*
|
|
1049
|
+
* All methods validate inputs client-side and throw structured errors for invalid
|
|
1050
|
+
* parameters. API errors throw {@link ServiceException}.
|
|
1051
|
+
*
|
|
1052
|
+
* @example
|
|
1053
|
+
* ```typescript
|
|
1054
|
+
* const tasks = new TaskStorageService(baseUrl, adapter);
|
|
1055
|
+
*
|
|
1056
|
+
* // Create a task
|
|
1057
|
+
* const task = await tasks.create({
|
|
1058
|
+
* title: 'Implement login flow',
|
|
1059
|
+
* type: 'feature',
|
|
1060
|
+
* created_id: 'user_123',
|
|
1061
|
+
* creator: { id: 'user_123', name: 'Alice' },
|
|
1062
|
+
* priority: 'high',
|
|
1063
|
+
* });
|
|
1064
|
+
*
|
|
1065
|
+
* // Add a comment
|
|
1066
|
+
* await tasks.createComment(task.id, 'Started working on this', 'user_123');
|
|
1067
|
+
*
|
|
1068
|
+
* // List open tasks
|
|
1069
|
+
* const { tasks: openTasks } = await tasks.list({ status: 'open' });
|
|
1070
|
+
* ```
|
|
1071
|
+
*/
|
|
307
1072
|
export class TaskStorageService implements TaskStorage {
|
|
308
1073
|
#adapter: FetchAdapter;
|
|
309
1074
|
#baseUrl: string;
|
|
310
1075
|
|
|
1076
|
+
/**
|
|
1077
|
+
* Creates a new TaskStorageService instance.
|
|
1078
|
+
*
|
|
1079
|
+
* @param baseUrl - The base URL of the task management API
|
|
1080
|
+
* @param adapter - The HTTP fetch adapter used for making API requests
|
|
1081
|
+
*/
|
|
311
1082
|
constructor(baseUrl: string, adapter: FetchAdapter) {
|
|
312
1083
|
this.#adapter = adapter;
|
|
313
1084
|
this.#baseUrl = baseUrl;
|
|
314
1085
|
}
|
|
315
1086
|
|
|
1087
|
+
/**
|
|
1088
|
+
* Create a new task.
|
|
1089
|
+
*
|
|
1090
|
+
* @param params - The task creation parameters including title, type, and optional fields
|
|
1091
|
+
* @returns The newly created task
|
|
1092
|
+
* @throws {@link TaskTitleRequiredError} if the title is empty or not a string
|
|
1093
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```typescript
|
|
1097
|
+
* const task = await tasks.create({
|
|
1098
|
+
* title: 'Fix login bug',
|
|
1099
|
+
* type: 'bug',
|
|
1100
|
+
* created_id: 'user_123',
|
|
1101
|
+
* priority: 'high',
|
|
1102
|
+
* creator: { id: 'user_123', name: 'Alice' },
|
|
1103
|
+
* project: { id: 'proj_456', name: 'Auth Service' },
|
|
1104
|
+
* });
|
|
1105
|
+
* console.log('Created:', task.id);
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
316
1108
|
async create(params: CreateTaskParams): Promise<Task> {
|
|
317
1109
|
if (!params?.title || typeof params.title !== 'string' || params.title.trim().length === 0) {
|
|
318
1110
|
throw new TaskTitleRequiredError();
|
|
@@ -349,6 +1141,24 @@ export class TaskStorageService implements TaskStorage {
|
|
|
349
1141
|
throw await toServiceException('POST', url, res.response);
|
|
350
1142
|
}
|
|
351
1143
|
|
|
1144
|
+
/**
|
|
1145
|
+
* Get a task by its ID.
|
|
1146
|
+
*
|
|
1147
|
+
* @param id - The unique task identifier
|
|
1148
|
+
* @returns The task if found, or `null` if the task does not exist
|
|
1149
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1150
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1151
|
+
*
|
|
1152
|
+
* @example
|
|
1153
|
+
* ```typescript
|
|
1154
|
+
* const task = await tasks.get('task_abc123');
|
|
1155
|
+
* if (task) {
|
|
1156
|
+
* console.log(task.title, task.status);
|
|
1157
|
+
* } else {
|
|
1158
|
+
* console.log('Task not found');
|
|
1159
|
+
* }
|
|
1160
|
+
* ```
|
|
1161
|
+
*/
|
|
352
1162
|
async get(id: string): Promise<Task | null> {
|
|
353
1163
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
354
1164
|
throw new TaskIdRequiredError();
|
|
@@ -383,6 +1193,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
383
1193
|
throw await toServiceException('GET', url, res.response);
|
|
384
1194
|
}
|
|
385
1195
|
|
|
1196
|
+
/**
|
|
1197
|
+
* List tasks with optional filtering and pagination.
|
|
1198
|
+
*
|
|
1199
|
+
* @param params - Optional filter and pagination parameters
|
|
1200
|
+
* @returns Paginated list of tasks matching the filters
|
|
1201
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1202
|
+
*
|
|
1203
|
+
* @example
|
|
1204
|
+
* ```typescript
|
|
1205
|
+
* // List all open high-priority bugs
|
|
1206
|
+
* const result = await tasks.list({
|
|
1207
|
+
* status: 'open',
|
|
1208
|
+
* type: 'bug',
|
|
1209
|
+
* priority: 'high',
|
|
1210
|
+
* sort: '-created_at',
|
|
1211
|
+
* limit: 20,
|
|
1212
|
+
* });
|
|
1213
|
+
* console.log(`Found ${result.total} bugs, showing ${result.tasks.length}`);
|
|
1214
|
+
* ```
|
|
1215
|
+
*/
|
|
386
1216
|
async list(params?: ListTasksParams): Promise<ListTasksResult> {
|
|
387
1217
|
const queryParams = new URLSearchParams();
|
|
388
1218
|
if (params?.status) queryParams.set('status', params.status);
|
|
@@ -431,6 +1261,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
431
1261
|
throw await toServiceException('GET', url, res.response);
|
|
432
1262
|
}
|
|
433
1263
|
|
|
1264
|
+
/**
|
|
1265
|
+
* Partially update an existing task.
|
|
1266
|
+
*
|
|
1267
|
+
* @param id - The unique task identifier
|
|
1268
|
+
* @param params - Fields to update; only provided fields are changed
|
|
1269
|
+
* @returns The updated task
|
|
1270
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1271
|
+
* @throws {@link TaskTitleRequiredError} if a title is provided but is empty
|
|
1272
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1273
|
+
*
|
|
1274
|
+
* @example
|
|
1275
|
+
* ```typescript
|
|
1276
|
+
* const updated = await tasks.update('task_abc123', {
|
|
1277
|
+
* status: 'in_progress',
|
|
1278
|
+
* priority: 'high',
|
|
1279
|
+
* assignee: { id: 'user_456', name: 'Bob' },
|
|
1280
|
+
* });
|
|
1281
|
+
* console.log('Updated status:', updated.status);
|
|
1282
|
+
* ```
|
|
1283
|
+
*/
|
|
434
1284
|
async update(id: string, params: UpdateTaskParams): Promise<Task> {
|
|
435
1285
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
436
1286
|
throw new TaskIdRequiredError();
|
|
@@ -469,6 +1319,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
469
1319
|
throw await toServiceException('PATCH', url, res.response);
|
|
470
1320
|
}
|
|
471
1321
|
|
|
1322
|
+
/**
|
|
1323
|
+
* Close a task by setting its status to closed.
|
|
1324
|
+
*
|
|
1325
|
+
* @param id - The unique task identifier
|
|
1326
|
+
* @returns The closed task with updated `closed_date`
|
|
1327
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1328
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1329
|
+
*
|
|
1330
|
+
* @example
|
|
1331
|
+
* ```typescript
|
|
1332
|
+
* const closed = await tasks.close('task_abc123');
|
|
1333
|
+
* console.log('Closed at:', closed.closed_date);
|
|
1334
|
+
* ```
|
|
1335
|
+
*/
|
|
472
1336
|
async close(id: string): Promise<Task> {
|
|
473
1337
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
474
1338
|
throw new TaskIdRequiredError();
|
|
@@ -499,6 +1363,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
499
1363
|
throw await toServiceException('DELETE', url, res.response);
|
|
500
1364
|
}
|
|
501
1365
|
|
|
1366
|
+
/**
|
|
1367
|
+
* Get the changelog (audit trail) for a task, showing all field changes over time.
|
|
1368
|
+
*
|
|
1369
|
+
* @param id - The unique task identifier
|
|
1370
|
+
* @param params - Optional pagination parameters
|
|
1371
|
+
* @returns Paginated list of changelog entries ordered by most recent first
|
|
1372
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1373
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1374
|
+
*
|
|
1375
|
+
* @example
|
|
1376
|
+
* ```typescript
|
|
1377
|
+
* const { changelog, total } = await tasks.changelog('task_abc123', {
|
|
1378
|
+
* limit: 10,
|
|
1379
|
+
* offset: 0,
|
|
1380
|
+
* });
|
|
1381
|
+
* for (const entry of changelog) {
|
|
1382
|
+
* console.log(`${entry.field}: ${entry.old_value} → ${entry.new_value}`);
|
|
1383
|
+
* }
|
|
1384
|
+
* ```
|
|
1385
|
+
*/
|
|
502
1386
|
async changelog(
|
|
503
1387
|
id: string,
|
|
504
1388
|
params?: { limit?: number; offset?: number }
|
|
@@ -542,6 +1426,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
542
1426
|
throw await toServiceException('GET', url, res.response);
|
|
543
1427
|
}
|
|
544
1428
|
|
|
1429
|
+
/**
|
|
1430
|
+
* Soft-delete a task, marking it as deleted without permanent removal.
|
|
1431
|
+
*
|
|
1432
|
+
* @param id - The unique task identifier
|
|
1433
|
+
* @returns The soft-deleted task
|
|
1434
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1435
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1436
|
+
*
|
|
1437
|
+
* @example
|
|
1438
|
+
* ```typescript
|
|
1439
|
+
* const deleted = await tasks.softDelete('task_abc123');
|
|
1440
|
+
* console.log('Soft-deleted task:', deleted.id);
|
|
1441
|
+
* ```
|
|
1442
|
+
*/
|
|
545
1443
|
async softDelete(id: string): Promise<Task> {
|
|
546
1444
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
547
1445
|
throw new TaskIdRequiredError();
|
|
@@ -575,6 +1473,101 @@ export class TaskStorageService implements TaskStorage {
|
|
|
575
1473
|
throw await toServiceException('POST', url, res.response);
|
|
576
1474
|
}
|
|
577
1475
|
|
|
1476
|
+
/**
|
|
1477
|
+
* Batch soft-delete tasks matching the given filters.
|
|
1478
|
+
* At least one filter must be provided. The server caps the limit at 200.
|
|
1479
|
+
*
|
|
1480
|
+
* @param params - Filters to select which tasks to delete
|
|
1481
|
+
* @returns The list of deleted tasks and count
|
|
1482
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1483
|
+
*
|
|
1484
|
+
* @example
|
|
1485
|
+
* ```typescript
|
|
1486
|
+
* const result = await tasks.batchDelete({ status: 'closed', older_than: '7d', limit: 50 });
|
|
1487
|
+
* console.log(`Deleted ${result.count} tasks`);
|
|
1488
|
+
* ```
|
|
1489
|
+
*/
|
|
1490
|
+
async batchDelete(params: BatchDeleteTasksParams): Promise<BatchDeleteTasksResult> {
|
|
1491
|
+
const hasFilter =
|
|
1492
|
+
params.status ||
|
|
1493
|
+
params.type ||
|
|
1494
|
+
params.priority ||
|
|
1495
|
+
params.parent_id ||
|
|
1496
|
+
params.created_id ||
|
|
1497
|
+
params.older_than;
|
|
1498
|
+
if (!hasFilter) {
|
|
1499
|
+
throw new Error('At least one filter is required for batch delete');
|
|
1500
|
+
}
|
|
1501
|
+
if (params.limit !== undefined && params.limit > MAX_BATCH_DELETE_LIMIT) {
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
`Batch delete limit must not exceed ${MAX_BATCH_DELETE_LIMIT} (got ${params.limit})`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
const url = buildUrl(this.#baseUrl, `/task/delete/batch/${TASK_API_VERSION}`);
|
|
1508
|
+
const signal = AbortSignal.timeout(60_000);
|
|
1509
|
+
|
|
1510
|
+
const body: Record<string, unknown> = {};
|
|
1511
|
+
if (params.status) body.status = params.status;
|
|
1512
|
+
if (params.type) body.type = params.type;
|
|
1513
|
+
if (params.priority) body.priority = params.priority;
|
|
1514
|
+
if (params.parent_id) body.parent_id = params.parent_id;
|
|
1515
|
+
if (params.created_id) body.created_id = params.created_id;
|
|
1516
|
+
if (params.older_than) body.older_than = params.older_than;
|
|
1517
|
+
if (params.limit !== undefined) body.limit = params.limit;
|
|
1518
|
+
|
|
1519
|
+
const res = await this.#adapter.invoke<TaskResponse<BatchDeleteTasksResult>>(url, {
|
|
1520
|
+
method: 'POST',
|
|
1521
|
+
body: safeStringify(body),
|
|
1522
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1523
|
+
signal,
|
|
1524
|
+
telemetry: {
|
|
1525
|
+
name: 'agentuity.task.batchDelete',
|
|
1526
|
+
attributes: {
|
|
1527
|
+
...(params.status ? { status: params.status } : {}),
|
|
1528
|
+
...(params.type ? { type: params.type } : {}),
|
|
1529
|
+
...(params.older_than ? { older_than: params.older_than } : {}),
|
|
1530
|
+
},
|
|
1531
|
+
},
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
if (res.ok) {
|
|
1535
|
+
if (res.data.success) {
|
|
1536
|
+
return res.data.data;
|
|
1537
|
+
}
|
|
1538
|
+
throw new TaskStorageResponseError({
|
|
1539
|
+
status: res.response.status,
|
|
1540
|
+
message: res.data.message,
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
throw await toServiceException('POST', url, res.response);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Create a comment on a task.
|
|
1549
|
+
*
|
|
1550
|
+
* @param taskId - The ID of the task to comment on
|
|
1551
|
+
* @param body - The comment text content (must be non-empty)
|
|
1552
|
+
* @param userId - The ID of the user authoring the comment
|
|
1553
|
+
* @param author - Optional entity reference with the author's display name
|
|
1554
|
+
* @returns The newly created comment
|
|
1555
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1556
|
+
* @throws {@link CommentBodyRequiredError} if the body is empty or not a string
|
|
1557
|
+
* @throws {@link UserIdRequiredError} if the user ID is empty or not a string
|
|
1558
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1559
|
+
*
|
|
1560
|
+
* @example
|
|
1561
|
+
* ```typescript
|
|
1562
|
+
* const comment = await tasks.createComment(
|
|
1563
|
+
* 'task_abc123',
|
|
1564
|
+
* 'This is ready for review.',
|
|
1565
|
+
* 'user_456',
|
|
1566
|
+
* { id: 'user_456', name: 'Bob' },
|
|
1567
|
+
* );
|
|
1568
|
+
* console.log('Comment created:', comment.id);
|
|
1569
|
+
* ```
|
|
1570
|
+
*/
|
|
578
1571
|
async createComment(
|
|
579
1572
|
taskId: string,
|
|
580
1573
|
body: string,
|
|
@@ -624,6 +1617,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
624
1617
|
throw await toServiceException('POST', url, res.response);
|
|
625
1618
|
}
|
|
626
1619
|
|
|
1620
|
+
/**
|
|
1621
|
+
* Get a comment by its ID.
|
|
1622
|
+
*
|
|
1623
|
+
* @param commentId - The unique comment identifier
|
|
1624
|
+
* @returns The comment
|
|
1625
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
1626
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1627
|
+
*
|
|
1628
|
+
* @example
|
|
1629
|
+
* ```typescript
|
|
1630
|
+
* const comment = await tasks.getComment('comment_xyz789');
|
|
1631
|
+
* console.log(`${comment.author?.name}: ${comment.body}`);
|
|
1632
|
+
* ```
|
|
1633
|
+
*/
|
|
627
1634
|
async getComment(commentId: string): Promise<Comment> {
|
|
628
1635
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
629
1636
|
throw new CommentIdRequiredError();
|
|
@@ -657,6 +1664,25 @@ export class TaskStorageService implements TaskStorage {
|
|
|
657
1664
|
throw await toServiceException('GET', url, res.response);
|
|
658
1665
|
}
|
|
659
1666
|
|
|
1667
|
+
/**
|
|
1668
|
+
* Update a comment's body text.
|
|
1669
|
+
*
|
|
1670
|
+
* @param commentId - The unique comment identifier
|
|
1671
|
+
* @param body - The new comment text (must be non-empty)
|
|
1672
|
+
* @returns The updated comment
|
|
1673
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
1674
|
+
* @throws {@link CommentBodyRequiredError} if the body is empty or not a string
|
|
1675
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1676
|
+
*
|
|
1677
|
+
* @example
|
|
1678
|
+
* ```typescript
|
|
1679
|
+
* const updated = await tasks.updateComment(
|
|
1680
|
+
* 'comment_xyz789',
|
|
1681
|
+
* 'Updated: This is now ready for final review.',
|
|
1682
|
+
* );
|
|
1683
|
+
* console.log('Updated at:', updated.updated_at);
|
|
1684
|
+
* ```
|
|
1685
|
+
*/
|
|
660
1686
|
async updateComment(commentId: string, body: string): Promise<Comment> {
|
|
661
1687
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
662
1688
|
throw new CommentIdRequiredError();
|
|
@@ -695,6 +1721,19 @@ export class TaskStorageService implements TaskStorage {
|
|
|
695
1721
|
throw await toServiceException('PATCH', url, res.response);
|
|
696
1722
|
}
|
|
697
1723
|
|
|
1724
|
+
/**
|
|
1725
|
+
* Delete a comment permanently.
|
|
1726
|
+
*
|
|
1727
|
+
* @param commentId - The unique comment identifier
|
|
1728
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
1729
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1730
|
+
*
|
|
1731
|
+
* @example
|
|
1732
|
+
* ```typescript
|
|
1733
|
+
* await tasks.deleteComment('comment_xyz789');
|
|
1734
|
+
* console.log('Comment deleted');
|
|
1735
|
+
* ```
|
|
1736
|
+
*/
|
|
698
1737
|
async deleteComment(commentId: string): Promise<void> {
|
|
699
1738
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
700
1739
|
throw new CommentIdRequiredError();
|
|
@@ -728,6 +1767,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
728
1767
|
throw await toServiceException('DELETE', url, res.response);
|
|
729
1768
|
}
|
|
730
1769
|
|
|
1770
|
+
/**
|
|
1771
|
+
* List comments on a task with optional pagination.
|
|
1772
|
+
*
|
|
1773
|
+
* @param taskId - The ID of the task whose comments to list
|
|
1774
|
+
* @param params - Optional pagination parameters
|
|
1775
|
+
* @returns Paginated list of comments
|
|
1776
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1777
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1778
|
+
*
|
|
1779
|
+
* @example
|
|
1780
|
+
* ```typescript
|
|
1781
|
+
* const { comments, total } = await tasks.listComments('task_abc123', {
|
|
1782
|
+
* limit: 25,
|
|
1783
|
+
* offset: 0,
|
|
1784
|
+
* });
|
|
1785
|
+
* for (const c of comments) {
|
|
1786
|
+
* console.log(`${c.author?.name}: ${c.body}`);
|
|
1787
|
+
* }
|
|
1788
|
+
* ```
|
|
1789
|
+
*/
|
|
731
1790
|
async listComments(
|
|
732
1791
|
taskId: string,
|
|
733
1792
|
params?: { limit?: number; offset?: number }
|
|
@@ -771,6 +1830,21 @@ export class TaskStorageService implements TaskStorage {
|
|
|
771
1830
|
throw await toServiceException('GET', url, res.response);
|
|
772
1831
|
}
|
|
773
1832
|
|
|
1833
|
+
/**
|
|
1834
|
+
* Create a new tag for categorizing tasks.
|
|
1835
|
+
*
|
|
1836
|
+
* @param name - The tag display name (must be non-empty)
|
|
1837
|
+
* @param color - Optional hex color code (e.g., `'#ff0000'`)
|
|
1838
|
+
* @returns The newly created tag
|
|
1839
|
+
* @throws {@link TagNameRequiredError} if the name is empty or not a string
|
|
1840
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1841
|
+
*
|
|
1842
|
+
* @example
|
|
1843
|
+
* ```typescript
|
|
1844
|
+
* const tag = await tasks.createTag('urgent', '#ff0000');
|
|
1845
|
+
* console.log('Created tag:', tag.id, tag.name);
|
|
1846
|
+
* ```
|
|
1847
|
+
*/
|
|
774
1848
|
async createTag(name: string, color?: string): Promise<Tag> {
|
|
775
1849
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
|
776
1850
|
throw new TagNameRequiredError();
|
|
@@ -806,6 +1880,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
806
1880
|
throw await toServiceException('POST', url, res.response);
|
|
807
1881
|
}
|
|
808
1882
|
|
|
1883
|
+
/**
|
|
1884
|
+
* Get a tag by its ID.
|
|
1885
|
+
*
|
|
1886
|
+
* @param tagId - The unique tag identifier
|
|
1887
|
+
* @returns The tag
|
|
1888
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1889
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1890
|
+
*
|
|
1891
|
+
* @example
|
|
1892
|
+
* ```typescript
|
|
1893
|
+
* const tag = await tasks.getTag('tag_def456');
|
|
1894
|
+
* console.log(`${tag.name} (${tag.color})`);
|
|
1895
|
+
* ```
|
|
1896
|
+
*/
|
|
809
1897
|
async getTag(tagId: string): Promise<Tag> {
|
|
810
1898
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
811
1899
|
throw new TagIdRequiredError();
|
|
@@ -839,6 +1927,23 @@ export class TaskStorageService implements TaskStorage {
|
|
|
839
1927
|
throw await toServiceException('GET', url, res.response);
|
|
840
1928
|
}
|
|
841
1929
|
|
|
1930
|
+
/**
|
|
1931
|
+
* Update a tag's name and optionally its color.
|
|
1932
|
+
*
|
|
1933
|
+
* @param tagId - The unique tag identifier
|
|
1934
|
+
* @param name - The new tag name (must be non-empty)
|
|
1935
|
+
* @param color - Optional new hex color code
|
|
1936
|
+
* @returns The updated tag
|
|
1937
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1938
|
+
* @throws {@link TagNameRequiredError} if the name is empty or not a string
|
|
1939
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1940
|
+
*
|
|
1941
|
+
* @example
|
|
1942
|
+
* ```typescript
|
|
1943
|
+
* const updated = await tasks.updateTag('tag_def456', 'critical', '#cc0000');
|
|
1944
|
+
* console.log('Updated:', updated.name);
|
|
1945
|
+
* ```
|
|
1946
|
+
*/
|
|
842
1947
|
async updateTag(tagId: string, name: string, color?: string): Promise<Tag> {
|
|
843
1948
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
844
1949
|
throw new TagIdRequiredError();
|
|
@@ -880,6 +1985,19 @@ export class TaskStorageService implements TaskStorage {
|
|
|
880
1985
|
throw await toServiceException('PATCH', url, res.response);
|
|
881
1986
|
}
|
|
882
1987
|
|
|
1988
|
+
/**
|
|
1989
|
+
* Delete a tag permanently.
|
|
1990
|
+
*
|
|
1991
|
+
* @param tagId - The unique tag identifier
|
|
1992
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1993
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1994
|
+
*
|
|
1995
|
+
* @example
|
|
1996
|
+
* ```typescript
|
|
1997
|
+
* await tasks.deleteTag('tag_def456');
|
|
1998
|
+
* console.log('Tag deleted');
|
|
1999
|
+
* ```
|
|
2000
|
+
*/
|
|
883
2001
|
async deleteTag(tagId: string): Promise<void> {
|
|
884
2002
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
885
2003
|
throw new TagIdRequiredError();
|
|
@@ -913,6 +2031,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
913
2031
|
throw await toServiceException('DELETE', url, res.response);
|
|
914
2032
|
}
|
|
915
2033
|
|
|
2034
|
+
/**
|
|
2035
|
+
* List all tags in the organization.
|
|
2036
|
+
*
|
|
2037
|
+
* @returns List of all tags
|
|
2038
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2039
|
+
*
|
|
2040
|
+
* @example
|
|
2041
|
+
* ```typescript
|
|
2042
|
+
* const { tags } = await tasks.listTags();
|
|
2043
|
+
* for (const tag of tags) {
|
|
2044
|
+
* console.log(`${tag.name} (${tag.color ?? 'no color'})`);
|
|
2045
|
+
* }
|
|
2046
|
+
* ```
|
|
2047
|
+
*/
|
|
916
2048
|
async listTags(): Promise<ListTagsResult> {
|
|
917
2049
|
const url = buildUrl(this.#baseUrl, `/task/tags/list/${TASK_API_VERSION}`);
|
|
918
2050
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -939,6 +2071,21 @@ export class TaskStorageService implements TaskStorage {
|
|
|
939
2071
|
throw await toServiceException('GET', url, res.response);
|
|
940
2072
|
}
|
|
941
2073
|
|
|
2074
|
+
/**
|
|
2075
|
+
* Associate a tag with a task.
|
|
2076
|
+
*
|
|
2077
|
+
* @param taskId - The ID of the task
|
|
2078
|
+
* @param tagId - The ID of the tag to add
|
|
2079
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2080
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
2081
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2082
|
+
*
|
|
2083
|
+
* @example
|
|
2084
|
+
* ```typescript
|
|
2085
|
+
* await tasks.addTagToTask('task_abc123', 'tag_def456');
|
|
2086
|
+
* console.log('Tag added to task');
|
|
2087
|
+
* ```
|
|
2088
|
+
*/
|
|
942
2089
|
async addTagToTask(taskId: string, tagId: string): Promise<void> {
|
|
943
2090
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
944
2091
|
throw new TaskIdRequiredError();
|
|
@@ -975,6 +2122,21 @@ export class TaskStorageService implements TaskStorage {
|
|
|
975
2122
|
throw await toServiceException('POST', url, res.response);
|
|
976
2123
|
}
|
|
977
2124
|
|
|
2125
|
+
/**
|
|
2126
|
+
* Remove a tag association from a task.
|
|
2127
|
+
*
|
|
2128
|
+
* @param taskId - The ID of the task
|
|
2129
|
+
* @param tagId - The ID of the tag to remove
|
|
2130
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2131
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
2132
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2133
|
+
*
|
|
2134
|
+
* @example
|
|
2135
|
+
* ```typescript
|
|
2136
|
+
* await tasks.removeTagFromTask('task_abc123', 'tag_def456');
|
|
2137
|
+
* console.log('Tag removed from task');
|
|
2138
|
+
* ```
|
|
2139
|
+
*/
|
|
978
2140
|
async removeTagFromTask(taskId: string, tagId: string): Promise<void> {
|
|
979
2141
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
980
2142
|
throw new TaskIdRequiredError();
|
|
@@ -1011,6 +2173,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1011
2173
|
throw await toServiceException('DELETE', url, res.response);
|
|
1012
2174
|
}
|
|
1013
2175
|
|
|
2176
|
+
/**
|
|
2177
|
+
* List all tags associated with a specific task.
|
|
2178
|
+
*
|
|
2179
|
+
* @param taskId - The ID of the task
|
|
2180
|
+
* @returns Array of tags on the task
|
|
2181
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2182
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2183
|
+
*
|
|
2184
|
+
* @example
|
|
2185
|
+
* ```typescript
|
|
2186
|
+
* const tags = await tasks.listTagsForTask('task_abc123');
|
|
2187
|
+
* console.log('Tags:', tags.map((t) => t.name).join(', '));
|
|
2188
|
+
* ```
|
|
2189
|
+
*/
|
|
1014
2190
|
async listTagsForTask(taskId: string): Promise<Tag[]> {
|
|
1015
2191
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
1016
2192
|
throw new TaskIdRequiredError();
|
|
@@ -1044,8 +2220,33 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1044
2220
|
throw await toServiceException('GET', url, res.response);
|
|
1045
2221
|
}
|
|
1046
2222
|
|
|
1047
|
-
|
|
1048
|
-
|
|
2223
|
+
/**
|
|
2224
|
+
* Initiate a file upload to a task. Returns a presigned S3 URL for direct upload.
|
|
2225
|
+
*
|
|
2226
|
+
* @remarks
|
|
2227
|
+
* After receiving the presigned URL, upload the file content via HTTP PUT to that URL.
|
|
2228
|
+
* Then call {@link TaskStorageService.confirmAttachment | confirmAttachment} to finalize.
|
|
2229
|
+
*
|
|
2230
|
+
* @param taskId - The ID of the task to attach the file to
|
|
2231
|
+
* @param params - Attachment metadata including filename, content type, and size
|
|
2232
|
+
* @returns The created attachment record and a presigned upload URL
|
|
2233
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2234
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2235
|
+
*
|
|
2236
|
+
* @example
|
|
2237
|
+
* ```typescript
|
|
2238
|
+
* const { attachment, presigned_url } = await tasks.uploadAttachment(
|
|
2239
|
+
* 'task_abc123',
|
|
2240
|
+
* { filename: 'report.pdf', content_type: 'application/pdf', size: 102400 },
|
|
2241
|
+
* );
|
|
2242
|
+
*
|
|
2243
|
+
* // Upload the file to S3
|
|
2244
|
+
* await fetch(presigned_url, { method: 'PUT', body: fileContent });
|
|
2245
|
+
*
|
|
2246
|
+
* // Confirm the upload
|
|
2247
|
+
* await tasks.confirmAttachment(attachment.id);
|
|
2248
|
+
* ```
|
|
2249
|
+
*/
|
|
1049
2250
|
async uploadAttachment(
|
|
1050
2251
|
taskId: string,
|
|
1051
2252
|
params: CreateAttachmentParams
|
|
@@ -1084,6 +2285,24 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1084
2285
|
throw await toServiceException('POST', url, res.response);
|
|
1085
2286
|
}
|
|
1086
2287
|
|
|
2288
|
+
/**
|
|
2289
|
+
* Confirm that a file upload has completed successfully.
|
|
2290
|
+
*
|
|
2291
|
+
* @remarks
|
|
2292
|
+
* Call this after successfully uploading the file to the presigned URL
|
|
2293
|
+
* returned by {@link TaskStorageService.uploadAttachment | uploadAttachment}.
|
|
2294
|
+
*
|
|
2295
|
+
* @param attachmentId - The unique attachment identifier
|
|
2296
|
+
* @returns The confirmed attachment record
|
|
2297
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
2298
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2299
|
+
*
|
|
2300
|
+
* @example
|
|
2301
|
+
* ```typescript
|
|
2302
|
+
* const confirmed = await tasks.confirmAttachment('att_ghi789');
|
|
2303
|
+
* console.log('Confirmed:', confirmed.filename);
|
|
2304
|
+
* ```
|
|
2305
|
+
*/
|
|
1087
2306
|
async confirmAttachment(attachmentId: string): Promise<Attachment> {
|
|
1088
2307
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
1089
2308
|
throw new AttachmentIdRequiredError();
|
|
@@ -1117,6 +2336,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1117
2336
|
throw await toServiceException('POST', url, res.response);
|
|
1118
2337
|
}
|
|
1119
2338
|
|
|
2339
|
+
/**
|
|
2340
|
+
* Get a presigned S3 URL for downloading an attachment.
|
|
2341
|
+
*
|
|
2342
|
+
* @param attachmentId - The unique attachment identifier
|
|
2343
|
+
* @returns A presigned download URL with expiry information
|
|
2344
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
2345
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2346
|
+
*
|
|
2347
|
+
* @example
|
|
2348
|
+
* ```typescript
|
|
2349
|
+
* const { presigned_url, expiry_seconds } = await tasks.downloadAttachment('att_ghi789');
|
|
2350
|
+
* console.log(`Download URL (expires in ${expiry_seconds}s):`, presigned_url);
|
|
2351
|
+
* ```
|
|
2352
|
+
*/
|
|
1120
2353
|
async downloadAttachment(attachmentId: string): Promise<PresignDownloadResponse> {
|
|
1121
2354
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
1122
2355
|
throw new AttachmentIdRequiredError();
|
|
@@ -1150,6 +2383,22 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1150
2383
|
throw await toServiceException('POST', url, res.response);
|
|
1151
2384
|
}
|
|
1152
2385
|
|
|
2386
|
+
/**
|
|
2387
|
+
* List all attachments on a task.
|
|
2388
|
+
*
|
|
2389
|
+
* @param taskId - The ID of the task
|
|
2390
|
+
* @returns List of attachments with total count
|
|
2391
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2392
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2393
|
+
*
|
|
2394
|
+
* @example
|
|
2395
|
+
* ```typescript
|
|
2396
|
+
* const { attachments, total } = await tasks.listAttachments('task_abc123');
|
|
2397
|
+
* for (const att of attachments) {
|
|
2398
|
+
* console.log(`${att.filename} (${att.content_type}, ${att.size} bytes)`);
|
|
2399
|
+
* }
|
|
2400
|
+
* ```
|
|
2401
|
+
*/
|
|
1153
2402
|
async listAttachments(taskId: string): Promise<ListAttachmentsResult> {
|
|
1154
2403
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
1155
2404
|
throw new TaskIdRequiredError();
|
|
@@ -1183,6 +2432,19 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1183
2432
|
throw await toServiceException('GET', url, res.response);
|
|
1184
2433
|
}
|
|
1185
2434
|
|
|
2435
|
+
/**
|
|
2436
|
+
* Delete an attachment permanently.
|
|
2437
|
+
*
|
|
2438
|
+
* @param attachmentId - The unique attachment identifier
|
|
2439
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
2440
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2441
|
+
*
|
|
2442
|
+
* @example
|
|
2443
|
+
* ```typescript
|
|
2444
|
+
* await tasks.deleteAttachment('att_ghi789');
|
|
2445
|
+
* console.log('Attachment deleted');
|
|
2446
|
+
* ```
|
|
2447
|
+
*/
|
|
1186
2448
|
async deleteAttachment(attachmentId: string): Promise<void> {
|
|
1187
2449
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
1188
2450
|
throw new AttachmentIdRequiredError();
|
|
@@ -1216,6 +2478,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1216
2478
|
throw await toServiceException('DELETE', url, res.response);
|
|
1217
2479
|
}
|
|
1218
2480
|
|
|
2481
|
+
/**
|
|
2482
|
+
* List all users who have been referenced in tasks (as creators, assignees, or closers).
|
|
2483
|
+
*
|
|
2484
|
+
* @returns List of user entity references
|
|
2485
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2486
|
+
*
|
|
2487
|
+
* @example
|
|
2488
|
+
* ```typescript
|
|
2489
|
+
* const { users } = await tasks.listUsers();
|
|
2490
|
+
* for (const user of users) {
|
|
2491
|
+
* console.log(`${user.name} (${user.id})`);
|
|
2492
|
+
* }
|
|
2493
|
+
* ```
|
|
2494
|
+
*/
|
|
1219
2495
|
async listUsers(): Promise<ListUsersResult> {
|
|
1220
2496
|
const url = buildUrl(this.#baseUrl, `/task/users/${TASK_API_VERSION}`);
|
|
1221
2497
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -1242,6 +2518,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1242
2518
|
throw await toServiceException('GET', url, res.response);
|
|
1243
2519
|
}
|
|
1244
2520
|
|
|
2521
|
+
/**
|
|
2522
|
+
* List all projects that have been referenced in tasks.
|
|
2523
|
+
*
|
|
2524
|
+
* @returns List of project entity references
|
|
2525
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2526
|
+
*
|
|
2527
|
+
* @example
|
|
2528
|
+
* ```typescript
|
|
2529
|
+
* const { projects } = await tasks.listProjects();
|
|
2530
|
+
* for (const project of projects) {
|
|
2531
|
+
* console.log(`${project.name} (${project.id})`);
|
|
2532
|
+
* }
|
|
2533
|
+
* ```
|
|
2534
|
+
*/
|
|
1245
2535
|
async listProjects(): Promise<ListProjectsResult> {
|
|
1246
2536
|
const url = buildUrl(this.#baseUrl, `/task/projects/${TASK_API_VERSION}`);
|
|
1247
2537
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -1268,6 +2558,22 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1268
2558
|
throw await toServiceException('GET', url, res.response);
|
|
1269
2559
|
}
|
|
1270
2560
|
|
|
2561
|
+
/**
|
|
2562
|
+
* Get task activity time-series data showing daily task counts by status.
|
|
2563
|
+
*
|
|
2564
|
+
* @param params - Optional parameters controlling the number of days to retrieve
|
|
2565
|
+
* @returns Time-series activity data with daily snapshots
|
|
2566
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2567
|
+
*
|
|
2568
|
+
* @example
|
|
2569
|
+
* ```typescript
|
|
2570
|
+
* const { activity, days } = await tasks.getActivity({ days: 30 });
|
|
2571
|
+
* console.log(`Activity over ${days} days:`);
|
|
2572
|
+
* for (const point of activity) {
|
|
2573
|
+
* console.log(`${point.date}: ${point.open} open, ${point.inProgress} in progress`);
|
|
2574
|
+
* }
|
|
2575
|
+
* ```
|
|
2576
|
+
*/
|
|
1271
2577
|
async getActivity(params?: TaskActivityParams): Promise<TaskActivityResult> {
|
|
1272
2578
|
const queryParams = new URLSearchParams();
|
|
1273
2579
|
if (params?.days !== undefined) queryParams.set('days', String(params.days));
|