@taskforcehq/taskforce 0.3.303 → 0.3.305
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/README.md +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- 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 +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- 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 +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- 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/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- 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 +63 -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 +123 -0
- package/dist/hooks/sync/transfers.js +435 -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 +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- 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/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- 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/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- 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/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -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.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +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.js +28 -17
- 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 +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -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 {};
|