@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,10 +1,285 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
|
-
import {
|
|
4
|
+
import { normalizeBinaryAssetDeleteSyncPayload, normalizeBinaryAssetSyncPayload, normalizeDocumentDeleteSyncPayload, normalizeDocumentSyncPayload, } from './contentSyncPayload.js';
|
|
5
|
+
import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload, normalizeDeleteSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, } from './collaborationSyncPayload.js';
|
|
6
|
+
import { normalizeInitiativeSyncPayload, normalizeTaxonomySyncPayload, normalizeWorkstreamSyncPayload, } from './planningSyncPayload.js';
|
|
7
|
+
import { normalizeTaskSyncPayload, taskSyncPayloadToCoreTask, } from './taskSyncPayload.js';
|
|
8
|
+
import { compareWorkspaceMutationOrdering, getTaskMutationTieBreak, getTaskMutationWatermark } from './workspaceSyncSurface.js';
|
|
9
|
+
function createIncomingApplyModel() {
|
|
10
|
+
return {
|
|
11
|
+
taskMutations: {
|
|
12
|
+
upserts: [],
|
|
13
|
+
deletes: new Map(),
|
|
14
|
+
},
|
|
15
|
+
planning: {
|
|
16
|
+
incomingTaxonomies: null,
|
|
17
|
+
incomingTaxonomyState: null,
|
|
18
|
+
incomingTaxonomiesUpdatedAt: '',
|
|
19
|
+
incomingInitiatives: new Map(),
|
|
20
|
+
incomingWorkstreams: new Map(),
|
|
21
|
+
},
|
|
22
|
+
profiles: {
|
|
23
|
+
incomingAiProfiles: new Map(),
|
|
24
|
+
},
|
|
25
|
+
content: {
|
|
26
|
+
incomingDocuments: [],
|
|
27
|
+
incomingDocumentDeletes: new Map(),
|
|
28
|
+
incomingAssets: [],
|
|
29
|
+
incomingAssetDeletes: new Map(),
|
|
30
|
+
},
|
|
31
|
+
collaboration: {
|
|
32
|
+
incomingDocumentReviewSessions: new Map(),
|
|
33
|
+
incomingDocumentReviewSessionDeletes: new Map(),
|
|
34
|
+
incomingDocumentReviewComments: new Map(),
|
|
35
|
+
incomingDocumentReviewCommentDeletes: new Map(),
|
|
36
|
+
incomingAnnotatedAttachmentSessions: new Map(),
|
|
37
|
+
incomingAnnotatedAttachmentSessionDeletes: new Map(),
|
|
38
|
+
},
|
|
39
|
+
taskActivity: {
|
|
40
|
+
incomingTaskEvents: new Map(),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
5
44
|
export function resolveSyncConflictOrdering(incomingWatermark, existingWatermark, incomingTieBreak, existingTieBreak) {
|
|
6
45
|
return compareWorkspaceMutationOrdering(String(incomingWatermark || '').trim(), String(existingWatermark || '').trim(), String(incomingTieBreak || '').trim(), String(existingTieBreak || '').trim());
|
|
7
46
|
}
|
|
47
|
+
function shouldSkipTaskClassification(op) {
|
|
48
|
+
return (op === 'delete'
|
|
49
|
+
|| op === 'initiative-upsert'
|
|
50
|
+
|| op === 'workstream-upsert'
|
|
51
|
+
|| op === 'document-upsert'
|
|
52
|
+
|| op === 'document-delete'
|
|
53
|
+
|| op === 'asset-upsert'
|
|
54
|
+
|| op === 'asset-delete'
|
|
55
|
+
|| op === 'document-review-session-upsert'
|
|
56
|
+
|| op === 'document-review-session-delete'
|
|
57
|
+
|| op === 'document-review-comment-upsert'
|
|
58
|
+
|| op === 'document-review-comment-delete'
|
|
59
|
+
|| op === 'annotated-attachment-session-upsert'
|
|
60
|
+
|| op === 'annotated-attachment-session-delete');
|
|
61
|
+
}
|
|
62
|
+
function recordTaskReferenceNumberMismatch(input) {
|
|
63
|
+
const { core, workspaceId, task, recordSyncDiagnostic, } = input;
|
|
64
|
+
const taskId = String(task?.id || '').trim();
|
|
65
|
+
const incomingReferenceNumber = Number.isFinite(Number(task?.referenceNumber))
|
|
66
|
+
? Math.max(1, Math.floor(Number(task?.referenceNumber)))
|
|
67
|
+
: null;
|
|
68
|
+
if (!incomingReferenceNumber)
|
|
69
|
+
return false;
|
|
70
|
+
const existingTask = core.resolveTaskIdentifier(`T-${incomingReferenceNumber}`, workspaceId);
|
|
71
|
+
if (!existingTask || existingTask.id === taskId)
|
|
72
|
+
return false;
|
|
73
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Detected task reference number mismatch during normal sync.', {
|
|
74
|
+
source: 'tasks.referenceNumber.mismatch',
|
|
75
|
+
taskId: taskId || null,
|
|
76
|
+
taskTitle: typeof task?.title === 'string' ? task.title.trim() || null : null,
|
|
77
|
+
existingTaskId: existingTask.id,
|
|
78
|
+
existingTaskTitle: existingTask.title || null,
|
|
79
|
+
referenceLabel: `T-${incomingReferenceNumber}`,
|
|
80
|
+
existingReferenceNumber: existingTask.referenceNumber ?? incomingReferenceNumber,
|
|
81
|
+
incomingReferenceNumber
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
export function classifyWorkspaceSyncChanges(input) {
|
|
86
|
+
const { workspaceId, incomingChanges, normalizeWorkspaceSyncDocumentPath, normalizeTaskAssetPath, normalizeIncomingAiProfileSurfaceType, normalizeTaskEventSyncPayload, recordSyncDiagnostic, } = input;
|
|
87
|
+
const model = createIncomingApplyModel();
|
|
88
|
+
for (const change of incomingChanges) {
|
|
89
|
+
const raw = change;
|
|
90
|
+
const op = String(raw.op || '').trim().toLowerCase();
|
|
91
|
+
if (op === 'taxonomy-upsert') {
|
|
92
|
+
const taxonomy = normalizeTaxonomySyncPayload(raw);
|
|
93
|
+
if (taxonomy?.taxonomyState)
|
|
94
|
+
model.planning.incomingTaxonomyState = taxonomy.taxonomyState;
|
|
95
|
+
if (Array.isArray(taxonomy?.taxonomies))
|
|
96
|
+
model.planning.incomingTaxonomies = taxonomy.taxonomies;
|
|
97
|
+
if (taxonomy?.updatedAt)
|
|
98
|
+
model.planning.incomingTaxonomiesUpdatedAt = taxonomy.updatedAt;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (op === 'initiative-upsert') {
|
|
102
|
+
const initiative = normalizeInitiativeSyncPayload(raw.initiative, String(raw.updatedAt || raw.watermark || '').trim());
|
|
103
|
+
if (initiative)
|
|
104
|
+
model.planning.incomingInitiatives.set(initiative.id, initiative);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (op === 'workstream-upsert') {
|
|
108
|
+
const workstream = normalizeWorkstreamSyncPayload(raw.workstream, String(raw.updatedAt || raw.watermark || '').trim());
|
|
109
|
+
if (workstream)
|
|
110
|
+
model.planning.incomingWorkstreams.set(workstream.id, workstream);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (op === 'ai-profile-upsert') {
|
|
114
|
+
const profile = normalizeAiProfileSyncPayload(raw.profile, workspaceId, normalizeIncomingAiProfileSurfaceType, 'apply.aiProfileUpsert');
|
|
115
|
+
const profileId = String(profile?.id || '').trim();
|
|
116
|
+
if (profileId && profile) {
|
|
117
|
+
const profileWorkspaceId = String(profile.workspaceId || '').trim();
|
|
118
|
+
if (profileWorkspaceId && profileWorkspaceId !== workspaceId) {
|
|
119
|
+
recordSyncDiagnostic(workspaceId, 'apply', `Rejected ai-profile-upsert for profile ${profileId}: workspace mismatch (expected ${workspaceId}, got ${profileWorkspaceId}).`, {
|
|
120
|
+
source: 'apply.aiProfileWorkspaceMismatch',
|
|
121
|
+
workspaceId,
|
|
122
|
+
profileId,
|
|
123
|
+
expectedWorkspaceId: workspaceId,
|
|
124
|
+
receivedWorkspaceId: profileWorkspaceId
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
model.profiles.incomingAiProfiles.set(profileId, {
|
|
129
|
+
...profile,
|
|
130
|
+
workspaceId,
|
|
131
|
+
createdAt: profile.createdAt || String(raw.updatedAt || raw.watermark || '').trim(),
|
|
132
|
+
updatedAt: profile.updatedAt || String(raw.updatedAt || raw.watermark || '').trim(),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (op === 'document-upsert') {
|
|
138
|
+
const document = normalizeDocumentSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
|
|
139
|
+
if (document)
|
|
140
|
+
model.content.incomingDocuments.push(document);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (op === 'document-delete') {
|
|
144
|
+
const deletedDocument = normalizeDocumentDeleteSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
|
|
145
|
+
if (deletedDocument)
|
|
146
|
+
model.content.incomingDocumentDeletes.set(deletedDocument.path, deletedDocument.deletedAt);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (op === 'asset-upsert') {
|
|
150
|
+
const asset = normalizeBinaryAssetSyncPayload(raw, normalizeTaskAssetPath);
|
|
151
|
+
if (asset)
|
|
152
|
+
model.content.incomingAssets.push(asset);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (op === 'asset-delete') {
|
|
156
|
+
const deletedAsset = normalizeBinaryAssetDeleteSyncPayload(raw, normalizeTaskAssetPath);
|
|
157
|
+
if (deletedAsset)
|
|
158
|
+
model.content.incomingAssetDeletes.set(deletedAsset.path, deletedAsset.deletedAt);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (op === 'document-review-session-upsert') {
|
|
162
|
+
const session = normalizeDocumentReviewSessionSyncPayload(raw.session, String(raw.updatedAt || raw.watermark || '').trim());
|
|
163
|
+
if (session)
|
|
164
|
+
model.collaboration.incomingDocumentReviewSessions.set(session.id, session);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (op === 'document-review-session-delete') {
|
|
168
|
+
const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
|
|
169
|
+
if (deletedSession)
|
|
170
|
+
model.collaboration.incomingDocumentReviewSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (op === 'document-review-comment-upsert') {
|
|
174
|
+
const comment = normalizeDocumentReviewCommentSyncPayload(raw.comment, { sourceWatermark: String(raw.updatedAt || raw.watermark || '').trim() });
|
|
175
|
+
if (comment)
|
|
176
|
+
model.collaboration.incomingDocumentReviewComments.set(comment.id, comment);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (op === 'document-review-comment-delete') {
|
|
180
|
+
const deletedComment = normalizeDeleteSyncPayload(raw, 'commentId');
|
|
181
|
+
if (deletedComment)
|
|
182
|
+
model.collaboration.incomingDocumentReviewCommentDeletes.set(deletedComment.id, deletedComment.deletedAt);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (op === 'annotated-attachment-session-upsert') {
|
|
186
|
+
const session = normalizeAnnotatedAttachmentSessionSyncPayload(raw.session, workspaceId, String(raw.updatedAt || raw.watermark || '').trim());
|
|
187
|
+
if (session)
|
|
188
|
+
model.collaboration.incomingAnnotatedAttachmentSessions.set(session.id, session);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (op === 'annotated-attachment-session-delete') {
|
|
192
|
+
const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
|
|
193
|
+
if (deletedSession)
|
|
194
|
+
model.collaboration.incomingAnnotatedAttachmentSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (op === 'task-event-upsert') {
|
|
198
|
+
const event = normalizeTaskEventSyncPayload(raw.event, workspaceId, String(raw.updatedAt || raw.watermark || '').trim());
|
|
199
|
+
if (event)
|
|
200
|
+
model.taskActivity.incomingTaskEvents.set(event.id, event);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (op !== 'delete')
|
|
204
|
+
continue;
|
|
205
|
+
const task = normalizeTaskSyncPayload(raw.task);
|
|
206
|
+
const deleteTaskId = String(raw.taskId || raw.id || task?.id || '').trim();
|
|
207
|
+
if (deleteTaskId) {
|
|
208
|
+
model.taskMutations.deletes.set(deleteTaskId, String(raw.deletedAt || raw.watermark || new Date().toISOString()).trim());
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const change of incomingChanges) {
|
|
212
|
+
const raw = change;
|
|
213
|
+
const op = String(raw.op || '').trim().toLowerCase();
|
|
214
|
+
if (shouldSkipTaskClassification(op))
|
|
215
|
+
continue;
|
|
216
|
+
const task = normalizeTaskSyncPayload(raw.task);
|
|
217
|
+
if (!task)
|
|
218
|
+
continue;
|
|
219
|
+
model.taskMutations.upserts.push({
|
|
220
|
+
archived: raw.archived === true || task.isArchived === true,
|
|
221
|
+
task,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return model;
|
|
225
|
+
}
|
|
226
|
+
export function prepareTaskSyncSnapshotApply(input) {
|
|
227
|
+
const { core, workspaceId, taskMutations, recordSyncDiagnostic, } = input;
|
|
228
|
+
const prepared = {
|
|
229
|
+
tasks: [],
|
|
230
|
+
archived: [],
|
|
231
|
+
};
|
|
232
|
+
for (const mutation of taskMutations.upserts) {
|
|
233
|
+
const task = mutation.task;
|
|
234
|
+
const taskId = String(task.id || '').trim();
|
|
235
|
+
recordTaskReferenceNumberMismatch({
|
|
236
|
+
core,
|
|
237
|
+
workspaceId,
|
|
238
|
+
task: task,
|
|
239
|
+
recordSyncDiagnostic,
|
|
240
|
+
});
|
|
241
|
+
const deleteWatermark = taskId ? String(taskMutations.deletes.get(taskId) || '').trim() : '';
|
|
242
|
+
if (taskId && deleteWatermark) {
|
|
243
|
+
const compareAgainstDelete = resolveSyncConflictOrdering(deleteWatermark, String(task.updatedAt || task.createdAt || '').trim(), `delete:${taskId}`, getTaskMutationTieBreak(task, mutation.archived));
|
|
244
|
+
if (compareAgainstDelete >= 0)
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (mutation.archived)
|
|
248
|
+
prepared.archived.push(task);
|
|
249
|
+
else
|
|
250
|
+
prepared.tasks.push(task);
|
|
251
|
+
}
|
|
252
|
+
return prepared;
|
|
253
|
+
}
|
|
254
|
+
export function applyTaskSyncDeletes(input) {
|
|
255
|
+
const { core, workspaceId, taskMutations, recordSyncDiagnostic, } = input;
|
|
256
|
+
for (const [taskId, deleteWatermark] of taskMutations.deletes.entries()) {
|
|
257
|
+
try {
|
|
258
|
+
const existingTask = core.getTask(taskId, workspaceId);
|
|
259
|
+
if (existingTask) {
|
|
260
|
+
const compareAgainstExisting = resolveSyncConflictOrdering(deleteWatermark, getTaskMutationWatermark(existingTask), `delete:${taskId}`, getTaskMutationTieBreak(existingTask, Boolean(existingTask.isArchived)));
|
|
261
|
+
if (compareAgainstExisting < 0)
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
core.deleteTaskForSync(taskId, workspaceId, { persistBackup: false });
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete task during sync apply.', {
|
|
268
|
+
source: 'apply.deleteTask',
|
|
269
|
+
taskId,
|
|
270
|
+
error: String(error?.message || error || '')
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return taskMutations.deletes.size;
|
|
275
|
+
}
|
|
276
|
+
export function applyPreparedTaskSyncSnapshot(input) {
|
|
277
|
+
const { core, workspaceId, prepared, } = input;
|
|
278
|
+
return core.applyWorkspaceSyncSnapshot({
|
|
279
|
+
tasks: prepared.tasks.map(taskSyncPayloadToCoreTask),
|
|
280
|
+
archived: prepared.archived.map(taskSyncPayloadToCoreTask)
|
|
281
|
+
}, workspaceId);
|
|
282
|
+
}
|
|
8
283
|
export function applyPlanningSyncChanges(input) {
|
|
9
284
|
const { core, workspaceId, incomingInitiatives, incomingWorkstreams, incomingTaxonomies, incomingTaxonomyState, incomingTaxonomiesUpdatedAt, workspaceTaxonomySyncMeta, recordSyncDiagnostic, } = input;
|
|
10
285
|
if (incomingInitiatives.size > 0) {
|
|
@@ -107,6 +382,10 @@ export function applyAiProfileAndMemberSyncChanges(input) {
|
|
|
107
382
|
username: profile.username,
|
|
108
383
|
icon: profile.icon,
|
|
109
384
|
color: profile.color,
|
|
385
|
+
description: profile.description ?? null,
|
|
386
|
+
role: profile.role ?? null,
|
|
387
|
+
provider: profile.provider ?? null,
|
|
388
|
+
model: profile.model ?? null,
|
|
110
389
|
surfaceType: profile.surfaceType,
|
|
111
390
|
providerMetadata: profile.providerMetadata,
|
|
112
391
|
createdAt: profile.createdAt,
|
|
@@ -150,21 +429,29 @@ export function applyAiProfileAndMemberSyncChanges(input) {
|
|
|
150
429
|
}
|
|
151
430
|
}
|
|
152
431
|
export async function applyContentSyncChanges(input) {
|
|
153
|
-
const { core, workspaceId, incomingDocuments, incomingDocumentDeletes, incomingAssets, incomingAssetDeletes, basePath, hasObjectStorageClient, hasDocumentSyncMetadataDrift, hasBinaryAssetSyncMetadataDrift, writeWorkspaceSyncDocument, deleteWorkspaceSyncDocument, writeWorkspaceSyncBinaryAsset, deleteWorkspaceSyncBinaryAsset, getExistingBinaryAsset, recordSyncDiagnostic, options, } = input;
|
|
432
|
+
const { core, workspaceId, incomingDocuments, incomingDocumentDeletes, incomingAssets, incomingAssetDeletes, basePath, hasObjectStorageClient, hasDocumentSyncMetadataDrift, hasBinaryAssetSyncMetadataDrift, getExistingDocument, writeWorkspaceSyncDocument, deleteWorkspaceSyncDocument, writeWorkspaceSyncBinaryAsset, deleteWorkspaceSyncBinaryAsset, getExistingBinaryAsset, recordSyncDiagnostic, options, } = input;
|
|
154
433
|
if (incomingDocuments.length > 0) {
|
|
155
434
|
for (const doc of incomingDocuments) {
|
|
156
435
|
try {
|
|
157
436
|
const existingDoc = core.getDocumentWatermark(workspaceId, doc.path);
|
|
437
|
+
const existingDocFallback = getExistingDocument ? getExistingDocument(doc.path, workspaceId) : null;
|
|
438
|
+
const localDocumentPath = path.resolve(basePath, doc.path);
|
|
439
|
+
const localDocumentHash = !hasObjectStorageClient() && fs.existsSync(localDocumentPath)
|
|
440
|
+
? createHash('sha256').update(fs.readFileSync(localDocumentPath, 'utf8')).digest('hex')
|
|
441
|
+
: '';
|
|
158
442
|
const incomingHash = createHash('sha256').update(String(doc.content || '')).digest('hex');
|
|
159
|
-
const existingUpdatedAt = String(existingDoc?.updatedAt || '').trim();
|
|
160
|
-
const existingHash = String(existingDoc?.contentHash || '').trim();
|
|
443
|
+
const existingUpdatedAt = String(existingDoc?.updatedAt || existingDocFallback?.updatedAt || '').trim();
|
|
444
|
+
const existingHash = String(existingDoc?.contentHash || localDocumentHash || existingDocFallback?.contentHash || '').trim();
|
|
161
445
|
const compare = resolveSyncConflictOrdering(doc.updatedAt, existingUpdatedAt, incomingHash, existingHash);
|
|
162
446
|
const metadataDrift = hasDocumentSyncMetadataDrift(workspaceId, doc, options);
|
|
163
447
|
if (compare < 0)
|
|
164
448
|
continue;
|
|
165
449
|
if (compare === 0 && existingHash === incomingHash && !metadataDrift)
|
|
166
450
|
continue;
|
|
167
|
-
await writeWorkspaceSyncDocument(basePath, doc, workspaceId,
|
|
451
|
+
await writeWorkspaceSyncDocument(basePath, doc, workspaceId, {
|
|
452
|
+
...options,
|
|
453
|
+
allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true || existingHash !== incomingHash
|
|
454
|
+
});
|
|
168
455
|
}
|
|
169
456
|
catch (error) {
|
|
170
457
|
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to apply synced document change.', {
|
|
@@ -190,18 +477,27 @@ export async function applyContentSyncChanges(input) {
|
|
|
190
477
|
if (incomingAssets.length > 0) {
|
|
191
478
|
for (const asset of incomingAssets) {
|
|
192
479
|
try {
|
|
480
|
+
const localAssetPath = path.resolve(basePath, asset.path);
|
|
193
481
|
const existing = getExistingBinaryAsset(asset.path, workspaceId);
|
|
194
482
|
const incomingHash = createHash('sha256').update(Buffer.from(String(asset.contentBase64 || ''), 'base64')).digest('hex');
|
|
483
|
+
const localFileMissing = !hasObjectStorageClient() && !fs.existsSync(localAssetPath);
|
|
484
|
+
const localAssetHash = !hasObjectStorageClient() && !localFileMissing
|
|
485
|
+
? createHash('sha256').update(fs.readFileSync(localAssetPath)).digest('hex')
|
|
486
|
+
: '';
|
|
195
487
|
const existingUpdatedAt = String(existing?.updatedAt || '').trim();
|
|
196
|
-
const existingHash = String(existing?.contentSha256 || '').trim();
|
|
197
|
-
const localFileMissing = !hasObjectStorageClient() && !fs.existsSync(path.resolve(basePath, asset.path));
|
|
488
|
+
const existingHash = String(localAssetHash || existing?.contentSha256 || '').trim();
|
|
198
489
|
const compare = resolveSyncConflictOrdering(asset.updatedAt, existingUpdatedAt, incomingHash, existingHash);
|
|
199
490
|
const metadataDrift = hasBinaryAssetSyncMetadataDrift(workspaceId, asset, options);
|
|
200
491
|
if (compare < 0)
|
|
201
492
|
continue;
|
|
202
493
|
if (compare === 0 && existingHash === incomingHash && !localFileMissing && !metadataDrift)
|
|
203
494
|
continue;
|
|
204
|
-
await writeWorkspaceSyncBinaryAsset(basePath, asset, workspaceId,
|
|
495
|
+
await writeWorkspaceSyncBinaryAsset(basePath, asset, workspaceId, {
|
|
496
|
+
...options,
|
|
497
|
+
allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true
|
|
498
|
+
|| existingHash !== incomingHash
|
|
499
|
+
|| localFileMissing
|
|
500
|
+
});
|
|
205
501
|
}
|
|
206
502
|
catch (error) {
|
|
207
503
|
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to apply synced asset change.', {
|
|
@@ -443,3 +739,69 @@ export function applyCollaborationSyncChanges(input) {
|
|
|
443
739
|
}
|
|
444
740
|
}
|
|
445
741
|
}
|
|
742
|
+
export function applyTaskActivitySyncChanges(input) {
|
|
743
|
+
const { core, workspaceId, incomingTaskEvents, recordSyncDiagnostic, createSyncRouteError, } = input;
|
|
744
|
+
if (incomingTaskEvents.size <= 0)
|
|
745
|
+
return;
|
|
746
|
+
for (const event of incomingTaskEvents.values()) {
|
|
747
|
+
try {
|
|
748
|
+
const taskId = String(event.taskId || '').trim();
|
|
749
|
+
if (!taskId)
|
|
750
|
+
continue;
|
|
751
|
+
const existingTask = core.getTask(taskId, workspaceId);
|
|
752
|
+
if (!existingTask) {
|
|
753
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Skipped task event during sync apply because the task does not exist locally.', {
|
|
754
|
+
source: 'apply.taskEventMissingTask',
|
|
755
|
+
eventId: event.id,
|
|
756
|
+
taskId,
|
|
757
|
+
});
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
const existing = core.listTaskEvents(taskId, workspaceId).find((entry) => entry.id === event.id) || null;
|
|
761
|
+
const incomingTieBreak = createHash('sha256')
|
|
762
|
+
.update(JSON.stringify({
|
|
763
|
+
taskId,
|
|
764
|
+
action: event.action,
|
|
765
|
+
actor: event.actor,
|
|
766
|
+
actorType: event.actorType,
|
|
767
|
+
details: event.details ?? {},
|
|
768
|
+
}))
|
|
769
|
+
.digest('hex');
|
|
770
|
+
const existingTieBreak = existing
|
|
771
|
+
? createHash('sha256')
|
|
772
|
+
.update(JSON.stringify({
|
|
773
|
+
taskId: existing.taskId,
|
|
774
|
+
action: existing.action,
|
|
775
|
+
actor: existing.actor,
|
|
776
|
+
actorType: existing.actorType,
|
|
777
|
+
details: existing.details ?? {},
|
|
778
|
+
}))
|
|
779
|
+
.digest('hex')
|
|
780
|
+
: '';
|
|
781
|
+
const compare = resolveSyncConflictOrdering(event.createdAt, String(existing?.createdAt || '').trim(), incomingTieBreak, existingTieBreak);
|
|
782
|
+
if (compare < 0)
|
|
783
|
+
continue;
|
|
784
|
+
core.upsertTaskEventForSync({
|
|
785
|
+
id: event.id,
|
|
786
|
+
taskId,
|
|
787
|
+
workspaceId,
|
|
788
|
+
action: event.action,
|
|
789
|
+
actor: event.actor,
|
|
790
|
+
actorType: event.actorType,
|
|
791
|
+
details: event.details,
|
|
792
|
+
createdAt: event.createdAt,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
const details = {
|
|
797
|
+
source: 'apply.taskEventUpsert',
|
|
798
|
+
eventId: event.id,
|
|
799
|
+
taskId: event.taskId,
|
|
800
|
+
error: String(error?.message || error || ''),
|
|
801
|
+
errorCode: String(error?.code || '').trim() || null
|
|
802
|
+
};
|
|
803
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert task event during sync apply.', details);
|
|
804
|
+
throw createSyncRouteError(`Failed to persist task event ${event.id} during sync apply: ${String(error?.message || error || 'Unknown error')}`, details, { code: 'TASK_EVENT_SYNC_APPLY_FAILED', statusCode: 500 });
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|