@taskforcehq/taskforce 0.3.303 → 0.3.304

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 (165) hide show
  1. package/dist/Taskforce.module.css +102 -5
  2. package/dist/TaskforceCore.js +514 -212
  3. package/dist/TaskforceCore.test.js +1172 -133
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  21. package/dist/components/task/TaskStatusActions.js +8 -3
  22. package/dist/components/task/TaskStatusActions.test.js +9 -0
  23. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  24. package/dist/components/task/taskKanbanDropRules.js +187 -0
  25. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  26. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  27. package/dist/components/ui/Modal.d.ts +1 -1
  28. package/dist/components/ui/Modal.js +1 -0
  29. package/dist/components/views/AppShellHeader.js +5 -1
  30. package/dist/components/views/AppShellHeader.test.js +4 -0
  31. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  32. package/dist/components/views/PlanComparisonPage.js +4 -2
  33. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  34. package/dist/components/views/PlansPage.d.ts +5 -1
  35. package/dist/components/views/PlansPage.js +39 -12
  36. package/dist/components/views/PlansPage.test.js +59 -2
  37. package/dist/components/views/StandaloneLayout.js +113 -38
  38. package/dist/components/views/WidgetView.js +11 -2
  39. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  40. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  41. package/dist/core/PlanEntitlementService.d.ts +3 -1
  42. package/dist/core/PlanEntitlementService.js +61 -11
  43. package/dist/core/SignupMonetizationSettings.test.js +37 -2
  44. package/dist/core/TaskActivityService.d.ts +4 -1
  45. package/dist/core/TaskActivityService.js +45 -0
  46. package/dist/core/TaskAuditTimeline.test.js +52 -1
  47. package/dist/core/Taskforce.d.ts +15 -11
  48. package/dist/core/Taskforce.js +43 -2
  49. package/dist/core/WorkspacePlanMode.test.js +65 -0
  50. package/dist/documentReviews/service.d.ts +1 -1
  51. package/dist/documentReviews/types.d.ts +1 -1
  52. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  53. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  54. package/dist/hooks/sync/auth.d.ts +37 -0
  55. package/dist/hooks/sync/auth.js +61 -0
  56. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  57. package/dist/hooks/sync/bootstrap.js +52 -0
  58. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  59. package/dist/hooks/sync/orchestratorShared.js +209 -0
  60. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  61. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  62. package/dist/hooks/sync/realtime.d.ts +25 -0
  63. package/dist/hooks/sync/realtime.js +60 -0
  64. package/dist/hooks/sync/recovery.d.ts +69 -0
  65. package/dist/hooks/sync/recovery.js +118 -0
  66. package/dist/hooks/sync/transfers.d.ts +122 -0
  67. package/dist/hooks/sync/transfers.js +431 -0
  68. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  69. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  70. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  71. package/dist/hooks/ui/useResourceExport.js +5 -2
  72. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  73. package/dist/hooks/useRealtimeSync.js +2 -1
  74. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  75. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  76. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  77. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  78. package/dist/hooks/useSyncOrchestrator.js +314 -873
  79. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
  80. package/dist/hooks/useTaskMutations.js +45 -6
  81. package/dist/hooks/useTaskMutations.test.js +147 -0
  82. package/dist/hooks/useTaskforce.d.ts +8 -2
  83. package/dist/hooks/useTaskforce.js +285 -812
  84. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  85. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  86. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  87. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  88. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  89. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  90. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  91. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  92. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  93. package/dist/localization/locales/en-US.js +2 -2
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  95. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  96. package/dist/mcp/clientIntegrations.d.ts +12 -1
  97. package/dist/mcp/clientIntegrations.js +161 -6
  98. package/dist/mcp/clientIntegrations.test.js +76 -4
  99. package/dist/mcp/documentHelpers.d.ts +100 -0
  100. package/dist/mcp/documentHelpers.js +161 -0
  101. package/dist/mcp/runtime.js +174 -476
  102. package/dist/mcp/runtime.test.js +81 -8
  103. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  104. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  105. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  106. package/dist/mcp/taskAttachmentTypes.js +1 -0
  107. package/dist/plugins/vite.js +1 -1
  108. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  109. package/dist/runtime/appGateDefinitions.js +1 -1
  110. package/dist/server/index.d.ts +1 -0
  111. package/dist/server/index.js +46 -3
  112. package/dist/server/index.test.js +12 -1
  113. package/dist/server/routes/admin.js +13 -19
  114. package/dist/server/routes/auth.js +37 -2
  115. package/dist/server/routes/billing.js +18 -67
  116. package/dist/server/routes/billing.test.js +43 -0
  117. package/dist/server/routes/shared.d.ts +30 -0
  118. package/dist/server/routes/shared.js +26 -0
  119. package/dist/server/routes/sync.integration.test.js +20 -0
  120. package/dist/server/routes/sync.js +110 -237
  121. package/dist/server/routes/tasks.js +49 -7
  122. package/dist/server/routes.js +8 -19
  123. package/dist/server/routes.test.js +243 -5
  124. package/dist/shared/taskActor.d.ts +10 -0
  125. package/dist/shared/taskActor.js +1 -0
  126. package/dist/sync/collaborationSyncPayload.js +4 -0
  127. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  128. package/dist/sync/collaborationSyncState.d.ts +3 -1
  129. package/dist/sync/collaborationSyncState.js +19 -0
  130. package/dist/sync/contentSyncState.js +12 -10
  131. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  132. package/dist/sync/syncApplyHandlers.js +370 -8
  133. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  134. package/dist/sync/syncApplyHandlers.test.js +377 -0
  135. package/dist/sync/syncService.d.ts +8 -1
  136. package/dist/sync/syncService.js +36 -2
  137. package/dist/sync/syncService.test.js +48 -0
  138. package/dist/sync/workspacePullFeed.d.ts +1 -0
  139. package/dist/sync/workspacePullFeed.js +66 -59
  140. package/dist/sync/workspacePullFeed.test.js +72 -4
  141. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  142. package/dist/sync/workspaceSyncModel.js +65 -0
  143. package/dist/sync/workspaceSyncModel.test.js +119 -0
  144. package/dist/types.d.ts +2 -10
  145. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  146. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  147. package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  148. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
  149. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  150. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  151. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  152. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
  153. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  154. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  155. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
  156. package/dist/ui/index.html +4 -4
  157. package/dist/utils/taskActivity.js +4 -0
  158. package/dist/utils/taskActivity.test.js +25 -1
  159. package/package.json +3 -1
  160. package/scripts/playwright-auth.sh +4 -0
  161. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  162. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  163. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  164. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  165. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -80,8 +80,7 @@ export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options) {
80
80
  incomingReferenceNumber: normalizedIncomingReference
81
81
  });
82
82
  }
83
- return referenceNumberMismatch
84
- || (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
83
+ return (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
85
84
  || existingAsset.version !== normalizedIncomingVersion
86
85
  || (existingAsset.documentId || null) !== normalizedIncomingDocumentId
87
86
  || (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
@@ -130,8 +129,7 @@ export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, option
130
129
  incomingReferenceNumber: normalizedIncomingReference
131
130
  });
132
131
  }
133
- return referenceNumberMismatch
134
- || (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
132
+ return (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
135
133
  || (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
136
134
  || (existingAsset.originalFilename || null) !== normalizedIncomingFilename
137
135
  || (primaryLink?.taskId || null) !== normalizedIncomingTaskId
@@ -733,8 +731,10 @@ export async function writeWorkspaceSyncDocument(deps, basePath, doc, workspaceI
733
731
  const incomingReferenceNumber = Number.isFinite(Number(doc.referenceNumber))
734
732
  ? Math.max(1, Math.floor(Number(doc.referenceNumber)))
735
733
  : null;
736
- const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true
737
- || Boolean(existing?.referenceNumber && incomingReferenceNumber && existing.referenceNumber !== incomingReferenceNumber);
734
+ const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
735
+ const nextReferenceNumber = allowReferenceNumberReassignment
736
+ ? (incomingReferenceNumber ?? existing?.referenceNumber ?? null)
737
+ : (existing?.referenceNumber ?? incomingReferenceNumber ?? null);
738
738
  const originalFilename = String(doc.originalFilename || existing?.originalFilename || path.basename(normalizedPath) || 'document.md').trim() || 'document.md';
739
739
  const asset = deps.workspaceAssetStore.upsertAsset({
740
740
  assetId: String(doc.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
@@ -750,7 +750,7 @@ export async function writeWorkspaceSyncDocument(deps, basePath, doc, workspaceI
750
750
  contentSha256: contentHash,
751
751
  sizeBytes: Buffer.byteLength(content, 'utf8'),
752
752
  documentId: String(doc.documentId || existing?.documentId || `doc-${randomUUID().replace(/-/g, '')}`),
753
- referenceNumber: incomingReferenceNumber ?? existing?.referenceNumber ?? null,
753
+ referenceNumber: nextReferenceNumber,
754
754
  version: Number.isFinite(Number(doc.version))
755
755
  ? Math.max(1, Math.floor(Number(doc.version)))
756
756
  : Math.max(1, Number(existing?.version || 0) + 1),
@@ -826,8 +826,10 @@ export async function writeWorkspaceSyncBinaryAsset(deps, basePath, asset, works
826
826
  const incomingReferenceNumber = Number.isFinite(Number(asset.referenceNumber))
827
827
  ? Math.max(1, Math.floor(Number(asset.referenceNumber)))
828
828
  : null;
829
- const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true
830
- || Boolean(existing?.referenceNumber && incomingReferenceNumber && existing.referenceNumber !== incomingReferenceNumber);
829
+ const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
830
+ const nextReferenceNumber = allowReferenceNumberReassignment
831
+ ? (incomingReferenceNumber ?? existing?.referenceNumber ?? null)
832
+ : (existing?.referenceNumber ?? incomingReferenceNumber ?? null);
831
833
  const assetRecord = deps.workspaceAssetStore.upsertAsset({
832
834
  assetId: String(asset.assetId || existing?.assetId || `asset-${randomUUID().replace(/-/g, '')}`),
833
835
  workspaceId,
@@ -842,7 +844,7 @@ export async function writeWorkspaceSyncBinaryAsset(deps, basePath, asset, works
842
844
  contentSha256: createHash('sha256').update(buffer).digest('hex'),
843
845
  sizeBytes: buffer.length,
844
846
  documentId: null,
845
- referenceNumber: incomingReferenceNumber ?? existing?.referenceNumber ?? null,
847
+ referenceNumber: nextReferenceNumber,
846
848
  version: null,
847
849
  isLatest: true,
848
850
  scanStatus: existing?.scanStatus || 'clean',
@@ -1,6 +1,7 @@
1
1
  import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
2
- import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncInitiativeSnapshot, WorkspaceSyncWorkspaceMemberSnapshot, WorkspaceSyncWorkstreamSnapshot } from './workspaceSyncModel.js';
2
+ import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncChange, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncInitiativeSnapshot, WorkspaceSyncTaskEventSnapshot, WorkspaceSyncTaskSnapshot, WorkspaceSyncWorkspaceMemberSnapshot, WorkspaceSyncWorkstreamSnapshot } from './workspaceSyncModel.js';
3
3
  import type { BinaryAssetSyncPayload, DocumentSyncPayload } from './contentSyncPayload.js';
4
+ import type { AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
4
5
  type SyncCore = TaskforceCore & TaskforceSyncCapable;
5
6
  type SyncRouteError = Error & {
6
7
  code?: string;
@@ -12,7 +13,81 @@ type CreateSyncRouteError = (message: string, details: Record<string, unknown>,
12
13
  code?: string;
13
14
  statusCode?: number;
14
15
  }) => SyncRouteError;
16
+ type NormalizeIncomingAiProfileSurfaceType = (value: unknown, context: {
17
+ source: string;
18
+ profileId?: string;
19
+ workspaceId?: string;
20
+ }) => AiProfileSurfaceType | null;
21
+ type NormalizeTaskEventSyncPayload = (raw: unknown, workspaceId: string, sourceWatermark?: string) => WorkspaceSyncTaskEventSnapshot | null;
22
+ export interface WorkspaceSyncIncomingTaskMutation {
23
+ archived: boolean;
24
+ task: WorkspaceSyncTaskSnapshot;
25
+ }
26
+ export interface WorkspaceSyncIncomingTaskApplyModel {
27
+ upserts: WorkspaceSyncIncomingTaskMutation[];
28
+ deletes: Map<string, string>;
29
+ }
30
+ export interface WorkspaceSyncIncomingApplyModel {
31
+ taskMutations: WorkspaceSyncIncomingTaskApplyModel;
32
+ planning: {
33
+ incomingTaxonomies: unknown[] | null;
34
+ incomingTaxonomyState: Record<string, unknown> | null;
35
+ incomingTaxonomiesUpdatedAt: string;
36
+ incomingInitiatives: Map<string, WorkspaceSyncInitiativeSnapshot>;
37
+ incomingWorkstreams: Map<string, WorkspaceSyncWorkstreamSnapshot>;
38
+ };
39
+ profiles: {
40
+ incomingAiProfiles: Map<string, WorkspaceSyncAiProfileSnapshot>;
41
+ };
42
+ content: {
43
+ incomingDocuments: DocumentSyncPayload[];
44
+ incomingDocumentDeletes: Map<string, string>;
45
+ incomingAssets: BinaryAssetSyncPayload[];
46
+ incomingAssetDeletes: Map<string, string>;
47
+ };
48
+ collaboration: {
49
+ incomingDocumentReviewSessions: Map<string, WorkspaceSyncDocumentReviewSessionSnapshot>;
50
+ incomingDocumentReviewSessionDeletes: Map<string, string>;
51
+ incomingDocumentReviewComments: Map<string, WorkspaceSyncDocumentReviewCommentSnapshot>;
52
+ incomingDocumentReviewCommentDeletes: Map<string, string>;
53
+ incomingAnnotatedAttachmentSessions: Map<string, WorkspaceSyncAnnotatedAttachmentSessionSnapshot>;
54
+ incomingAnnotatedAttachmentSessionDeletes: Map<string, string>;
55
+ };
56
+ taskActivity: {
57
+ incomingTaskEvents: Map<string, WorkspaceSyncTaskEventSnapshot>;
58
+ };
59
+ }
60
+ export interface PreparedWorkspaceTaskSyncSnapshot {
61
+ tasks: WorkspaceSyncTaskSnapshot[];
62
+ archived: WorkspaceSyncTaskSnapshot[];
63
+ }
15
64
  export declare function resolveSyncConflictOrdering(incomingWatermark: string, existingWatermark: string, incomingTieBreak: string, existingTieBreak: string): number;
65
+ export declare function classifyWorkspaceSyncChanges(input: {
66
+ workspaceId: string;
67
+ incomingChanges: WorkspaceSyncChange[];
68
+ normalizeWorkspaceSyncDocumentPath: (rawPath: unknown) => string | null;
69
+ normalizeTaskAssetPath: (rawPath: unknown) => string | null;
70
+ normalizeIncomingAiProfileSurfaceType: NormalizeIncomingAiProfileSurfaceType;
71
+ normalizeTaskEventSyncPayload: NormalizeTaskEventSyncPayload;
72
+ recordSyncDiagnostic: RecordSyncDiagnostic;
73
+ }): WorkspaceSyncIncomingApplyModel;
74
+ export declare function prepareTaskSyncSnapshotApply(input: {
75
+ core: SyncCore;
76
+ workspaceId: string;
77
+ taskMutations: WorkspaceSyncIncomingTaskApplyModel;
78
+ recordSyncDiagnostic: RecordSyncDiagnostic;
79
+ }): PreparedWorkspaceTaskSyncSnapshot;
80
+ export declare function applyTaskSyncDeletes(input: {
81
+ core: SyncCore;
82
+ workspaceId: string;
83
+ taskMutations: WorkspaceSyncIncomingTaskApplyModel;
84
+ recordSyncDiagnostic: RecordSyncDiagnostic;
85
+ }): number;
86
+ export declare function applyPreparedTaskSyncSnapshot(input: {
87
+ core: SyncCore;
88
+ workspaceId: string;
89
+ prepared: PreparedWorkspaceTaskSyncSnapshot;
90
+ }): ReturnType<SyncCore['applyWorkspaceSyncSnapshot']>;
16
91
  export declare function applyPlanningSyncChanges(input: {
17
92
  core: SyncCore;
18
93
  workspaceId: string;
@@ -51,6 +126,10 @@ export declare function applyContentSyncChanges(input: {
51
126
  hasBinaryAssetSyncMetadataDrift: (workspaceId: string, asset: BinaryAssetSyncPayload, options?: {
52
127
  allowReferenceNumberReassignment?: boolean;
53
128
  }) => boolean;
129
+ getExistingDocument?: (docPath: string, workspaceId: string) => {
130
+ updatedAt?: string | null;
131
+ contentHash?: string | null;
132
+ } | null;
54
133
  writeWorkspaceSyncDocument: (basePath: string, doc: DocumentSyncPayload, workspaceId?: string, options?: {
55
134
  allowReferenceNumberReassignment?: boolean;
56
135
  }) => Promise<void>;
@@ -80,4 +159,11 @@ export declare function applyCollaborationSyncChanges(input: {
80
159
  recordSyncDiagnostic: RecordSyncDiagnostic;
81
160
  createSyncRouteError: CreateSyncRouteError;
82
161
  }): void;
162
+ export declare function applyTaskActivitySyncChanges(input: {
163
+ core: SyncCore;
164
+ workspaceId: string;
165
+ incomingTaskEvents: Map<string, WorkspaceSyncTaskEventSnapshot>;
166
+ recordSyncDiagnostic: RecordSyncDiagnostic;
167
+ createSyncRouteError: CreateSyncRouteError;
168
+ }): void;
83
169
  export {};
@@ -1,10 +1,285 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { createHash } from 'crypto';
4
- import { compareWorkspaceMutationOrdering } from './workspaceSyncSurface.js';
4
+ import { normalizeBinaryAssetDeleteSyncPayload, normalizeBinaryAssetSyncPayload, normalizeDocumentDeleteSyncPayload, normalizeDocumentSyncPayload, } from './contentSyncPayload.js';
5
+ import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload, normalizeDeleteSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, } from './collaborationSyncPayload.js';
6
+ import { normalizeInitiativeSyncPayload, normalizeTaxonomySyncPayload, normalizeWorkstreamSyncPayload, } from './planningSyncPayload.js';
7
+ import { normalizeTaskSyncPayload, taskSyncPayloadToCoreTask, } from './taskSyncPayload.js';
8
+ import { compareWorkspaceMutationOrdering, getTaskMutationTieBreak, getTaskMutationWatermark } from './workspaceSyncSurface.js';
9
+ function createIncomingApplyModel() {
10
+ return {
11
+ taskMutations: {
12
+ upserts: [],
13
+ deletes: new Map(),
14
+ },
15
+ planning: {
16
+ incomingTaxonomies: null,
17
+ incomingTaxonomyState: null,
18
+ incomingTaxonomiesUpdatedAt: '',
19
+ incomingInitiatives: new Map(),
20
+ incomingWorkstreams: new Map(),
21
+ },
22
+ profiles: {
23
+ incomingAiProfiles: new Map(),
24
+ },
25
+ content: {
26
+ incomingDocuments: [],
27
+ incomingDocumentDeletes: new Map(),
28
+ incomingAssets: [],
29
+ incomingAssetDeletes: new Map(),
30
+ },
31
+ collaboration: {
32
+ incomingDocumentReviewSessions: new Map(),
33
+ incomingDocumentReviewSessionDeletes: new Map(),
34
+ incomingDocumentReviewComments: new Map(),
35
+ incomingDocumentReviewCommentDeletes: new Map(),
36
+ incomingAnnotatedAttachmentSessions: new Map(),
37
+ incomingAnnotatedAttachmentSessionDeletes: new Map(),
38
+ },
39
+ taskActivity: {
40
+ incomingTaskEvents: new Map(),
41
+ },
42
+ };
43
+ }
5
44
  export function resolveSyncConflictOrdering(incomingWatermark, existingWatermark, incomingTieBreak, existingTieBreak) {
6
45
  return compareWorkspaceMutationOrdering(String(incomingWatermark || '').trim(), String(existingWatermark || '').trim(), String(incomingTieBreak || '').trim(), String(existingTieBreak || '').trim());
7
46
  }
47
+ function shouldSkipTaskClassification(op) {
48
+ return (op === 'delete'
49
+ || op === 'initiative-upsert'
50
+ || op === 'workstream-upsert'
51
+ || op === 'document-upsert'
52
+ || op === 'document-delete'
53
+ || op === 'asset-upsert'
54
+ || op === 'asset-delete'
55
+ || op === 'document-review-session-upsert'
56
+ || op === 'document-review-session-delete'
57
+ || op === 'document-review-comment-upsert'
58
+ || op === 'document-review-comment-delete'
59
+ || op === 'annotated-attachment-session-upsert'
60
+ || op === 'annotated-attachment-session-delete');
61
+ }
62
+ function recordTaskReferenceNumberMismatch(input) {
63
+ const { core, workspaceId, task, recordSyncDiagnostic, } = input;
64
+ const taskId = String(task?.id || '').trim();
65
+ const incomingReferenceNumber = Number.isFinite(Number(task?.referenceNumber))
66
+ ? Math.max(1, Math.floor(Number(task?.referenceNumber)))
67
+ : null;
68
+ if (!incomingReferenceNumber)
69
+ return false;
70
+ const existingTask = core.resolveTaskIdentifier(`T-${incomingReferenceNumber}`, workspaceId);
71
+ if (!existingTask || existingTask.id === taskId)
72
+ return false;
73
+ recordSyncDiagnostic(workspaceId, 'apply', 'Detected task reference number mismatch during normal sync.', {
74
+ source: 'tasks.referenceNumber.mismatch',
75
+ taskId: taskId || null,
76
+ taskTitle: typeof task?.title === 'string' ? task.title.trim() || null : null,
77
+ existingTaskId: existingTask.id,
78
+ existingTaskTitle: existingTask.title || null,
79
+ referenceLabel: `T-${incomingReferenceNumber}`,
80
+ existingReferenceNumber: existingTask.referenceNumber ?? incomingReferenceNumber,
81
+ incomingReferenceNumber
82
+ });
83
+ return true;
84
+ }
85
+ export function classifyWorkspaceSyncChanges(input) {
86
+ const { workspaceId, incomingChanges, normalizeWorkspaceSyncDocumentPath, normalizeTaskAssetPath, normalizeIncomingAiProfileSurfaceType, normalizeTaskEventSyncPayload, recordSyncDiagnostic, } = input;
87
+ const model = createIncomingApplyModel();
88
+ for (const change of incomingChanges) {
89
+ const raw = change;
90
+ const op = String(raw.op || '').trim().toLowerCase();
91
+ if (op === 'taxonomy-upsert') {
92
+ const taxonomy = normalizeTaxonomySyncPayload(raw);
93
+ if (taxonomy?.taxonomyState)
94
+ model.planning.incomingTaxonomyState = taxonomy.taxonomyState;
95
+ if (Array.isArray(taxonomy?.taxonomies))
96
+ model.planning.incomingTaxonomies = taxonomy.taxonomies;
97
+ if (taxonomy?.updatedAt)
98
+ model.planning.incomingTaxonomiesUpdatedAt = taxonomy.updatedAt;
99
+ continue;
100
+ }
101
+ if (op === 'initiative-upsert') {
102
+ const initiative = normalizeInitiativeSyncPayload(raw.initiative, String(raw.updatedAt || raw.watermark || '').trim());
103
+ if (initiative)
104
+ model.planning.incomingInitiatives.set(initiative.id, initiative);
105
+ continue;
106
+ }
107
+ if (op === 'workstream-upsert') {
108
+ const workstream = normalizeWorkstreamSyncPayload(raw.workstream, String(raw.updatedAt || raw.watermark || '').trim());
109
+ if (workstream)
110
+ model.planning.incomingWorkstreams.set(workstream.id, workstream);
111
+ continue;
112
+ }
113
+ if (op === 'ai-profile-upsert') {
114
+ const profile = normalizeAiProfileSyncPayload(raw.profile, workspaceId, normalizeIncomingAiProfileSurfaceType, 'apply.aiProfileUpsert');
115
+ const profileId = String(profile?.id || '').trim();
116
+ if (profileId && profile) {
117
+ const profileWorkspaceId = String(profile.workspaceId || '').trim();
118
+ if (profileWorkspaceId && profileWorkspaceId !== workspaceId) {
119
+ recordSyncDiagnostic(workspaceId, 'apply', `Rejected ai-profile-upsert for profile ${profileId}: workspace mismatch (expected ${workspaceId}, got ${profileWorkspaceId}).`, {
120
+ source: 'apply.aiProfileWorkspaceMismatch',
121
+ workspaceId,
122
+ profileId,
123
+ expectedWorkspaceId: workspaceId,
124
+ receivedWorkspaceId: profileWorkspaceId
125
+ });
126
+ continue;
127
+ }
128
+ model.profiles.incomingAiProfiles.set(profileId, {
129
+ ...profile,
130
+ workspaceId,
131
+ createdAt: profile.createdAt || String(raw.updatedAt || raw.watermark || '').trim(),
132
+ updatedAt: profile.updatedAt || String(raw.updatedAt || raw.watermark || '').trim(),
133
+ });
134
+ }
135
+ continue;
136
+ }
137
+ if (op === 'document-upsert') {
138
+ const document = normalizeDocumentSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
139
+ if (document)
140
+ model.content.incomingDocuments.push(document);
141
+ continue;
142
+ }
143
+ if (op === 'document-delete') {
144
+ const deletedDocument = normalizeDocumentDeleteSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
145
+ if (deletedDocument)
146
+ model.content.incomingDocumentDeletes.set(deletedDocument.path, deletedDocument.deletedAt);
147
+ continue;
148
+ }
149
+ if (op === 'asset-upsert') {
150
+ const asset = normalizeBinaryAssetSyncPayload(raw, normalizeTaskAssetPath);
151
+ if (asset)
152
+ model.content.incomingAssets.push(asset);
153
+ continue;
154
+ }
155
+ if (op === 'asset-delete') {
156
+ const deletedAsset = normalizeBinaryAssetDeleteSyncPayload(raw, normalizeTaskAssetPath);
157
+ if (deletedAsset)
158
+ model.content.incomingAssetDeletes.set(deletedAsset.path, deletedAsset.deletedAt);
159
+ continue;
160
+ }
161
+ if (op === 'document-review-session-upsert') {
162
+ const session = normalizeDocumentReviewSessionSyncPayload(raw.session, String(raw.updatedAt || raw.watermark || '').trim());
163
+ if (session)
164
+ model.collaboration.incomingDocumentReviewSessions.set(session.id, session);
165
+ continue;
166
+ }
167
+ if (op === 'document-review-session-delete') {
168
+ const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
169
+ if (deletedSession)
170
+ model.collaboration.incomingDocumentReviewSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
171
+ continue;
172
+ }
173
+ if (op === 'document-review-comment-upsert') {
174
+ const comment = normalizeDocumentReviewCommentSyncPayload(raw.comment, { sourceWatermark: String(raw.updatedAt || raw.watermark || '').trim() });
175
+ if (comment)
176
+ model.collaboration.incomingDocumentReviewComments.set(comment.id, comment);
177
+ continue;
178
+ }
179
+ if (op === 'document-review-comment-delete') {
180
+ const deletedComment = normalizeDeleteSyncPayload(raw, 'commentId');
181
+ if (deletedComment)
182
+ model.collaboration.incomingDocumentReviewCommentDeletes.set(deletedComment.id, deletedComment.deletedAt);
183
+ continue;
184
+ }
185
+ if (op === 'annotated-attachment-session-upsert') {
186
+ const session = normalizeAnnotatedAttachmentSessionSyncPayload(raw.session, workspaceId, String(raw.updatedAt || raw.watermark || '').trim());
187
+ if (session)
188
+ model.collaboration.incomingAnnotatedAttachmentSessions.set(session.id, session);
189
+ continue;
190
+ }
191
+ if (op === 'annotated-attachment-session-delete') {
192
+ const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
193
+ if (deletedSession)
194
+ model.collaboration.incomingAnnotatedAttachmentSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
195
+ continue;
196
+ }
197
+ if (op === 'task-event-upsert') {
198
+ const event = normalizeTaskEventSyncPayload(raw.event, workspaceId, String(raw.updatedAt || raw.watermark || '').trim());
199
+ if (event)
200
+ model.taskActivity.incomingTaskEvents.set(event.id, event);
201
+ continue;
202
+ }
203
+ if (op !== 'delete')
204
+ continue;
205
+ const task = normalizeTaskSyncPayload(raw.task);
206
+ const deleteTaskId = String(raw.taskId || raw.id || task?.id || '').trim();
207
+ if (deleteTaskId) {
208
+ model.taskMutations.deletes.set(deleteTaskId, String(raw.deletedAt || raw.watermark || new Date().toISOString()).trim());
209
+ }
210
+ }
211
+ for (const change of incomingChanges) {
212
+ const raw = change;
213
+ const op = String(raw.op || '').trim().toLowerCase();
214
+ if (shouldSkipTaskClassification(op))
215
+ continue;
216
+ const task = normalizeTaskSyncPayload(raw.task);
217
+ if (!task)
218
+ continue;
219
+ model.taskMutations.upserts.push({
220
+ archived: raw.archived === true || task.isArchived === true,
221
+ task,
222
+ });
223
+ }
224
+ return model;
225
+ }
226
+ export function prepareTaskSyncSnapshotApply(input) {
227
+ const { core, workspaceId, taskMutations, recordSyncDiagnostic, } = input;
228
+ const prepared = {
229
+ tasks: [],
230
+ archived: [],
231
+ };
232
+ for (const mutation of taskMutations.upserts) {
233
+ const task = mutation.task;
234
+ const taskId = String(task.id || '').trim();
235
+ recordTaskReferenceNumberMismatch({
236
+ core,
237
+ workspaceId,
238
+ task: task,
239
+ recordSyncDiagnostic,
240
+ });
241
+ const deleteWatermark = taskId ? String(taskMutations.deletes.get(taskId) || '').trim() : '';
242
+ if (taskId && deleteWatermark) {
243
+ const compareAgainstDelete = resolveSyncConflictOrdering(deleteWatermark, String(task.updatedAt || task.createdAt || '').trim(), `delete:${taskId}`, getTaskMutationTieBreak(task, mutation.archived));
244
+ if (compareAgainstDelete >= 0)
245
+ continue;
246
+ }
247
+ if (mutation.archived)
248
+ prepared.archived.push(task);
249
+ else
250
+ prepared.tasks.push(task);
251
+ }
252
+ return prepared;
253
+ }
254
+ export function applyTaskSyncDeletes(input) {
255
+ const { core, workspaceId, taskMutations, recordSyncDiagnostic, } = input;
256
+ for (const [taskId, deleteWatermark] of taskMutations.deletes.entries()) {
257
+ try {
258
+ const existingTask = core.getTask(taskId, workspaceId);
259
+ if (existingTask) {
260
+ const compareAgainstExisting = resolveSyncConflictOrdering(deleteWatermark, getTaskMutationWatermark(existingTask), `delete:${taskId}`, getTaskMutationTieBreak(existingTask, Boolean(existingTask.isArchived)));
261
+ if (compareAgainstExisting < 0)
262
+ continue;
263
+ }
264
+ core.deleteTaskForSync(taskId, workspaceId, { persistBackup: false });
265
+ }
266
+ catch (error) {
267
+ recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete task during sync apply.', {
268
+ source: 'apply.deleteTask',
269
+ taskId,
270
+ error: String(error?.message || error || '')
271
+ });
272
+ }
273
+ }
274
+ return taskMutations.deletes.size;
275
+ }
276
+ export function applyPreparedTaskSyncSnapshot(input) {
277
+ const { core, workspaceId, prepared, } = input;
278
+ return core.applyWorkspaceSyncSnapshot({
279
+ tasks: prepared.tasks.map(taskSyncPayloadToCoreTask),
280
+ archived: prepared.archived.map(taskSyncPayloadToCoreTask)
281
+ }, workspaceId);
282
+ }
8
283
  export function applyPlanningSyncChanges(input) {
9
284
  const { core, workspaceId, incomingInitiatives, incomingWorkstreams, incomingTaxonomies, incomingTaxonomyState, incomingTaxonomiesUpdatedAt, workspaceTaxonomySyncMeta, recordSyncDiagnostic, } = input;
10
285
  if (incomingInitiatives.size > 0) {
@@ -107,6 +382,10 @@ export function applyAiProfileAndMemberSyncChanges(input) {
107
382
  username: profile.username,
108
383
  icon: profile.icon,
109
384
  color: profile.color,
385
+ description: profile.description ?? null,
386
+ role: profile.role ?? null,
387
+ provider: profile.provider ?? null,
388
+ model: profile.model ?? null,
110
389
  surfaceType: profile.surfaceType,
111
390
  providerMetadata: profile.providerMetadata,
112
391
  createdAt: profile.createdAt,
@@ -150,21 +429,29 @@ export function applyAiProfileAndMemberSyncChanges(input) {
150
429
  }
151
430
  }
152
431
  export async function applyContentSyncChanges(input) {
153
- const { core, workspaceId, incomingDocuments, incomingDocumentDeletes, incomingAssets, incomingAssetDeletes, basePath, hasObjectStorageClient, hasDocumentSyncMetadataDrift, hasBinaryAssetSyncMetadataDrift, writeWorkspaceSyncDocument, deleteWorkspaceSyncDocument, writeWorkspaceSyncBinaryAsset, deleteWorkspaceSyncBinaryAsset, getExistingBinaryAsset, recordSyncDiagnostic, options, } = input;
432
+ const { core, workspaceId, incomingDocuments, incomingDocumentDeletes, incomingAssets, incomingAssetDeletes, basePath, hasObjectStorageClient, hasDocumentSyncMetadataDrift, hasBinaryAssetSyncMetadataDrift, getExistingDocument, writeWorkspaceSyncDocument, deleteWorkspaceSyncDocument, writeWorkspaceSyncBinaryAsset, deleteWorkspaceSyncBinaryAsset, getExistingBinaryAsset, recordSyncDiagnostic, options, } = input;
154
433
  if (incomingDocuments.length > 0) {
155
434
  for (const doc of incomingDocuments) {
156
435
  try {
157
436
  const existingDoc = core.getDocumentWatermark(workspaceId, doc.path);
437
+ const existingDocFallback = getExistingDocument ? getExistingDocument(doc.path, workspaceId) : null;
438
+ const localDocumentPath = path.resolve(basePath, doc.path);
439
+ const localDocumentHash = !hasObjectStorageClient() && fs.existsSync(localDocumentPath)
440
+ ? createHash('sha256').update(fs.readFileSync(localDocumentPath, 'utf8')).digest('hex')
441
+ : '';
158
442
  const incomingHash = createHash('sha256').update(String(doc.content || '')).digest('hex');
159
- const existingUpdatedAt = String(existingDoc?.updatedAt || '').trim();
160
- const existingHash = String(existingDoc?.contentHash || '').trim();
443
+ const existingUpdatedAt = String(existingDoc?.updatedAt || existingDocFallback?.updatedAt || '').trim();
444
+ const existingHash = String(existingDoc?.contentHash || localDocumentHash || existingDocFallback?.contentHash || '').trim();
161
445
  const compare = resolveSyncConflictOrdering(doc.updatedAt, existingUpdatedAt, incomingHash, existingHash);
162
446
  const metadataDrift = hasDocumentSyncMetadataDrift(workspaceId, doc, options);
163
447
  if (compare < 0)
164
448
  continue;
165
449
  if (compare === 0 && existingHash === incomingHash && !metadataDrift)
166
450
  continue;
167
- await writeWorkspaceSyncDocument(basePath, doc, workspaceId, options);
451
+ await writeWorkspaceSyncDocument(basePath, doc, workspaceId, {
452
+ ...options,
453
+ allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true || existingHash !== incomingHash
454
+ });
168
455
  }
169
456
  catch (error) {
170
457
  recordSyncDiagnostic(workspaceId, 'apply', 'Failed to apply synced document change.', {
@@ -190,18 +477,27 @@ export async function applyContentSyncChanges(input) {
190
477
  if (incomingAssets.length > 0) {
191
478
  for (const asset of incomingAssets) {
192
479
  try {
480
+ const localAssetPath = path.resolve(basePath, asset.path);
193
481
  const existing = getExistingBinaryAsset(asset.path, workspaceId);
194
482
  const incomingHash = createHash('sha256').update(Buffer.from(String(asset.contentBase64 || ''), 'base64')).digest('hex');
483
+ const localFileMissing = !hasObjectStorageClient() && !fs.existsSync(localAssetPath);
484
+ const localAssetHash = !hasObjectStorageClient() && !localFileMissing
485
+ ? createHash('sha256').update(fs.readFileSync(localAssetPath)).digest('hex')
486
+ : '';
195
487
  const existingUpdatedAt = String(existing?.updatedAt || '').trim();
196
- const existingHash = String(existing?.contentSha256 || '').trim();
197
- const localFileMissing = !hasObjectStorageClient() && !fs.existsSync(path.resolve(basePath, asset.path));
488
+ const existingHash = String(localAssetHash || existing?.contentSha256 || '').trim();
198
489
  const compare = resolveSyncConflictOrdering(asset.updatedAt, existingUpdatedAt, incomingHash, existingHash);
199
490
  const metadataDrift = hasBinaryAssetSyncMetadataDrift(workspaceId, asset, options);
200
491
  if (compare < 0)
201
492
  continue;
202
493
  if (compare === 0 && existingHash === incomingHash && !localFileMissing && !metadataDrift)
203
494
  continue;
204
- await writeWorkspaceSyncBinaryAsset(basePath, asset, workspaceId, options);
495
+ await writeWorkspaceSyncBinaryAsset(basePath, asset, workspaceId, {
496
+ ...options,
497
+ allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true
498
+ || existingHash !== incomingHash
499
+ || localFileMissing
500
+ });
205
501
  }
206
502
  catch (error) {
207
503
  recordSyncDiagnostic(workspaceId, 'apply', 'Failed to apply synced asset change.', {
@@ -443,3 +739,69 @@ export function applyCollaborationSyncChanges(input) {
443
739
  }
444
740
  }
445
741
  }
742
+ export function applyTaskActivitySyncChanges(input) {
743
+ const { core, workspaceId, incomingTaskEvents, recordSyncDiagnostic, createSyncRouteError, } = input;
744
+ if (incomingTaskEvents.size <= 0)
745
+ return;
746
+ for (const event of incomingTaskEvents.values()) {
747
+ try {
748
+ const taskId = String(event.taskId || '').trim();
749
+ if (!taskId)
750
+ continue;
751
+ const existingTask = core.getTask(taskId, workspaceId);
752
+ if (!existingTask) {
753
+ recordSyncDiagnostic(workspaceId, 'apply', 'Skipped task event during sync apply because the task does not exist locally.', {
754
+ source: 'apply.taskEventMissingTask',
755
+ eventId: event.id,
756
+ taskId,
757
+ });
758
+ continue;
759
+ }
760
+ const existing = core.listTaskEvents(taskId, workspaceId).find((entry) => entry.id === event.id) || null;
761
+ const incomingTieBreak = createHash('sha256')
762
+ .update(JSON.stringify({
763
+ taskId,
764
+ action: event.action,
765
+ actor: event.actor,
766
+ actorType: event.actorType,
767
+ details: event.details ?? {},
768
+ }))
769
+ .digest('hex');
770
+ const existingTieBreak = existing
771
+ ? createHash('sha256')
772
+ .update(JSON.stringify({
773
+ taskId: existing.taskId,
774
+ action: existing.action,
775
+ actor: existing.actor,
776
+ actorType: existing.actorType,
777
+ details: existing.details ?? {},
778
+ }))
779
+ .digest('hex')
780
+ : '';
781
+ const compare = resolveSyncConflictOrdering(event.createdAt, String(existing?.createdAt || '').trim(), incomingTieBreak, existingTieBreak);
782
+ if (compare < 0)
783
+ continue;
784
+ core.upsertTaskEventForSync({
785
+ id: event.id,
786
+ taskId,
787
+ workspaceId,
788
+ action: event.action,
789
+ actor: event.actor,
790
+ actorType: event.actorType,
791
+ details: event.details,
792
+ createdAt: event.createdAt,
793
+ });
794
+ }
795
+ catch (error) {
796
+ const details = {
797
+ source: 'apply.taskEventUpsert',
798
+ eventId: event.id,
799
+ taskId: event.taskId,
800
+ error: String(error?.message || error || ''),
801
+ errorCode: String(error?.code || '').trim() || null
802
+ };
803
+ recordSyncDiagnostic(workspaceId, 'apply', 'Failed to upsert task event during sync apply.', details);
804
+ throw createSyncRouteError(`Failed to persist task event ${event.id} during sync apply: ${String(error?.message || error || 'Unknown error')}`, details, { code: 'TASK_EVENT_SYNC_APPLY_FAILED', statusCode: 500 });
805
+ }
806
+ }
807
+ }
@@ -0,0 +1 @@
1
+ export {};