@taskforcehq/taskforce 0.3.301 → 0.3.303

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 (171) hide show
  1. package/dist/Taskforce.module.css +37 -2
  2. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  3. package/dist/TaskforceCore.js +72 -33
  4. package/dist/TaskforceCore.test.js +848 -130
  5. package/dist/components/features/TaskSettings.js +14 -82
  6. package/dist/components/features/TaskSettings.test.js +33 -70
  7. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  8. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  9. package/dist/components/task/TaskActionHeader.js +6 -3
  10. package/dist/components/task/TaskCard.js +6 -3
  11. package/dist/components/task/TaskForm.js +2 -2
  12. package/dist/components/task/TaskForm.test.js +41 -2
  13. package/dist/components/task/TaskKanban.test.js +17 -0
  14. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  15. package/dist/components/views/PlansPage.d.ts +3 -1
  16. package/dist/components/views/PlansPage.js +109 -49
  17. package/dist/components/views/PlansPage.test.d.ts +1 -0
  18. package/dist/components/views/PlansPage.test.js +100 -0
  19. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  20. package/dist/components/views/StandaloneLayout.js +473 -256
  21. package/dist/components/views/panels/PlanningDrawer.js +18 -1
  22. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
  23. package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
  24. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  25. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  26. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  27. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  28. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  29. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  30. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  31. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  32. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  33. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  34. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  35. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  36. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  37. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  38. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  39. package/dist/components/views/standalone/modals/types.js +1 -0
  40. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  41. package/dist/components/views/teamManagementAccess.js +2 -2
  42. package/dist/components/views/teamManagementAccess.test.js +3 -3
  43. package/dist/config/deployment.js +2 -2
  44. package/dist/config/deployment.test.js +5 -3
  45. package/dist/core/AiProfileService.js +7 -2
  46. package/dist/core/AiProfiles.test.js +19 -0
  47. package/dist/core/EntitlementsPolicy.test.js +2 -2
  48. package/dist/core/GlobalSettingsService.js +78 -18
  49. package/dist/core/PlanEntitlementService.d.ts +29 -5
  50. package/dist/core/PlanEntitlementService.js +297 -219
  51. package/dist/core/SignupMonetizationSettings.test.js +173 -49
  52. package/dist/core/SyncReconciliationService.d.ts +2 -0
  53. package/dist/core/SyncReconciliationService.js +3 -0
  54. package/dist/core/SystemAdmin.test.js +186 -96
  55. package/dist/core/TaskAuditTimeline.test.js +40 -0
  56. package/dist/core/Taskforce.d.ts +100 -10
  57. package/dist/core/Taskforce.ids.test.js +133 -0
  58. package/dist/core/Taskforce.js +459 -127
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  61. package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
  62. package/dist/core/taskReferencePolicy.d.ts +28 -0
  63. package/dist/core/taskReferencePolicy.js +189 -0
  64. package/dist/core/types.d.ts +13 -12
  65. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  66. package/dist/hooks/useSyncOrchestrator.js +17 -3
  67. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
  68. package/dist/hooks/useTaskData.js +3 -2
  69. package/dist/hooks/useTaskData.test.js +66 -0
  70. package/dist/hooks/useTaskforce.d.ts +8 -0
  71. package/dist/hooks/useTaskforce.js +72 -19
  72. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  73. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  74. package/dist/mcp/aiProfileBootstrap.js +80 -0
  75. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  76. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  77. package/dist/mcp/clientIntegrations.d.ts +2 -8
  78. package/dist/mcp/clientIntegrations.js +21 -40
  79. package/dist/mcp/clientIntegrations.test.js +28 -2
  80. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  81. package/dist/mcp/clientProfileDefaults.js +37 -0
  82. package/dist/mcp/runtime.d.ts +10 -7
  83. package/dist/mcp/runtime.js +131 -48
  84. package/dist/mcp/runtime.test.js +321 -83
  85. package/dist/migrations/billingSchemaParity.test.js +3 -0
  86. package/dist/migrations/taskSchemaMigrations.js +68 -1
  87. package/dist/qaOpen.test.d.ts +1 -0
  88. package/dist/qaOpen.test.js +56 -0
  89. package/dist/server/index.cookieProxy.test.js +2 -0
  90. package/dist/server/index.d.ts +1 -0
  91. package/dist/server/index.js +17 -1
  92. package/dist/server/index.test.js +15 -1
  93. package/dist/server/mockStripe.d.ts +1 -0
  94. package/dist/server/mockStripe.js +2 -0
  95. package/dist/server/routes/admin.js +105 -45
  96. package/dist/server/routes/auth.d.ts +6 -0
  97. package/dist/server/routes/auth.js +505 -30
  98. package/dist/server/routes/billing.js +517 -24
  99. package/dist/server/routes/billing.test.js +967 -45
  100. package/dist/server/routes/documents.js +6 -1
  101. package/dist/server/routes/shared.d.ts +1 -1
  102. package/dist/server/routes/shared.js +16 -6
  103. package/dist/server/routes/sync.integration.test.js +290 -0
  104. package/dist/server/routes/sync.js +234 -1922
  105. package/dist/server/routes/tasks.js +18 -4
  106. package/dist/server/routes.js +29 -22
  107. package/dist/server/routes.test.js +639 -82
  108. package/dist/shared/runtimeContract.d.ts +1 -1
  109. package/dist/sync/cloudSyncApi.d.ts +1 -0
  110. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  111. package/dist/sync/collaborationSyncPayload.js +131 -0
  112. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  113. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  114. package/dist/sync/collaborationSyncState.d.ts +15 -0
  115. package/dist/sync/collaborationSyncState.js +60 -0
  116. package/dist/sync/contentSyncPayload.d.ts +45 -0
  117. package/dist/sync/contentSyncPayload.js +104 -0
  118. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  119. package/dist/sync/contentSyncPayload.test.js +104 -0
  120. package/dist/sync/contentSyncState.d.ts +98 -0
  121. package/dist/sync/contentSyncState.js +1004 -0
  122. package/dist/sync/planningSyncPayload.d.ts +9 -0
  123. package/dist/sync/planningSyncPayload.js +82 -0
  124. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  125. package/dist/sync/planningSyncPayload.test.js +68 -0
  126. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  127. package/dist/sync/syncApplyHandlers.js +445 -0
  128. package/dist/sync/syncService.d.ts +1 -0
  129. package/dist/sync/syncService.js +21 -8
  130. package/dist/sync/taskSyncPayload.d.ts +33 -0
  131. package/dist/sync/taskSyncPayload.js +126 -0
  132. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  133. package/dist/sync/taskSyncPayload.test.js +54 -0
  134. package/dist/sync/workspacePullFeed.d.ts +38 -0
  135. package/dist/sync/workspacePullFeed.js +349 -0
  136. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  137. package/dist/sync/workspacePullFeed.test.js +278 -0
  138. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  139. package/dist/sync/workspaceSyncModel.js +9 -2
  140. package/dist/sync/workspaceSyncModel.test.js +29 -0
  141. package/dist/test/setup.js +1 -1
  142. package/dist/types.d.ts +1 -0
  143. package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
  144. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
  145. package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
  146. package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
  147. package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
  148. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
  149. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  150. package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
  151. package/dist/ui/assets/index-CLIMgR6g.js +6 -0
  152. package/dist/ui/assets/index-DneETxcu.css +1 -0
  153. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
  154. package/dist/ui/index.html +3 -3
  155. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  156. package/dist/utils/accountProfileSummaryCache.js +97 -0
  157. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  158. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  159. package/dist/utils/taskActivity.js +17 -4
  160. package/dist/utils/taskActivity.test.js +27 -0
  161. package/dist/utils/taskNormalization.js +10 -2
  162. package/dist/utils/taskReferences.d.ts +11 -1
  163. package/dist/utils/taskReferences.js +33 -1
  164. package/package.json +4 -3
  165. package/scripts/qa-open-lib.mjs +175 -0
  166. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  167. package/scripts/qa-open.mjs +352 -0
  168. package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
  169. package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
  170. package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
  171. package/dist/ui/assets/index-xBWLPOEb.js +0 -6
@@ -0,0 +1,126 @@
1
+ function toOptionalString(value) {
2
+ if (value === undefined || value === null)
3
+ return undefined;
4
+ const normalized = String(value).trim();
5
+ return normalized.length > 0 ? normalized : undefined;
6
+ }
7
+ function toNullableString(value) {
8
+ if (value === undefined)
9
+ return undefined;
10
+ if (value === null)
11
+ return null;
12
+ const normalized = String(value).trim();
13
+ return normalized.length > 0 ? normalized : null;
14
+ }
15
+ function toOptionalBoolean(value) {
16
+ return typeof value === 'boolean' ? value : undefined;
17
+ }
18
+ function toOptionalInteger(value, options) {
19
+ if (value === undefined)
20
+ return undefined;
21
+ if (value === null || value === '')
22
+ return options?.nullable ? null : undefined;
23
+ const normalized = typeof value === 'number' ? value : Number(value);
24
+ if (!Number.isFinite(normalized))
25
+ return options?.nullable ? null : undefined;
26
+ const rounded = Math.floor(normalized);
27
+ if (Number.isFinite(options?.min) && rounded < Number(options?.min)) {
28
+ return options?.nullable ? null : undefined;
29
+ }
30
+ return rounded;
31
+ }
32
+ function cloneTaxonomies(value) {
33
+ if (!value || typeof value !== 'object' || Array.isArray(value))
34
+ return undefined;
35
+ const entries = Object.entries(value);
36
+ const normalized = entries.reduce((acc, [key, raw]) => {
37
+ if (Array.isArray(raw)) {
38
+ acc[key] = raw.map((entry) => String(entry ?? ''));
39
+ return acc;
40
+ }
41
+ if (raw === undefined || raw === null)
42
+ return acc;
43
+ acc[key] = String(raw);
44
+ return acc;
45
+ }, {});
46
+ return Object.keys(normalized).length > 0 ? normalized : {};
47
+ }
48
+ function cloneComments(value) {
49
+ return Array.isArray(value) ? value : undefined;
50
+ }
51
+ function cloneAttachments(value) {
52
+ return Array.isArray(value) ? value : undefined;
53
+ }
54
+ function cloneChecklistItems(value) {
55
+ return Array.isArray(value) ? value : undefined;
56
+ }
57
+ export function serializeTaskForSync(task) {
58
+ const source = task && typeof task === 'object' ? task : {};
59
+ return {
60
+ id: String(source.id || '').trim(),
61
+ title: String(source.title || '').trim(),
62
+ description: source.description === undefined ? undefined : (source.description || null),
63
+ status: (String(source.status || 'task').trim() || 'task'),
64
+ priority: toOptionalInteger(source.priority, { min: 1 }) ?? undefined,
65
+ complexity: toOptionalInteger(source.complexity, { min: 1, nullable: true }),
66
+ type: String(source.type || '').trim(),
67
+ category: String(source.category || '').trim(),
68
+ approach: toOptionalString(source.approach),
69
+ canceledReason: source.canceledReason === undefined ? undefined : (source.canceledReason || null),
70
+ createdAt: String(source.createdAt || '').trim(),
71
+ updatedAt: toOptionalString(source.updatedAt),
72
+ completedAt: source.completedAt === undefined ? undefined : (source.completedAt || null),
73
+ isArchived: toOptionalBoolean(source.isArchived),
74
+ taxonomies: cloneTaxonomies(source.taxonomies),
75
+ createdBy: toOptionalString(source.createdBy),
76
+ assignee: toOptionalString(source.assignee),
77
+ scheduledDate: toNullableString(source.scheduledDate),
78
+ dueDate: toNullableString(source.dueDate),
79
+ scheduledWeekKey: toNullableString(source.scheduledWeekKey),
80
+ orderInDay: toOptionalInteger(source.orderInDay, { min: 0, nullable: true }),
81
+ workstreamId: toNullableString(source.workstreamId),
82
+ referenceNumber: toOptionalInteger(source.referenceNumber, { min: 1, nullable: true }),
83
+ comments: cloneComments(source.comments),
84
+ attachments: cloneAttachments(source.attachments),
85
+ checklistItems: cloneChecklistItems(source.checklistItems),
86
+ };
87
+ }
88
+ export function normalizeTaskSyncPayload(raw) {
89
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
90
+ return null;
91
+ const source = raw;
92
+ const id = String(source.id || '').trim();
93
+ if (!id)
94
+ return null;
95
+ return serializeTaskForSync(source);
96
+ }
97
+ export function taskSyncPayloadToCoreTask(task) {
98
+ return {
99
+ id: task.id,
100
+ title: task.title,
101
+ description: task.description,
102
+ status: task.status,
103
+ priority: task.priority,
104
+ complexity: task.complexity ?? undefined,
105
+ type: task.type,
106
+ category: task.category,
107
+ approach: task.approach,
108
+ canceledReason: task.canceledReason ?? undefined,
109
+ createdAt: task.createdAt,
110
+ updatedAt: task.updatedAt,
111
+ completedAt: task.completedAt,
112
+ isArchived: task.isArchived,
113
+ taxonomies: task.taxonomies,
114
+ createdBy: task.createdBy,
115
+ assignee: task.assignee,
116
+ scheduledDate: task.scheduledDate,
117
+ dueDate: task.dueDate,
118
+ scheduledWeekKey: task.scheduledWeekKey,
119
+ orderInDay: task.orderInDay ?? undefined,
120
+ workstreamId: task.workstreamId,
121
+ referenceNumber: task.referenceNumber ?? undefined,
122
+ comments: task.comments,
123
+ attachments: task.attachments,
124
+ checklistItems: task.checklistItems
125
+ };
126
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeTaskSyncPayload, serializeTaskForSync, taskSyncPayloadToCoreTask, } from './taskSyncPayload';
3
+ describe('taskSyncPayload', () => {
4
+ it('omits local-only task reference fields from serialized sync payloads', () => {
5
+ const payload = serializeTaskForSync({
6
+ id: 'task-1',
7
+ title: 'Serialized task',
8
+ status: 'task',
9
+ priority: 2,
10
+ category: 'general',
11
+ type: 'task',
12
+ referenceNumber: 7,
13
+ localReferenceNumber: 3,
14
+ referenceLabel: 'LT-3',
15
+ });
16
+ expect(payload).toEqual(expect.objectContaining({
17
+ id: 'task-1',
18
+ referenceNumber: 7,
19
+ }));
20
+ expect(payload).not.toHaveProperty('localReferenceNumber');
21
+ expect(payload).not.toHaveProperty('referenceLabel');
22
+ });
23
+ it('normalizes task sync payload numbers before converting back to core input', () => {
24
+ const payload = normalizeTaskSyncPayload({
25
+ id: 'task-2',
26
+ title: 'Incoming task',
27
+ status: 'review',
28
+ priority: '3',
29
+ complexity: '4',
30
+ category: 'general',
31
+ type: 'task',
32
+ referenceNumber: '9',
33
+ orderInDay: '2',
34
+ localReferenceNumber: 99,
35
+ referenceLabel: 'LT-99',
36
+ });
37
+ expect(payload).toEqual(expect.objectContaining({
38
+ id: 'task-2',
39
+ priority: 3,
40
+ complexity: 4,
41
+ referenceNumber: 9,
42
+ orderInDay: 2,
43
+ }));
44
+ expect(payload).not.toHaveProperty('localReferenceNumber');
45
+ expect(payload).not.toHaveProperty('referenceLabel');
46
+ expect(taskSyncPayloadToCoreTask(payload)).toEqual(expect.objectContaining({
47
+ id: 'task-2',
48
+ priority: 3,
49
+ complexity: 4,
50
+ referenceNumber: 9,
51
+ orderInDay: 2,
52
+ }));
53
+ });
54
+ });
@@ -0,0 +1,38 @@
1
+ import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
2
+ import type { WorkspaceSyncChange, WorkspaceSyncWorkspaceMemberSnapshot } from './workspaceSyncModel.js';
3
+ import { createCollaborationSyncState } from './collaborationSyncState.js';
4
+ import { createContentSyncState } from './contentSyncState.js';
5
+ type SyncCore = TaskforceCore & TaskforceSyncCapable;
6
+ export interface WorkspaceSyncCursorValue {
7
+ u: string;
8
+ id: string;
9
+ }
10
+ type EncodedWorkspaceSyncChange = WorkspaceSyncChange & {
11
+ watermark: string;
12
+ };
13
+ export interface WorkspacePullFeedResult {
14
+ workspaceMembers: WorkspaceSyncWorkspaceMemberSnapshot[];
15
+ changes: EncodedWorkspaceSyncChange[];
16
+ nextCursor: string | null;
17
+ hasMore: boolean;
18
+ diagnostics: {
19
+ totalEntries: number;
20
+ pageEntries: number;
21
+ documentIndexCount: number;
22
+ assetIndexCount: number;
23
+ indexMs: number;
24
+ hydrationMs: number;
25
+ encodeMs: number;
26
+ buildMs: number;
27
+ };
28
+ }
29
+ export declare function buildWorkspacePullFeed(input: {
30
+ core: SyncCore;
31
+ workspaceId: string;
32
+ cursor: WorkspaceSyncCursorValue | null;
33
+ pageSize: number;
34
+ encodeWorkspaceSyncCursor: (cursor: WorkspaceSyncCursorValue | null) => string | null;
35
+ contentSyncState: ReturnType<typeof createContentSyncState>;
36
+ collaborationSyncState: ReturnType<typeof createCollaborationSyncState>;
37
+ }): Promise<WorkspacePullFeedResult>;
38
+ export {};
@@ -0,0 +1,349 @@
1
+ import { serializeTaskForSync } from './taskSyncPayload.js';
2
+ export async function buildWorkspacePullFeed(input) {
3
+ const buildStartedAtMs = Date.now();
4
+ const { core, workspaceId, cursor, pageSize, encodeWorkspaceSyncCursor, contentSyncState, collaborationSyncState } = input;
5
+ const syncCursor = cursor ? { watermark: cursor.u, id: cursor.id } : null;
6
+ const dbFetchLimit = pageSize * 2 + 10;
7
+ const taskRows = core.listTasksForSync(workspaceId, syncCursor, dbFetchLimit);
8
+ const upsertEntries = taskRows
9
+ .map((entry) => {
10
+ const watermark = String(entry.task?.updatedAt || entry.task?.createdAt || '').trim();
11
+ const taskId = String(entry.task?.id || '').trim();
12
+ return {
13
+ kind: 'upsert',
14
+ watermark,
15
+ sortId: taskId,
16
+ task: serializeTaskForSync(entry.task),
17
+ archived: Boolean(entry.archived)
18
+ };
19
+ })
20
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sortId.length > 0);
21
+ const rawDeleteEntries = core.listWorkspaceSyncDeletesSince(workspaceId, syncCursor, dbFetchLimit);
22
+ const deleteEntries = rawDeleteEntries
23
+ .map((entry) => ({
24
+ kind: 'delete',
25
+ watermark: String(entry.deletedAt || '').trim(),
26
+ sortId: String(entry.taskId || '').trim(),
27
+ taskId: String(entry.taskId || '').trim()
28
+ }))
29
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sortId.length > 0);
30
+ const taxonomySnapshot = core.getTaxonomiesSyncSnapshot(workspaceId);
31
+ const taxonomyEntries = (taxonomySnapshot
32
+ && Array.isArray(taxonomySnapshot.taxonomies)
33
+ && Number.isFinite(Date.parse(String(taxonomySnapshot.updatedAt || '').trim())))
34
+ ? [{
35
+ kind: 'taxonomy',
36
+ watermark: String(taxonomySnapshot.updatedAt || '').trim(),
37
+ sortId: '__taxonomy_config__',
38
+ taxonomies: taxonomySnapshot.taxonomies,
39
+ ...(taxonomySnapshot.taxonomyState ? { taxonomyState: taxonomySnapshot.taxonomyState } : {})
40
+ }]
41
+ : [];
42
+ const initiativeEntries = core.listInitiatives(workspaceId)
43
+ .map((initiative) => ({
44
+ kind: 'initiative',
45
+ watermark: String(initiative.updatedAt || initiative.createdAt || '').trim(),
46
+ sortId: `initiative:${String(initiative.id || '').trim()}`,
47
+ initiative
48
+ }))
49
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.initiative.id.length > 0);
50
+ const workstreamEntries = core.listWorkstreams(workspaceId)
51
+ .map((workstream) => ({
52
+ kind: 'workstream',
53
+ watermark: String(workstream.updatedAt || workstream.createdAt || '').trim(),
54
+ sortId: `workstream:${String(workstream.id || '').trim()}`,
55
+ workstream
56
+ }))
57
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.workstream.id.length > 0);
58
+ const aiProfileEntries = core.listAiProfiles(workspaceId)
59
+ .map((profile) => ({
60
+ kind: 'ai-profile',
61
+ watermark: String(profile.updatedAt || profile.createdAt || '').trim(),
62
+ sortId: `ai-profile:${String(profile.id || '').trim()}`,
63
+ profile
64
+ }))
65
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sortId.length > 0);
66
+ const indexStartedAtMs = Date.now();
67
+ const documentIndex = await contentSyncState.listWorkspaceSyncDocumentIndex(core.getPaths().basePath, workspaceId);
68
+ const documentEntries = documentIndex
69
+ .map((doc) => ({
70
+ kind: 'document',
71
+ watermark: String(doc.updatedAt || '').trim(),
72
+ sortId: `doc:${String(doc.path || '').trim()}`,
73
+ path: String(doc.path || '').trim(),
74
+ assetId: doc.assetId,
75
+ documentId: doc.documentId,
76
+ referenceNumber: doc.referenceNumber,
77
+ version: doc.version,
78
+ taskId: doc.taskId,
79
+ logicalName: doc.logicalName,
80
+ caption: doc.caption,
81
+ originalFilename: doc.originalFilename,
82
+ linkRole: (doc.linkRole === 'reference' ? 'reference' : 'attachment')
83
+ }))
84
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
85
+ const documentDeleteEntries = contentSyncState.listWorkspaceSyncDocumentDeletesSnapshot(workspaceId)
86
+ .map((doc) => ({
87
+ kind: 'document-delete',
88
+ watermark: String(doc.deletedAt || '').trim(),
89
+ sortId: `doc-delete:${String(doc.path || '').trim()}:${String(doc.assetId || '').trim()}`,
90
+ path: String(doc.path || '').trim(),
91
+ assetId: typeof doc.assetId === 'string' && doc.assetId.trim().length > 0 ? doc.assetId.trim() : undefined
92
+ }))
93
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
94
+ const documentReviewSessions = collaborationSyncState.listWorkspaceSyncDocumentReviewSessionsSnapshot(workspaceId);
95
+ const documentReviewSessionEntries = documentReviewSessions
96
+ .filter((session) => !session.deletedAt)
97
+ .map((session) => ({
98
+ kind: 'document-review-session',
99
+ watermark: String(session.updatedAt || session.createdAt || '').trim(),
100
+ sortId: `document-review-session:${String(session.id || '').trim()}`,
101
+ session
102
+ }))
103
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.session.id.length > 0 && entry.session.assetId.length > 0);
104
+ const documentReviewSessionDeleteEntries = documentReviewSessions
105
+ .filter((session) => typeof session.deletedAt === 'string' && session.deletedAt.trim().length > 0)
106
+ .map((session) => ({
107
+ kind: 'document-review-session-delete',
108
+ watermark: String(session.deletedAt || session.updatedAt || '').trim(),
109
+ sortId: `document-review-session-delete:${String(session.id || '').trim()}`,
110
+ sessionId: String(session.id || '').trim()
111
+ }))
112
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sessionId.length > 0);
113
+ const documentReviewComments = collaborationSyncState.listWorkspaceSyncDocumentReviewCommentsSnapshot(workspaceId);
114
+ const documentReviewCommentEntries = documentReviewComments
115
+ .filter((comment) => !comment.deletedAt)
116
+ .map((comment) => ({
117
+ kind: 'document-review-comment',
118
+ watermark: String(comment.updatedAt || comment.createdAt || '').trim(),
119
+ sortId: `document-review-comment:${String(comment.id || '').trim()}`,
120
+ comment
121
+ }))
122
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.comment.id.length > 0 && entry.comment.sessionId.length > 0);
123
+ const documentReviewCommentDeleteEntries = documentReviewComments
124
+ .filter((comment) => typeof comment.deletedAt === 'string' && comment.deletedAt.trim().length > 0)
125
+ .map((comment) => ({
126
+ kind: 'document-review-comment-delete',
127
+ watermark: String(comment.deletedAt || comment.updatedAt || '').trim(),
128
+ sortId: `document-review-comment-delete:${String(comment.id || '').trim()}`,
129
+ commentId: String(comment.id || '').trim()
130
+ }))
131
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.commentId.length > 0);
132
+ const annotatedAttachmentSessions = collaborationSyncState.listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(workspaceId);
133
+ const annotatedAttachmentSessionEntries = annotatedAttachmentSessions
134
+ .filter((session) => !session.deletedAt)
135
+ .map((session) => ({
136
+ kind: 'annotated-attachment-session',
137
+ watermark: String(session.updatedAt || session.createdAt || '').trim(),
138
+ sortId: `annotated-attachment-session:${String(session.id || '').trim()}`,
139
+ session
140
+ }))
141
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.session.id.length > 0 && entry.session.baseImageAssetId.length > 0);
142
+ const annotatedAttachmentSessionDeleteEntries = annotatedAttachmentSessions
143
+ .filter((session) => typeof session.deletedAt === 'string' && session.deletedAt.trim().length > 0)
144
+ .map((session) => ({
145
+ kind: 'annotated-attachment-session-delete',
146
+ watermark: String(session.deletedAt || session.updatedAt || '').trim(),
147
+ sortId: `annotated-attachment-session-delete:${String(session.id || '').trim()}`,
148
+ sessionId: String(session.id || '').trim()
149
+ }))
150
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sessionId.length > 0);
151
+ const assetIndex = await contentSyncState.listWorkspaceSyncBinaryAssetIndex(workspaceId);
152
+ const assetEntries = assetIndex
153
+ .map((asset) => ({
154
+ kind: 'asset',
155
+ watermark: String(asset.updatedAt || '').trim(),
156
+ sortId: `asset:${String(asset.path || '').trim()}`,
157
+ path: String(asset.path || '').trim(),
158
+ assetId: asset.assetId,
159
+ kindValue: asset.kind,
160
+ mimeType: asset.mimeType,
161
+ referenceNumber: asset.referenceNumber,
162
+ taskId: asset.taskId,
163
+ logicalName: asset.logicalName,
164
+ caption: asset.caption,
165
+ originalFilename: asset.originalFilename,
166
+ linkRole: asset.linkRole
167
+ }))
168
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
169
+ const assetDeleteEntries = contentSyncState.listWorkspaceSyncBinaryAssetDeletesSnapshot(workspaceId)
170
+ .map((asset) => ({
171
+ kind: 'asset-delete',
172
+ watermark: String(asset.deletedAt || '').trim(),
173
+ sortId: `asset-delete:${String(asset.path || '').trim()}:${String(asset.assetId || '').trim()}`,
174
+ path: String(asset.path || '').trim(),
175
+ assetId: typeof asset.assetId === 'string' && asset.assetId.trim().length > 0 ? asset.assetId.trim() : undefined
176
+ }))
177
+ .filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
178
+ const all = [
179
+ ...upsertEntries,
180
+ ...deleteEntries,
181
+ ...taxonomyEntries,
182
+ ...initiativeEntries,
183
+ ...workstreamEntries,
184
+ ...aiProfileEntries,
185
+ ...documentEntries,
186
+ ...documentDeleteEntries,
187
+ ...documentReviewSessionEntries,
188
+ ...documentReviewSessionDeleteEntries,
189
+ ...documentReviewCommentEntries,
190
+ ...documentReviewCommentDeleteEntries,
191
+ ...annotatedAttachmentSessionEntries,
192
+ ...annotatedAttachmentSessionDeleteEntries,
193
+ ...assetEntries,
194
+ ...assetDeleteEntries
195
+ ];
196
+ const afterCursor = all.filter((entry) => {
197
+ if (!cursor)
198
+ return true;
199
+ if (entry.watermark > cursor.u)
200
+ return true;
201
+ if (entry.watermark < cursor.u)
202
+ return false;
203
+ return entry.sortId > cursor.id;
204
+ });
205
+ afterCursor.sort((a, b) => {
206
+ if (a.watermark !== b.watermark)
207
+ return a.watermark.localeCompare(b.watermark);
208
+ return String(a.sortId || '').localeCompare(String(b.sortId || ''));
209
+ });
210
+ const page = afterCursor.slice(0, pageSize);
211
+ const workspaceMembers = core.listWorkspaceUsers(workspaceId).map((member) => ({
212
+ userId: member.userId,
213
+ workspaceId,
214
+ email: member.email,
215
+ displayName: member.displayName || null,
216
+ role: member.role,
217
+ permissionMode: member.permissionMode,
218
+ betaAccess: member.betaAccess,
219
+ status: member.status,
220
+ disabled: member.disabled,
221
+ createdAt: member.createdAt || null,
222
+ updatedAt: member.updatedAt || null,
223
+ emailVerifiedAt: member.emailVerifiedAt || null
224
+ }));
225
+ const indexMs = Date.now() - indexStartedAtMs;
226
+ const hydratedDocuments = new Map();
227
+ const hydratedAssets = new Map();
228
+ const hydrationStartedAtMs = Date.now();
229
+ await Promise.all(page.map(async (entry) => {
230
+ if (entry.kind === 'document') {
231
+ const payload = await contentSyncState.loadWorkspaceSyncDocumentPayload(core.getPaths().basePath, workspaceId, entry.path);
232
+ hydratedDocuments.set(entry.path, payload);
233
+ return;
234
+ }
235
+ if (entry.kind === 'asset') {
236
+ const payload = await contentSyncState.loadWorkspaceSyncBinaryAssetPayload(core.getPaths().basePath, workspaceId, entry.path);
237
+ hydratedAssets.set(entry.path, payload);
238
+ }
239
+ }));
240
+ const hydrationMs = Date.now() - hydrationStartedAtMs;
241
+ const effectivePage = [];
242
+ for (const entry of page) {
243
+ if (entry.kind === 'document' && !hydratedDocuments.get(entry.path))
244
+ break;
245
+ if (entry.kind === 'asset' && !hydratedAssets.get(entry.path))
246
+ break;
247
+ effectivePage.push(entry);
248
+ }
249
+ const hasMore = afterCursor.length > effectivePage.length;
250
+ const last = effectivePage.length > 0 ? effectivePage[effectivePage.length - 1] : null;
251
+ const nextCursor = encodeWorkspaceSyncCursor(last
252
+ ? { u: last.watermark, id: String(last.sortId || '') }
253
+ : cursor);
254
+ const encodeStartedAtMs = Date.now();
255
+ const changes = effectivePage.flatMap((entry) => (entry.kind === 'delete'
256
+ ? [{ op: 'delete', taskId: entry.taskId, watermark: entry.watermark }]
257
+ : entry.kind === 'taxonomy'
258
+ ? [{
259
+ op: 'taxonomy-upsert',
260
+ taxonomies: entry.taxonomies,
261
+ ...(entry.taxonomyState ? { taxonomyState: entry.taxonomyState } : {}),
262
+ updatedAt: entry.watermark,
263
+ watermark: entry.watermark
264
+ }]
265
+ : entry.kind === 'initiative'
266
+ ? [{ op: 'initiative-upsert', initiative: entry.initiative, watermark: entry.watermark }]
267
+ : entry.kind === 'workstream'
268
+ ? [{ op: 'workstream-upsert', workstream: entry.workstream, watermark: entry.watermark }]
269
+ : entry.kind === 'ai-profile'
270
+ ? [{ op: 'ai-profile-upsert', profile: entry.profile, watermark: entry.watermark }]
271
+ : entry.kind === 'document-review-session'
272
+ ? [{ op: 'document-review-session-upsert', session: entry.session, watermark: entry.watermark }]
273
+ : entry.kind === 'document-review-session-delete'
274
+ ? [{ op: 'document-review-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }]
275
+ : entry.kind === 'document-review-comment'
276
+ ? [{ op: 'document-review-comment-upsert', comment: entry.comment, watermark: entry.watermark }]
277
+ : entry.kind === 'document-review-comment-delete'
278
+ ? [{ op: 'document-review-comment-delete', commentId: entry.commentId, deletedAt: entry.watermark, watermark: entry.watermark }]
279
+ : entry.kind === 'annotated-attachment-session'
280
+ ? [{ op: 'annotated-attachment-session-upsert', session: entry.session, watermark: entry.watermark }]
281
+ : entry.kind === 'annotated-attachment-session-delete'
282
+ ? [{ op: 'annotated-attachment-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }]
283
+ : entry.kind === 'document'
284
+ ? (() => {
285
+ const payload = hydratedDocuments.get(entry.path);
286
+ if (!payload)
287
+ return [];
288
+ return [{
289
+ op: 'document-upsert',
290
+ path: entry.path,
291
+ updatedAt: entry.watermark,
292
+ content: payload.content,
293
+ assetId: payload.assetId,
294
+ documentId: payload.documentId,
295
+ referenceNumber: payload.referenceNumber,
296
+ version: payload.version,
297
+ taskId: payload.taskId,
298
+ logicalName: payload.logicalName,
299
+ caption: payload.caption,
300
+ originalFilename: payload.originalFilename,
301
+ linkRole: payload.linkRole === 'reference' ? 'reference' : 'attachment',
302
+ watermark: entry.watermark
303
+ }];
304
+ })()
305
+ : entry.kind === 'document-delete'
306
+ ? [{ op: 'document-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
307
+ : entry.kind === 'asset'
308
+ ? (() => {
309
+ const payload = hydratedAssets.get(entry.path);
310
+ if (!payload)
311
+ return [];
312
+ return [{
313
+ op: 'asset-upsert',
314
+ path: entry.path,
315
+ updatedAt: entry.watermark,
316
+ contentBase64: payload.contentBase64,
317
+ assetId: payload.assetId,
318
+ kind: payload.kind,
319
+ mimeType: payload.mimeType,
320
+ referenceNumber: payload.referenceNumber,
321
+ taskId: payload.taskId,
322
+ logicalName: payload.logicalName,
323
+ caption: payload.caption,
324
+ originalFilename: payload.originalFilename,
325
+ linkRole: payload.linkRole,
326
+ watermark: entry.watermark
327
+ }];
328
+ })()
329
+ : entry.kind === 'asset-delete'
330
+ ? [{ op: 'asset-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
331
+ : [{ op: 'upsert', task: entry.task, archived: Boolean(entry.archived), watermark: entry.watermark }]));
332
+ const encodeMs = Date.now() - encodeStartedAtMs;
333
+ return {
334
+ workspaceMembers,
335
+ changes,
336
+ nextCursor,
337
+ hasMore,
338
+ diagnostics: {
339
+ totalEntries: all.length,
340
+ pageEntries: page.length,
341
+ documentIndexCount: documentIndex.length,
342
+ assetIndexCount: assetIndex.length,
343
+ indexMs,
344
+ hydrationMs,
345
+ encodeMs,
346
+ buildMs: Date.now() - buildStartedAtMs
347
+ }
348
+ };
349
+ }
@@ -0,0 +1 @@
1
+ export {};