@taskforcehq/taskforce 0.3.302 → 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 +11 -0
- package/dist/TaskforceCore.js +59 -9
- package/dist/TaskforceCore.test.js +717 -14
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +110 -51
- 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 +106 -151
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/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/SystemAdmin.test.js +186 -96
- package/dist/core/Taskforce.d.ts +51 -10
- package/dist/core/Taskforce.js +218 -49
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/types.d.ts +11 -12
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskforce.d.ts +3 -0
- package/dist/hooks/useTaskforce.js +9 -4
- package/dist/mcp/runtime.js +90 -29
- package/dist/mcp/runtime.test.js +272 -81
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.test.js +2 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +80 -23
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +194 -53
- package/dist/server/routes/billing.js +506 -23
- package/dist/server/routes/billing.test.js +937 -45
- package/dist/server/routes/shared.js +5 -3
- package/dist/server/routes/sync.integration.test.js +160 -0
- package/dist/server/routes/sync.js +15 -5
- package/dist/server/routes.js +3 -3
- package/dist/server/routes.test.js +287 -76
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -16
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/workspacePullFeed.d.ts +10 -0
- package/dist/sync/workspacePullFeed.js +111 -63
- package/dist/sync/workspacePullFeed.test.js +112 -0
- package/dist/ui/assets/{AgentsModule-JG5jc3he.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-C3GyzrWm.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/{TaskSettings-Bqd4cCzW.js → TaskSettings-BOC5F6Ag.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.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-C8EED3Oh.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/package.json +3 -3
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -21,6 +21,8 @@ interface ContentSyncStateDeps {
|
|
|
21
21
|
recordSyncDiagnostic: RecordSyncDiagnostic;
|
|
22
22
|
workspaceDocumentSnapshotCache: Map<string, Map<string, CachedWorkspaceSyncDocument>>;
|
|
23
23
|
}
|
|
24
|
+
export type DocumentSyncIndexEntry = Omit<DocumentSyncPayload, 'content'>;
|
|
25
|
+
export type BinaryAssetSyncIndexEntry = Omit<BinaryAssetSyncPayload, 'contentBase64'>;
|
|
24
26
|
export declare function resolveCanonicalAssetSyncLinkState(deps: ContentSyncStateDeps, asset: WorkspaceAssetEntry, workspaceId: string): {
|
|
25
27
|
primaryLink: WorkspaceAssetLinkEntry | null;
|
|
26
28
|
effectiveUpdatedAt: string;
|
|
@@ -42,7 +44,11 @@ export declare function reconcileWorkspaceSyncLinks(deps: ContentSyncStateDeps,
|
|
|
42
44
|
}): void;
|
|
43
45
|
export declare function listWorkspaceSyncDocumentDeletesSnapshot(deps: ContentSyncStateDeps, workspaceId?: string): DocumentDeleteSyncPayload[];
|
|
44
46
|
export declare function listWorkspaceSyncDocumentsSnapshot(deps: ContentSyncStateDeps, basePath: string, workspaceId?: string): Promise<DocumentSyncPayload[]>;
|
|
47
|
+
export declare function listWorkspaceSyncDocumentIndex(deps: ContentSyncStateDeps, basePath: string, workspaceId?: string): Promise<DocumentSyncIndexEntry[]>;
|
|
45
48
|
export declare function listWorkspaceSyncBinaryAssetsSnapshot(deps: ContentSyncStateDeps, basePath: string, workspaceId?: string): Promise<BinaryAssetSyncPayload[]>;
|
|
49
|
+
export declare function listWorkspaceSyncBinaryAssetIndex(deps: ContentSyncStateDeps, workspaceId?: string): Promise<BinaryAssetSyncIndexEntry[]>;
|
|
50
|
+
export declare function loadWorkspaceSyncDocumentPayload(deps: ContentSyncStateDeps, basePath: string, workspaceId: string, relativePath: string): Promise<DocumentSyncPayload | null>;
|
|
51
|
+
export declare function loadWorkspaceSyncBinaryAssetPayload(deps: ContentSyncStateDeps, basePath: string, workspaceId: string, assetPath: string): Promise<BinaryAssetSyncPayload | null>;
|
|
46
52
|
export declare function listWorkspaceSyncBinaryAssetDeletesSnapshot(deps: ContentSyncStateDeps, workspaceId?: string): BinaryAssetDeleteSyncPayload[];
|
|
47
53
|
export declare function writeWorkspaceSyncDocument(deps: ContentSyncStateDeps, basePath: string, doc: DocumentSyncPayload, workspaceId?: string, options?: {
|
|
48
54
|
allowReferenceNumberReassignment?: boolean;
|
|
@@ -73,8 +79,12 @@ export declare function createContentSyncState(deps: ContentSyncStateDeps): {
|
|
|
73
79
|
updatedAt?: string | null;
|
|
74
80
|
}) => void;
|
|
75
81
|
listWorkspaceSyncDocumentDeletesSnapshot: (workspaceId?: string) => DocumentDeleteSyncPayload[];
|
|
82
|
+
listWorkspaceSyncDocumentIndex: (basePath: string, workspaceId?: string) => Promise<DocumentSyncIndexEntry[]>;
|
|
76
83
|
listWorkspaceSyncDocumentsSnapshot: (basePath: string, workspaceId?: string) => Promise<DocumentSyncPayload[]>;
|
|
84
|
+
loadWorkspaceSyncDocumentPayload: (basePath: string, workspaceId: string, relativePath: string) => Promise<DocumentSyncPayload | null>;
|
|
85
|
+
listWorkspaceSyncBinaryAssetIndex: (workspaceId?: string) => Promise<BinaryAssetSyncIndexEntry[]>;
|
|
77
86
|
listWorkspaceSyncBinaryAssetsSnapshot: (basePath: string, workspaceId?: string) => Promise<BinaryAssetSyncPayload[]>;
|
|
87
|
+
loadWorkspaceSyncBinaryAssetPayload: (basePath: string, workspaceId: string, assetPath: string) => Promise<BinaryAssetSyncPayload | null>;
|
|
78
88
|
listWorkspaceSyncBinaryAssetDeletesSnapshot: (workspaceId?: string) => BinaryAssetDeleteSyncPayload[];
|
|
79
89
|
writeWorkspaceSyncDocument: (basePath: string, doc: DocumentSyncPayload, workspaceId?: string, options?: {
|
|
80
90
|
allowReferenceNumberReassignment?: boolean;
|
|
@@ -68,10 +68,10 @@ export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options) {
|
|
|
68
68
|
: null;
|
|
69
69
|
const normalizedIncomingLinkRole = doc.linkRole === 'reference' ? 'reference' : 'attachment';
|
|
70
70
|
const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
|
|
71
|
-
|
|
72
|
-
&& existingAsset.referenceNumber
|
|
71
|
+
const referenceNumberMismatch = Boolean(existingAsset.referenceNumber
|
|
73
72
|
&& normalizedIncomingReference
|
|
74
|
-
&& existingAsset.referenceNumber !== normalizedIncomingReference)
|
|
73
|
+
&& existingAsset.referenceNumber !== normalizedIncomingReference);
|
|
74
|
+
if (referenceNumberMismatch) {
|
|
75
75
|
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Detected document reference number mismatch during normal sync.', {
|
|
76
76
|
source: 'documents.referenceNumber.mismatch',
|
|
77
77
|
path: doc.path,
|
|
@@ -80,7 +80,8 @@ export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options) {
|
|
|
80
80
|
incomingReferenceNumber: normalizedIncomingReference
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
|
-
return
|
|
83
|
+
return referenceNumberMismatch
|
|
84
|
+
|| (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
|
|
84
85
|
|| existingAsset.version !== normalizedIncomingVersion
|
|
85
86
|
|| (existingAsset.documentId || null) !== normalizedIncomingDocumentId
|
|
86
87
|
|| (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
|
|
@@ -117,10 +118,10 @@ export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, option
|
|
|
117
118
|
? 'image'
|
|
118
119
|
: 'attachment';
|
|
119
120
|
const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
|
|
120
|
-
|
|
121
|
-
&& existingAsset.referenceNumber
|
|
121
|
+
const referenceNumberMismatch = Boolean(existingAsset.referenceNumber
|
|
122
122
|
&& normalizedIncomingReference
|
|
123
|
-
&& existingAsset.referenceNumber !== normalizedIncomingReference)
|
|
123
|
+
&& existingAsset.referenceNumber !== normalizedIncomingReference);
|
|
124
|
+
if (referenceNumberMismatch) {
|
|
124
125
|
deps.recordSyncDiagnostic(workspaceId, 'apply', 'Detected asset reference number mismatch during normal sync.', {
|
|
125
126
|
source: 'assets.referenceNumber.mismatch',
|
|
126
127
|
path: asset.path,
|
|
@@ -129,7 +130,8 @@ export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, option
|
|
|
129
130
|
incomingReferenceNumber: normalizedIncomingReference
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
|
-
return
|
|
133
|
+
return referenceNumberMismatch
|
|
134
|
+
|| (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
|
|
133
135
|
|| (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
|
|
134
136
|
|| (existingAsset.originalFilename || null) !== normalizedIncomingFilename
|
|
135
137
|
|| (primaryLink?.taskId || null) !== normalizedIncomingTaskId
|
|
@@ -344,6 +346,80 @@ export async function listWorkspaceSyncDocumentsSnapshot(deps, basePath, workspa
|
|
|
344
346
|
}
|
|
345
347
|
return docs;
|
|
346
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
|
+
}
|
|
347
423
|
export async function listWorkspaceSyncBinaryAssetsSnapshot(deps, basePath, workspaceId = 'default') {
|
|
348
424
|
if (!deps.workspaceAssetStore)
|
|
349
425
|
return [];
|
|
@@ -418,6 +494,181 @@ export async function listWorkspaceSyncBinaryAssetsSnapshot(deps, basePath, work
|
|
|
418
494
|
}
|
|
419
495
|
return snapshots;
|
|
420
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
|
+
}
|
|
421
672
|
export function listWorkspaceSyncBinaryAssetDeletesSnapshot(deps, workspaceId = 'default') {
|
|
422
673
|
if (!deps.workspaceAssetStore)
|
|
423
674
|
return [];
|
|
@@ -479,6 +730,11 @@ export async function writeWorkspaceSyncDocument(deps, basePath, doc, workspaceI
|
|
|
479
730
|
if (deps.workspaceAssetStore) {
|
|
480
731
|
const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
|
|
481
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);
|
|
482
738
|
const originalFilename = String(doc.originalFilename || existing?.originalFilename || path.basename(normalizedPath) || 'document.md').trim() || 'document.md';
|
|
483
739
|
const asset = deps.workspaceAssetStore.upsertAsset({
|
|
484
740
|
assetId: String(doc.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
|
|
@@ -494,9 +750,7 @@ export async function writeWorkspaceSyncDocument(deps, basePath, doc, workspaceI
|
|
|
494
750
|
contentSha256: contentHash,
|
|
495
751
|
sizeBytes: Buffer.byteLength(content, 'utf8'),
|
|
496
752
|
documentId: String(doc.documentId || existing?.documentId || `doc-${randomUUID().replace(/-/g, '')}`),
|
|
497
|
-
referenceNumber:
|
|
498
|
-
? Math.max(1, Math.floor(Number(doc.referenceNumber)))
|
|
499
|
-
: existing?.referenceNumber || null,
|
|
753
|
+
referenceNumber: incomingReferenceNumber ?? existing?.referenceNumber ?? null,
|
|
500
754
|
version: Number.isFinite(Number(doc.version))
|
|
501
755
|
? Math.max(1, Math.floor(Number(doc.version)))
|
|
502
756
|
: Math.max(1, Number(existing?.version || 0) + 1),
|
|
@@ -509,7 +763,7 @@ export async function writeWorkspaceSyncDocument(deps, basePath, doc, workspaceI
|
|
|
509
763
|
updatedAt: effectiveUpdatedAt,
|
|
510
764
|
deletedAt: null,
|
|
511
765
|
purgeAfter: null,
|
|
512
|
-
allowReferenceNumberReassignment
|
|
766
|
+
allowReferenceNumberReassignment
|
|
513
767
|
});
|
|
514
768
|
const taskId = String(doc.taskId || '').trim();
|
|
515
769
|
reconcileWorkspaceSyncLinks(deps, {
|
|
@@ -569,6 +823,11 @@ export async function writeWorkspaceSyncBinaryAsset(deps, basePath, asset, works
|
|
|
569
823
|
try {
|
|
570
824
|
const existing = deps.workspaceAssetStore.getByStorageKey(normalizedPath, workspaceId);
|
|
571
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);
|
|
572
831
|
const assetRecord = deps.workspaceAssetStore.upsertAsset({
|
|
573
832
|
assetId: String(asset.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
|
|
574
833
|
workspaceId,
|
|
@@ -583,9 +842,7 @@ export async function writeWorkspaceSyncBinaryAsset(deps, basePath, asset, works
|
|
|
583
842
|
contentSha256: createHash('sha256').update(buffer).digest('hex'),
|
|
584
843
|
sizeBytes: buffer.length,
|
|
585
844
|
documentId: null,
|
|
586
|
-
referenceNumber:
|
|
587
|
-
? Math.max(1, Math.floor(Number(asset.referenceNumber)))
|
|
588
|
-
: existing?.referenceNumber || null,
|
|
845
|
+
referenceNumber: incomingReferenceNumber ?? existing?.referenceNumber ?? null,
|
|
589
846
|
version: null,
|
|
590
847
|
isLatest: true,
|
|
591
848
|
scanStatus: existing?.scanStatus || 'clean',
|
|
@@ -596,7 +853,7 @@ export async function writeWorkspaceSyncBinaryAsset(deps, basePath, asset, works
|
|
|
596
853
|
updatedAt: effectiveUpdatedAt,
|
|
597
854
|
deletedAt: null,
|
|
598
855
|
purgeAfter: null,
|
|
599
|
-
allowReferenceNumberReassignment
|
|
856
|
+
allowReferenceNumberReassignment
|
|
600
857
|
});
|
|
601
858
|
const taskId = String(asset.taskId || '').trim();
|
|
602
859
|
reconcileWorkspaceSyncLinks(deps, {
|
|
@@ -732,8 +989,12 @@ export function createContentSyncState(deps) {
|
|
|
732
989
|
hasBinaryAssetSyncMetadataDrift: (workspaceId, asset, options) => hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, options),
|
|
733
990
|
reconcileWorkspaceSyncLinks: (input) => reconcileWorkspaceSyncLinks(deps, input),
|
|
734
991
|
listWorkspaceSyncDocumentDeletesSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentDeletesSnapshot(deps, workspaceId),
|
|
992
|
+
listWorkspaceSyncDocumentIndex: (basePath, workspaceId = 'default') => listWorkspaceSyncDocumentIndex(deps, basePath, workspaceId),
|
|
735
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),
|
|
736
996
|
listWorkspaceSyncBinaryAssetsSnapshot: (basePath, workspaceId = 'default') => listWorkspaceSyncBinaryAssetsSnapshot(deps, basePath, workspaceId),
|
|
997
|
+
loadWorkspaceSyncBinaryAssetPayload: (basePath, workspaceId, assetPath) => loadWorkspaceSyncBinaryAssetPayload(deps, basePath, workspaceId, assetPath),
|
|
737
998
|
listWorkspaceSyncBinaryAssetDeletesSnapshot: (workspaceId = 'default') => listWorkspaceSyncBinaryAssetDeletesSnapshot(deps, workspaceId),
|
|
738
999
|
writeWorkspaceSyncDocument: (basePath, doc, workspaceId = 'default', options) => writeWorkspaceSyncDocument(deps, basePath, doc, workspaceId, options),
|
|
739
1000
|
writeWorkspaceSyncBinaryAsset: (basePath, asset, workspaceId = 'default', options) => writeWorkspaceSyncBinaryAsset(deps, basePath, asset, workspaceId, options),
|
|
@@ -115,6 +115,7 @@ export interface WorkspacePullSyncResult {
|
|
|
115
115
|
pulledArchivedUpserts: boolean;
|
|
116
116
|
documentDecryptFailures?: string[];
|
|
117
117
|
emittedEventIds?: string[];
|
|
118
|
+
serverDiagnostics?: Record<string, unknown> | null;
|
|
118
119
|
}
|
|
119
120
|
export declare function runWorkspacePullSyncService(input: WorkspacePullSyncInput): Promise<WorkspacePullSyncResult>;
|
|
120
121
|
export declare function syncUserGlobalSettingsService(input: UserSettingsSyncInput): Promise<UserSettingsSyncResult>;
|
package/dist/sync/syncService.js
CHANGED
|
@@ -243,6 +243,7 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
243
243
|
const documentDecryptFailures = new Set();
|
|
244
244
|
const emittedEventIds = new Set();
|
|
245
245
|
const pulledAnnotatedAttachmentSessionIds = new Set();
|
|
246
|
+
let lastServerDiagnostics = null;
|
|
246
247
|
const maxPages = Number.isFinite(Number(input.maxPages)) ? Math.max(1, Math.floor(Number(input.maxPages))) : 20;
|
|
247
248
|
try {
|
|
248
249
|
while (hasMore && pages < maxPages) {
|
|
@@ -279,11 +280,16 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
279
280
|
pulledDeleteTaskIds,
|
|
280
281
|
pulledActiveUpserts,
|
|
281
282
|
pulledArchivedUpserts,
|
|
282
|
-
documentDecryptFailures: Array.from(documentDecryptFailures)
|
|
283
|
+
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
284
|
+
serverDiagnostics: null
|
|
283
285
|
};
|
|
284
286
|
}
|
|
285
287
|
const pullData = pullRes.data || {};
|
|
286
288
|
const changes = Array.isArray(pullData?.changes) ? pullData.changes : [];
|
|
289
|
+
const serverDiagnostics = pullData?.diagnostics && typeof pullData.diagnostics === 'object'
|
|
290
|
+
? pullData.diagnostics
|
|
291
|
+
: null;
|
|
292
|
+
lastServerDiagnostics = serverDiagnostics;
|
|
287
293
|
const hasWorkspaceMembersSnapshot = Array.isArray(pullData?.workspaceMembers);
|
|
288
294
|
const workspaceMembers = hasWorkspaceMembersSnapshot
|
|
289
295
|
? pullData.workspaceMembers
|
|
@@ -365,7 +371,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
365
371
|
pulledDeleteTaskIds,
|
|
366
372
|
pulledActiveUpserts,
|
|
367
373
|
pulledArchivedUpserts,
|
|
368
|
-
documentDecryptFailures: Array.from(documentDecryptFailures)
|
|
374
|
+
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
375
|
+
serverDiagnostics
|
|
369
376
|
};
|
|
370
377
|
}
|
|
371
378
|
const hasTransientApplyFailure = applyFailures.some((entry) => {
|
|
@@ -392,7 +399,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
392
399
|
pulledActiveUpserts,
|
|
393
400
|
pulledArchivedUpserts,
|
|
394
401
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
395
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
402
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
403
|
+
serverDiagnostics: lastServerDiagnostics
|
|
396
404
|
};
|
|
397
405
|
}
|
|
398
406
|
const applyPayloadData = asObjectRecord(applyResult.data);
|
|
@@ -412,7 +420,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
412
420
|
pulledActiveUpserts,
|
|
413
421
|
pulledArchivedUpserts,
|
|
414
422
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
415
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
423
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
424
|
+
serverDiagnostics: lastServerDiagnostics
|
|
416
425
|
};
|
|
417
426
|
}
|
|
418
427
|
applyMs += (Date.now() - applyStartedAtMs);
|
|
@@ -461,7 +470,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
461
470
|
pulledActiveUpserts,
|
|
462
471
|
pulledArchivedUpserts,
|
|
463
472
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
464
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
473
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
474
|
+
serverDiagnostics: lastServerDiagnostics
|
|
465
475
|
};
|
|
466
476
|
}
|
|
467
477
|
const hasTransientApplyFailure = bootstrapApplyFailures.some((entry) => {
|
|
@@ -488,7 +498,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
488
498
|
pulledActiveUpserts,
|
|
489
499
|
pulledArchivedUpserts,
|
|
490
500
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
491
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
501
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
502
|
+
serverDiagnostics: lastServerDiagnostics
|
|
492
503
|
};
|
|
493
504
|
}
|
|
494
505
|
applyMs += (Date.now() - bootstrapApplyStartedAtMs);
|
|
@@ -505,7 +516,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
505
516
|
pulledActiveUpserts,
|
|
506
517
|
pulledArchivedUpserts,
|
|
507
518
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
508
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
519
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
520
|
+
serverDiagnostics: lastServerDiagnostics
|
|
509
521
|
};
|
|
510
522
|
}
|
|
511
523
|
catch (error) {
|
|
@@ -524,7 +536,8 @@ export async function runWorkspacePullSyncService(input) {
|
|
|
524
536
|
pulledActiveUpserts,
|
|
525
537
|
pulledArchivedUpserts,
|
|
526
538
|
documentDecryptFailures: Array.from(documentDecryptFailures),
|
|
527
|
-
emittedEventIds: Array.from(emittedEventIds)
|
|
539
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
540
|
+
serverDiagnostics: null
|
|
528
541
|
};
|
|
529
542
|
}
|
|
530
543
|
}
|
|
@@ -15,6 +15,16 @@ export interface WorkspacePullFeedResult {
|
|
|
15
15
|
changes: EncodedWorkspaceSyncChange[];
|
|
16
16
|
nextCursor: string | null;
|
|
17
17
|
hasMore: boolean;
|
|
18
|
+
diagnostics: {
|
|
19
|
+
totalEntries: number;
|
|
20
|
+
pageEntries: number;
|
|
21
|
+
documentIndexCount: number;
|
|
22
|
+
assetIndexCount: number;
|
|
23
|
+
indexMs: number;
|
|
24
|
+
hydrationMs: number;
|
|
25
|
+
encodeMs: number;
|
|
26
|
+
buildMs: number;
|
|
27
|
+
};
|
|
18
28
|
}
|
|
19
29
|
export declare function buildWorkspacePullFeed(input: {
|
|
20
30
|
core: SyncCore;
|