@taskforcehq/taskforce 0.3.302 → 0.3.304
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/Taskforce.module.css +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { serializeTaskForSync } from './taskSyncPayload.js';
|
|
2
2
|
export async function buildWorkspacePullFeed(input) {
|
|
3
|
+
const buildStartedAtMs = Date.now();
|
|
3
4
|
const { core, workspaceId, cursor, pageSize, encodeWorkspaceSyncCursor, contentSyncState, collaborationSyncState } = input;
|
|
4
5
|
const syncCursor = cursor ? { watermark: cursor.u, id: cursor.id } : null;
|
|
5
6
|
const dbFetchLimit = pageSize * 2 + 10;
|
|
@@ -62,13 +63,14 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
62
63
|
profile
|
|
63
64
|
}))
|
|
64
65
|
.filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sortId.length > 0);
|
|
65
|
-
const
|
|
66
|
+
const indexStartedAtMs = Date.now();
|
|
67
|
+
const documentIndex = await contentSyncState.listWorkspaceSyncDocumentIndex(core.getPaths().basePath, workspaceId);
|
|
68
|
+
const documentEntries = documentIndex
|
|
66
69
|
.map((doc) => ({
|
|
67
70
|
kind: 'document',
|
|
68
71
|
watermark: String(doc.updatedAt || '').trim(),
|
|
69
72
|
sortId: `doc:${String(doc.path || '').trim()}`,
|
|
70
73
|
path: String(doc.path || '').trim(),
|
|
71
|
-
content: String(doc.content || ''),
|
|
72
74
|
assetId: doc.assetId,
|
|
73
75
|
documentId: doc.documentId,
|
|
74
76
|
referenceNumber: doc.referenceNumber,
|
|
@@ -146,13 +148,21 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
146
148
|
sessionId: String(session.id || '').trim()
|
|
147
149
|
}))
|
|
148
150
|
.filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.sessionId.length > 0);
|
|
149
|
-
const
|
|
151
|
+
const taskEventEntries = collaborationSyncState.listWorkspaceSyncTaskEventsSnapshot(workspaceId)
|
|
152
|
+
.map((event) => ({
|
|
153
|
+
kind: 'task-event',
|
|
154
|
+
watermark: String(event.createdAt || '').trim(),
|
|
155
|
+
sortId: `task-event:${String(event.id || '').trim()}`,
|
|
156
|
+
event
|
|
157
|
+
}))
|
|
158
|
+
.filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.event.id.length > 0 && entry.event.taskId.length > 0);
|
|
159
|
+
const assetIndex = await contentSyncState.listWorkspaceSyncBinaryAssetIndex(workspaceId);
|
|
160
|
+
const assetEntries = assetIndex
|
|
150
161
|
.map((asset) => ({
|
|
151
162
|
kind: 'asset',
|
|
152
163
|
watermark: String(asset.updatedAt || '').trim(),
|
|
153
164
|
sortId: `asset:${String(asset.path || '').trim()}`,
|
|
154
165
|
path: String(asset.path || '').trim(),
|
|
155
|
-
contentBase64: String(asset.contentBase64 || ''),
|
|
156
166
|
assetId: asset.assetId,
|
|
157
167
|
kindValue: asset.kind,
|
|
158
168
|
mimeType: asset.mimeType,
|
|
@@ -163,7 +173,7 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
163
173
|
originalFilename: asset.originalFilename,
|
|
164
174
|
linkRole: asset.linkRole
|
|
165
175
|
}))
|
|
166
|
-
.filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0
|
|
176
|
+
.filter((entry) => Number.isFinite(Date.parse(entry.watermark)) && entry.path.length > 0);
|
|
167
177
|
const assetDeleteEntries = contentSyncState.listWorkspaceSyncBinaryAssetDeletesSnapshot(workspaceId)
|
|
168
178
|
.map((asset) => ({
|
|
169
179
|
kind: 'asset-delete',
|
|
@@ -188,6 +198,7 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
188
198
|
...documentReviewCommentDeleteEntries,
|
|
189
199
|
...annotatedAttachmentSessionEntries,
|
|
190
200
|
...annotatedAttachmentSessionDeleteEntries,
|
|
201
|
+
...taskEventEntries,
|
|
191
202
|
...assetEntries,
|
|
192
203
|
...assetDeleteEntries
|
|
193
204
|
];
|
|
@@ -206,11 +217,6 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
206
217
|
return String(a.sortId || '').localeCompare(String(b.sortId || ''));
|
|
207
218
|
});
|
|
208
219
|
const page = afterCursor.slice(0, pageSize);
|
|
209
|
-
const hasMore = afterCursor.length > pageSize;
|
|
210
|
-
const last = page.length > 0 ? page[page.length - 1] : null;
|
|
211
|
-
const nextCursor = encodeWorkspaceSyncCursor(last
|
|
212
|
-
? { u: last.watermark, id: String(last.sortId || '') }
|
|
213
|
-
: cursor);
|
|
214
220
|
const workspaceMembers = core.listWorkspaceUsers(workspaceId).map((member) => ({
|
|
215
221
|
userId: member.userId,
|
|
216
222
|
workspaceId,
|
|
@@ -225,77 +231,126 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
225
231
|
updatedAt: member.updatedAt || null,
|
|
226
232
|
emailVerifiedAt: member.emailVerifiedAt || null
|
|
227
233
|
}));
|
|
228
|
-
const
|
|
229
|
-
|
|
234
|
+
const indexMs = Date.now() - indexStartedAtMs;
|
|
235
|
+
const hydratedDocuments = new Map();
|
|
236
|
+
const hydratedAssets = new Map();
|
|
237
|
+
const hydrationStartedAtMs = Date.now();
|
|
238
|
+
await Promise.all(page.map(async (entry) => {
|
|
239
|
+
if (entry.kind === 'document') {
|
|
240
|
+
const payload = await contentSyncState.loadWorkspaceSyncDocumentPayload(core.getPaths().basePath, workspaceId, entry.path);
|
|
241
|
+
hydratedDocuments.set(entry.path, payload);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (entry.kind === 'asset') {
|
|
245
|
+
const payload = await contentSyncState.loadWorkspaceSyncBinaryAssetPayload(core.getPaths().basePath, workspaceId, entry.path);
|
|
246
|
+
hydratedAssets.set(entry.path, payload);
|
|
247
|
+
}
|
|
248
|
+
}));
|
|
249
|
+
const hydrationMs = Date.now() - hydrationStartedAtMs;
|
|
250
|
+
const skippedHydrationEntries = page.filter((entry) => ((entry.kind === 'document' && !hydratedDocuments.get(entry.path))
|
|
251
|
+
|| (entry.kind === 'asset' && !hydratedAssets.get(entry.path)))).length;
|
|
252
|
+
const effectivePage = page;
|
|
253
|
+
const hasMore = afterCursor.length > page.length;
|
|
254
|
+
const last = page.length > 0 ? page[page.length - 1] : null;
|
|
255
|
+
const nextCursor = encodeWorkspaceSyncCursor(last
|
|
256
|
+
? { u: last.watermark, id: String(last.sortId || '') }
|
|
257
|
+
: cursor);
|
|
258
|
+
const encodeStartedAtMs = Date.now();
|
|
259
|
+
const changes = effectivePage.flatMap((entry) => (entry.kind === 'delete'
|
|
260
|
+
? [{ op: 'delete', taskId: entry.taskId, watermark: entry.watermark }]
|
|
230
261
|
: entry.kind === 'taxonomy'
|
|
231
|
-
? {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
262
|
+
? [{
|
|
263
|
+
op: 'taxonomy-upsert',
|
|
264
|
+
taxonomies: entry.taxonomies,
|
|
265
|
+
...(entry.taxonomyState ? { taxonomyState: entry.taxonomyState } : {}),
|
|
266
|
+
updatedAt: entry.watermark,
|
|
267
|
+
watermark: entry.watermark
|
|
268
|
+
}]
|
|
238
269
|
: entry.kind === 'initiative'
|
|
239
|
-
? { op: 'initiative-upsert', initiative: entry.initiative,
|
|
270
|
+
? [{ op: 'initiative-upsert', initiative: entry.initiative, watermark: entry.watermark }]
|
|
240
271
|
: entry.kind === 'workstream'
|
|
241
|
-
? { op: 'workstream-upsert', workstream: entry.workstream,
|
|
272
|
+
? [{ op: 'workstream-upsert', workstream: entry.workstream, watermark: entry.watermark }]
|
|
242
273
|
: entry.kind === 'ai-profile'
|
|
243
|
-
? { op: 'ai-profile-upsert', profile: entry.profile,
|
|
274
|
+
? [{ op: 'ai-profile-upsert', profile: entry.profile, watermark: entry.watermark }]
|
|
244
275
|
: entry.kind === 'document-review-session'
|
|
245
|
-
? { op: 'document-review-session-upsert', session: entry.session,
|
|
276
|
+
? [{ op: 'document-review-session-upsert', session: entry.session, watermark: entry.watermark }]
|
|
246
277
|
: entry.kind === 'document-review-session-delete'
|
|
247
|
-
? { op: 'document-review-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }
|
|
278
|
+
? [{ op: 'document-review-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
248
279
|
: entry.kind === 'document-review-comment'
|
|
249
|
-
? { op: 'document-review-comment-upsert', comment: entry.comment,
|
|
280
|
+
? [{ op: 'document-review-comment-upsert', comment: entry.comment, watermark: entry.watermark }]
|
|
250
281
|
: entry.kind === 'document-review-comment-delete'
|
|
251
|
-
? { op: 'document-review-comment-delete', commentId: entry.commentId, deletedAt: entry.watermark, watermark: entry.watermark }
|
|
282
|
+
? [{ op: 'document-review-comment-delete', commentId: entry.commentId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
252
283
|
: entry.kind === 'annotated-attachment-session'
|
|
253
|
-
? { op: 'annotated-attachment-session-upsert', session: entry.session,
|
|
284
|
+
? [{ op: 'annotated-attachment-session-upsert', session: entry.session, watermark: entry.watermark }]
|
|
254
285
|
: entry.kind === 'annotated-attachment-session-delete'
|
|
255
|
-
? { op: 'annotated-attachment-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }
|
|
256
|
-
: entry.kind === '
|
|
257
|
-
? {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
286
|
+
? [{ op: 'annotated-attachment-session-delete', sessionId: entry.sessionId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
287
|
+
: entry.kind === 'task-event'
|
|
288
|
+
? [{ op: 'task-event-upsert', event: entry.event, watermark: entry.watermark }]
|
|
289
|
+
: entry.kind === 'document'
|
|
290
|
+
? (() => {
|
|
291
|
+
const payload = hydratedDocuments.get(entry.path);
|
|
292
|
+
if (!payload)
|
|
293
|
+
return [];
|
|
294
|
+
return [{
|
|
295
|
+
op: 'document-upsert',
|
|
296
|
+
path: entry.path,
|
|
297
|
+
updatedAt: entry.watermark,
|
|
298
|
+
content: payload.content,
|
|
299
|
+
assetId: payload.assetId,
|
|
300
|
+
documentId: payload.documentId,
|
|
301
|
+
referenceNumber: payload.referenceNumber,
|
|
302
|
+
version: payload.version,
|
|
303
|
+
taskId: payload.taskId,
|
|
304
|
+
logicalName: payload.logicalName,
|
|
305
|
+
caption: payload.caption,
|
|
306
|
+
originalFilename: payload.originalFilename,
|
|
307
|
+
linkRole: payload.linkRole === 'reference' ? 'reference' : 'attachment',
|
|
308
|
+
watermark: entry.watermark
|
|
309
|
+
}];
|
|
310
|
+
})()
|
|
311
|
+
: entry.kind === 'document-delete'
|
|
312
|
+
? [{ op: 'document-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
313
|
+
: entry.kind === 'asset'
|
|
314
|
+
? (() => {
|
|
315
|
+
const payload = hydratedAssets.get(entry.path);
|
|
316
|
+
if (!payload)
|
|
317
|
+
return [];
|
|
318
|
+
return [{
|
|
319
|
+
op: 'asset-upsert',
|
|
320
|
+
path: entry.path,
|
|
321
|
+
updatedAt: entry.watermark,
|
|
322
|
+
contentBase64: payload.contentBase64,
|
|
323
|
+
assetId: payload.assetId,
|
|
324
|
+
kind: payload.kind,
|
|
325
|
+
mimeType: payload.mimeType,
|
|
326
|
+
referenceNumber: payload.referenceNumber,
|
|
327
|
+
taskId: payload.taskId,
|
|
328
|
+
logicalName: payload.logicalName,
|
|
329
|
+
caption: payload.caption,
|
|
330
|
+
originalFilename: payload.originalFilename,
|
|
331
|
+
linkRole: payload.linkRole,
|
|
332
|
+
watermark: entry.watermark
|
|
333
|
+
}];
|
|
334
|
+
})()
|
|
335
|
+
: entry.kind === 'asset-delete'
|
|
336
|
+
? [{ op: 'asset-delete', path: entry.path, assetId: entry.assetId, deletedAt: entry.watermark, watermark: entry.watermark }]
|
|
337
|
+
: [{ op: 'upsert', task: entry.task, archived: Boolean(entry.archived), watermark: entry.watermark }]));
|
|
338
|
+
const encodeMs = Date.now() - encodeStartedAtMs;
|
|
295
339
|
return {
|
|
296
340
|
workspaceMembers,
|
|
297
341
|
changes,
|
|
298
342
|
nextCursor,
|
|
299
|
-
hasMore
|
|
343
|
+
hasMore,
|
|
344
|
+
diagnostics: {
|
|
345
|
+
totalEntries: all.length,
|
|
346
|
+
pageEntries: page.length,
|
|
347
|
+
skippedHydrationEntries,
|
|
348
|
+
documentIndexCount: documentIndex.length,
|
|
349
|
+
assetIndexCount: assetIndex.length,
|
|
350
|
+
indexMs,
|
|
351
|
+
hydrationMs,
|
|
352
|
+
encodeMs,
|
|
353
|
+
buildMs: Date.now() - buildStartedAtMs
|
|
354
|
+
}
|
|
300
355
|
};
|
|
301
356
|
}
|
|
@@ -14,9 +14,13 @@ function createCoreMock() {
|
|
|
14
14
|
}
|
|
15
15
|
function createContentSyncStateMock() {
|
|
16
16
|
return {
|
|
17
|
+
listWorkspaceSyncDocumentIndex: async () => [],
|
|
17
18
|
listWorkspaceSyncDocumentsSnapshot: async () => [],
|
|
19
|
+
loadWorkspaceSyncDocumentPayload: async () => null,
|
|
18
20
|
listWorkspaceSyncDocumentDeletesSnapshot: () => [],
|
|
21
|
+
listWorkspaceSyncBinaryAssetIndex: async () => [],
|
|
19
22
|
listWorkspaceSyncBinaryAssetsSnapshot: async () => [],
|
|
23
|
+
loadWorkspaceSyncBinaryAssetPayload: async () => null,
|
|
20
24
|
listWorkspaceSyncBinaryAssetDeletesSnapshot: () => [],
|
|
21
25
|
};
|
|
22
26
|
}
|
|
@@ -25,6 +29,7 @@ function createCollaborationSyncStateMock() {
|
|
|
25
29
|
listWorkspaceSyncDocumentReviewSessionsSnapshot: () => [],
|
|
26
30
|
listWorkspaceSyncDocumentReviewCommentsSnapshot: () => [],
|
|
27
31
|
listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: () => [],
|
|
32
|
+
listWorkspaceSyncTaskEventsSnapshot: () => [],
|
|
28
33
|
};
|
|
29
34
|
}
|
|
30
35
|
function encodeCursor(cursor) {
|
|
@@ -163,4 +168,179 @@ describe('workspacePullFeed', () => {
|
|
|
163
168
|
})
|
|
164
169
|
]);
|
|
165
170
|
});
|
|
171
|
+
it('hydrates document and asset payloads only for entries included in the current page', async () => {
|
|
172
|
+
const core = createCoreMock();
|
|
173
|
+
const content = createContentSyncStateMock();
|
|
174
|
+
const loadedDocs = [];
|
|
175
|
+
const loadedAssets = [];
|
|
176
|
+
content.listWorkspaceSyncDocumentIndex = async () => [
|
|
177
|
+
{
|
|
178
|
+
path: 'docs/a.md',
|
|
179
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
180
|
+
assetId: 'asset-doc-a',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
path: 'docs/z.md',
|
|
184
|
+
updatedAt: '2026-04-12T10:00:00.000Z',
|
|
185
|
+
assetId: 'asset-doc-z',
|
|
186
|
+
}
|
|
187
|
+
];
|
|
188
|
+
content.loadWorkspaceSyncDocumentPayload = async (_basePath, _workspaceId, relativePath) => {
|
|
189
|
+
loadedDocs.push(relativePath);
|
|
190
|
+
return {
|
|
191
|
+
path: relativePath,
|
|
192
|
+
updatedAt: relativePath === 'docs/a.md' ? '2026-04-10T10:00:00.000Z' : '2026-04-12T10:00:00.000Z',
|
|
193
|
+
content: `content:${relativePath}`
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
content.listWorkspaceSyncBinaryAssetIndex = async () => [
|
|
197
|
+
{
|
|
198
|
+
path: 'workspaces/ws/assets/images/a.png',
|
|
199
|
+
updatedAt: '2026-04-11T10:00:00.000Z',
|
|
200
|
+
assetId: 'asset-image-a',
|
|
201
|
+
kind: 'image',
|
|
202
|
+
mimeType: 'image/png'
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
content.loadWorkspaceSyncBinaryAssetPayload = async (_basePath, _workspaceId, assetPath) => {
|
|
206
|
+
loadedAssets.push(assetPath);
|
|
207
|
+
return {
|
|
208
|
+
path: assetPath,
|
|
209
|
+
updatedAt: '2026-04-11T10:00:00.000Z',
|
|
210
|
+
contentBase64: 'Zm9v',
|
|
211
|
+
kind: 'image',
|
|
212
|
+
mimeType: 'image/png'
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
const result = await buildWorkspacePullFeed({
|
|
216
|
+
core,
|
|
217
|
+
workspaceId: 'workspace-3',
|
|
218
|
+
cursor: null,
|
|
219
|
+
pageSize: 2,
|
|
220
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
221
|
+
contentSyncState: content,
|
|
222
|
+
collaborationSyncState: createCollaborationSyncStateMock(),
|
|
223
|
+
});
|
|
224
|
+
expect(result.changes).toHaveLength(2);
|
|
225
|
+
expect(result.changes[0]).toEqual(expect.objectContaining({
|
|
226
|
+
op: 'document-upsert',
|
|
227
|
+
path: 'docs/a.md',
|
|
228
|
+
content: 'content:docs/a.md'
|
|
229
|
+
}));
|
|
230
|
+
expect(result.changes[1]).toEqual(expect.objectContaining({
|
|
231
|
+
op: 'asset-upsert',
|
|
232
|
+
path: 'workspaces/ws/assets/images/a.png',
|
|
233
|
+
contentBase64: 'Zm9v'
|
|
234
|
+
}));
|
|
235
|
+
expect(loadedDocs).toEqual(['docs/a.md']);
|
|
236
|
+
expect(loadedAssets).toEqual(['workspaces/ws/assets/images/a.png']);
|
|
237
|
+
});
|
|
238
|
+
it('skips hydration misses without blocking later feed progress', async () => {
|
|
239
|
+
const core = createCoreMock();
|
|
240
|
+
const content = createContentSyncStateMock();
|
|
241
|
+
core.listTasksForSync = () => [
|
|
242
|
+
{
|
|
243
|
+
archived: false,
|
|
244
|
+
task: {
|
|
245
|
+
id: 'task-after-miss',
|
|
246
|
+
title: 'Task after miss',
|
|
247
|
+
status: 'task',
|
|
248
|
+
category: 'general',
|
|
249
|
+
type: 'task',
|
|
250
|
+
createdAt: '2026-04-12T10:00:00.000Z',
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
];
|
|
254
|
+
content.listWorkspaceSyncDocumentIndex = async () => [
|
|
255
|
+
{
|
|
256
|
+
path: 'docs/a.md',
|
|
257
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
258
|
+
assetId: 'asset-doc-a',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
path: 'docs/b.md',
|
|
262
|
+
updatedAt: '2026-04-11T10:00:00.000Z',
|
|
263
|
+
assetId: 'asset-doc-b',
|
|
264
|
+
}
|
|
265
|
+
];
|
|
266
|
+
content.loadWorkspaceSyncDocumentPayload = async (_basePath, _workspaceId, relativePath) => (relativePath === 'docs/a.md'
|
|
267
|
+
? {
|
|
268
|
+
path: relativePath,
|
|
269
|
+
updatedAt: '2026-04-10T10:00:00.000Z',
|
|
270
|
+
content: 'content:docs/a.md'
|
|
271
|
+
}
|
|
272
|
+
: null);
|
|
273
|
+
const result = await buildWorkspacePullFeed({
|
|
274
|
+
core,
|
|
275
|
+
workspaceId: 'workspace-4',
|
|
276
|
+
cursor: null,
|
|
277
|
+
pageSize: 3,
|
|
278
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
279
|
+
contentSyncState: content,
|
|
280
|
+
collaborationSyncState: createCollaborationSyncStateMock(),
|
|
281
|
+
});
|
|
282
|
+
expect(result.changes).toEqual([
|
|
283
|
+
expect.objectContaining({
|
|
284
|
+
op: 'document-upsert',
|
|
285
|
+
path: 'docs/a.md',
|
|
286
|
+
content: 'content:docs/a.md'
|
|
287
|
+
}),
|
|
288
|
+
expect.objectContaining({
|
|
289
|
+
op: 'upsert',
|
|
290
|
+
task: expect.objectContaining({
|
|
291
|
+
id: 'task-after-miss'
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
]);
|
|
295
|
+
expect(result.nextCursor).toBe('2026-04-12T10:00:00.000Z|task-after-miss');
|
|
296
|
+
expect(result.hasMore).toBe(false);
|
|
297
|
+
expect(result.diagnostics.skippedHydrationEntries).toBe(1);
|
|
298
|
+
});
|
|
299
|
+
it('includes task-event upserts in the pull feed ordered by event watermark', async () => {
|
|
300
|
+
const core = createCoreMock();
|
|
301
|
+
const collaboration = createCollaborationSyncStateMock();
|
|
302
|
+
collaboration.listWorkspaceSyncTaskEventsSnapshot = () => [
|
|
303
|
+
{
|
|
304
|
+
id: 'task-event-2',
|
|
305
|
+
taskId: 'task-2',
|
|
306
|
+
workspaceId: 'workspace-5',
|
|
307
|
+
action: 'task-archived',
|
|
308
|
+
actor: 'ai-profile-2',
|
|
309
|
+
actorType: 'ai',
|
|
310
|
+
createdAt: '2026-04-12T10:06:00.000Z',
|
|
311
|
+
details: {}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: 'task-event-1',
|
|
315
|
+
taskId: 'task-1',
|
|
316
|
+
workspaceId: 'workspace-5',
|
|
317
|
+
action: 'task-status-changed',
|
|
318
|
+
actor: 'ai-profile-1',
|
|
319
|
+
actorType: 'ai',
|
|
320
|
+
createdAt: '2026-04-12T10:05:00.000Z',
|
|
321
|
+
details: {}
|
|
322
|
+
}
|
|
323
|
+
];
|
|
324
|
+
const result = await buildWorkspacePullFeed({
|
|
325
|
+
core,
|
|
326
|
+
workspaceId: 'workspace-5',
|
|
327
|
+
cursor: null,
|
|
328
|
+
pageSize: 5,
|
|
329
|
+
encodeWorkspaceSyncCursor: encodeCursor,
|
|
330
|
+
contentSyncState: createContentSyncStateMock(),
|
|
331
|
+
collaborationSyncState: collaboration,
|
|
332
|
+
});
|
|
333
|
+
expect(result.changes).toEqual([
|
|
334
|
+
expect.objectContaining({
|
|
335
|
+
op: 'task-event-upsert',
|
|
336
|
+
watermark: '2026-04-12T10:05:00.000Z',
|
|
337
|
+
event: expect.objectContaining({ id: 'task-event-1' })
|
|
338
|
+
}),
|
|
339
|
+
expect.objectContaining({
|
|
340
|
+
op: 'task-event-upsert',
|
|
341
|
+
watermark: '2026-04-12T10:06:00.000Z',
|
|
342
|
+
event: expect.objectContaining({ id: 'task-event-2' })
|
|
343
|
+
})
|
|
344
|
+
]);
|
|
345
|
+
});
|
|
166
346
|
});
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import type { InitiativeItem, TaskItem, WorkstreamItem } from '../types';
|
|
1
|
+
import type { InitiativeItem, TaskEvent, TaskItem, WorkstreamItem } from '../types';
|
|
2
2
|
import type { DocumentReviewCommentRecord, DocumentReviewSessionStatus } from '../documentReviews/types.js';
|
|
3
3
|
import type { AnnotatedAttachmentAnnotation } from '../annotatedAttachments/types.js';
|
|
4
4
|
import type { WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
|
|
5
5
|
import { type AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
6
6
|
import { type TaskSyncPayload } from './taskSyncPayload.js';
|
|
7
7
|
export type WorkspaceSyncTaskSnapshot = TaskSyncPayload;
|
|
8
|
+
export interface WorkspaceSyncTaskEventSnapshot {
|
|
9
|
+
id: string;
|
|
10
|
+
taskId: string;
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
action: string;
|
|
13
|
+
actor: string;
|
|
14
|
+
actorType: 'system' | 'human' | 'ai';
|
|
15
|
+
details?: TaskEvent['details'];
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
8
18
|
export interface WorkspaceSyncAiProfileSnapshot {
|
|
9
19
|
id: string;
|
|
10
20
|
workspaceId: string;
|
|
@@ -13,6 +23,10 @@ export interface WorkspaceSyncAiProfileSnapshot {
|
|
|
13
23
|
username: string;
|
|
14
24
|
icon: string;
|
|
15
25
|
color: string;
|
|
26
|
+
description?: string | null;
|
|
27
|
+
role?: string | null;
|
|
28
|
+
provider?: string | null;
|
|
29
|
+
model?: string | null;
|
|
16
30
|
surfaceType?: AiProfileSurfaceType | null;
|
|
17
31
|
providerMetadata?: Record<string, unknown> | null;
|
|
18
32
|
createdAt: string;
|
|
@@ -177,6 +191,9 @@ export type WorkspaceSyncChange = {
|
|
|
177
191
|
op: 'annotated-attachment-session-delete';
|
|
178
192
|
sessionId: string;
|
|
179
193
|
deletedAt?: string;
|
|
194
|
+
} | {
|
|
195
|
+
op: 'task-event-upsert';
|
|
196
|
+
event: WorkspaceSyncTaskEventSnapshot;
|
|
180
197
|
};
|
|
181
198
|
export interface WorkspaceSyncPayload {
|
|
182
199
|
changes: WorkspaceSyncChange[];
|
|
@@ -203,6 +220,7 @@ export interface WorkspaceSyncPayload {
|
|
|
203
220
|
currentAnnotatedAttachmentSessionIds: Set<string>;
|
|
204
221
|
deleteAnnotatedAttachmentSessionIds: Set<string>;
|
|
205
222
|
currentAnnotatedAttachmentSessionWatermarks: Map<string, string>;
|
|
223
|
+
currentTaskEventWatermarks: Map<string, string>;
|
|
206
224
|
/** Watermarks for every task upsert included in this payload (id → updatedAt|createdAt). */
|
|
207
225
|
pushedWatermarks: Map<string, string>;
|
|
208
226
|
}
|
|
@@ -250,6 +268,7 @@ export declare function buildWorkspaceSyncPayloadModel(input: {
|
|
|
250
268
|
documentReviewSessions?: WorkspaceSyncDocumentReviewSessionSnapshot[];
|
|
251
269
|
documentReviewComments?: WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
252
270
|
annotatedAttachmentSessions?: WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
|
|
271
|
+
taskEvents?: WorkspaceSyncTaskEventSnapshot[];
|
|
253
272
|
lastPushedInitiativeIds?: Set<string>;
|
|
254
273
|
lastPushedInitiativeWatermarks?: Map<string, string>;
|
|
255
274
|
lastPushedWorkstreamIds?: Set<string>;
|
|
@@ -266,6 +285,7 @@ export declare function buildWorkspaceSyncPayloadModel(input: {
|
|
|
266
285
|
lastPushedDocumentReviewCommentWatermarks?: Map<string, string>;
|
|
267
286
|
lastPushedAnnotatedAttachmentSessionIds?: Set<string>;
|
|
268
287
|
lastPushedAnnotatedAttachmentSessionWatermarks?: Map<string, string>;
|
|
288
|
+
lastPushedTaskEventWatermarks?: Map<string, string>;
|
|
269
289
|
/**
|
|
270
290
|
* Watermark (updatedAt|createdAt) of each task as of its last successful push.
|
|
271
291
|
* When provided, only tasks whose watermark differs from this map are included
|
|
@@ -289,4 +309,5 @@ export declare function buildWorkspaceSyncSignatureModel(input: {
|
|
|
289
309
|
documentReviewSessions?: WorkspaceSyncDocumentReviewSessionSnapshot[];
|
|
290
310
|
documentReviewComments?: WorkspaceSyncDocumentReviewCommentSnapshot[];
|
|
291
311
|
annotatedAttachmentSessions?: WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
|
|
312
|
+
taskEvents?: WorkspaceSyncTaskEventSnapshot[];
|
|
292
313
|
}): string;
|
|
@@ -3,6 +3,16 @@ import { serializeTaskForSync } from './taskSyncPayload.js';
|
|
|
3
3
|
export function toWorkspaceSyncTaskSnapshot(task) {
|
|
4
4
|
return serializeTaskForSync(task);
|
|
5
5
|
}
|
|
6
|
+
function sortTaskEventsForSync(taskEvents) {
|
|
7
|
+
return [...taskEvents].sort((left, right) => {
|
|
8
|
+
const leftCreatedAt = String(left?.createdAt || '').trim();
|
|
9
|
+
const rightCreatedAt = String(right?.createdAt || '').trim();
|
|
10
|
+
const watermarkDelta = leftCreatedAt.localeCompare(rightCreatedAt);
|
|
11
|
+
if (watermarkDelta !== 0)
|
|
12
|
+
return watermarkDelta;
|
|
13
|
+
return String(left?.id || '').trim().localeCompare(String(right?.id || '').trim());
|
|
14
|
+
});
|
|
15
|
+
}
|
|
6
16
|
function sortTasksForSync(tasks) {
|
|
7
17
|
return [...tasks].sort((left, right) => {
|
|
8
18
|
const leftId = String(left?.id || '').trim();
|
|
@@ -175,6 +185,10 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
175
185
|
username: String(profile.username || '').trim(),
|
|
176
186
|
icon: String(profile.icon || 'Bot').trim() || 'Bot',
|
|
177
187
|
color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
|
|
188
|
+
description: typeof profile.description === 'string' ? profile.description.trim() || null : null,
|
|
189
|
+
role: typeof profile.role === 'string' ? profile.role.trim() || null : null,
|
|
190
|
+
provider: typeof profile.provider === 'string' ? profile.provider.trim() || null : null,
|
|
191
|
+
model: typeof profile.model === 'string' ? profile.model.trim() || null : null,
|
|
178
192
|
surfaceType: parseAiProfileSurfaceType(profile.surfaceType),
|
|
179
193
|
providerMetadata: profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
|
|
180
194
|
? profile.providerMetadata
|
|
@@ -391,6 +405,40 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
391
405
|
return true;
|
|
392
406
|
return previousAnnotatedAttachmentSessionWatermarks.get(change.session.id) !== change.session.updatedAt;
|
|
393
407
|
});
|
|
408
|
+
const taskEvents = sortTaskEventsForSync(Array.isArray(input.taskEvents) ? input.taskEvents : []);
|
|
409
|
+
const currentTaskEventWatermarks = new Map(taskEvents
|
|
410
|
+
.map((event) => [String(event?.id || '').trim(), String(event?.createdAt || '').trim()])
|
|
411
|
+
.filter(([id]) => id.length > 0));
|
|
412
|
+
const previousTaskEventWatermarks = input.lastPushedTaskEventWatermarks;
|
|
413
|
+
const shouldSendAllTaskEvents = !previousTaskEventWatermarks || previousTaskEventWatermarks.size === 0;
|
|
414
|
+
const taskEventUpsertChanges = taskEvents
|
|
415
|
+
.map((event) => ({
|
|
416
|
+
op: 'task-event-upsert',
|
|
417
|
+
event: {
|
|
418
|
+
id: String(event.id || '').trim(),
|
|
419
|
+
taskId: String(event.taskId || '').trim(),
|
|
420
|
+
workspaceId: String(event.workspaceId || '').trim(),
|
|
421
|
+
action: String(event.action || '').trim(),
|
|
422
|
+
actor: String(event.actor || 'system').trim() || 'system',
|
|
423
|
+
actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
|
|
424
|
+
? event.actorType
|
|
425
|
+
: 'system',
|
|
426
|
+
details: event.details && typeof event.details === 'object' && !Array.isArray(event.details)
|
|
427
|
+
? event.details
|
|
428
|
+
: {},
|
|
429
|
+
createdAt: String(event.createdAt || '').trim(),
|
|
430
|
+
}
|
|
431
|
+
}))
|
|
432
|
+
.filter((change) => (change.event.id.length > 0
|
|
433
|
+
&& change.event.taskId.length > 0
|
|
434
|
+
&& change.event.workspaceId.length > 0
|
|
435
|
+
&& change.event.action.length > 0
|
|
436
|
+
&& Number.isFinite(Date.parse(change.event.createdAt))))
|
|
437
|
+
.filter((change) => {
|
|
438
|
+
if (shouldSendAllTaskEvents)
|
|
439
|
+
return true;
|
|
440
|
+
return previousTaskEventWatermarks.get(change.event.id) !== change.event.createdAt;
|
|
441
|
+
});
|
|
394
442
|
const changes = [
|
|
395
443
|
...initiativeUpsertChanges,
|
|
396
444
|
...workstreamUpsertChanges,
|
|
@@ -438,6 +486,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
438
486
|
sessionId,
|
|
439
487
|
deletedAt: payloadBuiltAt
|
|
440
488
|
})),
|
|
489
|
+
...taskEventUpsertChanges,
|
|
441
490
|
...activeChanges,
|
|
442
491
|
...archivedChanges,
|
|
443
492
|
...Array.from(deleteTaskIds).map((taskId) => ({ op: 'delete', taskId }))
|
|
@@ -467,6 +516,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
|
|
|
467
516
|
currentAnnotatedAttachmentSessionIds,
|
|
468
517
|
deleteAnnotatedAttachmentSessionIds,
|
|
469
518
|
currentAnnotatedAttachmentSessionWatermarks,
|
|
519
|
+
currentTaskEventWatermarks,
|
|
470
520
|
pushedWatermarks
|
|
471
521
|
};
|
|
472
522
|
}
|
|
@@ -516,6 +566,21 @@ export function buildWorkspaceSyncSignatureModel(input) {
|
|
|
516
566
|
.filter((profile) => profile.id.length > 0)
|
|
517
567
|
.sort((a, b) => a.id.localeCompare(b.id))
|
|
518
568
|
: [],
|
|
569
|
+
taskEvents: Array.isArray(input.taskEvents)
|
|
570
|
+
? input.taskEvents
|
|
571
|
+
.map((event) => ({
|
|
572
|
+
id: String(event.id || '').trim(),
|
|
573
|
+
taskId: String(event.taskId || '').trim(),
|
|
574
|
+
action: String(event.action || '').trim(),
|
|
575
|
+
actor: String(event.actor || '').trim(),
|
|
576
|
+
actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
|
|
577
|
+
? event.actorType
|
|
578
|
+
: 'system',
|
|
579
|
+
createdAt: String(event.createdAt || '').trim()
|
|
580
|
+
}))
|
|
581
|
+
.filter((event) => event.id.length > 0 && event.taskId.length > 0)
|
|
582
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
583
|
+
: [],
|
|
519
584
|
documents: Array.isArray(input.documents)
|
|
520
585
|
? input.documents
|
|
521
586
|
.map((doc) => ({
|