@taskforcehq/taskforce 0.3.300 → 0.3.302

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.
Files changed (153) hide show
  1. package/README.md +19 -0
  2. package/dist/Taskforce.module.css +26 -2
  3. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  4. package/dist/TaskforceCore.js +13 -24
  5. package/dist/TaskforceCore.test.js +167 -152
  6. package/dist/components/features/TaskSettings.js +14 -82
  7. package/dist/components/features/TaskSettings.test.js +33 -70
  8. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  9. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  10. package/dist/components/task/TaskActionHeader.js +6 -3
  11. package/dist/components/task/TaskCard.js +6 -3
  12. package/dist/components/task/TaskForm.js +2 -2
  13. package/dist/components/task/TaskForm.test.js +41 -2
  14. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  15. package/dist/components/views/PlansPage.js +9 -8
  16. package/dist/components/views/StandaloneLayout.js +490 -207
  17. package/dist/components/views/panels/FilterToolbar.d.ts +2 -0
  18. package/dist/components/views/panels/FilterToolbarControls.d.ts +1 -0
  19. package/dist/components/views/panels/FilterToolbarControls.js +1 -1
  20. package/dist/components/views/panels/PlanningDrawer.js +19 -2
  21. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
  22. package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
  23. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  24. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  25. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  26. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  27. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  28. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  29. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  30. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  31. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  32. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  33. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  34. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  35. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  36. package/dist/components/views/standalone/modals/types.js +1 -0
  37. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  38. package/dist/components/views/teamManagementAccess.js +2 -2
  39. package/dist/components/views/teamManagementAccess.test.js +3 -3
  40. package/dist/config/deployment.js +2 -2
  41. package/dist/config/deployment.test.js +5 -3
  42. package/dist/core/AiProfileService.js +7 -2
  43. package/dist/core/AiProfiles.test.js +19 -0
  44. package/dist/core/SyncReconciliationService.d.ts +2 -0
  45. package/dist/core/SyncReconciliationService.js +3 -0
  46. package/dist/core/TaskAuditTimeline.test.js +40 -0
  47. package/dist/core/Taskforce.d.ts +49 -0
  48. package/dist/core/Taskforce.ids.test.js +133 -0
  49. package/dist/core/Taskforce.js +241 -78
  50. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  51. package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
  52. package/dist/core/taskReferencePolicy.d.ts +28 -0
  53. package/dist/core/taskReferencePolicy.js +189 -0
  54. package/dist/core/types.d.ts +2 -0
  55. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  56. package/dist/hooks/useTaskData.js +16 -11
  57. package/dist/hooks/useTaskData.test.js +127 -0
  58. package/dist/hooks/useTaskforce.d.ts +5 -0
  59. package/dist/hooks/useTaskforce.js +63 -15
  60. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  61. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  62. package/dist/mcp/aiProfileBootstrap.js +80 -0
  63. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  64. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  65. package/dist/mcp/clientIntegrations.d.ts +2 -8
  66. package/dist/mcp/clientIntegrations.js +21 -40
  67. package/dist/mcp/clientIntegrations.test.js +28 -2
  68. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  69. package/dist/mcp/clientProfileDefaults.js +37 -0
  70. package/dist/mcp/runtime.d.ts +10 -7
  71. package/dist/mcp/runtime.js +45 -23
  72. package/dist/mcp/runtime.test.js +56 -3
  73. package/dist/migrations/taskSchemaMigrations.js +53 -1
  74. package/dist/qaOpen.test.d.ts +1 -0
  75. package/dist/qaOpen.test.js +56 -0
  76. package/dist/server/index.cookieProxy.test.js +1 -0
  77. package/dist/server/index.d.ts +1 -0
  78. package/dist/server/index.js +15 -1
  79. package/dist/server/index.test.js +14 -1
  80. package/dist/server/routes/admin.js +25 -22
  81. package/dist/server/routes/auth.d.ts +5 -0
  82. package/dist/server/routes/auth.js +350 -16
  83. package/dist/server/routes/billing.js +11 -1
  84. package/dist/server/routes/billing.test.js +32 -2
  85. package/dist/server/routes/documents.js +6 -1
  86. package/dist/server/routes/shared.d.ts +1 -1
  87. package/dist/server/routes/shared.js +11 -3
  88. package/dist/server/routes/sync.integration.test.js +130 -0
  89. package/dist/server/routes/sync.js +226 -1924
  90. package/dist/server/routes/tasks.js +18 -4
  91. package/dist/server/routes.js +26 -19
  92. package/dist/server/routes.test.js +352 -6
  93. package/dist/shared/runtimeContract.d.ts +1 -1
  94. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  95. package/dist/sync/collaborationSyncPayload.js +131 -0
  96. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  97. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  98. package/dist/sync/collaborationSyncState.d.ts +15 -0
  99. package/dist/sync/collaborationSyncState.js +60 -0
  100. package/dist/sync/contentSyncPayload.d.ts +45 -0
  101. package/dist/sync/contentSyncPayload.js +104 -0
  102. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  103. package/dist/sync/contentSyncPayload.test.js +104 -0
  104. package/dist/sync/contentSyncState.d.ts +88 -0
  105. package/dist/sync/contentSyncState.js +743 -0
  106. package/dist/sync/planningSyncPayload.d.ts +9 -0
  107. package/dist/sync/planningSyncPayload.js +82 -0
  108. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  109. package/dist/sync/planningSyncPayload.test.js +68 -0
  110. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  111. package/dist/sync/syncApplyHandlers.js +445 -0
  112. package/dist/sync/taskSyncPayload.d.ts +33 -0
  113. package/dist/sync/taskSyncPayload.js +126 -0
  114. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  115. package/dist/sync/taskSyncPayload.test.js +54 -0
  116. package/dist/sync/workspacePullFeed.d.ts +28 -0
  117. package/dist/sync/workspacePullFeed.js +301 -0
  118. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  119. package/dist/sync/workspacePullFeed.test.js +166 -0
  120. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  121. package/dist/sync/workspaceSyncModel.js +9 -2
  122. package/dist/sync/workspaceSyncModel.test.js +29 -0
  123. package/dist/test/setup.js +1 -1
  124. package/dist/types.d.ts +1 -0
  125. package/dist/ui/assets/{AgentsModule-TVmCgizS.js → AgentsModule-JG5jc3he.js} +1 -1
  126. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CAyhnPeR.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
  127. package/dist/ui/assets/{DocumentWorkspace-BOCw5e4D.js → DocumentWorkspace-iys3zfca.js} +2 -2
  128. package/dist/ui/assets/{InitiativesModule-CN0r02Oi.js → InitiativesModule-DJvLJI3A.js} +1 -1
  129. package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
  130. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
  131. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  132. package/dist/ui/assets/{WorkflowsModule-CY9R2zPB.js → WorkflowsModule-BguHeQaV.js} +1 -1
  133. package/dist/ui/assets/index-MXeWoebC.css +1 -0
  134. package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
  135. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
  136. package/dist/ui/index.html +3 -3
  137. package/dist/utils/billingStatusCache.d.ts +21 -0
  138. package/dist/utils/billingStatusCache.js +70 -0
  139. package/dist/utils/taskActivity.js +17 -4
  140. package/dist/utils/taskActivity.test.js +27 -0
  141. package/dist/utils/taskNormalization.js +10 -2
  142. package/dist/utils/taskReferences.d.ts +11 -1
  143. package/dist/utils/taskReferences.js +33 -1
  144. package/package.json +4 -1
  145. package/scripts/deploy-prod.sh +41 -0
  146. package/scripts/deploy-staging.sh +23 -0
  147. package/scripts/qa-open-lib.mjs +175 -0
  148. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  149. package/scripts/qa-open.mjs +352 -0
  150. package/dist/ui/assets/PlansPage-FcQSNLZd.js +0 -1
  151. package/dist/ui/assets/TaskSettings-Cw5zYD1L.js +0 -8
  152. package/dist/ui/assets/index-Bzqy5QGm.js +0 -6
  153. package/dist/ui/assets/index-DxRiI3tY.css +0 -1
@@ -0,0 +1,743 @@
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
+ if (!allowReferenceNumberReassignment
72
+ && existingAsset.referenceNumber
73
+ && normalizedIncomingReference
74
+ && existingAsset.referenceNumber !== normalizedIncomingReference) {
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 (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
84
+ || existingAsset.version !== normalizedIncomingVersion
85
+ || (existingAsset.documentId || null) !== normalizedIncomingDocumentId
86
+ || (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
87
+ || (existingAsset.originalFilename || null) !== normalizedIncomingFilename
88
+ || (primaryLink?.taskId || null) !== normalizedIncomingTaskId
89
+ || (primaryLink?.displayName || null) !== normalizedIncomingCaption
90
+ || ((primaryLink?.role === 'reference' ? 'reference' : 'attachment')) !== normalizedIncomingLinkRole;
91
+ }
92
+ export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, options) {
93
+ if (!deps.workspaceAssetStore)
94
+ return false;
95
+ const existingAsset = deps.workspaceAssetStore.getByStorageKey(asset.path, workspaceId);
96
+ if (!existingAsset || (existingAsset.kind !== 'image' && existingAsset.kind !== 'file'))
97
+ return false;
98
+ const { primaryLink } = resolveCanonicalAssetSyncLinkState(deps, existingAsset, workspaceId);
99
+ const normalizedIncomingReference = Number.isFinite(Number(asset.referenceNumber))
100
+ ? Math.max(1, Math.floor(Number(asset.referenceNumber)))
101
+ : null;
102
+ const normalizedIncomingLogicalName = typeof asset.logicalName === 'string' && asset.logicalName.trim().length > 0
103
+ ? asset.logicalName.trim()
104
+ : null;
105
+ const normalizedIncomingCaption = typeof asset.caption === 'string' && asset.caption.trim().length > 0
106
+ ? asset.caption.trim()
107
+ : null;
108
+ const normalizedIncomingFilename = typeof asset.originalFilename === 'string' && asset.originalFilename.trim().length > 0
109
+ ? asset.originalFilename.trim()
110
+ : null;
111
+ const normalizedIncomingTaskId = typeof asset.taskId === 'string' && asset.taskId.trim().length > 0
112
+ ? asset.taskId.trim()
113
+ : null;
114
+ const normalizedIncomingLinkRole = asset.linkRole === 'reference'
115
+ ? 'reference'
116
+ : asset.kind === 'image'
117
+ ? 'image'
118
+ : 'attachment';
119
+ const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
120
+ if (!allowReferenceNumberReassignment
121
+ && existingAsset.referenceNumber
122
+ && normalizedIncomingReference
123
+ && existingAsset.referenceNumber !== normalizedIncomingReference) {
124
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Detected asset reference number mismatch during normal sync.', {
125
+ source: 'assets.referenceNumber.mismatch',
126
+ path: asset.path,
127
+ assetId: existingAsset.assetId,
128
+ existingReferenceNumber: existingAsset.referenceNumber,
129
+ incomingReferenceNumber: normalizedIncomingReference
130
+ });
131
+ }
132
+ return (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
133
+ || (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
134
+ || (existingAsset.originalFilename || null) !== normalizedIncomingFilename
135
+ || (primaryLink?.taskId || null) !== normalizedIncomingTaskId
136
+ || (primaryLink?.displayName || null) !== normalizedIncomingCaption
137
+ || ((primaryLink?.role === 'reference'
138
+ ? 'reference'
139
+ : existingAsset.kind === 'image'
140
+ ? 'image'
141
+ : 'attachment')) !== normalizedIncomingLinkRole;
142
+ }
143
+ export function reconcileWorkspaceSyncLinks(deps, input) {
144
+ if (!deps.workspaceAssetStore)
145
+ return;
146
+ const workspaceId = String(input.workspaceId || '').trim() || 'default';
147
+ const assetId = String(input.assetId || '').trim();
148
+ if (!assetId)
149
+ return;
150
+ const desiredTaskId = String(input.taskId || '').trim();
151
+ const desiredRole = input.role === 'reference'
152
+ ? 'reference'
153
+ : input.role === 'image'
154
+ ? 'image'
155
+ : 'attachment';
156
+ const linkUpdatedAt = String(input.updatedAt || '').trim() || new Date().toISOString();
157
+ const existingLinks = deps.workspaceAssetStore.listLinksForAsset(assetId, workspaceId, { includeDeleted: true });
158
+ if (desiredTaskId) {
159
+ deps.workspaceAssetStore.upsertLink({
160
+ workspaceId,
161
+ assetId,
162
+ taskId: desiredTaskId,
163
+ role: desiredRole,
164
+ displayName: typeof input.displayName === 'string' && input.displayName.trim().length > 0 ? input.displayName.trim() : null,
165
+ updatedAt: linkUpdatedAt,
166
+ deletedAt: null
167
+ });
168
+ }
169
+ if (desiredTaskId) {
170
+ for (const link of existingLinks) {
171
+ if (link.taskId === desiredTaskId && link.role === desiredRole)
172
+ continue;
173
+ if (link.deletedAt)
174
+ continue;
175
+ deps.workspaceAssetStore.deleteLink({
176
+ workspaceId,
177
+ assetId,
178
+ taskId: link.taskId,
179
+ role: link.role
180
+ });
181
+ }
182
+ }
183
+ }
184
+ export function listWorkspaceSyncDocumentDeletesSnapshot(deps, workspaceId = 'default') {
185
+ if (!deps.workspaceAssetStore)
186
+ return [];
187
+ const entries = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
188
+ includeDeleted: true,
189
+ kind: 'document'
190
+ })
191
+ .filter((asset) => typeof asset.deletedAt === 'string' && asset.deletedAt.trim().length > 0)
192
+ .map((asset) => {
193
+ const normalizedPath = deps.normalizeWorkspaceSyncDocumentPath(asset.storageKey);
194
+ if (!normalizedPath)
195
+ return null;
196
+ return {
197
+ path: normalizedPath,
198
+ deletedAt: String(asset.deletedAt || '').trim(),
199
+ assetId: asset.assetId
200
+ };
201
+ });
202
+ return entries.filter((entry) => Boolean(entry && entry.path && entry.deletedAt));
203
+ }
204
+ export async function listWorkspaceSyncDocumentsSnapshot(deps, basePath, workspaceId = 'default') {
205
+ const root = path.resolve(basePath);
206
+ const objectStorageClient = deps.getObjectStorageClient();
207
+ const candidatePaths = new Set();
208
+ const docsRoot = path.join(root, 'docs');
209
+ const collectMarkdownPaths = (directory) => {
210
+ if (!fs.existsSync(directory))
211
+ return;
212
+ const entries = fs.readdirSync(directory, { withFileTypes: true });
213
+ for (const entry of entries) {
214
+ const absolutePath = path.join(directory, entry.name);
215
+ if (entry.isDirectory()) {
216
+ collectMarkdownPaths(absolutePath);
217
+ continue;
218
+ }
219
+ if (!entry.isFile())
220
+ continue;
221
+ const relativeFromRoot = path.relative(root, absolutePath).replace(/\\/g, '/');
222
+ const normalized = deps.normalizeWorkspaceSyncDocumentPath(relativeFromRoot);
223
+ if (normalized)
224
+ candidatePaths.add(normalized);
225
+ }
226
+ };
227
+ collectMarkdownPaths(docsRoot);
228
+ if (deps.workspaceAssetStore) {
229
+ const canonicalAssets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
230
+ includeDeleted: false,
231
+ kind: 'document'
232
+ });
233
+ for (const asset of canonicalAssets) {
234
+ const normalized = deps.normalizeWorkspaceSyncDocumentPath(asset.storageKey);
235
+ if (normalized)
236
+ candidatePaths.add(normalized);
237
+ }
238
+ }
239
+ const uniquePaths = Array.from(candidatePaths).sort((a, b) => a.localeCompare(b));
240
+ const workspaceCache = deps.workspaceDocumentSnapshotCache.get(workspaceId) || new Map();
241
+ deps.workspaceDocumentSnapshotCache.set(workspaceId, workspaceCache);
242
+ const remainingPaths = new Set(uniquePaths);
243
+ const docs = [];
244
+ for (const relativePath of uniquePaths) {
245
+ try {
246
+ const canonicalAsset = deps.workspaceAssetStore?.getByStorageKey(relativePath, workspaceId);
247
+ let stat = null;
248
+ let content = null;
249
+ let updatedAt = '';
250
+ if (canonicalAsset?.kind === 'document') {
251
+ updatedAt = resolveCanonicalAssetSyncLinkState(deps, canonicalAsset, workspaceId).effectiveUpdatedAt;
252
+ if (canonicalAsset.storageProvider === 'r2' && objectStorageClient) {
253
+ const object = await objectStorageClient.getObject(relativePath);
254
+ if (!object?.body)
255
+ continue;
256
+ content = object.body.toString('utf8');
257
+ }
258
+ else {
259
+ const absPath = path.resolve(path.join(root, relativePath));
260
+ if (absPath !== root && !absPath.startsWith(`${root}${path.sep}`))
261
+ continue;
262
+ if (!fs.existsSync(absPath))
263
+ continue;
264
+ stat = fs.statSync(absPath);
265
+ if (!stat.isFile())
266
+ continue;
267
+ content = fs.readFileSync(absPath, 'utf8');
268
+ }
269
+ }
270
+ else {
271
+ const absPath = path.resolve(path.join(basePath, relativePath));
272
+ const safeRoot = path.resolve(basePath);
273
+ if (absPath !== safeRoot && !absPath.startsWith(`${safeRoot}${path.sep}`))
274
+ continue;
275
+ stat = fs.statSync(absPath);
276
+ if (!stat.isFile())
277
+ continue;
278
+ content = fs.readFileSync(absPath, 'utf8');
279
+ }
280
+ if (content == null)
281
+ continue;
282
+ const cached = workspaceCache.get(relativePath);
283
+ if (cached
284
+ && cached.size === Buffer.byteLength(content, 'utf8')
285
+ && (canonicalAsset
286
+ ? cached.updatedAt === updatedAt
287
+ : (stat != null && cached.mtimeMs === stat.mtimeMs && cached.ctimeMs === stat.ctimeMs))) {
288
+ docs.push({
289
+ path: relativePath,
290
+ updatedAt: cached.updatedAt,
291
+ content: cached.content,
292
+ ...getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId)
293
+ });
294
+ continue;
295
+ }
296
+ if (!canonicalAsset) {
297
+ const contentHash = createHash('sha256').update(content).digest('hex');
298
+ const existing = deps.core.getDocumentWatermark(workspaceId, relativePath);
299
+ if (existing && existing.contentHash === contentHash) {
300
+ updatedAt = existing.updatedAt;
301
+ }
302
+ else {
303
+ updatedAt = stat && Number.isFinite(stat.mtimeMs)
304
+ ? new Date(stat.mtimeMs).toISOString()
305
+ : new Date().toISOString();
306
+ try {
307
+ deps.core.upsertDocumentWatermark(workspaceId, relativePath, contentHash, updatedAt);
308
+ }
309
+ catch (error) {
310
+ deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to persist document watermark during snapshot.', {
311
+ source: 'documents.snapshot.upsertWatermark',
312
+ path: relativePath,
313
+ error: String(error?.message || error || '')
314
+ });
315
+ }
316
+ }
317
+ }
318
+ docs.push({
319
+ path: relativePath,
320
+ updatedAt,
321
+ content,
322
+ ...getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId)
323
+ });
324
+ workspaceCache.set(relativePath, {
325
+ path: relativePath,
326
+ mtimeMs: stat?.mtimeMs ?? 0,
327
+ ctimeMs: stat?.ctimeMs ?? 0,
328
+ size: Buffer.byteLength(content, 'utf8'),
329
+ updatedAt,
330
+ content
331
+ });
332
+ }
333
+ catch (error) {
334
+ deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to read sync document snapshot entry.', {
335
+ source: 'documents.snapshot.readEntry',
336
+ path: relativePath,
337
+ error: String(error?.message || error || '')
338
+ });
339
+ }
340
+ }
341
+ for (const cachedPath of Array.from(workspaceCache.keys())) {
342
+ if (!remainingPaths.has(cachedPath))
343
+ workspaceCache.delete(cachedPath);
344
+ }
345
+ return docs;
346
+ }
347
+ export async function listWorkspaceSyncBinaryAssetsSnapshot(deps, basePath, workspaceId = 'default') {
348
+ if (!deps.workspaceAssetStore)
349
+ return [];
350
+ const root = path.resolve(basePath);
351
+ const objectStorageClient = deps.getObjectStorageClient();
352
+ const assets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
353
+ includeDeleted: false
354
+ });
355
+ const snapshots = [];
356
+ for (const asset of assets) {
357
+ if (asset.kind !== 'image' && asset.kind !== 'file')
358
+ continue;
359
+ if (asset.storageProvider === 'external')
360
+ continue;
361
+ const storageKey = String(asset.storageKey || '').trim();
362
+ if (!storageKey)
363
+ continue;
364
+ if (!deps.normalizeTaskDataRelativePath(storageKey))
365
+ continue;
366
+ try {
367
+ let buffer = null;
368
+ if (asset.storageProvider === 'r2' && objectStorageClient) {
369
+ const object = await objectStorageClient.getObject(storageKey);
370
+ buffer = object?.body || null;
371
+ }
372
+ else {
373
+ const absolutePath = path.resolve(root, storageKey);
374
+ if (absolutePath !== root && !absolutePath.startsWith(`${root}${path.sep}`))
375
+ continue;
376
+ if (!fs.existsSync(absolutePath)) {
377
+ deps.recordSyncDiagnostic(workspaceId, 'push', 'Asset registered in DB but file missing from disk — skipped from sync payload.', {
378
+ source: 'assets.snapshot.missingFile',
379
+ assetId: asset.assetId,
380
+ path: storageKey
381
+ });
382
+ continue;
383
+ }
384
+ buffer = fs.readFileSync(absolutePath);
385
+ }
386
+ if (!buffer) {
387
+ deps.recordSyncDiagnostic(workspaceId, 'push', 'Asset registered in DB but content could not be read — skipped from sync payload.', {
388
+ source: 'assets.snapshot.emptyBuffer',
389
+ assetId: asset.assetId,
390
+ path: storageKey,
391
+ storageProvider: asset.storageProvider
392
+ });
393
+ continue;
394
+ }
395
+ const { primaryLink, effectiveUpdatedAt } = resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId);
396
+ snapshots.push({
397
+ path: storageKey,
398
+ updatedAt: effectiveUpdatedAt,
399
+ contentBase64: buffer.toString('base64'),
400
+ assetId: asset.assetId,
401
+ kind: asset.kind,
402
+ mimeType: asset.mimeType || 'application/octet-stream',
403
+ referenceNumber: asset.referenceNumber,
404
+ taskId: primaryLink?.taskId || null,
405
+ logicalName: asset.logicalName || null,
406
+ caption: primaryLink?.displayName || null,
407
+ originalFilename: asset.originalFilename || path.basename(storageKey),
408
+ linkRole: primaryLink?.role || (asset.kind === 'image' ? 'image' : 'attachment')
409
+ });
410
+ }
411
+ catch (error) {
412
+ deps.recordSyncDiagnostic(workspaceId, 'pull', 'Failed to read binary asset snapshot entry.', {
413
+ source: 'assets.snapshot.readEntry',
414
+ path: storageKey,
415
+ error: String(error?.message || error || '')
416
+ });
417
+ }
418
+ }
419
+ return snapshots;
420
+ }
421
+ export function listWorkspaceSyncBinaryAssetDeletesSnapshot(deps, workspaceId = 'default') {
422
+ if (!deps.workspaceAssetStore)
423
+ return [];
424
+ const entries = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
425
+ includeDeleted: true
426
+ })
427
+ .filter((asset) => ((asset.kind === 'image' || asset.kind === 'file')
428
+ && asset.storageProvider !== 'external'
429
+ && typeof asset.deletedAt === 'string'
430
+ && asset.deletedAt.trim().length > 0))
431
+ .map((asset) => {
432
+ const normalizedPath = deps.normalizeTaskDataRelativePath(asset.storageKey);
433
+ if (!normalizedPath)
434
+ return null;
435
+ return {
436
+ path: normalizedPath,
437
+ deletedAt: String(asset.deletedAt || '').trim(),
438
+ assetId: asset.assetId
439
+ };
440
+ });
441
+ return entries.filter((entry) => Boolean(entry && entry.path && entry.deletedAt));
442
+ }
443
+ export async function writeWorkspaceSyncDocument(deps, basePath, doc, workspaceId = 'default', options) {
444
+ const normalizedPath = deps.normalizeWorkspaceSyncDocumentPath(doc.path);
445
+ if (!normalizedPath)
446
+ return;
447
+ const root = path.resolve(basePath);
448
+ const targetPath = path.resolve(root, normalizedPath);
449
+ if (targetPath !== root && !targetPath.startsWith(`${root}${path.sep}`))
450
+ return;
451
+ const objectStorageClient = deps.getObjectStorageClient();
452
+ const content = String(doc.content || '');
453
+ if (objectStorageClient) {
454
+ try {
455
+ await objectStorageClient.putObject(normalizedPath, Buffer.from(content, 'utf8'), 'text/markdown');
456
+ }
457
+ catch (error) {
458
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to write document to object storage.', {
459
+ source: 'documents.write.putObject',
460
+ path: normalizedPath,
461
+ error: String(error?.message || error || '')
462
+ });
463
+ return;
464
+ }
465
+ }
466
+ else {
467
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
468
+ fs.writeFileSync(targetPath, content, 'utf8');
469
+ }
470
+ const updatedAt = String(doc.updatedAt || '').trim();
471
+ const watermarkMs = Date.parse(updatedAt);
472
+ if (!objectStorageClient && Number.isFinite(watermarkMs) && watermarkMs > 0) {
473
+ const when = new Date(watermarkMs);
474
+ fs.utimesSync(targetPath, when, when);
475
+ }
476
+ try {
477
+ const contentHash = createHash('sha256').update(content).digest('hex');
478
+ deps.core.upsertDocumentWatermark(workspaceId, normalizedPath, contentHash, updatedAt || new Date().toISOString());
479
+ if (deps.workspaceAssetStore) {
480
+ const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
481
+ const effectiveUpdatedAt = updatedAt || new Date().toISOString();
482
+ const originalFilename = String(doc.originalFilename || existing?.originalFilename || path.basename(normalizedPath) || 'document.md').trim() || 'document.md';
483
+ const asset = deps.workspaceAssetStore.upsertAsset({
484
+ assetId: String(doc.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
485
+ workspaceId,
486
+ kind: 'document',
487
+ logicalName: typeof doc.logicalName === 'string' && doc.logicalName.trim().length > 0
488
+ ? doc.logicalName.trim()
489
+ : existing?.logicalName || null,
490
+ originalFilename,
491
+ mimeType: existing?.mimeType || 'text/markdown',
492
+ storageProvider: objectStorageClient ? 'r2' : 'local',
493
+ storageKey: normalizedPath,
494
+ contentSha256: contentHash,
495
+ sizeBytes: Buffer.byteLength(content, 'utf8'),
496
+ documentId: String(doc.documentId || existing?.documentId || `doc-${randomUUID().replace(/-/g, '')}`),
497
+ referenceNumber: Number.isFinite(Number(doc.referenceNumber))
498
+ ? Math.max(1, Math.floor(Number(doc.referenceNumber)))
499
+ : existing?.referenceNumber || null,
500
+ version: Number.isFinite(Number(doc.version))
501
+ ? Math.max(1, Math.floor(Number(doc.version)))
502
+ : Math.max(1, Number(existing?.version || 0) + 1),
503
+ isLatest: true,
504
+ scanStatus: existing?.scanStatus || 'clean',
505
+ scanEngine: existing?.scanEngine || null,
506
+ scanDetails: existing?.scanDetails || null,
507
+ createdByUserId: existing?.createdByUserId || null,
508
+ createdAt: existing?.createdAt || effectiveUpdatedAt,
509
+ updatedAt: effectiveUpdatedAt,
510
+ deletedAt: null,
511
+ purgeAfter: null,
512
+ allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true
513
+ });
514
+ const taskId = String(doc.taskId || '').trim();
515
+ reconcileWorkspaceSyncLinks(deps, {
516
+ workspaceId,
517
+ assetId: asset.assetId,
518
+ taskId,
519
+ displayName: typeof doc.caption === 'string' && doc.caption.trim().length > 0 ? doc.caption.trim() : null,
520
+ role: doc.linkRole === 'reference' ? 'reference' : 'attachment',
521
+ updatedAt: effectiveUpdatedAt
522
+ });
523
+ }
524
+ }
525
+ catch (error) {
526
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to persist document watermark after write.', {
527
+ source: 'documents.write.upsertWatermark',
528
+ path: normalizedPath,
529
+ error: String(error?.message || error || '')
530
+ });
531
+ }
532
+ deps.workspaceDocumentSnapshotCache.get(workspaceId)?.delete(normalizedPath);
533
+ }
534
+ export async function writeWorkspaceSyncBinaryAsset(deps, basePath, asset, workspaceId = 'default', options) {
535
+ const normalizedPath = deps.normalizeTaskDataRelativePath(asset.path);
536
+ if (!normalizedPath)
537
+ return;
538
+ const root = path.resolve(basePath);
539
+ const targetPath = path.resolve(root, normalizedPath);
540
+ if (targetPath !== root && !targetPath.startsWith(`${root}${path.sep}`))
541
+ return;
542
+ const objectStorageClient = deps.getObjectStorageClient();
543
+ const buffer = Buffer.from(String(asset.contentBase64 || ''), 'base64');
544
+ if (objectStorageClient) {
545
+ try {
546
+ await objectStorageClient.putObject(normalizedPath, buffer, String(asset.mimeType || 'application/octet-stream').trim() || 'application/octet-stream');
547
+ }
548
+ catch (error) {
549
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to write binary asset to object storage.', {
550
+ source: 'assets.write.putObject',
551
+ path: normalizedPath,
552
+ error: String(error?.message || error || '')
553
+ });
554
+ return;
555
+ }
556
+ }
557
+ else {
558
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
559
+ fs.writeFileSync(targetPath, buffer);
560
+ }
561
+ const updatedAt = String(asset.updatedAt || '').trim();
562
+ const watermarkMs = Date.parse(updatedAt);
563
+ if (!objectStorageClient && Number.isFinite(watermarkMs) && watermarkMs > 0) {
564
+ const when = new Date(watermarkMs);
565
+ fs.utimesSync(targetPath, when, when);
566
+ }
567
+ if (!deps.workspaceAssetStore)
568
+ return;
569
+ try {
570
+ const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
571
+ const effectiveUpdatedAt = updatedAt || new Date().toISOString();
572
+ const assetRecord = deps.workspaceAssetStore.upsertAsset({
573
+ assetId: String(asset.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
574
+ workspaceId,
575
+ kind: asset.kind === 'image' ? 'image' : 'file',
576
+ logicalName: typeof asset.logicalName === 'string' && asset.logicalName.trim().length > 0
577
+ ? asset.logicalName.trim()
578
+ : existing?.logicalName || null,
579
+ originalFilename: String(asset.originalFilename || existing?.originalFilename || path.basename(normalizedPath) || 'asset').trim() || 'asset',
580
+ mimeType: String(asset.mimeType || existing?.mimeType || 'application/octet-stream').trim() || 'application/octet-stream',
581
+ storageProvider: objectStorageClient ? 'r2' : 'local',
582
+ storageKey: normalizedPath,
583
+ contentSha256: createHash('sha256').update(buffer).digest('hex'),
584
+ sizeBytes: buffer.length,
585
+ documentId: null,
586
+ referenceNumber: Number.isFinite(Number(asset.referenceNumber))
587
+ ? Math.max(1, Math.floor(Number(asset.referenceNumber)))
588
+ : existing?.referenceNumber || null,
589
+ version: null,
590
+ isLatest: true,
591
+ scanStatus: existing?.scanStatus || 'clean',
592
+ scanEngine: existing?.scanEngine || null,
593
+ scanDetails: existing?.scanDetails || null,
594
+ createdByUserId: existing?.createdByUserId || null,
595
+ createdAt: existing?.createdAt || effectiveUpdatedAt,
596
+ updatedAt: effectiveUpdatedAt,
597
+ deletedAt: null,
598
+ purgeAfter: null,
599
+ allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true
600
+ });
601
+ const taskId = String(asset.taskId || '').trim();
602
+ reconcileWorkspaceSyncLinks(deps, {
603
+ workspaceId,
604
+ assetId: assetRecord.assetId,
605
+ taskId,
606
+ displayName: typeof asset.caption === 'string' && asset.caption.trim().length > 0 ? asset.caption.trim() : null,
607
+ role: asset.linkRole === 'reference' ? 'reference' : asset.kind === 'image' ? 'image' : 'attachment',
608
+ updatedAt: effectiveUpdatedAt
609
+ });
610
+ }
611
+ catch (error) {
612
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to persist binary asset metadata after write.', {
613
+ source: 'assets.write.upsertAsset',
614
+ path: normalizedPath,
615
+ assetId: typeof asset.assetId === 'string' ? asset.assetId : null,
616
+ kind: asset.kind,
617
+ error: String(error?.message || error || '')
618
+ });
619
+ }
620
+ }
621
+ export function deleteWorkspaceSyncBinaryAsset(deps, basePath, assetPath, workspaceId = 'default') {
622
+ const normalizedPath = deps.normalizeTaskDataRelativePath(assetPath);
623
+ if (!normalizedPath)
624
+ return;
625
+ const root = path.resolve(basePath);
626
+ const targetPath = path.resolve(root, normalizedPath);
627
+ const objectStorageClient = deps.getObjectStorageClient();
628
+ try {
629
+ if (objectStorageClient) {
630
+ void objectStorageClient.deleteObject(normalizedPath);
631
+ }
632
+ else if (targetPath === root || targetPath.startsWith(`${root}${path.sep}`)) {
633
+ if (fs.existsSync(targetPath))
634
+ fs.unlinkSync(targetPath);
635
+ }
636
+ }
637
+ catch (error) {
638
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete workspace sync binary asset.', {
639
+ source: 'assets.delete.file',
640
+ path: normalizedPath,
641
+ error: String(error?.message || error || '')
642
+ });
643
+ }
644
+ if (deps.workspaceAssetStore) {
645
+ const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
646
+ if (existing) {
647
+ const deletedAt = new Date().toISOString();
648
+ deps.workspaceAssetStore.markDeleted(existing.assetId, workspaceId, deletedAt);
649
+ if (existing.kind === 'image') {
650
+ deps.core.deleteAnnotatedAttachmentSessionsForImage({
651
+ workspaceId,
652
+ imageAssetId: existing.assetId,
653
+ deletedAt,
654
+ });
655
+ }
656
+ const links = deps.workspaceAssetStore.listLinksForAsset(existing.assetId, workspaceId, { includeDeleted: true });
657
+ for (const link of links) {
658
+ deps.workspaceAssetStore.deleteLink({
659
+ workspaceId,
660
+ assetId: existing.assetId,
661
+ taskId: link.taskId,
662
+ role: link.role
663
+ });
664
+ }
665
+ }
666
+ }
667
+ }
668
+ export function deleteWorkspaceSyncDocument(deps, basePath, docPath, workspaceId = 'default') {
669
+ const normalizedPath = deps.normalizeWorkspaceSyncDocumentPath(docPath);
670
+ if (!normalizedPath)
671
+ return;
672
+ const root = path.resolve(basePath);
673
+ const targetPath = path.resolve(root, normalizedPath);
674
+ if (targetPath !== root && !targetPath.startsWith(`${root}${path.sep}`))
675
+ return;
676
+ const objectStorageClient = deps.getObjectStorageClient();
677
+ try {
678
+ if (objectStorageClient) {
679
+ void objectStorageClient.deleteObject(normalizedPath);
680
+ }
681
+ else if (fs.existsSync(targetPath)) {
682
+ fs.unlinkSync(targetPath);
683
+ }
684
+ }
685
+ catch (error) {
686
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete workspace sync document.', {
687
+ source: 'documents.delete.file',
688
+ path: normalizedPath,
689
+ error: String(error?.message || error || '')
690
+ });
691
+ }
692
+ try {
693
+ deps.core.deleteDocumentWatermark(workspaceId, normalizedPath);
694
+ if (deps.workspaceAssetStore) {
695
+ const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
696
+ if (existing) {
697
+ const deletedAt = new Date().toISOString();
698
+ deps.workspaceAssetStore.markDeleted(existing.assetId, workspaceId, deletedAt);
699
+ if (existing.kind === 'image') {
700
+ deps.core.deleteAnnotatedAttachmentSessionsForImage({
701
+ workspaceId,
702
+ imageAssetId: existing.assetId,
703
+ deletedAt,
704
+ });
705
+ }
706
+ const links = deps.workspaceAssetStore.listLinksForAsset(existing.assetId, workspaceId, { includeDeleted: true });
707
+ for (const link of links) {
708
+ deps.workspaceAssetStore.deleteLink({
709
+ workspaceId,
710
+ assetId: existing.assetId,
711
+ taskId: link.taskId,
712
+ role: link.role
713
+ });
714
+ }
715
+ }
716
+ }
717
+ }
718
+ catch (error) {
719
+ deps.recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete document watermark.', {
720
+ source: 'documents.delete.watermark',
721
+ path: normalizedPath,
722
+ error: String(error?.message || error || '')
723
+ });
724
+ }
725
+ deps.workspaceDocumentSnapshotCache.get(workspaceId)?.delete(normalizedPath);
726
+ }
727
+ export function createContentSyncState(deps) {
728
+ return {
729
+ resolveCanonicalAssetSyncLinkState: (asset, workspaceId) => resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId),
730
+ getCanonicalSyncDocumentMetadata: (relativePath, workspaceId) => getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId),
731
+ hasDocumentSyncMetadataDrift: (workspaceId, doc, options) => hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options),
732
+ hasBinaryAssetSyncMetadataDrift: (workspaceId, asset, options) => hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, options),
733
+ reconcileWorkspaceSyncLinks: (input) => reconcileWorkspaceSyncLinks(deps, input),
734
+ listWorkspaceSyncDocumentDeletesSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentDeletesSnapshot(deps, workspaceId),
735
+ listWorkspaceSyncDocumentsSnapshot: (basePath, workspaceId = 'default') => listWorkspaceSyncDocumentsSnapshot(deps, basePath, workspaceId),
736
+ listWorkspaceSyncBinaryAssetsSnapshot: (basePath, workspaceId = 'default') => listWorkspaceSyncBinaryAssetsSnapshot(deps, basePath, workspaceId),
737
+ listWorkspaceSyncBinaryAssetDeletesSnapshot: (workspaceId = 'default') => listWorkspaceSyncBinaryAssetDeletesSnapshot(deps, workspaceId),
738
+ writeWorkspaceSyncDocument: (basePath, doc, workspaceId = 'default', options) => writeWorkspaceSyncDocument(deps, basePath, doc, workspaceId, options),
739
+ writeWorkspaceSyncBinaryAsset: (basePath, asset, workspaceId = 'default', options) => writeWorkspaceSyncBinaryAsset(deps, basePath, asset, workspaceId, options),
740
+ deleteWorkspaceSyncBinaryAsset: (basePath, assetPath, workspaceId = 'default') => deleteWorkspaceSyncBinaryAsset(deps, basePath, assetPath, workspaceId),
741
+ deleteWorkspaceSyncDocument: (basePath, docPath, workspaceId = 'default') => deleteWorkspaceSyncDocument(deps, basePath, docPath, workspaceId),
742
+ };
743
+ }