@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.
- package/dist/Taskforce.module.css +37 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +72 -33
- package/dist/TaskforceCore.test.js +848 -130
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +109 -49
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +473 -256
- package/dist/components/views/panels/PlanningDrawer.js +18 -1
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
- 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/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +100 -10
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +459 -127
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +13 -12
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskData.js +3 -2
- package/dist/hooks/useTaskData.test.js +66 -0
- package/dist/hooks/useTaskforce.d.ts +8 -0
- package/dist/hooks/useTaskforce.js +72 -19
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +131 -48
- package/dist/mcp/runtime.test.js +321 -83
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +68 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +17 -1
- package/dist/server/index.test.js +15 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +105 -45
- package/dist/server/routes/auth.d.ts +6 -0
- package/dist/server/routes/auth.js +505 -30
- package/dist/server/routes/billing.js +517 -24
- package/dist/server/routes/billing.test.js +967 -45
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +16 -6
- package/dist/server/routes/sync.integration.test.js +290 -0
- package/dist/server/routes/sync.js +234 -1922
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +29 -22
- package/dist/server/routes.test.js +639 -82
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +98 -0
- package/dist/sync/contentSyncState.js +1004 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +38 -0
- package/dist/sync/workspacePullFeed.js +349 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +278 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- 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 +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +4 -3
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
- package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
- package/dist/ui/assets/index-xBWLPOEb.js +0 -6
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { compareWorkspaceMutationOrdering } from './workspaceSyncSurface.js';
|
|
5
|
+
export function resolveSyncConflictOrdering(incomingWatermark, existingWatermark, incomingTieBreak, existingTieBreak) {
|
|
6
|
+
return compareWorkspaceMutationOrdering(String(incomingWatermark || '').trim(), String(existingWatermark || '').trim(), String(incomingTieBreak || '').trim(), String(existingTieBreak || '').trim());
|
|
7
|
+
}
|
|
8
|
+
export function applyPlanningSyncChanges(input) {
|
|
9
|
+
const { core, workspaceId, incomingInitiatives, incomingWorkstreams, incomingTaxonomies, incomingTaxonomyState, incomingTaxonomiesUpdatedAt, workspaceTaxonomySyncMeta, recordSyncDiagnostic, } = input;
|
|
10
|
+
if (incomingInitiatives.size > 0) {
|
|
11
|
+
for (const initiative of incomingInitiatives.values()) {
|
|
12
|
+
try {
|
|
13
|
+
core.upsertInitiativeForSync({
|
|
14
|
+
id: initiative.id,
|
|
15
|
+
referenceNumber: initiative.referenceNumber,
|
|
16
|
+
referenceLabel: initiative.referenceLabel,
|
|
17
|
+
title: initiative.title,
|
|
18
|
+
description: initiative.description,
|
|
19
|
+
ownerId: initiative.ownerId,
|
|
20
|
+
createdAt: initiative.createdAt,
|
|
21
|
+
updatedAt: initiative.updatedAt,
|
|
22
|
+
order: initiative.order,
|
|
23
|
+
isArchived: initiative.isArchived
|
|
24
|
+
}, workspaceId);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert initiative during sync apply.', {
|
|
28
|
+
source: 'apply.initiativeUpsert',
|
|
29
|
+
initiativeId: initiative.id,
|
|
30
|
+
error: String(error?.message || error || '')
|
|
31
|
+
});
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (incomingWorkstreams.size > 0) {
|
|
37
|
+
for (const workstream of incomingWorkstreams.values()) {
|
|
38
|
+
try {
|
|
39
|
+
core.upsertWorkstreamForSync({
|
|
40
|
+
id: workstream.id,
|
|
41
|
+
referenceNumber: workstream.referenceNumber,
|
|
42
|
+
referenceLabel: workstream.referenceLabel,
|
|
43
|
+
initiativeId: workstream.initiativeId,
|
|
44
|
+
title: workstream.title,
|
|
45
|
+
description: workstream.description,
|
|
46
|
+
ownerId: workstream.ownerId,
|
|
47
|
+
createdAt: workstream.createdAt,
|
|
48
|
+
updatedAt: workstream.updatedAt,
|
|
49
|
+
order: workstream.order,
|
|
50
|
+
isArchived: workstream.isArchived
|
|
51
|
+
}, workspaceId);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert workstream during sync apply.', {
|
|
55
|
+
source: 'apply.workstreamUpsert',
|
|
56
|
+
workstreamId: workstream.id,
|
|
57
|
+
error: String(error?.message || error || '')
|
|
58
|
+
});
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (incomingTaxonomyState || Array.isArray(incomingTaxonomies)) {
|
|
64
|
+
const nextTaxonomyState = incomingTaxonomyState || { taxonomies: incomingTaxonomies || [] };
|
|
65
|
+
if (!incomingTaxonomiesUpdatedAt) {
|
|
66
|
+
if (typeof core.updateTaxonomyState === 'function') {
|
|
67
|
+
core.updateTaxonomyState(nextTaxonomyState, workspaceId);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
core.updateTaxonomies(Array.isArray(incomingTaxonomies) ? incomingTaxonomies : []);
|
|
71
|
+
}
|
|
72
|
+
const fallbackHash = createHash('sha256').update(JSON.stringify(nextTaxonomyState)).digest('hex');
|
|
73
|
+
workspaceTaxonomySyncMeta.set(workspaceId, {
|
|
74
|
+
updatedAt: new Date().toISOString(),
|
|
75
|
+
hash: fallbackHash
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const incomingTaxonomyHash = createHash('sha256').update(JSON.stringify(nextTaxonomyState)).digest('hex');
|
|
80
|
+
const currentMeta = workspaceTaxonomySyncMeta.get(workspaceId) || { updatedAt: '', hash: '' };
|
|
81
|
+
const compare = resolveSyncConflictOrdering(incomingTaxonomiesUpdatedAt, currentMeta.updatedAt, incomingTaxonomyHash, currentMeta.hash);
|
|
82
|
+
if (compare >= 0) {
|
|
83
|
+
if (typeof core.updateTaxonomyState === 'function') {
|
|
84
|
+
core.updateTaxonomyState(nextTaxonomyState, workspaceId);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
core.updateTaxonomies(Array.isArray(incomingTaxonomies) ? incomingTaxonomies : []);
|
|
88
|
+
}
|
|
89
|
+
workspaceTaxonomySyncMeta.set(workspaceId, {
|
|
90
|
+
updatedAt: incomingTaxonomiesUpdatedAt,
|
|
91
|
+
hash: incomingTaxonomyHash
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function applyAiProfileAndMemberSyncChanges(input) {
|
|
98
|
+
const { core, workspaceId, incomingAiProfiles, workspaceMembers, recordSyncDiagnostic, assertSyncedAiProfilesPersisted, createSyncRouteError, } = input;
|
|
99
|
+
if (incomingAiProfiles.size > 0) {
|
|
100
|
+
for (const profile of incomingAiProfiles.values()) {
|
|
101
|
+
try {
|
|
102
|
+
core.upsertAiProfileForSync({
|
|
103
|
+
id: profile.id,
|
|
104
|
+
workspaceId,
|
|
105
|
+
profileToken: profile.profileToken,
|
|
106
|
+
name: profile.name,
|
|
107
|
+
username: profile.username,
|
|
108
|
+
icon: profile.icon,
|
|
109
|
+
color: profile.color,
|
|
110
|
+
surfaceType: profile.surfaceType,
|
|
111
|
+
providerMetadata: profile.providerMetadata,
|
|
112
|
+
createdAt: profile.createdAt,
|
|
113
|
+
updatedAt: profile.updatedAt,
|
|
114
|
+
lastActiveAt: profile.lastActiveAt
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const details = {
|
|
119
|
+
source: 'apply.aiProfileUpsert',
|
|
120
|
+
workspaceId,
|
|
121
|
+
profileId: profile.id,
|
|
122
|
+
error: String(error?.message || error || ''),
|
|
123
|
+
errorCode: String(error?.code || '').trim() || null
|
|
124
|
+
};
|
|
125
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert AI profile during sync apply.', details);
|
|
126
|
+
throw createSyncRouteError(`Failed to persist AI profile ${profile.id} during sync apply: ${String(error?.message || error || 'Unknown error')}`, details, { code: 'AI_PROFILE_SYNC_APPLY_FAILED', statusCode: 500 });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
assertSyncedAiProfilesPersisted(workspaceId, incomingAiProfiles);
|
|
130
|
+
}
|
|
131
|
+
if (Array.isArray(workspaceMembers)) {
|
|
132
|
+
core.replaceWorkspaceUsersForSync({
|
|
133
|
+
workspaceId,
|
|
134
|
+
users: workspaceMembers
|
|
135
|
+
.filter((member) => String(member?.workspaceId || workspaceId).trim() === workspaceId)
|
|
136
|
+
.map((member) => ({
|
|
137
|
+
userId: member.userId,
|
|
138
|
+
email: member.email,
|
|
139
|
+
displayName: member.displayName,
|
|
140
|
+
role: member.role,
|
|
141
|
+
permissionMode: member.permissionMode,
|
|
142
|
+
betaAccess: member.betaAccess,
|
|
143
|
+
status: member.status,
|
|
144
|
+
disabled: member.disabled,
|
|
145
|
+
createdAt: member.createdAt,
|
|
146
|
+
updatedAt: member.updatedAt,
|
|
147
|
+
emailVerifiedAt: member.emailVerifiedAt
|
|
148
|
+
}))
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
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;
|
|
154
|
+
if (incomingDocuments.length > 0) {
|
|
155
|
+
for (const doc of incomingDocuments) {
|
|
156
|
+
try {
|
|
157
|
+
const existingDoc = core.getDocumentWatermark(workspaceId, doc.path);
|
|
158
|
+
const incomingHash = createHash('sha256').update(String(doc.content || '')).digest('hex');
|
|
159
|
+
const existingUpdatedAt = String(existingDoc?.updatedAt || '').trim();
|
|
160
|
+
const existingHash = String(existingDoc?.contentHash || '').trim();
|
|
161
|
+
const compare = resolveSyncConflictOrdering(doc.updatedAt, existingUpdatedAt, incomingHash, existingHash);
|
|
162
|
+
const metadataDrift = hasDocumentSyncMetadataDrift(workspaceId, doc, options);
|
|
163
|
+
if (compare < 0)
|
|
164
|
+
continue;
|
|
165
|
+
if (compare === 0 && existingHash === incomingHash && !metadataDrift)
|
|
166
|
+
continue;
|
|
167
|
+
await writeWorkspaceSyncDocument(basePath, doc, workspaceId, options);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to apply synced document change.', {
|
|
171
|
+
source: 'documents.apply.write',
|
|
172
|
+
path: doc.path,
|
|
173
|
+
assetId: typeof doc.assetId === 'string' ? doc.assetId : null,
|
|
174
|
+
error: String(error?.message || error || '')
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (incomingDocumentDeletes.size > 0) {
|
|
180
|
+
for (const [docPath, deletedAt] of incomingDocumentDeletes.entries()) {
|
|
181
|
+
const existingDoc = core.getDocumentWatermark(workspaceId, docPath);
|
|
182
|
+
const existingUpdatedAt = String(existingDoc?.updatedAt || '').trim();
|
|
183
|
+
const existingHash = String(existingDoc?.contentHash || '').trim();
|
|
184
|
+
const compare = resolveSyncConflictOrdering(deletedAt, existingUpdatedAt, `delete:${docPath}`, existingHash);
|
|
185
|
+
if (existingDoc && compare < 0)
|
|
186
|
+
continue;
|
|
187
|
+
deleteWorkspaceSyncDocument(basePath, docPath, workspaceId);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (incomingAssets.length > 0) {
|
|
191
|
+
for (const asset of incomingAssets) {
|
|
192
|
+
try {
|
|
193
|
+
const existing = getExistingBinaryAsset(asset.path, workspaceId);
|
|
194
|
+
const incomingHash = createHash('sha256').update(Buffer.from(String(asset.contentBase64 || ''), 'base64')).digest('hex');
|
|
195
|
+
const existingUpdatedAt = String(existing?.updatedAt || '').trim();
|
|
196
|
+
const existingHash = String(existing?.contentSha256 || '').trim();
|
|
197
|
+
const localFileMissing = !hasObjectStorageClient() && !fs.existsSync(path.resolve(basePath, asset.path));
|
|
198
|
+
const compare = resolveSyncConflictOrdering(asset.updatedAt, existingUpdatedAt, incomingHash, existingHash);
|
|
199
|
+
const metadataDrift = hasBinaryAssetSyncMetadataDrift(workspaceId, asset, options);
|
|
200
|
+
if (compare < 0)
|
|
201
|
+
continue;
|
|
202
|
+
if (compare === 0 && existingHash === incomingHash && !localFileMissing && !metadataDrift)
|
|
203
|
+
continue;
|
|
204
|
+
await writeWorkspaceSyncBinaryAsset(basePath, asset, workspaceId, options);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to apply synced asset change.', {
|
|
208
|
+
source: 'assets.apply.write',
|
|
209
|
+
path: asset.path,
|
|
210
|
+
assetId: typeof asset.assetId === 'string' ? asset.assetId : null,
|
|
211
|
+
kind: asset.kind,
|
|
212
|
+
error: String(error?.message || error || '')
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (incomingAssetDeletes.size > 0) {
|
|
218
|
+
for (const [assetPath, deletedAt] of incomingAssetDeletes.entries()) {
|
|
219
|
+
const existing = getExistingBinaryAsset(assetPath, workspaceId);
|
|
220
|
+
const existingUpdatedAt = String(existing?.updatedAt || '').trim();
|
|
221
|
+
const existingHash = String(existing?.contentSha256 || '').trim();
|
|
222
|
+
const compare = resolveSyncConflictOrdering(deletedAt, existingUpdatedAt, `delete:${assetPath}`, existingHash);
|
|
223
|
+
if (existing && compare < 0)
|
|
224
|
+
continue;
|
|
225
|
+
deleteWorkspaceSyncBinaryAsset(basePath, assetPath, workspaceId);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (incomingDocuments.length > 0 || incomingAssets.length > 0) {
|
|
229
|
+
core.notifyDocumentMetadataMutation(workspaceId, [
|
|
230
|
+
...incomingDocuments.map((entry) => entry.path),
|
|
231
|
+
...incomingAssets.map((entry) => entry.path)
|
|
232
|
+
], 'upsert');
|
|
233
|
+
}
|
|
234
|
+
if (incomingDocumentDeletes.size > 0 || incomingAssetDeletes.size > 0) {
|
|
235
|
+
core.notifyDocumentMetadataMutation(workspaceId, [
|
|
236
|
+
...Array.from(incomingDocumentDeletes.keys()),
|
|
237
|
+
...Array.from(incomingAssetDeletes.keys())
|
|
238
|
+
], 'delete');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
export function applyCollaborationSyncChanges(input) {
|
|
242
|
+
const { core, workspaceId, incomingDocumentReviewSessions, incomingDocumentReviewSessionDeletes, incomingDocumentReviewComments, incomingDocumentReviewCommentDeletes, incomingAnnotatedAttachmentSessions, incomingAnnotatedAttachmentSessionDeletes, recordSyncDiagnostic, createSyncRouteError, } = input;
|
|
243
|
+
if (incomingDocumentReviewSessions.size > 0) {
|
|
244
|
+
for (const session of incomingDocumentReviewSessions.values()) {
|
|
245
|
+
try {
|
|
246
|
+
const existing = core.getDocumentReviewSession({
|
|
247
|
+
workspaceId,
|
|
248
|
+
sessionId: session.id,
|
|
249
|
+
includeDeleted: true
|
|
250
|
+
});
|
|
251
|
+
const incomingTieBreak = createHash('sha256').update(JSON.stringify(session.comments || [])).digest('hex');
|
|
252
|
+
const existingTieBreak = existing
|
|
253
|
+
? createHash('sha256').update(JSON.stringify(existing.comments || [])).digest('hex')
|
|
254
|
+
: '';
|
|
255
|
+
const compare = resolveSyncConflictOrdering(session.updatedAt, String(existing?.updatedAt || '').trim(), incomingTieBreak, existingTieBreak);
|
|
256
|
+
if (compare < 0)
|
|
257
|
+
continue;
|
|
258
|
+
core.upsertDocumentReviewSessionForSync({
|
|
259
|
+
id: session.id,
|
|
260
|
+
tenantId: core.getTenantId(),
|
|
261
|
+
workspaceId,
|
|
262
|
+
assetId: session.assetId,
|
|
263
|
+
documentId: session.documentId,
|
|
264
|
+
documentVersion: session.documentVersion,
|
|
265
|
+
title: session.title,
|
|
266
|
+
status: session.status,
|
|
267
|
+
comments: [],
|
|
268
|
+
createdByActorId: session.createdByActorId,
|
|
269
|
+
updatedByActorId: session.updatedByActorId,
|
|
270
|
+
createdAt: session.createdAt,
|
|
271
|
+
updatedAt: session.updatedAt,
|
|
272
|
+
deletedAt: session.deletedAt,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
const details = {
|
|
277
|
+
source: 'apply.documentReviewUpsert',
|
|
278
|
+
sessionId: session.id,
|
|
279
|
+
error: String(error?.message || error || ''),
|
|
280
|
+
errorCode: String(error?.code || '').trim() || null
|
|
281
|
+
};
|
|
282
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert document review session during sync apply.', details);
|
|
283
|
+
throw createSyncRouteError(`Failed to persist document review session ${session.id} during sync apply: ${String(error?.message || error || 'Unknown error')}`, details, { code: 'DOCUMENT_REVIEW_SESSION_SYNC_APPLY_FAILED', statusCode: 500 });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (incomingDocumentReviewComments.size > 0) {
|
|
288
|
+
for (const comment of incomingDocumentReviewComments.values()) {
|
|
289
|
+
try {
|
|
290
|
+
const existingComments = core.listDocumentReviewCommentsForSync({
|
|
291
|
+
workspaceId,
|
|
292
|
+
includeDeleted: true
|
|
293
|
+
});
|
|
294
|
+
const existing = existingComments.find((entry) => entry.id === comment.id) || null;
|
|
295
|
+
const incomingTieBreak = createHash('sha256')
|
|
296
|
+
.update(JSON.stringify({ body: comment.body, anchor: comment.anchor, deletedAt: comment.deletedAt }))
|
|
297
|
+
.digest('hex');
|
|
298
|
+
const existingTieBreak = existing
|
|
299
|
+
? createHash('sha256')
|
|
300
|
+
.update(JSON.stringify({ body: existing.body, anchor: existing.anchor, deletedAt: existing.deletedAt }))
|
|
301
|
+
.digest('hex')
|
|
302
|
+
: '';
|
|
303
|
+
const compare = resolveSyncConflictOrdering(comment.updatedAt, String(existing?.updatedAt || '').trim(), incomingTieBreak, existingTieBreak);
|
|
304
|
+
if (compare < 0)
|
|
305
|
+
continue;
|
|
306
|
+
core.upsertDocumentReviewCommentForSync({
|
|
307
|
+
id: comment.id,
|
|
308
|
+
tenantId: core.getTenantId(),
|
|
309
|
+
workspaceId,
|
|
310
|
+
sessionId: comment.sessionId,
|
|
311
|
+
body: comment.body,
|
|
312
|
+
anchor: comment.anchor,
|
|
313
|
+
order: comment.order,
|
|
314
|
+
authorActorId: comment.authorActorId,
|
|
315
|
+
createdAt: comment.createdAt,
|
|
316
|
+
updatedAt: comment.updatedAt,
|
|
317
|
+
deletedAt: comment.deletedAt,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert document review comment during sync apply.', {
|
|
322
|
+
source: 'apply.documentReviewCommentUpsert',
|
|
323
|
+
commentId: comment.id,
|
|
324
|
+
error: String(error?.message || error || '')
|
|
325
|
+
});
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (incomingDocumentReviewSessionDeletes.size > 0) {
|
|
331
|
+
for (const [sessionId, deletedAt] of incomingDocumentReviewSessionDeletes.entries()) {
|
|
332
|
+
try {
|
|
333
|
+
const existing = core.getDocumentReviewSession({
|
|
334
|
+
workspaceId,
|
|
335
|
+
sessionId,
|
|
336
|
+
includeDeleted: true
|
|
337
|
+
});
|
|
338
|
+
const compare = resolveSyncConflictOrdering(deletedAt, String(existing?.updatedAt || '').trim(), `delete:${sessionId}`, String(existing?.id || '').trim());
|
|
339
|
+
if (existing && compare < 0)
|
|
340
|
+
continue;
|
|
341
|
+
core.deleteDocumentReviewSessionForSync({ workspaceId, sessionId, deletedAt });
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
const details = {
|
|
345
|
+
source: 'apply.documentReviewDelete',
|
|
346
|
+
sessionId,
|
|
347
|
+
error: String(error?.message || error || ''),
|
|
348
|
+
errorCode: String(error?.code || '').trim() || null
|
|
349
|
+
};
|
|
350
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete document review session during sync apply.', details);
|
|
351
|
+
throw createSyncRouteError(`Failed to delete document review session ${sessionId} during sync apply: ${String(error?.message || error || 'Unknown error')}`, details, { code: 'DOCUMENT_REVIEW_SESSION_SYNC_DELETE_FAILED', statusCode: 500 });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (incomingDocumentReviewCommentDeletes.size > 0) {
|
|
356
|
+
for (const [commentId, deletedAt] of incomingDocumentReviewCommentDeletes.entries()) {
|
|
357
|
+
try {
|
|
358
|
+
const existing = core.listDocumentReviewCommentsForSync({
|
|
359
|
+
workspaceId,
|
|
360
|
+
includeDeleted: true
|
|
361
|
+
}).find((entry) => entry.id === commentId) || null;
|
|
362
|
+
const compare = resolveSyncConflictOrdering(deletedAt, String(existing?.updatedAt || '').trim(), `delete:${commentId}`, String(existing?.id || '').trim());
|
|
363
|
+
if (existing && compare < 0)
|
|
364
|
+
continue;
|
|
365
|
+
core.deleteDocumentReviewCommentForSync({ workspaceId, commentId, deletedAt });
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete document review comment during sync apply.', {
|
|
369
|
+
source: 'apply.documentReviewCommentDelete',
|
|
370
|
+
commentId,
|
|
371
|
+
error: String(error?.message || error || '')
|
|
372
|
+
});
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (incomingAnnotatedAttachmentSessions.size > 0) {
|
|
378
|
+
for (const session of incomingAnnotatedAttachmentSessions.values()) {
|
|
379
|
+
try {
|
|
380
|
+
const existing = core.getAnnotatedAttachmentSession({
|
|
381
|
+
workspaceId,
|
|
382
|
+
sessionId: session.id,
|
|
383
|
+
includeDeleted: true
|
|
384
|
+
});
|
|
385
|
+
const incomingTieBreak = createHash('sha256').update(JSON.stringify(session.annotations || [])).digest('hex');
|
|
386
|
+
const existingTieBreak = existing
|
|
387
|
+
? createHash('sha256').update(JSON.stringify(existing.annotations || [])).digest('hex')
|
|
388
|
+
: '';
|
|
389
|
+
const compare = resolveSyncConflictOrdering(session.updatedAt, String(existing?.updatedAt || '').trim(), incomingTieBreak, existingTieBreak);
|
|
390
|
+
if (compare < 0)
|
|
391
|
+
continue;
|
|
392
|
+
core.upsertAnnotatedAttachmentSessionForSync({
|
|
393
|
+
id: session.id,
|
|
394
|
+
tenantId: core.getTenantId(),
|
|
395
|
+
workspaceId,
|
|
396
|
+
taskId: session.taskId,
|
|
397
|
+
baseImageAssetId: session.baseImageAssetId,
|
|
398
|
+
title: session.title,
|
|
399
|
+
globalInstruction: session.globalInstruction,
|
|
400
|
+
annotations: session.annotations,
|
|
401
|
+
createdByActorId: session.createdByActorId,
|
|
402
|
+
updatedByActorId: session.updatedByActorId,
|
|
403
|
+
createdAt: session.createdAt,
|
|
404
|
+
updatedAt: session.updatedAt,
|
|
405
|
+
deletedAt: session.deletedAt,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
const details = {
|
|
410
|
+
source: 'apply.annotatedAttachmentUpsert',
|
|
411
|
+
sessionId: session.id,
|
|
412
|
+
error: String(error?.message || error || ''),
|
|
413
|
+
errorCode: String(error?.code || '').trim() || null
|
|
414
|
+
};
|
|
415
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert annotated attachment session during sync apply.', details);
|
|
416
|
+
throw createSyncRouteError(`Failed to persist annotated attachment session ${session.id} during sync apply: ${String(error?.message || error || 'Unknown error')}`, details, { code: 'ANNOTATED_ATTACHMENT_SESSION_SYNC_APPLY_FAILED', statusCode: 500 });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (incomingAnnotatedAttachmentSessionDeletes.size > 0) {
|
|
421
|
+
for (const [sessionId, deletedAt] of incomingAnnotatedAttachmentSessionDeletes.entries()) {
|
|
422
|
+
try {
|
|
423
|
+
const existing = core.getAnnotatedAttachmentSession({
|
|
424
|
+
workspaceId,
|
|
425
|
+
sessionId,
|
|
426
|
+
includeDeleted: true
|
|
427
|
+
});
|
|
428
|
+
const compare = resolveSyncConflictOrdering(deletedAt, String(existing?.updatedAt || '').trim(), `delete:${sessionId}`, String(existing?.id || '').trim());
|
|
429
|
+
if (existing && compare < 0)
|
|
430
|
+
continue;
|
|
431
|
+
core.deleteAnnotatedAttachmentSessionForSync({ workspaceId, sessionId, deletedAt });
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
const details = {
|
|
435
|
+
source: 'apply.annotatedAttachmentDelete',
|
|
436
|
+
sessionId,
|
|
437
|
+
error: String(error?.message || error || ''),
|
|
438
|
+
errorCode: String(error?.code || '').trim() || null
|
|
439
|
+
};
|
|
440
|
+
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete annotated attachment session during sync apply.', details);
|
|
441
|
+
throw createSyncRouteError(`Failed to delete annotated attachment session ${sessionId} during sync apply: ${String(error?.message || error || 'Unknown error')}`, details, { code: 'ANNOTATED_ATTACHMENT_SESSION_SYNC_DELETE_FAILED', statusCode: 500 });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
@@ -115,6 +115,7 @@ export interface WorkspacePullSyncResult {
|
|
|
115
115
|
pulledArchivedUpserts: boolean;
|
|
116
116
|
documentDecryptFailures?: string[];
|
|
117
117
|
emittedEventIds?: string[];
|
|
118
|
+
serverDiagnostics?: Record<string, unknown> | null;
|
|
118
119
|
}
|
|
119
120
|
export declare function runWorkspacePullSyncService(input: WorkspacePullSyncInput): Promise<WorkspacePullSyncResult>;
|
|
120
121
|
export declare function syncUserGlobalSettingsService(input: UserSettingsSyncInput): Promise<UserSettingsSyncResult>;
|
package/dist/sync/syncService.js
CHANGED
|
@@ -243,6 +243,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
243
243
|
const documentDecryptFailures = new Set();
|
|
244
244
|
const emittedEventIds = new Set();
|
|
245
245
|
const pulledAnnotatedAttachmentSessionIds = new Set();
|
|
246
|
+
let lastServerDiagnostics = null;
|
|
246
247
|
const maxPages = Number.isFinite(Number(input.maxPages)) ? Math.max(1, Math.floor(Number(input.maxPages))) : 20;
|
|
247
248
|
try {
|
|
248
249
|
while (hasMore && pages < maxPages) {
|
|
@@ -279,11 +280,16 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
279
280
|
pulledDeleteTaskIds,
|
|
280
281
|
pulledActiveUpserts,
|
|
281
282
|
pulledArchivedUpserts,
|
|
282
|
-
documentDecryptFailures: Array.from(documentDecryptFailures)
|
|
283
|
+
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
284
|
+
serverDiagnostics: null
|
|
283
285
|
};
|
|
284
286
|
}
|
|
285
287
|
const pullData = pullRes.data || {};
|
|
286
288
|
const changes = Array.isArray(pullData?.changes) ? pullData.changes : [];
|
|
289
|
+
const serverDiagnostics = pullData?.diagnostics && typeof pullData.diagnostics === 'object'
|
|
290
|
+
? pullData.diagnostics
|
|
291
|
+
: null;
|
|
292
|
+
lastServerDiagnostics = serverDiagnostics;
|
|
287
293
|
const hasWorkspaceMembersSnapshot = Array.isArray(pullData?.workspaceMembers);
|
|
288
294
|
const workspaceMembers = hasWorkspaceMembersSnapshot
|
|
289
295
|
? pullData.workspaceMembers
|
|
@@ -365,7 +371,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
365
371
|
pulledDeleteTaskIds,
|
|
366
372
|
pulledActiveUpserts,
|
|
367
373
|
pulledArchivedUpserts,
|
|
368
|
-
documentDecryptFailures: Array.from(documentDecryptFailures)
|
|
374
|
+
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
375
|
+
serverDiagnostics
|
|
369
376
|
};
|
|
370
377
|
}
|
|
371
378
|
const hasTransientApplyFailure = applyFailures.some((entry) => {
|
|
@@ -392,7 +399,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
392
399
|
pulledActiveUpserts,
|
|
393
400
|
pulledArchivedUpserts,
|
|
394
401
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
395
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
402
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
403
|
+
serverDiagnostics: lastServerDiagnostics
|
|
396
404
|
};
|
|
397
405
|
}
|
|
398
406
|
const applyPayloadData = asObjectRecord(applyResult.data);
|
|
@@ -412,7 +420,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
412
420
|
pulledActiveUpserts,
|
|
413
421
|
pulledArchivedUpserts,
|
|
414
422
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
415
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
423
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
424
|
+
serverDiagnostics: lastServerDiagnostics
|
|
416
425
|
};
|
|
417
426
|
}
|
|
418
427
|
applyMs += (Date.now() - applyStartedAtMs);
|
|
@@ -461,7 +470,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
461
470
|
pulledActiveUpserts,
|
|
462
471
|
pulledArchivedUpserts,
|
|
463
472
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
464
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
473
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
474
|
+
serverDiagnostics: lastServerDiagnostics
|
|
465
475
|
};
|
|
466
476
|
}
|
|
467
477
|
const hasTransientApplyFailure = bootstrapApplyFailures.some((entry) => {
|
|
@@ -488,7 +498,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
488
498
|
pulledActiveUpserts,
|
|
489
499
|
pulledArchivedUpserts,
|
|
490
500
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
491
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
501
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
502
|
+
serverDiagnostics: lastServerDiagnostics
|
|
492
503
|
};
|
|
493
504
|
}
|
|
494
505
|
applyMs += (Date.now() - bootstrapApplyStartedAtMs);
|
|
@@ -505,7 +516,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
505
516
|
pulledActiveUpserts,
|
|
506
517
|
pulledArchivedUpserts,
|
|
507
518
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
508
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
519
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
520
|
+
serverDiagnostics: lastServerDiagnostics
|
|
509
521
|
};
|
|
510
522
|
}
|
|
511
523
|
catch (error) {
|
|
@@ -524,7 +536,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
524
536
|
pulledActiveUpserts,
|
|
525
537
|
pulledArchivedUpserts,
|
|
526
538
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
527
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
539
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
540
|
+
serverDiagnostics: null
|
|
528
541
|
};
|
|
529
542
|
}
|
|
530
543
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AttachmentItem, ChecklistItem, Comment, TaskItem, TaskStatus } from '../types.js';
|
|
2
|
+
import type { TaskItem as CoreTaskItem } from '../core/Taskforce.js';
|
|
3
|
+
export interface TaskSyncPayload {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string | null;
|
|
7
|
+
status: TaskStatus;
|
|
8
|
+
priority?: number;
|
|
9
|
+
complexity?: number | null;
|
|
10
|
+
type: string;
|
|
11
|
+
category: string;
|
|
12
|
+
approach?: string;
|
|
13
|
+
canceledReason?: string | null;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt?: string;
|
|
16
|
+
completedAt?: string | null;
|
|
17
|
+
isArchived?: boolean;
|
|
18
|
+
taxonomies?: Record<string, string | string[]>;
|
|
19
|
+
createdBy?: string;
|
|
20
|
+
assignee?: string;
|
|
21
|
+
scheduledDate?: string | null;
|
|
22
|
+
dueDate?: string | null;
|
|
23
|
+
scheduledWeekKey?: string | null;
|
|
24
|
+
orderInDay?: number | null;
|
|
25
|
+
workstreamId?: string | null;
|
|
26
|
+
referenceNumber?: number | null;
|
|
27
|
+
comments?: Comment[];
|
|
28
|
+
attachments?: Array<string | AttachmentItem>;
|
|
29
|
+
checklistItems?: ChecklistItem[];
|
|
30
|
+
}
|
|
31
|
+
export declare function serializeTaskForSync(task: Partial<TaskItem> | null | undefined): TaskSyncPayload;
|
|
32
|
+
export declare function normalizeTaskSyncPayload(raw: unknown): TaskSyncPayload | null;
|
|
33
|
+
export declare function taskSyncPayloadToCoreTask(task: TaskSyncPayload): Partial<CoreTaskItem>;
|