@agentuity/core 1.0.23 → 1.0.25

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.
@@ -6,7 +6,13 @@ import { safeStringify } from '../json.ts';
6
6
  // Task type enums
7
7
  export type TaskPriority = 'high' | 'medium' | 'low' | 'none';
8
8
  export type TaskType = 'epic' | 'feature' | 'enhancement' | 'bug' | 'task';
9
- export type TaskStatus = 'open' | 'in_progress' | 'closed';
9
+ export type TaskStatus = 'open' | 'in_progress' | 'closed' | 'done' | 'cancelled';
10
+
11
+ // Entity reference (user/project with id + name)
12
+ export interface EntityRef {
13
+ id: string;
14
+ name: string;
15
+ }
10
16
 
11
17
  // Task object (returned from API)
12
18
  export interface Task {
@@ -26,6 +32,33 @@ export interface Task {
26
32
  created_id: string;
27
33
  assigned_id?: string;
28
34
  closed_id?: string;
35
+ creator?: EntityRef;
36
+ assignee?: EntityRef;
37
+ closer?: EntityRef;
38
+ project?: EntityRef;
39
+ cancelled_date?: string;
40
+ deleted: boolean;
41
+ tags?: Tag[];
42
+ comments?: Comment[];
43
+ }
44
+
45
+ // Comment object (returned from API)
46
+ export interface Comment {
47
+ id: string;
48
+ created_at: string;
49
+ updated_at: string;
50
+ task_id: string;
51
+ user_id: string;
52
+ author?: EntityRef;
53
+ body: string;
54
+ }
55
+
56
+ // Tag object (returned from API)
57
+ export interface Tag {
58
+ id: string;
59
+ created_at: string;
60
+ name: string;
61
+ color?: string;
29
62
  }
30
63
 
31
64
  // Changelog entry
@@ -49,6 +82,10 @@ export interface CreateTaskParams {
49
82
  status?: TaskStatus;
50
83
  created_id: string;
51
84
  assigned_id?: string;
85
+ creator?: EntityRef;
86
+ assignee?: EntityRef;
87
+ project?: EntityRef;
88
+ tag_ids?: string[];
52
89
  }
53
90
 
54
91
  export interface UpdateTaskParams {
@@ -61,6 +98,9 @@ export interface UpdateTaskParams {
61
98
  status?: TaskStatus;
62
99
  assigned_id?: string;
63
100
  closed_id?: string;
101
+ assignee?: EntityRef;
102
+ closer?: EntityRef;
103
+ project?: EntityRef;
64
104
  }
65
105
 
66
106
  export interface ListTasksParams {
@@ -69,6 +109,9 @@ export interface ListTasksParams {
69
109
  priority?: TaskPriority;
70
110
  assigned_id?: string;
71
111
  parent_id?: string;
112
+ project_id?: string;
113
+ tag_id?: string;
114
+ deleted?: boolean;
72
115
  sort?: string;
73
116
  order?: 'asc' | 'desc';
74
117
  limit?: number;
@@ -89,19 +132,121 @@ export interface TaskChangelogResult {
89
132
  offset: number;
90
133
  }
91
134
 
135
+ export interface ListCommentsResult {
136
+ comments: Comment[];
137
+ total: number;
138
+ limit: number;
139
+ offset: number;
140
+ }
141
+
142
+ export interface ListTagsResult {
143
+ tags: Tag[];
144
+ }
145
+
146
+ // Attachment types
147
+ export interface Attachment {
148
+ id: string;
149
+ created_at: string;
150
+ task_id: string;
151
+ user_id: string;
152
+ author?: EntityRef;
153
+ filename: string;
154
+ content_type?: string;
155
+ size?: number;
156
+ }
157
+
158
+ export interface CreateAttachmentParams {
159
+ filename: string;
160
+ content_type?: string;
161
+ size?: number;
162
+ }
163
+
164
+ export interface PresignUploadResponse {
165
+ attachment: Attachment;
166
+ presigned_url: string;
167
+ expiry_seconds: number;
168
+ }
169
+
170
+ export interface PresignDownloadResponse {
171
+ presigned_url: string;
172
+ expiry_seconds: number;
173
+ }
174
+
175
+ export interface ListAttachmentsResult {
176
+ attachments: Attachment[];
177
+ total: number;
178
+ }
179
+
180
+ export interface ListUsersResult {
181
+ users: EntityRef[];
182
+ }
183
+
184
+ export interface ListProjectsResult {
185
+ projects: EntityRef[];
186
+ }
187
+
188
+ export interface TaskActivityParams {
189
+ days?: number; // min 7, max 365, default 90
190
+ }
191
+
192
+ export interface TaskActivityDataPoint {
193
+ date: string; // "2026-02-28"
194
+ open: number;
195
+ inProgress: number;
196
+ done: number;
197
+ closed: number;
198
+ cancelled: number;
199
+ }
200
+
201
+ export interface TaskActivityResult {
202
+ activity: TaskActivityDataPoint[];
203
+ days: number;
204
+ }
205
+
92
206
  export interface TaskStorage {
93
207
  create(params: CreateTaskParams): Promise<Task>;
94
208
  get(id: string): Promise<Task | null>;
95
209
  list(params?: ListTasksParams): Promise<ListTasksResult>;
96
210
  update(id: string, params: UpdateTaskParams): Promise<Task>;
97
211
  close(id: string): Promise<Task>;
212
+ softDelete(id: string): Promise<Task>;
98
213
  changelog(
99
214
  id: string,
100
215
  params?: { limit?: number; offset?: number }
101
216
  ): Promise<TaskChangelogResult>;
217
+ createComment(
218
+ taskId: string,
219
+ body: string,
220
+ userId: string,
221
+ author?: EntityRef
222
+ ): Promise<Comment>;
223
+ getComment(commentId: string): Promise<Comment>;
224
+ updateComment(commentId: string, body: string): Promise<Comment>;
225
+ deleteComment(commentId: string): Promise<void>;
226
+ listComments(
227
+ taskId: string,
228
+ params?: { limit?: number; offset?: number }
229
+ ): Promise<ListCommentsResult>;
230
+ createTag(name: string, color?: string): Promise<Tag>;
231
+ getTag(tagId: string): Promise<Tag>;
232
+ updateTag(tagId: string, name: string, color?: string): Promise<Tag>;
233
+ deleteTag(tagId: string): Promise<void>;
234
+ listTags(): Promise<ListTagsResult>;
235
+ addTagToTask(taskId: string, tagId: string): Promise<void>;
236
+ removeTagFromTask(taskId: string, tagId: string): Promise<void>;
237
+ listTagsForTask(taskId: string): Promise<Tag[]>;
238
+ uploadAttachment(taskId: string, params: CreateAttachmentParams): Promise<PresignUploadResponse>;
239
+ confirmAttachment(attachmentId: string): Promise<Attachment>;
240
+ downloadAttachment(attachmentId: string): Promise<PresignDownloadResponse>;
241
+ listAttachments(taskId: string): Promise<ListAttachmentsResult>;
242
+ deleteAttachment(attachmentId: string): Promise<void>;
243
+ listUsers(): Promise<ListUsersResult>;
244
+ listProjects(): Promise<ListProjectsResult>;
245
+ getActivity(params?: TaskActivityParams): Promise<TaskActivityResult>;
102
246
  }
103
247
 
104
248
  const TASK_API_VERSION = '2026-02-24';
249
+ const TASK_ACTIVITY_API_VERSION = '2026-02-28';
105
250
 
106
251
  const TaskIdRequiredError = StructuredError(
107
252
  'TaskIdRequiredError',
@@ -113,6 +258,36 @@ const TaskTitleRequiredError = StructuredError(
113
258
  'Task title is required and must be a non-empty string'
114
259
  );
115
260
 
261
+ const CommentIdRequiredError = StructuredError(
262
+ 'CommentIdRequiredError',
263
+ 'Comment ID is required and must be a non-empty string'
264
+ );
265
+
266
+ const CommentBodyRequiredError = StructuredError(
267
+ 'CommentBodyRequiredError',
268
+ 'Comment body is required and must be a non-empty string'
269
+ );
270
+
271
+ const TagIdRequiredError = StructuredError(
272
+ 'TagIdRequiredError',
273
+ 'Tag ID is required and must be a non-empty string'
274
+ );
275
+
276
+ const TagNameRequiredError = StructuredError(
277
+ 'TagNameRequiredError',
278
+ 'Tag name is required and must be a non-empty string'
279
+ );
280
+
281
+ const AttachmentIdRequiredError = StructuredError(
282
+ 'AttachmentIdRequiredError',
283
+ 'Attachment ID is required and must be a non-empty string'
284
+ );
285
+
286
+ const UserIdRequiredError = StructuredError(
287
+ 'UserIdRequiredError',
288
+ 'User ID is required and must be a non-empty string'
289
+ );
290
+
116
291
  const TaskStorageResponseError = StructuredError('TaskStorageResponseError')<{
117
292
  status: number;
118
293
  }>();
@@ -215,6 +390,9 @@ export class TaskStorageService implements TaskStorage {
215
390
  if (params?.priority) queryParams.set('priority', params.priority);
216
391
  if (params?.assigned_id) queryParams.set('assigned_id', params.assigned_id);
217
392
  if (params?.parent_id) queryParams.set('parent_id', params.parent_id);
393
+ if (params?.project_id) queryParams.set('project_id', params.project_id);
394
+ if (params?.tag_id) queryParams.set('tag_id', params.tag_id);
395
+ if (params?.deleted !== undefined) queryParams.set('deleted', String(params.deleted));
218
396
  if (params?.sort) queryParams.set('sort', params.sort);
219
397
  if (params?.order) queryParams.set('order', params.order);
220
398
  if (params?.limit !== undefined) queryParams.set('limit', String(params.limit));
@@ -336,7 +514,7 @@ export class TaskStorageService implements TaskStorage {
336
514
 
337
515
  const url = buildUrl(
338
516
  this.#baseUrl,
339
- `/task-changelog/${TASK_API_VERSION}/${encodeURIComponent(id)}${
517
+ `/task/changelog/${TASK_API_VERSION}/${encodeURIComponent(id)}${
340
518
  queryString ? `?${queryString}` : ''
341
519
  }`
342
520
  );
@@ -363,4 +541,765 @@ export class TaskStorageService implements TaskStorage {
363
541
 
364
542
  throw await toServiceException('GET', url, res.response);
365
543
  }
544
+
545
+ async softDelete(id: string): Promise<Task> {
546
+ if (!id || typeof id !== 'string' || id.trim().length === 0) {
547
+ throw new TaskIdRequiredError();
548
+ }
549
+
550
+ const url = buildUrl(
551
+ this.#baseUrl,
552
+ `/task/delete/${TASK_API_VERSION}/${encodeURIComponent(id)}`
553
+ );
554
+ const signal = AbortSignal.timeout(30_000);
555
+
556
+ const res = await this.#adapter.invoke<TaskResponse<Task>>(url, {
557
+ method: 'POST',
558
+ signal,
559
+ telemetry: {
560
+ name: 'agentuity.task.softDelete',
561
+ attributes: { id },
562
+ },
563
+ });
564
+
565
+ if (res.ok) {
566
+ if (res.data.success) {
567
+ return res.data.data;
568
+ }
569
+ throw new TaskStorageResponseError({
570
+ status: res.response.status,
571
+ message: res.data.message,
572
+ });
573
+ }
574
+
575
+ throw await toServiceException('POST', url, res.response);
576
+ }
577
+
578
+ async createComment(
579
+ taskId: string,
580
+ body: string,
581
+ userId: string,
582
+ author?: EntityRef
583
+ ): Promise<Comment> {
584
+ if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
585
+ throw new TaskIdRequiredError();
586
+ }
587
+ if (!body || typeof body !== 'string' || body.trim().length === 0) {
588
+ throw new CommentBodyRequiredError();
589
+ }
590
+ if (!userId || typeof userId !== 'string' || userId.trim().length === 0) {
591
+ throw new UserIdRequiredError();
592
+ }
593
+
594
+ const url = buildUrl(
595
+ this.#baseUrl,
596
+ `/task/comments/create/${TASK_API_VERSION}/${encodeURIComponent(taskId)}`
597
+ );
598
+ const signal = AbortSignal.timeout(30_000);
599
+
600
+ const commentBody: Record<string, unknown> = { body, user_id: userId };
601
+ if (author) commentBody.author = author;
602
+
603
+ const res = await this.#adapter.invoke<TaskResponse<Comment>>(url, {
604
+ method: 'POST',
605
+ body: safeStringify(commentBody),
606
+ contentType: 'application/json',
607
+ signal,
608
+ telemetry: {
609
+ name: 'agentuity.task.createComment',
610
+ attributes: { taskId },
611
+ },
612
+ });
613
+
614
+ if (res.ok) {
615
+ if (res.data.success) {
616
+ return res.data.data;
617
+ }
618
+ throw new TaskStorageResponseError({
619
+ status: res.response.status,
620
+ message: res.data.message,
621
+ });
622
+ }
623
+
624
+ throw await toServiceException('POST', url, res.response);
625
+ }
626
+
627
+ async getComment(commentId: string): Promise<Comment> {
628
+ if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
629
+ throw new CommentIdRequiredError();
630
+ }
631
+
632
+ const url = buildUrl(
633
+ this.#baseUrl,
634
+ `/task/comments/get/${TASK_API_VERSION}/${encodeURIComponent(commentId)}`
635
+ );
636
+ const signal = AbortSignal.timeout(30_000);
637
+
638
+ const res = await this.#adapter.invoke<TaskResponse<Comment>>(url, {
639
+ method: 'GET',
640
+ signal,
641
+ telemetry: {
642
+ name: 'agentuity.task.getComment',
643
+ attributes: { commentId },
644
+ },
645
+ });
646
+
647
+ if (res.ok) {
648
+ if (res.data.success) {
649
+ return res.data.data;
650
+ }
651
+ throw new TaskStorageResponseError({
652
+ status: res.response.status,
653
+ message: res.data.message,
654
+ });
655
+ }
656
+
657
+ throw await toServiceException('GET', url, res.response);
658
+ }
659
+
660
+ async updateComment(commentId: string, body: string): Promise<Comment> {
661
+ if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
662
+ throw new CommentIdRequiredError();
663
+ }
664
+ if (!body || typeof body !== 'string' || body.trim().length === 0) {
665
+ throw new CommentBodyRequiredError();
666
+ }
667
+
668
+ const url = buildUrl(
669
+ this.#baseUrl,
670
+ `/task/comments/update/${TASK_API_VERSION}/${encodeURIComponent(commentId)}`
671
+ );
672
+ const signal = AbortSignal.timeout(30_000);
673
+
674
+ const res = await this.#adapter.invoke<TaskResponse<Comment>>(url, {
675
+ method: 'PATCH',
676
+ body: safeStringify({ body }),
677
+ contentType: 'application/json',
678
+ signal,
679
+ telemetry: {
680
+ name: 'agentuity.task.updateComment',
681
+ attributes: { commentId },
682
+ },
683
+ });
684
+
685
+ if (res.ok) {
686
+ if (res.data.success) {
687
+ return res.data.data;
688
+ }
689
+ throw new TaskStorageResponseError({
690
+ status: res.response.status,
691
+ message: res.data.message,
692
+ });
693
+ }
694
+
695
+ throw await toServiceException('PATCH', url, res.response);
696
+ }
697
+
698
+ async deleteComment(commentId: string): Promise<void> {
699
+ if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
700
+ throw new CommentIdRequiredError();
701
+ }
702
+
703
+ const url = buildUrl(
704
+ this.#baseUrl,
705
+ `/task/comments/delete/${TASK_API_VERSION}/${encodeURIComponent(commentId)}`
706
+ );
707
+ const signal = AbortSignal.timeout(30_000);
708
+
709
+ const res = await this.#adapter.invoke<TaskResponse<void>>(url, {
710
+ method: 'DELETE',
711
+ signal,
712
+ telemetry: {
713
+ name: 'agentuity.task.deleteComment',
714
+ attributes: { commentId },
715
+ },
716
+ });
717
+
718
+ if (res.ok) {
719
+ if (res.data?.success === false) {
720
+ throw new TaskStorageResponseError({
721
+ status: res.response.status,
722
+ message: res.data.message ?? 'Operation failed',
723
+ });
724
+ }
725
+ return;
726
+ }
727
+
728
+ throw await toServiceException('DELETE', url, res.response);
729
+ }
730
+
731
+ async listComments(
732
+ taskId: string,
733
+ params?: { limit?: number; offset?: number }
734
+ ): Promise<ListCommentsResult> {
735
+ if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
736
+ throw new TaskIdRequiredError();
737
+ }
738
+
739
+ const queryParams = new URLSearchParams();
740
+ if (params?.limit !== undefined) queryParams.set('limit', String(params.limit));
741
+ if (params?.offset !== undefined) queryParams.set('offset', String(params.offset));
742
+ const queryString = queryParams.toString();
743
+
744
+ const url = buildUrl(
745
+ this.#baseUrl,
746
+ `/task/comments/list/${TASK_API_VERSION}/${encodeURIComponent(taskId)}${
747
+ queryString ? `?${queryString}` : ''
748
+ }`
749
+ );
750
+ const signal = AbortSignal.timeout(30_000);
751
+
752
+ const res = await this.#adapter.invoke<TaskResponse<ListCommentsResult>>(url, {
753
+ method: 'GET',
754
+ signal,
755
+ telemetry: {
756
+ name: 'agentuity.task.listComments',
757
+ attributes: { taskId },
758
+ },
759
+ });
760
+
761
+ if (res.ok) {
762
+ if (res.data.success) {
763
+ return res.data.data;
764
+ }
765
+ throw new TaskStorageResponseError({
766
+ status: res.response.status,
767
+ message: res.data.message,
768
+ });
769
+ }
770
+
771
+ throw await toServiceException('GET', url, res.response);
772
+ }
773
+
774
+ async createTag(name: string, color?: string): Promise<Tag> {
775
+ if (!name || typeof name !== 'string' || name.trim().length === 0) {
776
+ throw new TagNameRequiredError();
777
+ }
778
+
779
+ const url = buildUrl(this.#baseUrl, `/task/tags/create/${TASK_API_VERSION}`);
780
+ const signal = AbortSignal.timeout(30_000);
781
+
782
+ const body: Record<string, string> = { name };
783
+ if (color !== undefined) body.color = color;
784
+
785
+ const res = await this.#adapter.invoke<TaskResponse<Tag>>(url, {
786
+ method: 'POST',
787
+ body: safeStringify(body),
788
+ contentType: 'application/json',
789
+ signal,
790
+ telemetry: {
791
+ name: 'agentuity.task.createTag',
792
+ attributes: { tagName: name },
793
+ },
794
+ });
795
+
796
+ if (res.ok) {
797
+ if (res.data.success) {
798
+ return res.data.data;
799
+ }
800
+ throw new TaskStorageResponseError({
801
+ status: res.response.status,
802
+ message: res.data.message,
803
+ });
804
+ }
805
+
806
+ throw await toServiceException('POST', url, res.response);
807
+ }
808
+
809
+ async getTag(tagId: string): Promise<Tag> {
810
+ if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
811
+ throw new TagIdRequiredError();
812
+ }
813
+
814
+ const url = buildUrl(
815
+ this.#baseUrl,
816
+ `/task/tags/get/${TASK_API_VERSION}/${encodeURIComponent(tagId)}`
817
+ );
818
+ const signal = AbortSignal.timeout(30_000);
819
+
820
+ const res = await this.#adapter.invoke<TaskResponse<Tag>>(url, {
821
+ method: 'GET',
822
+ signal,
823
+ telemetry: {
824
+ name: 'agentuity.task.getTag',
825
+ attributes: { tagId },
826
+ },
827
+ });
828
+
829
+ if (res.ok) {
830
+ if (res.data.success) {
831
+ return res.data.data;
832
+ }
833
+ throw new TaskStorageResponseError({
834
+ status: res.response.status,
835
+ message: res.data.message,
836
+ });
837
+ }
838
+
839
+ throw await toServiceException('GET', url, res.response);
840
+ }
841
+
842
+ async updateTag(tagId: string, name: string, color?: string): Promise<Tag> {
843
+ if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
844
+ throw new TagIdRequiredError();
845
+ }
846
+ if (!name || typeof name !== 'string' || name.trim().length === 0) {
847
+ throw new TagNameRequiredError();
848
+ }
849
+
850
+ const url = buildUrl(
851
+ this.#baseUrl,
852
+ `/task/tags/update/${TASK_API_VERSION}/${encodeURIComponent(tagId)}`
853
+ );
854
+ const signal = AbortSignal.timeout(30_000);
855
+
856
+ const body: Record<string, string> = { name };
857
+ if (color !== undefined) body.color = color;
858
+
859
+ const res = await this.#adapter.invoke<TaskResponse<Tag>>(url, {
860
+ method: 'PATCH',
861
+ body: safeStringify(body),
862
+ contentType: 'application/json',
863
+ signal,
864
+ telemetry: {
865
+ name: 'agentuity.task.updateTag',
866
+ attributes: { tagId },
867
+ },
868
+ });
869
+
870
+ if (res.ok) {
871
+ if (res.data.success) {
872
+ return res.data.data;
873
+ }
874
+ throw new TaskStorageResponseError({
875
+ status: res.response.status,
876
+ message: res.data.message,
877
+ });
878
+ }
879
+
880
+ throw await toServiceException('PATCH', url, res.response);
881
+ }
882
+
883
+ async deleteTag(tagId: string): Promise<void> {
884
+ if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
885
+ throw new TagIdRequiredError();
886
+ }
887
+
888
+ const url = buildUrl(
889
+ this.#baseUrl,
890
+ `/task/tags/delete/${TASK_API_VERSION}/${encodeURIComponent(tagId)}`
891
+ );
892
+ const signal = AbortSignal.timeout(30_000);
893
+
894
+ const res = await this.#adapter.invoke<TaskResponse<void>>(url, {
895
+ method: 'DELETE',
896
+ signal,
897
+ telemetry: {
898
+ name: 'agentuity.task.deleteTag',
899
+ attributes: { tagId },
900
+ },
901
+ });
902
+
903
+ if (res.ok) {
904
+ if (res.data?.success === false) {
905
+ throw new TaskStorageResponseError({
906
+ status: res.response.status,
907
+ message: res.data.message ?? 'Operation failed',
908
+ });
909
+ }
910
+ return;
911
+ }
912
+
913
+ throw await toServiceException('DELETE', url, res.response);
914
+ }
915
+
916
+ async listTags(): Promise<ListTagsResult> {
917
+ const url = buildUrl(this.#baseUrl, `/task/tags/list/${TASK_API_VERSION}`);
918
+ const signal = AbortSignal.timeout(30_000);
919
+
920
+ const res = await this.#adapter.invoke<TaskResponse<ListTagsResult>>(url, {
921
+ method: 'GET',
922
+ signal,
923
+ telemetry: {
924
+ name: 'agentuity.task.listTags',
925
+ attributes: {},
926
+ },
927
+ });
928
+
929
+ if (res.ok) {
930
+ if (res.data.success) {
931
+ return res.data.data;
932
+ }
933
+ throw new TaskStorageResponseError({
934
+ status: res.response.status,
935
+ message: res.data.message,
936
+ });
937
+ }
938
+
939
+ throw await toServiceException('GET', url, res.response);
940
+ }
941
+
942
+ async addTagToTask(taskId: string, tagId: string): Promise<void> {
943
+ if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
944
+ throw new TaskIdRequiredError();
945
+ }
946
+ if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
947
+ throw new TagIdRequiredError();
948
+ }
949
+
950
+ const url = buildUrl(
951
+ this.#baseUrl,
952
+ `/task/tags/add/${TASK_API_VERSION}/${encodeURIComponent(taskId)}/${encodeURIComponent(tagId)}`
953
+ );
954
+ const signal = AbortSignal.timeout(30_000);
955
+
956
+ const res = await this.#adapter.invoke<TaskResponse<void>>(url, {
957
+ method: 'POST',
958
+ signal,
959
+ telemetry: {
960
+ name: 'agentuity.task.addTagToTask',
961
+ attributes: { taskId, tagId },
962
+ },
963
+ });
964
+
965
+ if (res.ok) {
966
+ if (res.data?.success === false) {
967
+ throw new TaskStorageResponseError({
968
+ status: res.response.status,
969
+ message: res.data.message ?? 'Operation failed',
970
+ });
971
+ }
972
+ return;
973
+ }
974
+
975
+ throw await toServiceException('POST', url, res.response);
976
+ }
977
+
978
+ async removeTagFromTask(taskId: string, tagId: string): Promise<void> {
979
+ if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
980
+ throw new TaskIdRequiredError();
981
+ }
982
+ if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
983
+ throw new TagIdRequiredError();
984
+ }
985
+
986
+ const url = buildUrl(
987
+ this.#baseUrl,
988
+ `/task/tags/remove/${TASK_API_VERSION}/${encodeURIComponent(taskId)}/${encodeURIComponent(tagId)}`
989
+ );
990
+ const signal = AbortSignal.timeout(30_000);
991
+
992
+ const res = await this.#adapter.invoke<TaskResponse<void>>(url, {
993
+ method: 'DELETE',
994
+ signal,
995
+ telemetry: {
996
+ name: 'agentuity.task.removeTagFromTask',
997
+ attributes: { taskId, tagId },
998
+ },
999
+ });
1000
+
1001
+ if (res.ok) {
1002
+ if (res.data?.success === false) {
1003
+ throw new TaskStorageResponseError({
1004
+ status: res.response.status,
1005
+ message: res.data.message ?? 'Operation failed',
1006
+ });
1007
+ }
1008
+ return;
1009
+ }
1010
+
1011
+ throw await toServiceException('DELETE', url, res.response);
1012
+ }
1013
+
1014
+ async listTagsForTask(taskId: string): Promise<Tag[]> {
1015
+ if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
1016
+ throw new TaskIdRequiredError();
1017
+ }
1018
+
1019
+ const url = buildUrl(
1020
+ this.#baseUrl,
1021
+ `/task/tags/task/${TASK_API_VERSION}/${encodeURIComponent(taskId)}`
1022
+ );
1023
+ const signal = AbortSignal.timeout(30_000);
1024
+
1025
+ const res = await this.#adapter.invoke<TaskResponse<Tag[]>>(url, {
1026
+ method: 'GET',
1027
+ signal,
1028
+ telemetry: {
1029
+ name: 'agentuity.task.listTagsForTask',
1030
+ attributes: { taskId },
1031
+ },
1032
+ });
1033
+
1034
+ if (res.ok) {
1035
+ if (res.data.success) {
1036
+ return res.data.data;
1037
+ }
1038
+ throw new TaskStorageResponseError({
1039
+ status: res.response.status,
1040
+ message: res.data.message,
1041
+ });
1042
+ }
1043
+
1044
+ throw await toServiceException('GET', url, res.response);
1045
+ }
1046
+
1047
+ // Attachment methods
1048
+
1049
+ async uploadAttachment(
1050
+ taskId: string,
1051
+ params: CreateAttachmentParams
1052
+ ): Promise<PresignUploadResponse> {
1053
+ if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
1054
+ throw new TaskIdRequiredError();
1055
+ }
1056
+
1057
+ const url = buildUrl(
1058
+ this.#baseUrl,
1059
+ `/task/attachments/presign-upload/${TASK_API_VERSION}/${encodeURIComponent(taskId)}`
1060
+ );
1061
+ const signal = AbortSignal.timeout(30_000);
1062
+
1063
+ const res = await this.#adapter.invoke<TaskResponse<PresignUploadResponse>>(url, {
1064
+ method: 'POST',
1065
+ body: safeStringify(params),
1066
+ contentType: 'application/json',
1067
+ signal,
1068
+ telemetry: {
1069
+ name: 'agentuity.task.uploadAttachment',
1070
+ attributes: { taskId },
1071
+ },
1072
+ });
1073
+
1074
+ if (res.ok) {
1075
+ if (res.data.success) {
1076
+ return res.data.data;
1077
+ }
1078
+ throw new TaskStorageResponseError({
1079
+ status: res.response.status,
1080
+ message: res.data.message,
1081
+ });
1082
+ }
1083
+
1084
+ throw await toServiceException('POST', url, res.response);
1085
+ }
1086
+
1087
+ async confirmAttachment(attachmentId: string): Promise<Attachment> {
1088
+ if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
1089
+ throw new AttachmentIdRequiredError();
1090
+ }
1091
+
1092
+ const url = buildUrl(
1093
+ this.#baseUrl,
1094
+ `/task/attachments/confirm/${TASK_API_VERSION}/${encodeURIComponent(attachmentId)}`
1095
+ );
1096
+ const signal = AbortSignal.timeout(30_000);
1097
+
1098
+ const res = await this.#adapter.invoke<TaskResponse<Attachment>>(url, {
1099
+ method: 'POST',
1100
+ signal,
1101
+ telemetry: {
1102
+ name: 'agentuity.task.confirmAttachment',
1103
+ attributes: { attachmentId },
1104
+ },
1105
+ });
1106
+
1107
+ if (res.ok) {
1108
+ if (res.data.success) {
1109
+ return res.data.data;
1110
+ }
1111
+ throw new TaskStorageResponseError({
1112
+ status: res.response.status,
1113
+ message: res.data.message,
1114
+ });
1115
+ }
1116
+
1117
+ throw await toServiceException('POST', url, res.response);
1118
+ }
1119
+
1120
+ async downloadAttachment(attachmentId: string): Promise<PresignDownloadResponse> {
1121
+ if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
1122
+ throw new AttachmentIdRequiredError();
1123
+ }
1124
+
1125
+ const url = buildUrl(
1126
+ this.#baseUrl,
1127
+ `/task/attachments/presign-download/${TASK_API_VERSION}/${encodeURIComponent(attachmentId)}`
1128
+ );
1129
+ const signal = AbortSignal.timeout(30_000);
1130
+
1131
+ const res = await this.#adapter.invoke<TaskResponse<PresignDownloadResponse>>(url, {
1132
+ method: 'POST',
1133
+ signal,
1134
+ telemetry: {
1135
+ name: 'agentuity.task.downloadAttachment',
1136
+ attributes: { attachmentId },
1137
+ },
1138
+ });
1139
+
1140
+ if (res.ok) {
1141
+ if (res.data.success) {
1142
+ return res.data.data;
1143
+ }
1144
+ throw new TaskStorageResponseError({
1145
+ status: res.response.status,
1146
+ message: res.data.message,
1147
+ });
1148
+ }
1149
+
1150
+ throw await toServiceException('POST', url, res.response);
1151
+ }
1152
+
1153
+ async listAttachments(taskId: string): Promise<ListAttachmentsResult> {
1154
+ if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
1155
+ throw new TaskIdRequiredError();
1156
+ }
1157
+
1158
+ const url = buildUrl(
1159
+ this.#baseUrl,
1160
+ `/task/attachments/list/${TASK_API_VERSION}/${encodeURIComponent(taskId)}`
1161
+ );
1162
+ const signal = AbortSignal.timeout(30_000);
1163
+
1164
+ const res = await this.#adapter.invoke<TaskResponse<ListAttachmentsResult>>(url, {
1165
+ method: 'GET',
1166
+ signal,
1167
+ telemetry: {
1168
+ name: 'agentuity.task.listAttachments',
1169
+ attributes: { taskId },
1170
+ },
1171
+ });
1172
+
1173
+ if (res.ok) {
1174
+ if (res.data.success) {
1175
+ return res.data.data;
1176
+ }
1177
+ throw new TaskStorageResponseError({
1178
+ status: res.response.status,
1179
+ message: res.data.message,
1180
+ });
1181
+ }
1182
+
1183
+ throw await toServiceException('GET', url, res.response);
1184
+ }
1185
+
1186
+ async deleteAttachment(attachmentId: string): Promise<void> {
1187
+ if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
1188
+ throw new AttachmentIdRequiredError();
1189
+ }
1190
+
1191
+ const url = buildUrl(
1192
+ this.#baseUrl,
1193
+ `/task/attachments/delete/${TASK_API_VERSION}/${encodeURIComponent(attachmentId)}`
1194
+ );
1195
+ const signal = AbortSignal.timeout(30_000);
1196
+
1197
+ const res = await this.#adapter.invoke<TaskResponse<void>>(url, {
1198
+ method: 'DELETE',
1199
+ signal,
1200
+ telemetry: {
1201
+ name: 'agentuity.task.deleteAttachment',
1202
+ attributes: { attachmentId },
1203
+ },
1204
+ });
1205
+
1206
+ if (res.ok) {
1207
+ if (res.data?.success === false) {
1208
+ throw new TaskStorageResponseError({
1209
+ status: res.response.status,
1210
+ message: res.data.message ?? 'Operation failed',
1211
+ });
1212
+ }
1213
+ return;
1214
+ }
1215
+
1216
+ throw await toServiceException('DELETE', url, res.response);
1217
+ }
1218
+
1219
+ async listUsers(): Promise<ListUsersResult> {
1220
+ const url = buildUrl(this.#baseUrl, `/task/users/${TASK_API_VERSION}`);
1221
+ const signal = AbortSignal.timeout(30_000);
1222
+
1223
+ const res = await this.#adapter.invoke<TaskResponse<ListUsersResult>>(url, {
1224
+ method: 'GET',
1225
+ signal,
1226
+ telemetry: {
1227
+ name: 'agentuity.task.listUsers',
1228
+ attributes: {},
1229
+ },
1230
+ });
1231
+
1232
+ if (res.ok) {
1233
+ if (res.data.success) {
1234
+ return res.data.data;
1235
+ }
1236
+ throw new TaskStorageResponseError({
1237
+ status: res.response.status,
1238
+ message: res.data.message,
1239
+ });
1240
+ }
1241
+
1242
+ throw await toServiceException('GET', url, res.response);
1243
+ }
1244
+
1245
+ async listProjects(): Promise<ListProjectsResult> {
1246
+ const url = buildUrl(this.#baseUrl, `/task/projects/${TASK_API_VERSION}`);
1247
+ const signal = AbortSignal.timeout(30_000);
1248
+
1249
+ const res = await this.#adapter.invoke<TaskResponse<ListProjectsResult>>(url, {
1250
+ method: 'GET',
1251
+ signal,
1252
+ telemetry: {
1253
+ name: 'agentuity.task.listProjects',
1254
+ attributes: {},
1255
+ },
1256
+ });
1257
+
1258
+ if (res.ok) {
1259
+ if (res.data.success) {
1260
+ return res.data.data;
1261
+ }
1262
+ throw new TaskStorageResponseError({
1263
+ status: res.response.status,
1264
+ message: res.data.message,
1265
+ });
1266
+ }
1267
+
1268
+ throw await toServiceException('GET', url, res.response);
1269
+ }
1270
+
1271
+ async getActivity(params?: TaskActivityParams): Promise<TaskActivityResult> {
1272
+ const queryParams = new URLSearchParams();
1273
+ if (params?.days !== undefined) queryParams.set('days', String(params.days));
1274
+
1275
+ const queryString = queryParams.toString();
1276
+ const url = buildUrl(
1277
+ this.#baseUrl,
1278
+ `/task/activity/${TASK_ACTIVITY_API_VERSION}${queryString ? `?${queryString}` : ''}`
1279
+ );
1280
+ const signal = AbortSignal.timeout(30_000);
1281
+
1282
+ const res = await this.#adapter.invoke<TaskResponse<TaskActivityResult>>(url, {
1283
+ method: 'GET',
1284
+ signal,
1285
+ telemetry: {
1286
+ name: 'agentuity.task.activity',
1287
+ attributes: {
1288
+ ...(params?.days !== undefined ? { days: String(params.days) } : {}),
1289
+ },
1290
+ },
1291
+ });
1292
+
1293
+ if (res.ok) {
1294
+ if (res.data.success) {
1295
+ return res.data.data;
1296
+ }
1297
+ throw new TaskStorageResponseError({
1298
+ status: res.response.status,
1299
+ message: res.data.message,
1300
+ });
1301
+ }
1302
+
1303
+ throw await toServiceException('GET', url, res.response);
1304
+ }
366
1305
  }