@agentuity/runtime 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.
Files changed (40) hide show
  1. package/dist/dev-patches/aisdk.d.ts +17 -0
  2. package/dist/dev-patches/aisdk.d.ts.map +1 -0
  3. package/dist/dev-patches/aisdk.js +154 -0
  4. package/dist/dev-patches/aisdk.js.map +1 -0
  5. package/dist/dev-patches/gateway.d.ts +16 -0
  6. package/dist/dev-patches/gateway.d.ts.map +1 -0
  7. package/dist/dev-patches/gateway.js +55 -0
  8. package/dist/dev-patches/gateway.js.map +1 -0
  9. package/dist/dev-patches/index.d.ts +21 -0
  10. package/dist/dev-patches/index.d.ts.map +1 -0
  11. package/dist/dev-patches/index.js +33 -0
  12. package/dist/dev-patches/index.js.map +1 -0
  13. package/dist/dev-patches/otel-llm.d.ts +12 -0
  14. package/dist/dev-patches/otel-llm.d.ts.map +1 -0
  15. package/dist/dev-patches/otel-llm.js +345 -0
  16. package/dist/dev-patches/otel-llm.js.map +1 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/services/local/_db.d.ts.map +1 -1
  22. package/dist/services/local/_db.js +78 -1
  23. package/dist/services/local/_db.js.map +1 -1
  24. package/dist/services/local/email.d.ts +2 -0
  25. package/dist/services/local/email.d.ts.map +1 -1
  26. package/dist/services/local/email.js +6 -0
  27. package/dist/services/local/email.js.map +1 -1
  28. package/dist/services/local/task.d.ts +26 -1
  29. package/dist/services/local/task.d.ts.map +1 -1
  30. package/dist/services/local/task.js +304 -4
  31. package/dist/services/local/task.js.map +1 -1
  32. package/package.json +7 -7
  33. package/src/dev-patches/aisdk.ts +172 -0
  34. package/src/dev-patches/gateway.ts +70 -0
  35. package/src/dev-patches/index.ts +37 -0
  36. package/src/dev-patches/otel-llm.ts +408 -0
  37. package/src/index.ts +3 -0
  38. package/src/services/local/_db.ts +98 -5
  39. package/src/services/local/email.ts +9 -4
  40. package/src/services/local/task.ts +448 -4
@@ -11,6 +11,19 @@ import type {
11
11
  ListTasksParams,
12
12
  ListTasksResult,
13
13
  TaskChangelogResult,
14
+ Comment,
15
+ Tag,
16
+ ListCommentsResult,
17
+ ListTagsResult,
18
+ ListUsersResult,
19
+ ListProjectsResult,
20
+ Attachment,
21
+ CreateAttachmentParams,
22
+ PresignUploadResponse,
23
+ PresignDownloadResponse,
24
+ ListAttachmentsResult,
25
+ TaskActivityParams,
26
+ TaskActivityResult,
14
27
  } from '@agentuity/core';
15
28
  import { StructuredError } from '@agentuity/core';
16
29
  import { now } from './_util';
@@ -24,6 +37,46 @@ const TaskNotFoundError = StructuredError('TaskNotFoundError', 'Task not found')
24
37
 
25
38
  const TaskAlreadyClosedError = StructuredError('TaskAlreadyClosedError', 'Task is already closed');
26
39
 
40
+ const CommentNotFoundError = StructuredError('CommentNotFoundError', 'Comment not found');
41
+
42
+ const TagNotFoundError = StructuredError('TagNotFoundError', 'Tag not found');
43
+
44
+ const CommentBodyRequiredError = StructuredError(
45
+ 'CommentBodyRequiredError',
46
+ 'Comment body is required and must be a non-empty string'
47
+ );
48
+
49
+ const CommentUserRequiredError = StructuredError(
50
+ 'CommentUserRequiredError',
51
+ 'Comment user ID is required and must be a non-empty string'
52
+ );
53
+
54
+ const TagNameRequiredError = StructuredError(
55
+ 'TagNameRequiredError',
56
+ 'Tag name is required and must be a non-empty string'
57
+ );
58
+
59
+ const AttachmentNotSupportedError = StructuredError(
60
+ 'AttachmentNotSupportedError',
61
+ 'Attachments are not supported in local task storage'
62
+ );
63
+
64
+ type CommentRow = {
65
+ id: string;
66
+ created_at: number;
67
+ updated_at: number;
68
+ task_id: string;
69
+ user_id: string;
70
+ body: string;
71
+ };
72
+
73
+ type TagRow = {
74
+ id: string;
75
+ created_at: number;
76
+ name: string;
77
+ color: string | null;
78
+ };
79
+
27
80
  type TaskRow = {
28
81
  id: string;
29
82
  created_at: number;
@@ -41,6 +94,7 @@ type TaskRow = {
41
94
  created_id: string;
42
95
  assigned_id: string | null;
43
96
  closed_id: string | null;
97
+ deleted: number;
44
98
  };
45
99
 
46
100
  type TaskChangelogRow = {
@@ -92,9 +146,38 @@ function toTask(row: TaskRow): Task {
92
146
  created_id: row.created_id,
93
147
  assigned_id: row.assigned_id ?? undefined,
94
148
  closed_id: row.closed_id ?? undefined,
149
+ deleted: Boolean(row.deleted),
95
150
  };
96
151
  }
97
152
 
153
+ function toComment(row: CommentRow): Comment {
154
+ return {
155
+ id: row.id,
156
+ created_at: new Date(row.created_at).toISOString(),
157
+ updated_at: new Date(row.updated_at).toISOString(),
158
+ task_id: row.task_id,
159
+ user_id: row.user_id,
160
+ body: row.body,
161
+ };
162
+ }
163
+
164
+ function toTag(row: TagRow): Tag {
165
+ return {
166
+ id: row.id,
167
+ created_at: new Date(row.created_at).toISOString(),
168
+ name: row.name,
169
+ color: row.color ?? undefined,
170
+ };
171
+ }
172
+
173
+ function generateCommentId(): string {
174
+ return `comment_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
175
+ }
176
+
177
+ function generateTagId(): string {
178
+ return `tag_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`;
179
+ }
180
+
98
181
  function toChangelogEntry(row: TaskChangelogRow): TaskChangelogEntry {
99
182
  return {
100
183
  id: row.id,
@@ -168,6 +251,7 @@ export class LocalTaskStorage implements TaskStorage {
168
251
  created_id: params.created_id,
169
252
  assigned_id: params.assigned_id ?? null,
170
253
  closed_id: null,
254
+ deleted: 0,
171
255
  };
172
256
 
173
257
  stmt.run(
@@ -211,7 +295,8 @@ export class LocalTaskStorage implements TaskStorage {
211
295
  closed_date,
212
296
  created_id,
213
297
  assigned_id,
214
- closed_id
298
+ closed_id,
299
+ deleted
215
300
  FROM task_storage
216
301
  WHERE project_path = ? AND id = ?
217
302
  `);
@@ -278,7 +363,8 @@ export class LocalTaskStorage implements TaskStorage {
278
363
  closed_date,
279
364
  created_id,
280
365
  assigned_id,
281
- closed_id
366
+ closed_id,
367
+ deleted
282
368
  FROM task_storage
283
369
  ${whereClause}
284
370
  ORDER BY ${sortField} ${sortOrder}
@@ -314,7 +400,8 @@ export class LocalTaskStorage implements TaskStorage {
314
400
  closed_date,
315
401
  created_id,
316
402
  assigned_id,
317
- closed_id
403
+ closed_id,
404
+ deleted
318
405
  FROM task_storage
319
406
  WHERE project_path = ? AND id = ?
320
407
  `);
@@ -493,7 +580,8 @@ export class LocalTaskStorage implements TaskStorage {
493
580
  closed_date,
494
581
  created_id,
495
582
  assigned_id,
496
- closed_id
583
+ closed_id,
584
+ deleted
497
585
  FROM task_storage
498
586
  WHERE project_path = ? AND id = ?
499
587
  `);
@@ -592,4 +680,360 @@ export class LocalTaskStorage implements TaskStorage {
592
680
  offset,
593
681
  };
594
682
  }
683
+
684
+ async softDelete(id: string): Promise<Task> {
685
+ const task = await this.get(id);
686
+ if (!task) {
687
+ throw new TaskNotFoundError();
688
+ }
689
+
690
+ const timestamp = now();
691
+
692
+ const updateStmt = this.#db.prepare(`
693
+ UPDATE task_storage
694
+ SET status = 'closed', deleted = 1, closed_date = COALESCE(closed_date, ?), updated_at = ?
695
+ WHERE project_path = ? AND id = ?
696
+ `);
697
+
698
+ updateStmt.run(new Date(timestamp).toISOString(), timestamp, this.#projectPath, id);
699
+
700
+ const changelogStmt = this.#db.prepare(`
701
+ INSERT INTO task_changelog_storage (
702
+ project_path, id, task_id, field, old_value, new_value, created_at
703
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
704
+ `);
705
+
706
+ changelogStmt.run(
707
+ this.#projectPath,
708
+ generateChangelogId(),
709
+ id,
710
+ 'deleted',
711
+ 'false',
712
+ 'true',
713
+ timestamp
714
+ );
715
+
716
+ const updated = await this.get(id);
717
+ return { ...updated!, deleted: true };
718
+ }
719
+
720
+ async createComment(taskId: string, body: string, userId: string): Promise<Comment> {
721
+ const trimmedBody = body?.trim();
722
+ if (!trimmedBody) {
723
+ throw new CommentBodyRequiredError();
724
+ }
725
+
726
+ const trimmedUserId = userId?.trim();
727
+ if (!trimmedUserId) {
728
+ throw new CommentUserRequiredError();
729
+ }
730
+
731
+ const task = await this.get(taskId);
732
+ if (!task) {
733
+ throw new TaskNotFoundError();
734
+ }
735
+
736
+ const id = generateCommentId();
737
+ const timestamp = now();
738
+
739
+ const stmt = this.#db.prepare(`
740
+ INSERT INTO task_comment_storage (
741
+ project_path, id, task_id, user_id, body, created_at, updated_at
742
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
743
+ `);
744
+
745
+ stmt.run(this.#projectPath, id, taskId, trimmedUserId, trimmedBody, timestamp, timestamp);
746
+
747
+ return toComment({
748
+ id,
749
+ created_at: timestamp,
750
+ updated_at: timestamp,
751
+ task_id: taskId,
752
+ user_id: trimmedUserId,
753
+ body: trimmedBody,
754
+ });
755
+ }
756
+
757
+ async getComment(commentId: string): Promise<Comment> {
758
+ const query = this.#db.query(`
759
+ SELECT id, created_at, updated_at, task_id, user_id, body
760
+ FROM task_comment_storage
761
+ WHERE project_path = ? AND id = ?
762
+ `);
763
+
764
+ const row = query.get(this.#projectPath, commentId) as CommentRow | null;
765
+ if (!row) {
766
+ throw new CommentNotFoundError();
767
+ }
768
+
769
+ return toComment(row);
770
+ }
771
+
772
+ async updateComment(commentId: string, body: string): Promise<Comment> {
773
+ const trimmedBody = body?.trim();
774
+ if (!trimmedBody) {
775
+ throw new CommentBodyRequiredError();
776
+ }
777
+
778
+ const existing = await this.getComment(commentId);
779
+ const timestamp = now();
780
+
781
+ const stmt = this.#db.prepare(`
782
+ UPDATE task_comment_storage
783
+ SET body = ?, updated_at = ?
784
+ WHERE project_path = ? AND id = ?
785
+ `);
786
+
787
+ stmt.run(trimmedBody, timestamp, this.#projectPath, commentId);
788
+
789
+ return {
790
+ ...existing,
791
+ body: trimmedBody,
792
+ updated_at: new Date(timestamp).toISOString(),
793
+ };
794
+ }
795
+
796
+ async deleteComment(commentId: string): Promise<void> {
797
+ const stmt = this.#db.prepare(`
798
+ DELETE FROM task_comment_storage
799
+ WHERE project_path = ? AND id = ?
800
+ `);
801
+
802
+ stmt.run(this.#projectPath, commentId);
803
+ }
804
+
805
+ async listComments(
806
+ taskId: string,
807
+ params?: { limit?: number; offset?: number }
808
+ ): Promise<ListCommentsResult> {
809
+ const limit = params?.limit ?? DEFAULT_LIMIT;
810
+ const offset = params?.offset ?? 0;
811
+
812
+ const totalQuery = this.#db.query(
813
+ `SELECT COUNT(*) as count FROM task_comment_storage WHERE project_path = ? AND task_id = ?`
814
+ );
815
+ const totalRow = totalQuery.get(this.#projectPath, taskId) as { count: number };
816
+
817
+ const query = this.#db.query(`
818
+ SELECT id, created_at, updated_at, task_id, user_id, body
819
+ FROM task_comment_storage
820
+ WHERE project_path = ? AND task_id = ?
821
+ ORDER BY created_at DESC
822
+ LIMIT ? OFFSET ?
823
+ `);
824
+
825
+ const rows = query.all(this.#projectPath, taskId, limit, offset) as CommentRow[];
826
+
827
+ return {
828
+ comments: rows.map(toComment),
829
+ total: totalRow.count,
830
+ limit,
831
+ offset,
832
+ };
833
+ }
834
+
835
+ async createTag(name: string, color?: string): Promise<Tag> {
836
+ const trimmedName = name?.trim();
837
+ if (!trimmedName) {
838
+ throw new TagNameRequiredError();
839
+ }
840
+
841
+ const id = generateTagId();
842
+ const timestamp = now();
843
+
844
+ const stmt = this.#db.prepare(`
845
+ INSERT INTO task_tag_storage (
846
+ project_path, id, name, color, created_at
847
+ ) VALUES (?, ?, ?, ?, ?)
848
+ `);
849
+
850
+ stmt.run(this.#projectPath, id, trimmedName, color ?? null, timestamp);
851
+
852
+ return toTag({
853
+ id,
854
+ created_at: timestamp,
855
+ name: trimmedName,
856
+ color: color ?? null,
857
+ });
858
+ }
859
+
860
+ async getTag(tagId: string): Promise<Tag> {
861
+ const query = this.#db.query(`
862
+ SELECT id, created_at, name, color
863
+ FROM task_tag_storage
864
+ WHERE project_path = ? AND id = ?
865
+ `);
866
+
867
+ const row = query.get(this.#projectPath, tagId) as TagRow | null;
868
+ if (!row) {
869
+ throw new TagNotFoundError();
870
+ }
871
+
872
+ return toTag(row);
873
+ }
874
+
875
+ async updateTag(tagId: string, name: string, color?: string): Promise<Tag> {
876
+ const trimmedName = name?.trim();
877
+ if (!trimmedName) {
878
+ throw new TagNameRequiredError();
879
+ }
880
+
881
+ // Verify exists
882
+ await this.getTag(tagId);
883
+
884
+ const stmt = this.#db.prepare(`
885
+ UPDATE task_tag_storage
886
+ SET name = ?, color = ?
887
+ WHERE project_path = ? AND id = ?
888
+ `);
889
+
890
+ stmt.run(trimmedName, color ?? null, this.#projectPath, tagId);
891
+
892
+ return this.getTag(tagId);
893
+ }
894
+
895
+ async deleteTag(tagId: string): Promise<void> {
896
+ // Also remove tag associations
897
+ const deleteAssocStmt = this.#db.prepare(`
898
+ DELETE FROM task_tag_association_storage
899
+ WHERE project_path = ? AND tag_id = ?
900
+ `);
901
+ deleteAssocStmt.run(this.#projectPath, tagId);
902
+
903
+ const stmt = this.#db.prepare(`
904
+ DELETE FROM task_tag_storage
905
+ WHERE project_path = ? AND id = ?
906
+ `);
907
+ stmt.run(this.#projectPath, tagId);
908
+ }
909
+
910
+ async listTags(): Promise<ListTagsResult> {
911
+ const query = this.#db.query(`
912
+ SELECT id, created_at, name, color
913
+ FROM task_tag_storage
914
+ WHERE project_path = ?
915
+ ORDER BY name ASC
916
+ `);
917
+
918
+ const rows = query.all(this.#projectPath) as TagRow[];
919
+
920
+ return {
921
+ tags: rows.map(toTag),
922
+ };
923
+ }
924
+
925
+ async addTagToTask(taskId: string, tagId: string): Promise<void> {
926
+ // Verify task and tag exist
927
+ const task = await this.get(taskId);
928
+ if (!task) {
929
+ throw new TaskNotFoundError();
930
+ }
931
+ await this.getTag(tagId);
932
+
933
+ const stmt = this.#db.prepare(`
934
+ INSERT OR IGNORE INTO task_tag_association_storage (
935
+ project_path, task_id, tag_id
936
+ ) VALUES (?, ?, ?)
937
+ `);
938
+
939
+ stmt.run(this.#projectPath, taskId, tagId);
940
+ }
941
+
942
+ async removeTagFromTask(taskId: string, tagId: string): Promise<void> {
943
+ const stmt = this.#db.prepare(`
944
+ DELETE FROM task_tag_association_storage
945
+ WHERE project_path = ? AND task_id = ? AND tag_id = ?
946
+ `);
947
+
948
+ stmt.run(this.#projectPath, taskId, tagId);
949
+ }
950
+
951
+ async listTagsForTask(taskId: string): Promise<Tag[]> {
952
+ const query = this.#db.query(`
953
+ SELECT t.id, t.created_at, t.name, t.color
954
+ FROM task_tag_storage t
955
+ INNER JOIN task_tag_association_storage a ON t.id = a.tag_id AND t.project_path = a.project_path
956
+ WHERE a.project_path = ? AND a.task_id = ?
957
+ ORDER BY t.name ASC
958
+ `);
959
+
960
+ const rows = query.all(this.#projectPath, taskId) as TagRow[];
961
+
962
+ return rows.map(toTag);
963
+ }
964
+
965
+ // Attachment methods — not supported in local storage
966
+
967
+ async uploadAttachment(
968
+ _taskId: string,
969
+ _params: CreateAttachmentParams
970
+ ): Promise<PresignUploadResponse> {
971
+ throw new AttachmentNotSupportedError();
972
+ }
973
+
974
+ async confirmAttachment(_attachmentId: string): Promise<Attachment> {
975
+ throw new AttachmentNotSupportedError();
976
+ }
977
+
978
+ async downloadAttachment(_attachmentId: string): Promise<PresignDownloadResponse> {
979
+ throw new AttachmentNotSupportedError();
980
+ }
981
+
982
+ async listAttachments(_taskId: string): Promise<ListAttachmentsResult> {
983
+ throw new AttachmentNotSupportedError();
984
+ }
985
+
986
+ async deleteAttachment(_attachmentId: string): Promise<void> {
987
+ throw new AttachmentNotSupportedError();
988
+ }
989
+
990
+ async listUsers(): Promise<ListUsersResult> {
991
+ return { users: [] };
992
+ }
993
+
994
+ async listProjects(): Promise<ListProjectsResult> {
995
+ return { projects: [] };
996
+ }
997
+
998
+ async getActivity(params?: TaskActivityParams): Promise<TaskActivityResult> {
999
+ const days = Math.min(365, Math.max(7, params?.days ?? 90));
1000
+ const activity = [];
1001
+ const now = new Date();
1002
+
1003
+ for (let i = days - 1; i >= 0; i--) {
1004
+ const date = new Date(now);
1005
+ date.setDate(date.getDate() - i);
1006
+ const dateStr = date.toISOString().slice(0, 10);
1007
+
1008
+ const row = this.#db
1009
+ .prepare(
1010
+ `SELECT
1011
+ COALESCE(SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END), 0) as open,
1012
+ COALESCE(SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END), 0) as in_progress,
1013
+ COALESCE(SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END), 0) as done,
1014
+ COALESCE(SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END), 0) as closed,
1015
+ COALESCE(SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END), 0) as cancelled
1016
+ FROM task_storage
1017
+ WHERE project_path = ? AND date(created_at) = ?`
1018
+ )
1019
+ .get(this.#projectPath, dateStr) as {
1020
+ open: number;
1021
+ in_progress: number;
1022
+ done: number;
1023
+ closed: number;
1024
+ cancelled: number;
1025
+ };
1026
+
1027
+ activity.push({
1028
+ date: dateStr,
1029
+ open: row.open,
1030
+ inProgress: row.in_progress,
1031
+ done: row.done,
1032
+ closed: row.closed,
1033
+ cancelled: row.cancelled,
1034
+ });
1035
+ }
1036
+
1037
+ return { activity, days };
1038
+ }
595
1039
  }