@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,1004 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createHash, randomUUID } from 'crypto';
|
|
4
|
+
export function resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId) {
|
|
5
|
+
if (!deps.workspaceAssetStore) {
|
|
6
|
+
return {
|
|
7
|
+
primaryLink: null,
|
|
8
|
+
effectiveUpdatedAt: String(asset.updatedAt || asset.createdAt || '').trim() || new Date().toISOString()
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const links = deps.workspaceAssetStore.listLinksForAsset(asset.assetId, workspaceId, { includeDeleted: true });
|
|
12
|
+
const activeLinks = links.filter((entry) => !entry.deletedAt);
|
|
13
|
+
const primaryLink = activeLinks[0] || null;
|
|
14
|
+
let effectiveUpdatedAt = String(asset.updatedAt || asset.createdAt || '').trim() || new Date().toISOString();
|
|
15
|
+
for (const link of links) {
|
|
16
|
+
const linkUpdatedAt = String(link.updatedAt || link.createdAt || '').trim();
|
|
17
|
+
if (linkUpdatedAt && linkUpdatedAt > effectiveUpdatedAt)
|
|
18
|
+
effectiveUpdatedAt = linkUpdatedAt;
|
|
19
|
+
}
|
|
20
|
+
return { primaryLink, effectiveUpdatedAt };
|
|
21
|
+
}
|
|
22
|
+
export function getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId) {
|
|
23
|
+
if (!deps.workspaceAssetStore)
|
|
24
|
+
return {};
|
|
25
|
+
const asset = deps.workspaceAssetStore.getByStorageKey(relativePath, workspaceId);
|
|
26
|
+
if (!asset || asset.kind !== 'document')
|
|
27
|
+
return {};
|
|
28
|
+
const { primaryLink } = resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId);
|
|
29
|
+
return {
|
|
30
|
+
assetId: asset.assetId,
|
|
31
|
+
documentId: asset.documentId,
|
|
32
|
+
referenceNumber: asset.referenceNumber,
|
|
33
|
+
version: asset.version,
|
|
34
|
+
taskId: primaryLink?.taskId || null,
|
|
35
|
+
logicalName: asset.logicalName || null,
|
|
36
|
+
caption: primaryLink?.displayName || null,
|
|
37
|
+
originalFilename: asset.originalFilename || path.basename(relativePath),
|
|
38
|
+
linkRole: primaryLink?.role === 'reference' ? 'reference' : 'attachment'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options) {
|
|
42
|
+
if (!deps.workspaceAssetStore)
|
|
43
|
+
return false;
|
|
44
|
+
const existingAsset = deps.workspaceAssetStore.getByStorageKey(doc.path, workspaceId);
|
|
45
|
+
if (!existingAsset || existingAsset.kind !== 'document')
|
|
46
|
+
return false;
|
|
47
|
+
const { primaryLink } = resolveCanonicalAssetSyncLinkState(deps, existingAsset, workspaceId);
|
|
48
|
+
const normalizedIncomingReference = Number.isFinite(Number(doc.referenceNumber))
|
|
49
|
+
? Math.max(1, Math.floor(Number(doc.referenceNumber)))
|
|
50
|
+
: null;
|
|
51
|
+
const normalizedIncomingVersion = Number.isFinite(Number(doc.version))
|
|
52
|
+
? Math.max(1, Math.floor(Number(doc.version)))
|
|
53
|
+
: null;
|
|
54
|
+
const normalizedIncomingDocumentId = typeof doc.documentId === 'string' && doc.documentId.trim().length > 0
|
|
55
|
+
? doc.documentId.trim()
|
|
56
|
+
: null;
|
|
57
|
+
const normalizedIncomingLogicalName = typeof doc.logicalName === 'string' && doc.logicalName.trim().length > 0
|
|
58
|
+
? doc.logicalName.trim()
|
|
59
|
+
: null;
|
|
60
|
+
const normalizedIncomingCaption = typeof doc.caption === 'string' && doc.caption.trim().length > 0
|
|
61
|
+
? doc.caption.trim()
|
|
62
|
+
: null;
|
|
63
|
+
const normalizedIncomingFilename = typeof doc.originalFilename === 'string' && doc.originalFilename.trim().length > 0
|
|
64
|
+
? doc.originalFilename.trim()
|
|
65
|
+
: null;
|
|
66
|
+
const normalizedIncomingTaskId = typeof doc.taskId === 'string' && doc.taskId.trim().length > 0
|
|
67
|
+
? doc.taskId.trim()
|
|
68
|
+
: null;
|
|
69
|
+
const normalizedIncomingLinkRole = doc.linkRole === 'reference' ? 'reference' : 'attachment';
|
|
70
|
+
const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
|
|
71
|
+
const referenceNumberMismatch = Boolean(existingAsset.referenceNumber
|
|
72
|
+
&& normalizedIncomingReference
|
|
73
|
+
&& existingAsset.referenceNumber !== normalizedIncomingReference);
|
|
74
|
+
if (referenceNumberMismatch) {
|
|
75
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Detected document reference number mismatch during normal sync.', {
|
|
76
|
+
source: 'documents.referenceNumber.mismatch',
|
|
77
|
+
path: doc.path,
|
|
78
|
+
assetId: existingAsset.assetId,
|
|
79
|
+
existingReferenceNumber: existingAsset.referenceNumber,
|
|
80
|
+
incomingReferenceNumber: normalizedIncomingReference
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return referenceNumberMismatch
|
|
84
|
+
|| (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
|
|
85
|
+
|| existingAsset.version !== normalizedIncomingVersion
|
|
86
|
+
|| (existingAsset.documentId || null) !== normalizedIncomingDocumentId
|
|
87
|
+
|| (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
|
|
88
|
+
|| (existingAsset.originalFilename || null) !== normalizedIncomingFilename
|
|
89
|
+
|| (primaryLink?.taskId || null) !== normalizedIncomingTaskId
|
|
90
|
+
|| (primaryLink?.displayName || null) !== normalizedIncomingCaption
|
|
91
|
+
|| ((primaryLink?.role === 'reference' ? 'reference' : 'attachment')) !== normalizedIncomingLinkRole;
|
|
92
|
+
}
|
|
93
|
+
export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, options) {
|
|
94
|
+
if (!deps.workspaceAssetStore)
|
|
95
|
+
return false;
|
|
96
|
+
const existingAsset = deps.workspaceAssetStore.getByStorageKey(asset.path, workspaceId);
|
|
97
|
+
if (!existingAsset || (existingAsset.kind !== 'image' && existingAsset.kind !== 'file'))
|
|
98
|
+
return false;
|
|
99
|
+
const { primaryLink } = resolveCanonicalAssetSyncLinkState(deps, existingAsset, workspaceId);
|
|
100
|
+
const normalizedIncomingReference = Number.isFinite(Number(asset.referenceNumber))
|
|
101
|
+
? Math.max(1, Math.floor(Number(asset.referenceNumber)))
|
|
102
|
+
: null;
|
|
103
|
+
const normalizedIncomingLogicalName = typeof asset.logicalName === 'string' && asset.logicalName.trim().length > 0
|
|
104
|
+
? asset.logicalName.trim()
|
|
105
|
+
: null;
|
|
106
|
+
const normalizedIncomingCaption = typeof asset.caption === 'string' && asset.caption.trim().length > 0
|
|
107
|
+
? asset.caption.trim()
|
|
108
|
+
: null;
|
|
109
|
+
const normalizedIncomingFilename = typeof asset.originalFilename === 'string' && asset.originalFilename.trim().length > 0
|
|
110
|
+
? asset.originalFilename.trim()
|
|
111
|
+
: null;
|
|
112
|
+
const normalizedIncomingTaskId = typeof asset.taskId === 'string' && asset.taskId.trim().length > 0
|
|
113
|
+
? asset.taskId.trim()
|
|
114
|
+
: null;
|
|
115
|
+
const normalizedIncomingLinkRole = asset.linkRole === 'reference'
|
|
116
|
+
? 'reference'
|
|
117
|
+
: asset.kind === 'image'
|
|
118
|
+
? 'image'
|
|
119
|
+
: 'attachment';
|
|
120
|
+
const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
|
|
121
|
+
const referenceNumberMismatch = Boolean(existingAsset.referenceNumber
|
|
122
|
+
&& normalizedIncomingReference
|
|
123
|
+
&& existingAsset.referenceNumber !== normalizedIncomingReference);
|
|
124
|
+
if (referenceNumberMismatch) {
|
|
125
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Detected asset reference number mismatch during normal sync.', {
|
|
126
|
+
source: 'assets.referenceNumber.mismatch',
|
|
127
|
+
path: asset.path,
|
|
128
|
+
assetId: existingAsset.assetId,
|
|
129
|
+
existingReferenceNumber: existingAsset.referenceNumber,
|
|
130
|
+
incomingReferenceNumber: normalizedIncomingReference
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return referenceNumberMismatch
|
|
134
|
+
|| (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
|
|
135
|
+
|| (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
|
|
136
|
+
|| (existingAsset.originalFilename || null) !== normalizedIncomingFilename
|
|
137
|
+
|| (primaryLink?.taskId || null) !== normalizedIncomingTaskId
|
|
138
|
+
|| (primaryLink?.displayName || null) !== normalizedIncomingCaption
|
|
139
|
+
|| ((primaryLink?.role === 'reference'
|
|
140
|
+
? 'reference'
|
|
141
|
+
: existingAsset.kind === 'image'
|
|
142
|
+
? 'image'
|
|
143
|
+
: 'attachment')) !== normalizedIncomingLinkRole;
|
|
144
|
+
}
|
|
145
|
+
export function reconcileWorkspaceSyncLinks(deps, input) {
|
|
146
|
+
if (!deps.workspaceAssetStore)
|
|
147
|
+
return;
|
|
148
|
+
const workspaceId = String(input.workspaceId || '').trim() || 'default';
|
|
149
|
+
const assetId = String(input.assetId || '').trim();
|
|
150
|
+
if (!assetId)
|
|
151
|
+
return;
|
|
152
|
+
const desiredTaskId = String(input.taskId || '').trim();
|
|
153
|
+
const desiredRole = input.role === 'reference'
|
|
154
|
+
? 'reference'
|
|
155
|
+
: input.role === 'image'
|
|
156
|
+
? 'image'
|
|
157
|
+
: 'attachment';
|
|
158
|
+
const linkUpdatedAt = String(input.updatedAt || '').trim() || new Date().toISOString();
|
|
159
|
+
const existingLinks = deps.workspaceAssetStore.listLinksForAsset(assetId, workspaceId, { includeDeleted: true });
|
|
160
|
+
if (desiredTaskId) {
|
|
161
|
+
deps.workspaceAssetStore.upsertLink({
|
|
162
|
+
workspaceId,
|
|
163
|
+
assetId,
|
|
164
|
+
taskId: desiredTaskId,
|
|
165
|
+
role: desiredRole,
|
|
166
|
+
displayName: typeof input.displayName === 'string' && input.displayName.trim().length > 0 ? input.displayName.trim() : null,
|
|
167
|
+
updatedAt: linkUpdatedAt,
|
|
168
|
+
deletedAt: null
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (desiredTaskId) {
|
|
172
|
+
for (const link of existingLinks) {
|
|
173
|
+
if (link.taskId === desiredTaskId && link.role === desiredRole)
|
|
174
|
+
continue;
|
|
175
|
+
if (link.deletedAt)
|
|
176
|
+
continue;
|
|
177
|
+
deps.workspaceAssetStore.deleteLink({
|
|
178
|
+
workspaceId,
|
|
179
|
+
assetId,
|
|
180
|
+
taskId: link.taskId,
|
|
181
|
+
role: link.role
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export function listWorkspaceSyncDocumentDeletesSnapshot(deps, workspaceId = 'default') {
|
|
187
|
+
if (!deps.workspaceAssetStore)
|
|
188
|
+
return [];
|
|
189
|
+
const entries = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
|
|
190
|
+
includeDeleted: true,
|
|
191
|
+
kind: 'document'
|
|
192
|
+
})
|
|
193
|
+
.filter((asset) => typeof asset.deletedAt === 'string' && asset.deletedAt.trim().length > 0)
|
|
194
|
+
.map((asset) => {
|
|
195
|
+
const normalizedPath = deps.normalizeWorkspaceSyncDocumentPath(asset.storageKey);
|
|
196
|
+
if (!normalizedPath)
|
|
197
|
+
return null;
|
|
198
|
+
return {
|
|
199
|
+
path: normalizedPath,
|
|
200
|
+
deletedAt: String(asset.deletedAt || '').trim(),
|
|
201
|
+
assetId: asset.assetId
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
return entries.filter((entry) => Boolean(entry && entry.path && entry.deletedAt));
|
|
205
|
+
}
|
|
206
|
+
export async function listWorkspaceSyncDocumentsSnapshot(deps, basePath, workspaceId = 'default') {
|
|
207
|
+
const root = path.resolve(basePath);
|
|
208
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
209
|
+
const candidatePaths = new Set();
|
|
210
|
+
const docsRoot = path.join(root, 'docs');
|
|
211
|
+
const collectMarkdownPaths = (directory) => {
|
|
212
|
+
if (!fs.existsSync(directory))
|
|
213
|
+
return;
|
|
214
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
const absolutePath = path.join(directory, entry.name);
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
collectMarkdownPaths(absolutePath);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!entry.isFile())
|
|
222
|
+
continue;
|
|
223
|
+
const relativeFromRoot = path.relative(root, absolutePath).replace(/\\/g, '/');
|
|
224
|
+
const normalized = deps.normalizeWorkspaceSyncDocumentPath(relativeFromRoot);
|
|
225
|
+
if (normalized)
|
|
226
|
+
candidatePaths.add(normalized);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
collectMarkdownPaths(docsRoot);
|
|
230
|
+
if (deps.workspaceAssetStore) {
|
|
231
|
+
const canonicalAssets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
|
|
232
|
+
includeDeleted: false,
|
|
233
|
+
kind: 'document'
|
|
234
|
+
});
|
|
235
|
+
for (const asset of canonicalAssets) {
|
|
236
|
+
const normalized = deps.normalizeWorkspaceSyncDocumentPath(asset.storageKey);
|
|
237
|
+
if (normalized)
|
|
238
|
+
candidatePaths.add(normalized);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const uniquePaths = Array.from(candidatePaths).sort((a, b) => a.localeCompare(b));
|
|
242
|
+
const workspaceCache = deps.workspaceDocumentSnapshotCache.get(workspaceId) || new Map();
|
|
243
|
+
deps.workspaceDocumentSnapshotCache.set(workspaceId, workspaceCache);
|
|
244
|
+
const remainingPaths = new Set(uniquePaths);
|
|
245
|
+
const docs = [];
|
|
246
|
+
for (const relativePath of uniquePaths) {
|
|
247
|
+
try {
|
|
248
|
+
const canonicalAsset = deps.workspaceAssetStore?.getByStorageKey(relativePath, workspaceId);
|
|
249
|
+
let stat = null;
|
|
250
|
+
let content = null;
|
|
251
|
+
let updatedAt = '';
|
|
252
|
+
if (canonicalAsset?.kind === 'document') {
|
|
253
|
+
updatedAt = resolveCanonicalAssetSyncLinkState(deps, canonicalAsset, workspaceId).effectiveUpdatedAt;
|
|
254
|
+
if (canonicalAsset.storageProvider === 'r2' && objectStorageClient) {
|
|
255
|
+
const object = await objectStorageClient.getObject(relativePath);
|
|
256
|
+
if (!object?.body)
|
|
257
|
+
continue;
|
|
258
|
+
content = object.body.toString('utf8');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const absPath = path.resolve(path.join(root, relativePath));
|
|
262
|
+
if (absPath !== root && !absPath.startsWith(`${root}${path.sep}`))
|
|
263
|
+
continue;
|
|
264
|
+
if (!fs.existsSync(absPath))
|
|
265
|
+
continue;
|
|
266
|
+
stat = fs.statSync(absPath);
|
|
267
|
+
if (!stat.isFile())
|
|
268
|
+
continue;
|
|
269
|
+
content = fs.readFileSync(absPath, 'utf8');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const absPath = path.resolve(path.join(basePath, relativePath));
|
|
274
|
+
const safeRoot = path.resolve(basePath);
|
|
275
|
+
if (absPath !== safeRoot && !absPath.startsWith(`${safeRoot}${path.sep}`))
|
|
276
|
+
continue;
|
|
277
|
+
stat = fs.statSync(absPath);
|
|
278
|
+
if (!stat.isFile())
|
|
279
|
+
continue;
|
|
280
|
+
content = fs.readFileSync(absPath, 'utf8');
|
|
281
|
+
}
|
|
282
|
+
if (content == null)
|
|
283
|
+
continue;
|
|
284
|
+
const cached = workspaceCache.get(relativePath);
|
|
285
|
+
if (cached
|
|
286
|
+
&& cached.size === Buffer.byteLength(content, 'utf8')
|
|
287
|
+
&& (canonicalAsset
|
|
288
|
+
? cached.updatedAt === updatedAt
|
|
289
|
+
: (stat != null && cached.mtimeMs === stat.mtimeMs && cached.ctimeMs === stat.ctimeMs))) {
|
|
290
|
+
docs.push({
|
|
291
|
+
path: relativePath,
|
|
292
|
+
updatedAt: cached.updatedAt,
|
|
293
|
+
content: cached.content,
|
|
294
|
+
...getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId)
|
|
295
|
+
});
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (!canonicalAsset) {
|
|
299
|
+
const contentHash = createHash('sha256').update(content).digest('hex');
|
|
300
|
+
const existing = deps.core.getDocumentWatermark(workspaceId, relativePath);
|
|
301
|
+
if (existing && existing.contentHash === contentHash) {
|
|
302
|
+
updatedAt = existing.updatedAt;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
updatedAt = stat && Number.isFinite(stat.mtimeMs)
|
|
306
|
+
? new Date(stat.mtimeMs).toISOString()
|
|
307
|
+
: new Date().toISOString();
|
|
308
|
+
try {
|
|
309
|
+
deps.core.upsertDocumentWatermark(workspaceId, relativePath, contentHash, updatedAt);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to persist document watermark during snapshot.', {
|
|
313
|
+
source: 'documents.snapshot.upsertWatermark',
|
|
314
|
+
path: relativePath,
|
|
315
|
+
error: String(error?.message || error || '')
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
docs.push({
|
|
321
|
+
path: relativePath,
|
|
322
|
+
updatedAt,
|
|
323
|
+
content,
|
|
324
|
+
...getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId)
|
|
325
|
+
});
|
|
326
|
+
workspaceCache.set(relativePath, {
|
|
327
|
+
path: relativePath,
|
|
328
|
+
mtimeMs: stat?.mtimeMs ?? 0,
|
|
329
|
+
ctimeMs: stat?.ctimeMs ?? 0,
|
|
330
|
+
size: Buffer.byteLength(content, 'utf8'),
|
|
331
|
+
updatedAt,
|
|
332
|
+
content
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to read sync document snapshot entry.', {
|
|
337
|
+
source: 'documents.snapshot.readEntry',
|
|
338
|
+
path: relativePath,
|
|
339
|
+
error: String(error?.message || error || '')
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const cachedPath of Array.from(workspaceCache.keys())) {
|
|
344
|
+
if (!remainingPaths.has(cachedPath))
|
|
345
|
+
workspaceCache.delete(cachedPath);
|
|
346
|
+
}
|
|
347
|
+
return docs;
|
|
348
|
+
}
|
|
349
|
+
export async function listWorkspaceSyncDocumentIndex(deps, basePath, workspaceId = 'default') {
|
|
350
|
+
const root = path.resolve(basePath);
|
|
351
|
+
const candidatePaths = new Set();
|
|
352
|
+
const docsRoot = path.join(root, 'docs');
|
|
353
|
+
const collectMarkdownPaths = (directory) => {
|
|
354
|
+
if (!fs.existsSync(directory))
|
|
355
|
+
return;
|
|
356
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
357
|
+
for (const entry of entries) {
|
|
358
|
+
const absolutePath = path.join(directory, entry.name);
|
|
359
|
+
if (entry.isDirectory()) {
|
|
360
|
+
collectMarkdownPaths(absolutePath);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (!entry.isFile())
|
|
364
|
+
continue;
|
|
365
|
+
const relativeFromRoot = path.relative(root, absolutePath).replace(/\\/g, '/');
|
|
366
|
+
const normalized = deps.normalizeWorkspaceSyncDocumentPath(relativeFromRoot);
|
|
367
|
+
if (normalized)
|
|
368
|
+
candidatePaths.add(normalized);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
collectMarkdownPaths(docsRoot);
|
|
372
|
+
if (deps.workspaceAssetStore) {
|
|
373
|
+
const canonicalAssets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
|
|
374
|
+
includeDeleted: false,
|
|
375
|
+
kind: 'document'
|
|
376
|
+
});
|
|
377
|
+
for (const asset of canonicalAssets) {
|
|
378
|
+
const normalized = deps.normalizeWorkspaceSyncDocumentPath(asset.storageKey);
|
|
379
|
+
if (normalized)
|
|
380
|
+
candidatePaths.add(normalized);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const docs = [];
|
|
384
|
+
for (const relativePath of Array.from(candidatePaths).sort((a, b) => a.localeCompare(b))) {
|
|
385
|
+
try {
|
|
386
|
+
const canonicalAsset = deps.workspaceAssetStore?.getByStorageKey(relativePath, workspaceId);
|
|
387
|
+
let updatedAt = '';
|
|
388
|
+
if (canonicalAsset?.kind === 'document') {
|
|
389
|
+
updatedAt = resolveCanonicalAssetSyncLinkState(deps, canonicalAsset, workspaceId).effectiveUpdatedAt;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const absPath = path.resolve(path.join(basePath, relativePath));
|
|
393
|
+
const safeRoot = path.resolve(basePath);
|
|
394
|
+
if (absPath !== safeRoot && !absPath.startsWith(`${safeRoot}${path.sep}`))
|
|
395
|
+
continue;
|
|
396
|
+
if (!fs.existsSync(absPath))
|
|
397
|
+
continue;
|
|
398
|
+
const stat = fs.statSync(absPath);
|
|
399
|
+
if (!stat.isFile())
|
|
400
|
+
continue;
|
|
401
|
+
const existing = deps.core.getDocumentWatermark(workspaceId, relativePath);
|
|
402
|
+
updatedAt = existing?.updatedAt
|
|
403
|
+
|| (Number.isFinite(stat.mtimeMs) ? new Date(stat.mtimeMs).toISOString() : new Date().toISOString());
|
|
404
|
+
}
|
|
405
|
+
if (!Number.isFinite(Date.parse(String(updatedAt || '').trim())))
|
|
406
|
+
continue;
|
|
407
|
+
docs.push({
|
|
408
|
+
path: relativePath,
|
|
409
|
+
updatedAt: String(updatedAt || '').trim(),
|
|
410
|
+
...getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId)
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to index sync document snapshot entry.', {
|
|
415
|
+
source: 'documents.snapshot.indexEntry',
|
|
416
|
+
path: relativePath,
|
|
417
|
+
error: String(error?.message || error || '')
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return docs;
|
|
422
|
+
}
|
|
423
|
+
export async function listWorkspaceSyncBinaryAssetsSnapshot(deps, basePath, workspaceId = 'default') {
|
|
424
|
+
if (!deps.workspaceAssetStore)
|
|
425
|
+
return [];
|
|
426
|
+
const root = path.resolve(basePath);
|
|
427
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
428
|
+
const assets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
|
|
429
|
+
includeDeleted: false
|
|
430
|
+
});
|
|
431
|
+
const snapshots = [];
|
|
432
|
+
for (const asset of assets) {
|
|
433
|
+
if (asset.kind !== 'image' && asset.kind !== 'file')
|
|
434
|
+
continue;
|
|
435
|
+
if (asset.storageProvider === 'external')
|
|
436
|
+
continue;
|
|
437
|
+
const storageKey = String(asset.storageKey || '').trim();
|
|
438
|
+
if (!storageKey)
|
|
439
|
+
continue;
|
|
440
|
+
if (!deps.normalizeTaskDataRelativePath(storageKey))
|
|
441
|
+
continue;
|
|
442
|
+
try {
|
|
443
|
+
let buffer = null;
|
|
444
|
+
if (asset.storageProvider === 'r2' && objectStorageClient) {
|
|
445
|
+
const object = await objectStorageClient.getObject(storageKey);
|
|
446
|
+
buffer = object?.body || null;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
const absolutePath = path.resolve(root, storageKey);
|
|
450
|
+
if (absolutePath !== root && !absolutePath.startsWith(`${root}${path.sep}`))
|
|
451
|
+
continue;
|
|
452
|
+
if (!fs.existsSync(absolutePath)) {
|
|
453
|
+
deps.recordSyncDiagnostic(workspaceId, 'push', 'Asset registered in DB but file missing from disk — skipped from sync payload.', {
|
|
454
|
+
source: 'assets.snapshot.missingFile',
|
|
455
|
+
assetId: asset.assetId,
|
|
456
|
+
path: storageKey
|
|
457
|
+
});
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
buffer = fs.readFileSync(absolutePath);
|
|
461
|
+
}
|
|
462
|
+
if (!buffer) {
|
|
463
|
+
deps.recordSyncDiagnostic(workspaceId, 'push', 'Asset registered in DB but content could not be read — skipped from sync payload.', {
|
|
464
|
+
source: 'assets.snapshot.emptyBuffer',
|
|
465
|
+
assetId: asset.assetId,
|
|
466
|
+
path: storageKey,
|
|
467
|
+
storageProvider: asset.storageProvider
|
|
468
|
+
});
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const { primaryLink, effectiveUpdatedAt } = resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId);
|
|
472
|
+
snapshots.push({
|
|
473
|
+
path: storageKey,
|
|
474
|
+
updatedAt: effectiveUpdatedAt,
|
|
475
|
+
contentBase64: buffer.toString('base64'),
|
|
476
|
+
assetId: asset.assetId,
|
|
477
|
+
kind: asset.kind,
|
|
478
|
+
mimeType: asset.mimeType || 'application/octet-stream',
|
|
479
|
+
referenceNumber: asset.referenceNumber,
|
|
480
|
+
taskId: primaryLink?.taskId || null,
|
|
481
|
+
logicalName: asset.logicalName || null,
|
|
482
|
+
caption: primaryLink?.displayName || null,
|
|
483
|
+
originalFilename: asset.originalFilename || path.basename(storageKey),
|
|
484
|
+
linkRole: primaryLink?.role || (asset.kind === 'image' ? 'image' : 'attachment')
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to read binary asset snapshot entry.', {
|
|
489
|
+
source: 'assets.snapshot.readEntry',
|
|
490
|
+
path: storageKey,
|
|
491
|
+
error: String(error?.message || error || '')
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return snapshots;
|
|
496
|
+
}
|
|
497
|
+
export async function listWorkspaceSyncBinaryAssetIndex(deps, workspaceId = 'default') {
|
|
498
|
+
if (!deps.workspaceAssetStore)
|
|
499
|
+
return [];
|
|
500
|
+
const assets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
|
|
501
|
+
includeDeleted: false
|
|
502
|
+
});
|
|
503
|
+
return assets
|
|
504
|
+
.filter((asset) => asset.kind === 'image' || asset.kind === 'file')
|
|
505
|
+
.filter((asset) => asset.storageProvider !== 'external')
|
|
506
|
+
.filter((asset) => {
|
|
507
|
+
const storageKey = String(asset.storageKey || '').trim();
|
|
508
|
+
return Boolean(storageKey && deps.normalizeTaskDataRelativePath(storageKey));
|
|
509
|
+
})
|
|
510
|
+
.map((asset) => {
|
|
511
|
+
const storageKey = String(asset.storageKey || '').trim();
|
|
512
|
+
const { primaryLink, effectiveUpdatedAt } = resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId);
|
|
513
|
+
const assetKind = asset.kind === 'image' ? 'image' : 'file';
|
|
514
|
+
return {
|
|
515
|
+
path: storageKey,
|
|
516
|
+
updatedAt: effectiveUpdatedAt,
|
|
517
|
+
assetId: asset.assetId,
|
|
518
|
+
kind: assetKind,
|
|
519
|
+
mimeType: asset.mimeType || 'application/octet-stream',
|
|
520
|
+
referenceNumber: asset.referenceNumber,
|
|
521
|
+
taskId: primaryLink?.taskId || null,
|
|
522
|
+
logicalName: asset.logicalName || null,
|
|
523
|
+
caption: primaryLink?.displayName || null,
|
|
524
|
+
originalFilename: asset.originalFilename || path.basename(storageKey),
|
|
525
|
+
linkRole: primaryLink?.role || (asset.kind === 'image' ? 'image' : 'attachment')
|
|
526
|
+
};
|
|
527
|
+
})
|
|
528
|
+
.filter((entry) => Number.isFinite(Date.parse(entry.updatedAt)) && entry.path.length > 0);
|
|
529
|
+
}
|
|
530
|
+
export async function loadWorkspaceSyncDocumentPayload(deps, basePath, workspaceId, relativePath) {
|
|
531
|
+
const normalizedPath = deps.normalizeWorkspaceSyncDocumentPath(relativePath);
|
|
532
|
+
if (!normalizedPath)
|
|
533
|
+
return null;
|
|
534
|
+
const root = path.resolve(basePath);
|
|
535
|
+
const absolutePath = path.resolve(path.join(root, normalizedPath));
|
|
536
|
+
if (absolutePath !== root && !absolutePath.startsWith(`${root}${path.sep}`))
|
|
537
|
+
return null;
|
|
538
|
+
let content = '';
|
|
539
|
+
let stat = null;
|
|
540
|
+
try {
|
|
541
|
+
if (!fs.existsSync(absolutePath))
|
|
542
|
+
return null;
|
|
543
|
+
stat = fs.statSync(absolutePath);
|
|
544
|
+
if (!stat.isFile())
|
|
545
|
+
return null;
|
|
546
|
+
content = fs.readFileSync(absolutePath, 'utf8');
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to load targeted sync document payload.', {
|
|
550
|
+
source: 'documents.snapshot.loadPayload',
|
|
551
|
+
path: normalizedPath,
|
|
552
|
+
error: String(error?.message || error || '')
|
|
553
|
+
});
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
const workspaceCache = deps.workspaceDocumentSnapshotCache.get(workspaceId)
|
|
557
|
+
?? (() => {
|
|
558
|
+
const next = new Map();
|
|
559
|
+
deps.workspaceDocumentSnapshotCache.set(workspaceId, next);
|
|
560
|
+
return next;
|
|
561
|
+
})();
|
|
562
|
+
const cached = workspaceCache.get(normalizedPath);
|
|
563
|
+
const canonicalAsset = deps.workspaceAssetStore?.getByStorageKey(normalizedPath, workspaceId);
|
|
564
|
+
let updatedAt = '';
|
|
565
|
+
if (canonicalAsset?.kind === 'document') {
|
|
566
|
+
updatedAt = resolveCanonicalAssetSyncLinkState(deps, canonicalAsset, workspaceId).effectiveUpdatedAt;
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
const contentHash = createHash('sha256').update(content).digest('hex');
|
|
570
|
+
const existing = deps.core.getDocumentWatermark(workspaceId, normalizedPath);
|
|
571
|
+
if (existing && existing.contentHash === contentHash) {
|
|
572
|
+
updatedAt = existing.updatedAt;
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
updatedAt = stat && Number.isFinite(stat.mtimeMs)
|
|
576
|
+
? new Date(stat.mtimeMs).toISOString()
|
|
577
|
+
: new Date().toISOString();
|
|
578
|
+
try {
|
|
579
|
+
deps.core.upsertDocumentWatermark(workspaceId, normalizedPath, contentHash, updatedAt);
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to persist document watermark during targeted payload load.', {
|
|
583
|
+
source: 'documents.snapshot.loadPayload.upsertWatermark',
|
|
584
|
+
path: normalizedPath,
|
|
585
|
+
error: String(error?.message || error || '')
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (cached
|
|
591
|
+
&& stat
|
|
592
|
+
&& cached.size === Buffer.byteLength(content, 'utf8')
|
|
593
|
+
&& (canonicalAsset
|
|
594
|
+
? cached.updatedAt === updatedAt
|
|
595
|
+
: cached.mtimeMs === stat.mtimeMs && cached.ctimeMs === stat.ctimeMs)) {
|
|
596
|
+
return {
|
|
597
|
+
path: normalizedPath,
|
|
598
|
+
updatedAt: cached.updatedAt,
|
|
599
|
+
content: cached.content,
|
|
600
|
+
...getCanonicalSyncDocumentMetadata(deps, normalizedPath, workspaceId)
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
workspaceCache.set(normalizedPath, {
|
|
604
|
+
path: normalizedPath,
|
|
605
|
+
mtimeMs: stat?.mtimeMs ?? 0,
|
|
606
|
+
ctimeMs: stat?.ctimeMs ?? 0,
|
|
607
|
+
size: Buffer.byteLength(content, 'utf8'),
|
|
608
|
+
updatedAt,
|
|
609
|
+
content
|
|
610
|
+
});
|
|
611
|
+
return {
|
|
612
|
+
path: normalizedPath,
|
|
613
|
+
updatedAt,
|
|
614
|
+
content,
|
|
615
|
+
...getCanonicalSyncDocumentMetadata(deps, normalizedPath, workspaceId)
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
export async function loadWorkspaceSyncBinaryAssetPayload(deps, basePath, workspaceId, assetPath) {
|
|
619
|
+
const normalizedPath = deps.normalizeTaskDataRelativePath(assetPath);
|
|
620
|
+
if (!normalizedPath)
|
|
621
|
+
return null;
|
|
622
|
+
if (!deps.workspaceAssetStore)
|
|
623
|
+
return null;
|
|
624
|
+
const asset = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
|
|
625
|
+
if (!asset || (asset.kind !== 'image' && asset.kind !== 'file') || asset.storageProvider === 'external') {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
629
|
+
let buffer = null;
|
|
630
|
+
try {
|
|
631
|
+
if (asset.storageProvider === 'r2' && objectStorageClient) {
|
|
632
|
+
const object = await objectStorageClient.getObject(normalizedPath);
|
|
633
|
+
buffer = object?.body || null;
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
const root = path.resolve(basePath);
|
|
637
|
+
const absolutePath = path.resolve(root, normalizedPath);
|
|
638
|
+
if (absolutePath !== root && !absolutePath.startsWith(`${root}${path.sep}`))
|
|
639
|
+
return null;
|
|
640
|
+
if (!fs.existsSync(absolutePath))
|
|
641
|
+
return null;
|
|
642
|
+
buffer = fs.readFileSync(absolutePath);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
catch (error) {
|
|
646
|
+
deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to load targeted binary asset payload.', {
|
|
647
|
+
source: 'assets.snapshot.loadPayload',
|
|
648
|
+
path: normalizedPath,
|
|
649
|
+
assetId: asset.assetId,
|
|
650
|
+
error: String(error?.message || error || '')
|
|
651
|
+
});
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
if (!buffer)
|
|
655
|
+
return null;
|
|
656
|
+
const { primaryLink, effectiveUpdatedAt } = resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId);
|
|
657
|
+
return {
|
|
658
|
+
path: normalizedPath,
|
|
659
|
+
updatedAt: effectiveUpdatedAt,
|
|
660
|
+
contentBase64: buffer.toString('base64'),
|
|
661
|
+
assetId: asset.assetId,
|
|
662
|
+
kind: asset.kind,
|
|
663
|
+
mimeType: asset.mimeType || 'application/octet-stream',
|
|
664
|
+
referenceNumber: asset.referenceNumber,
|
|
665
|
+
taskId: primaryLink?.taskId || null,
|
|
666
|
+
logicalName: asset.logicalName || null,
|
|
667
|
+
caption: primaryLink?.displayName || null,
|
|
668
|
+
originalFilename: asset.originalFilename || path.basename(normalizedPath),
|
|
669
|
+
linkRole: primaryLink?.role || (asset.kind === 'image' ? 'image' : 'attachment')
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
export function listWorkspaceSyncBinaryAssetDeletesSnapshot(deps, workspaceId = 'default') {
|
|
673
|
+
if (!deps.workspaceAssetStore)
|
|
674
|
+
return [];
|
|
675
|
+
const entries = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
|
|
676
|
+
includeDeleted: true
|
|
677
|
+
})
|
|
678
|
+
.filter((asset) => ((asset.kind === 'image' || asset.kind === 'file')
|
|
679
|
+
&& asset.storageProvider !== 'external'
|
|
680
|
+
&& typeof asset.deletedAt === 'string'
|
|
681
|
+
&& asset.deletedAt.trim().length > 0))
|
|
682
|
+
.map((asset) => {
|
|
683
|
+
const normalizedPath = deps.normalizeTaskDataRelativePath(asset.storageKey);
|
|
684
|
+
if (!normalizedPath)
|
|
685
|
+
return null;
|
|
686
|
+
return {
|
|
687
|
+
path: normalizedPath,
|
|
688
|
+
deletedAt: String(asset.deletedAt || '').trim(),
|
|
689
|
+
assetId: asset.assetId
|
|
690
|
+
};
|
|
691
|
+
});
|
|
692
|
+
return entries.filter((entry) => Boolean(entry && entry.path && entry.deletedAt));
|
|
693
|
+
}
|
|
694
|
+
export async function writeWorkspaceSyncDocument(deps, basePath, doc, workspaceId = 'default', options) {
|
|
695
|
+
const normalizedPath = deps.normalizeWorkspaceSyncDocumentPath(doc.path);
|
|
696
|
+
if (!normalizedPath)
|
|
697
|
+
return;
|
|
698
|
+
const root = path.resolve(basePath);
|
|
699
|
+
const targetPath = path.resolve(root, normalizedPath);
|
|
700
|
+
if (targetPath !== root && !targetPath.startsWith(`${root}${path.sep}`))
|
|
701
|
+
return;
|
|
702
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
703
|
+
const content = String(doc.content || '');
|
|
704
|
+
if (objectStorageClient) {
|
|
705
|
+
try {
|
|
706
|
+
await objectStorageClient.putObject(normalizedPath, Buffer.from(content, 'utf8'), 'text/markdown');
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to write document to object storage.', {
|
|
710
|
+
source: 'documents.write.putObject',
|
|
711
|
+
path: normalizedPath,
|
|
712
|
+
error: String(error?.message || error || '')
|
|
713
|
+
});
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
719
|
+
fs.writeFileSync(targetPath, content, 'utf8');
|
|
720
|
+
}
|
|
721
|
+
const updatedAt = String(doc.updatedAt || '').trim();
|
|
722
|
+
const watermarkMs = Date.parse(updatedAt);
|
|
723
|
+
if (!objectStorageClient && Number.isFinite(watermarkMs) && watermarkMs > 0) {
|
|
724
|
+
const when = new Date(watermarkMs);
|
|
725
|
+
fs.utimesSync(targetPath, when, when);
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
const contentHash = createHash('sha256').update(content).digest('hex');
|
|
729
|
+
deps.core.upsertDocumentWatermark(workspaceId, normalizedPath, contentHash, updatedAt || new Date().toISOString());
|
|
730
|
+
if (deps.workspaceAssetStore) {
|
|
731
|
+
const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
|
|
732
|
+
const effectiveUpdatedAt = updatedAt || new Date().toISOString();
|
|
733
|
+
const incomingReferenceNumber = Number.isFinite(Number(doc.referenceNumber))
|
|
734
|
+
? Math.max(1, Math.floor(Number(doc.referenceNumber)))
|
|
735
|
+
: null;
|
|
736
|
+
const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true
|
|
737
|
+
|| Boolean(existing?.referenceNumber && incomingReferenceNumber && existing.referenceNumber !== incomingReferenceNumber);
|
|
738
|
+
const originalFilename = String(doc.originalFilename || existing?.originalFilename || path.basename(normalizedPath) || 'document.md').trim() || 'document.md';
|
|
739
|
+
const asset = deps.workspaceAssetStore.upsertAsset({
|
|
740
|
+
assetId: String(doc.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
|
|
741
|
+
workspaceId,
|
|
742
|
+
kind: 'document',
|
|
743
|
+
logicalName: typeof doc.logicalName === 'string' && doc.logicalName.trim().length > 0
|
|
744
|
+
? doc.logicalName.trim()
|
|
745
|
+
: existing?.logicalName || null,
|
|
746
|
+
originalFilename,
|
|
747
|
+
mimeType: existing?.mimeType || 'text/markdown',
|
|
748
|
+
storageProvider: objectStorageClient ? 'r2' : 'local',
|
|
749
|
+
storageKey: normalizedPath,
|
|
750
|
+
contentSha256: contentHash,
|
|
751
|
+
sizeBytes: Buffer.byteLength(content, 'utf8'),
|
|
752
|
+
documentId: String(doc.documentId || existing?.documentId || `doc-${randomUUID().replace(/-/g, '')}`),
|
|
753
|
+
referenceNumber: incomingReferenceNumber ?? existing?.referenceNumber ?? null,
|
|
754
|
+
version: Number.isFinite(Number(doc.version))
|
|
755
|
+
? Math.max(1, Math.floor(Number(doc.version)))
|
|
756
|
+
: Math.max(1, Number(existing?.version || 0) + 1),
|
|
757
|
+
isLatest: true,
|
|
758
|
+
scanStatus: existing?.scanStatus || 'clean',
|
|
759
|
+
scanEngine: existing?.scanEngine || null,
|
|
760
|
+
scanDetails: existing?.scanDetails || null,
|
|
761
|
+
createdByUserId: existing?.createdByUserId || null,
|
|
762
|
+
createdAt: existing?.createdAt || effectiveUpdatedAt,
|
|
763
|
+
updatedAt: effectiveUpdatedAt,
|
|
764
|
+
deletedAt: null,
|
|
765
|
+
purgeAfter: null,
|
|
766
|
+
allowReferenceNumberReassignment
|
|
767
|
+
});
|
|
768
|
+
const taskId = String(doc.taskId || '').trim();
|
|
769
|
+
reconcileWorkspaceSyncLinks(deps, {
|
|
770
|
+
workspaceId,
|
|
771
|
+
assetId: asset.assetId,
|
|
772
|
+
taskId,
|
|
773
|
+
displayName: typeof doc.caption === 'string' && doc.caption.trim().length > 0 ? doc.caption.trim() : null,
|
|
774
|
+
role: doc.linkRole === 'reference' ? 'reference' : 'attachment',
|
|
775
|
+
updatedAt: effectiveUpdatedAt
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
catch (error) {
|
|
780
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to persist document watermark after write.', {
|
|
781
|
+
source: 'documents.write.upsertWatermark',
|
|
782
|
+
path: normalizedPath,
|
|
783
|
+
error: String(error?.message || error || '')
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
deps.workspaceDocumentSnapshotCache.get(workspaceId)?.delete(normalizedPath);
|
|
787
|
+
}
|
|
788
|
+
export async function writeWorkspaceSyncBinaryAsset(deps, basePath, asset, workspaceId = 'default', options) {
|
|
789
|
+
const normalizedPath = deps.normalizeTaskDataRelativePath(asset.path);
|
|
790
|
+
if (!normalizedPath)
|
|
791
|
+
return;
|
|
792
|
+
const root = path.resolve(basePath);
|
|
793
|
+
const targetPath = path.resolve(root, normalizedPath);
|
|
794
|
+
if (targetPath !== root && !targetPath.startsWith(`${root}${path.sep}`))
|
|
795
|
+
return;
|
|
796
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
797
|
+
const buffer = Buffer.from(String(asset.contentBase64 || ''), 'base64');
|
|
798
|
+
if (objectStorageClient) {
|
|
799
|
+
try {
|
|
800
|
+
await objectStorageClient.putObject(normalizedPath, buffer, String(asset.mimeType || 'application/octet-stream').trim() || 'application/octet-stream');
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to write binary asset to object storage.', {
|
|
804
|
+
source: 'assets.write.putObject',
|
|
805
|
+
path: normalizedPath,
|
|
806
|
+
error: String(error?.message || error || '')
|
|
807
|
+
});
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
813
|
+
fs.writeFileSync(targetPath, buffer);
|
|
814
|
+
}
|
|
815
|
+
const updatedAt = String(asset.updatedAt || '').trim();
|
|
816
|
+
const watermarkMs = Date.parse(updatedAt);
|
|
817
|
+
if (!objectStorageClient && Number.isFinite(watermarkMs) && watermarkMs > 0) {
|
|
818
|
+
const when = new Date(watermarkMs);
|
|
819
|
+
fs.utimesSync(targetPath, when, when);
|
|
820
|
+
}
|
|
821
|
+
if (!deps.workspaceAssetStore)
|
|
822
|
+
return;
|
|
823
|
+
try {
|
|
824
|
+
const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
|
|
825
|
+
const effectiveUpdatedAt = updatedAt || new Date().toISOString();
|
|
826
|
+
const incomingReferenceNumber = Number.isFinite(Number(asset.referenceNumber))
|
|
827
|
+
? Math.max(1, Math.floor(Number(asset.referenceNumber)))
|
|
828
|
+
: null;
|
|
829
|
+
const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true
|
|
830
|
+
|| Boolean(existing?.referenceNumber && incomingReferenceNumber && existing.referenceNumber !== incomingReferenceNumber);
|
|
831
|
+
const assetRecord = deps.workspaceAssetStore.upsertAsset({
|
|
832
|
+
assetId: String(asset.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
|
|
833
|
+
workspaceId,
|
|
834
|
+
kind: asset.kind === 'image' ? 'image' : 'file',
|
|
835
|
+
logicalName: typeof asset.logicalName === 'string' && asset.logicalName.trim().length > 0
|
|
836
|
+
? asset.logicalName.trim()
|
|
837
|
+
: existing?.logicalName || null,
|
|
838
|
+
originalFilename: String(asset.originalFilename || existing?.originalFilename || path.basename(normalizedPath) || 'asset').trim() || 'asset',
|
|
839
|
+
mimeType: String(asset.mimeType || existing?.mimeType || 'application/octet-stream').trim() || 'application/octet-stream',
|
|
840
|
+
storageProvider: objectStorageClient ? 'r2' : 'local',
|
|
841
|
+
storageKey: normalizedPath,
|
|
842
|
+
contentSha256: createHash('sha256').update(buffer).digest('hex'),
|
|
843
|
+
sizeBytes: buffer.length,
|
|
844
|
+
documentId: null,
|
|
845
|
+
referenceNumber: incomingReferenceNumber ?? existing?.referenceNumber ?? null,
|
|
846
|
+
version: null,
|
|
847
|
+
isLatest: true,
|
|
848
|
+
scanStatus: existing?.scanStatus || 'clean',
|
|
849
|
+
scanEngine: existing?.scanEngine || null,
|
|
850
|
+
scanDetails: existing?.scanDetails || null,
|
|
851
|
+
createdByUserId: existing?.createdByUserId || null,
|
|
852
|
+
createdAt: existing?.createdAt || effectiveUpdatedAt,
|
|
853
|
+
updatedAt: effectiveUpdatedAt,
|
|
854
|
+
deletedAt: null,
|
|
855
|
+
purgeAfter: null,
|
|
856
|
+
allowReferenceNumberReassignment
|
|
857
|
+
});
|
|
858
|
+
const taskId = String(asset.taskId || '').trim();
|
|
859
|
+
reconcileWorkspaceSyncLinks(deps, {
|
|
860
|
+
workspaceId,
|
|
861
|
+
assetId: assetRecord.assetId,
|
|
862
|
+
taskId,
|
|
863
|
+
displayName: typeof asset.caption === 'string' && asset.caption.trim().length > 0 ? asset.caption.trim() : null,
|
|
864
|
+
role: asset.linkRole === 'reference' ? 'reference' : asset.kind === 'image' ? 'image' : 'attachment',
|
|
865
|
+
updatedAt: effectiveUpdatedAt
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
catch (error) {
|
|
869
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to persist binary asset metadata after write.', {
|
|
870
|
+
source: 'assets.write.upsertAsset',
|
|
871
|
+
path: normalizedPath,
|
|
872
|
+
assetId: typeof asset.assetId === 'string' ? asset.assetId : null,
|
|
873
|
+
kind: asset.kind,
|
|
874
|
+
error: String(error?.message || error || '')
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
export function deleteWorkspaceSyncBinaryAsset(deps, basePath, assetPath, workspaceId = 'default') {
|
|
879
|
+
const normalizedPath = deps.normalizeTaskDataRelativePath(assetPath);
|
|
880
|
+
if (!normalizedPath)
|
|
881
|
+
return;
|
|
882
|
+
const root = path.resolve(basePath);
|
|
883
|
+
const targetPath = path.resolve(root, normalizedPath);
|
|
884
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
885
|
+
try {
|
|
886
|
+
if (objectStorageClient) {
|
|
887
|
+
void objectStorageClient.deleteObject(normalizedPath);
|
|
888
|
+
}
|
|
889
|
+
else if (targetPath === root || targetPath.startsWith(`${root}${path.sep}`)) {
|
|
890
|
+
if (fs.existsSync(targetPath))
|
|
891
|
+
fs.unlinkSync(targetPath);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
catch (error) {
|
|
895
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete workspace sync binary asset.', {
|
|
896
|
+
source: 'assets.delete.file',
|
|
897
|
+
path: normalizedPath,
|
|
898
|
+
error: String(error?.message || error || '')
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
if (deps.workspaceAssetStore) {
|
|
902
|
+
const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
|
|
903
|
+
if (existing) {
|
|
904
|
+
const deletedAt = new Date().toISOString();
|
|
905
|
+
deps.workspaceAssetStore.markDeleted(existing.assetId, workspaceId, deletedAt);
|
|
906
|
+
if (existing.kind === 'image') {
|
|
907
|
+
deps.core.deleteAnnotatedAttachmentSessionsForImage({
|
|
908
|
+
workspaceId,
|
|
909
|
+
imageAssetId: existing.assetId,
|
|
910
|
+
deletedAt,
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
const links = deps.workspaceAssetStore.listLinksForAsset(existing.assetId, workspaceId, { includeDeleted: true });
|
|
914
|
+
for (const link of links) {
|
|
915
|
+
deps.workspaceAssetStore.deleteLink({
|
|
916
|
+
workspaceId,
|
|
917
|
+
assetId: existing.assetId,
|
|
918
|
+
taskId: link.taskId,
|
|
919
|
+
role: link.role
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
export function deleteWorkspaceSyncDocument(deps, basePath, docPath, workspaceId = 'default') {
|
|
926
|
+
const normalizedPath = deps.normalizeWorkspaceSyncDocumentPath(docPath);
|
|
927
|
+
if (!normalizedPath)
|
|
928
|
+
return;
|
|
929
|
+
const root = path.resolve(basePath);
|
|
930
|
+
const targetPath = path.resolve(root, normalizedPath);
|
|
931
|
+
if (targetPath !== root && !targetPath.startsWith(`${root}${path.sep}`))
|
|
932
|
+
return;
|
|
933
|
+
const objectStorageClient = deps.getObjectStorageClient();
|
|
934
|
+
try {
|
|
935
|
+
if (objectStorageClient) {
|
|
936
|
+
void objectStorageClient.deleteObject(normalizedPath);
|
|
937
|
+
}
|
|
938
|
+
else if (fs.existsSync(targetPath)) {
|
|
939
|
+
fs.unlinkSync(targetPath);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
catch (error) {
|
|
943
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete workspace sync document.', {
|
|
944
|
+
source: 'documents.delete.file',
|
|
945
|
+
path: normalizedPath,
|
|
946
|
+
error: String(error?.message || error || '')
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
deps.core.deleteDocumentWatermark(workspaceId, normalizedPath);
|
|
951
|
+
if (deps.workspaceAssetStore) {
|
|
952
|
+
const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
|
|
953
|
+
if (existing) {
|
|
954
|
+
const deletedAt = new Date().toISOString();
|
|
955
|
+
deps.workspaceAssetStore.markDeleted(existing.assetId, workspaceId, deletedAt);
|
|
956
|
+
if (existing.kind === 'image') {
|
|
957
|
+
deps.core.deleteAnnotatedAttachmentSessionsForImage({
|
|
958
|
+
workspaceId,
|
|
959
|
+
imageAssetId: existing.assetId,
|
|
960
|
+
deletedAt,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
const links = deps.workspaceAssetStore.listLinksForAsset(existing.assetId, workspaceId, { includeDeleted: true });
|
|
964
|
+
for (const link of links) {
|
|
965
|
+
deps.workspaceAssetStore.deleteLink({
|
|
966
|
+
workspaceId,
|
|
967
|
+
assetId: existing.assetId,
|
|
968
|
+
taskId: link.taskId,
|
|
969
|
+
role: link.role
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
976
|
+
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete document watermark.', {
|
|
977
|
+
source: 'documents.delete.watermark',
|
|
978
|
+
path: normalizedPath,
|
|
979
|
+
error: String(error?.message || error || '')
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
deps.workspaceDocumentSnapshotCache.get(workspaceId)?.delete(normalizedPath);
|
|
983
|
+
}
|
|
984
|
+
export function createContentSyncState(deps) {
|
|
985
|
+
return {
|
|
986
|
+
resolveCanonicalAssetSyncLinkState: (asset, workspaceId) => resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId),
|
|
987
|
+
getCanonicalSyncDocumentMetadata: (relativePath, workspaceId) => getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId),
|
|
988
|
+
hasDocumentSyncMetadataDrift: (workspaceId, doc, options) => hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options),
|
|
989
|
+
hasBinaryAssetSyncMetadataDrift: (workspaceId, asset, options) => hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, options),
|
|
990
|
+
reconcileWorkspaceSyncLinks: (input) => reconcileWorkspaceSyncLinks(deps, input),
|
|
991
|
+
listWorkspaceSyncDocumentDeletesSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentDeletesSnapshot(deps, workspaceId),
|
|
992
|
+
listWorkspaceSyncDocumentIndex: (basePath, workspaceId = 'default') => listWorkspaceSyncDocumentIndex(deps, basePath, workspaceId),
|
|
993
|
+
listWorkspaceSyncDocumentsSnapshot: (basePath, workspaceId = 'default') => listWorkspaceSyncDocumentsSnapshot(deps, basePath, workspaceId),
|
|
994
|
+
loadWorkspaceSyncDocumentPayload: (basePath, workspaceId, relativePath) => loadWorkspaceSyncDocumentPayload(deps, basePath, workspaceId, relativePath),
|
|
995
|
+
listWorkspaceSyncBinaryAssetIndex: (workspaceId = 'default') => listWorkspaceSyncBinaryAssetIndex(deps, workspaceId),
|
|
996
|
+
listWorkspaceSyncBinaryAssetsSnapshot: (basePath, workspaceId = 'default') => listWorkspaceSyncBinaryAssetsSnapshot(deps, basePath, workspaceId),
|
|
997
|
+
loadWorkspaceSyncBinaryAssetPayload: (basePath, workspaceId, assetPath) => loadWorkspaceSyncBinaryAssetPayload(deps, basePath, workspaceId, assetPath),
|
|
998
|
+
listWorkspaceSyncBinaryAssetDeletesSnapshot: (workspaceId = 'default') => listWorkspaceSyncBinaryAssetDeletesSnapshot(deps, workspaceId),
|
|
999
|
+
writeWorkspaceSyncDocument: (basePath, doc, workspaceId = 'default', options) => writeWorkspaceSyncDocument(deps, basePath, doc, workspaceId, options),
|
|
1000
|
+
writeWorkspaceSyncBinaryAsset: (basePath, asset, workspaceId = 'default', options) => writeWorkspaceSyncBinaryAsset(deps, basePath, asset, workspaceId, options),
|
|
1001
|
+
deleteWorkspaceSyncBinaryAsset: (basePath, assetPath, workspaceId = 'default') => deleteWorkspaceSyncBinaryAsset(deps, basePath, assetPath, workspaceId),
|
|
1002
|
+
deleteWorkspaceSyncDocument: (basePath, docPath, workspaceId = 'default') => deleteWorkspaceSyncDocument(deps, basePath, docPath, workspaceId),
|
|
1003
|
+
};
|
|
1004
|
+
}
|