@taskforcehq/taskforce 0.3.301 → 0.3.303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/dist/Taskforce.module.css +37 -2
  2. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  3. package/dist/TaskforceCore.js +72 -33
  4. package/dist/TaskforceCore.test.js +848 -130
  5. package/dist/components/features/TaskSettings.js +14 -82
  6. package/dist/components/features/TaskSettings.test.js +33 -70
  7. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  8. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  9. package/dist/components/task/TaskActionHeader.js +6 -3
  10. package/dist/components/task/TaskCard.js +6 -3
  11. package/dist/components/task/TaskForm.js +2 -2
  12. package/dist/components/task/TaskForm.test.js +41 -2
  13. package/dist/components/task/TaskKanban.test.js +17 -0
  14. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  15. package/dist/components/views/PlansPage.d.ts +3 -1
  16. package/dist/components/views/PlansPage.js +109 -49
  17. package/dist/components/views/PlansPage.test.d.ts +1 -0
  18. package/dist/components/views/PlansPage.test.js +100 -0
  19. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  20. package/dist/components/views/StandaloneLayout.js +473 -256
  21. package/dist/components/views/panels/PlanningDrawer.js +18 -1
  22. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
  23. package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
  24. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  25. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  26. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  27. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  28. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  29. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  30. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  31. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  32. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  33. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  34. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  35. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  36. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  37. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  38. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  39. package/dist/components/views/standalone/modals/types.js +1 -0
  40. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  41. package/dist/components/views/teamManagementAccess.js +2 -2
  42. package/dist/components/views/teamManagementAccess.test.js +3 -3
  43. package/dist/config/deployment.js +2 -2
  44. package/dist/config/deployment.test.js +5 -3
  45. package/dist/core/AiProfileService.js +7 -2
  46. package/dist/core/AiProfiles.test.js +19 -0
  47. package/dist/core/EntitlementsPolicy.test.js +2 -2
  48. package/dist/core/GlobalSettingsService.js +78 -18
  49. package/dist/core/PlanEntitlementService.d.ts +29 -5
  50. package/dist/core/PlanEntitlementService.js +297 -219
  51. package/dist/core/SignupMonetizationSettings.test.js +173 -49
  52. package/dist/core/SyncReconciliationService.d.ts +2 -0
  53. package/dist/core/SyncReconciliationService.js +3 -0
  54. package/dist/core/SystemAdmin.test.js +186 -96
  55. package/dist/core/TaskAuditTimeline.test.js +40 -0
  56. package/dist/core/Taskforce.d.ts +100 -10
  57. package/dist/core/Taskforce.ids.test.js +133 -0
  58. package/dist/core/Taskforce.js +459 -127
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  61. package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
  62. package/dist/core/taskReferencePolicy.d.ts +28 -0
  63. package/dist/core/taskReferencePolicy.js +189 -0
  64. package/dist/core/types.d.ts +13 -12
  65. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  66. package/dist/hooks/useSyncOrchestrator.js +17 -3
  67. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
  68. package/dist/hooks/useTaskData.js +3 -2
  69. package/dist/hooks/useTaskData.test.js +66 -0
  70. package/dist/hooks/useTaskforce.d.ts +8 -0
  71. package/dist/hooks/useTaskforce.js +72 -19
  72. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  73. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  74. package/dist/mcp/aiProfileBootstrap.js +80 -0
  75. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  76. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  77. package/dist/mcp/clientIntegrations.d.ts +2 -8
  78. package/dist/mcp/clientIntegrations.js +21 -40
  79. package/dist/mcp/clientIntegrations.test.js +28 -2
  80. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  81. package/dist/mcp/clientProfileDefaults.js +37 -0
  82. package/dist/mcp/runtime.d.ts +10 -7
  83. package/dist/mcp/runtime.js +131 -48
  84. package/dist/mcp/runtime.test.js +321 -83
  85. package/dist/migrations/billingSchemaParity.test.js +3 -0
  86. package/dist/migrations/taskSchemaMigrations.js +68 -1
  87. package/dist/qaOpen.test.d.ts +1 -0
  88. package/dist/qaOpen.test.js +56 -0
  89. package/dist/server/index.cookieProxy.test.js +2 -0
  90. package/dist/server/index.d.ts +1 -0
  91. package/dist/server/index.js +17 -1
  92. package/dist/server/index.test.js +15 -1
  93. package/dist/server/mockStripe.d.ts +1 -0
  94. package/dist/server/mockStripe.js +2 -0
  95. package/dist/server/routes/admin.js +105 -45
  96. package/dist/server/routes/auth.d.ts +6 -0
  97. package/dist/server/routes/auth.js +505 -30
  98. package/dist/server/routes/billing.js +517 -24
  99. package/dist/server/routes/billing.test.js +967 -45
  100. package/dist/server/routes/documents.js +6 -1
  101. package/dist/server/routes/shared.d.ts +1 -1
  102. package/dist/server/routes/shared.js +16 -6
  103. package/dist/server/routes/sync.integration.test.js +290 -0
  104. package/dist/server/routes/sync.js +234 -1922
  105. package/dist/server/routes/tasks.js +18 -4
  106. package/dist/server/routes.js +29 -22
  107. package/dist/server/routes.test.js +639 -82
  108. package/dist/shared/runtimeContract.d.ts +1 -1
  109. package/dist/sync/cloudSyncApi.d.ts +1 -0
  110. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  111. package/dist/sync/collaborationSyncPayload.js +131 -0
  112. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  113. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  114. package/dist/sync/collaborationSyncState.d.ts +15 -0
  115. package/dist/sync/collaborationSyncState.js +60 -0
  116. package/dist/sync/contentSyncPayload.d.ts +45 -0
  117. package/dist/sync/contentSyncPayload.js +104 -0
  118. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  119. package/dist/sync/contentSyncPayload.test.js +104 -0
  120. package/dist/sync/contentSyncState.d.ts +98 -0
  121. package/dist/sync/contentSyncState.js +1004 -0
  122. package/dist/sync/planningSyncPayload.d.ts +9 -0
  123. package/dist/sync/planningSyncPayload.js +82 -0
  124. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  125. package/dist/sync/planningSyncPayload.test.js +68 -0
  126. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  127. package/dist/sync/syncApplyHandlers.js +445 -0
  128. package/dist/sync/syncService.d.ts +1 -0
  129. package/dist/sync/syncService.js +21 -8
  130. package/dist/sync/taskSyncPayload.d.ts +33 -0
  131. package/dist/sync/taskSyncPayload.js +126 -0
  132. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  133. package/dist/sync/taskSyncPayload.test.js +54 -0
  134. package/dist/sync/workspacePullFeed.d.ts +38 -0
  135. package/dist/sync/workspacePullFeed.js +349 -0
  136. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  137. package/dist/sync/workspacePullFeed.test.js +278 -0
  138. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  139. package/dist/sync/workspaceSyncModel.js +9 -2
  140. package/dist/sync/workspaceSyncModel.test.js +29 -0
  141. package/dist/test/setup.js +1 -1
  142. package/dist/types.d.ts +1 -0
  143. package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
  144. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
  145. package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
  146. package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
  147. package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
  148. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
  149. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  150. package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
  151. package/dist/ui/assets/index-CLIMgR6g.js +6 -0
  152. package/dist/ui/assets/index-DneETxcu.css +1 -0
  153. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
  154. package/dist/ui/index.html +3 -3
  155. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  156. package/dist/utils/accountProfileSummaryCache.js +97 -0
  157. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  158. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  159. package/dist/utils/taskActivity.js +17 -4
  160. package/dist/utils/taskActivity.test.js +27 -0
  161. package/dist/utils/taskNormalization.js +10 -2
  162. package/dist/utils/taskReferences.d.ts +11 -1
  163. package/dist/utils/taskReferences.js +33 -1
  164. package/package.json +4 -3
  165. package/scripts/qa-open-lib.mjs +175 -0
  166. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  167. package/scripts/qa-open.mjs +352 -0
  168. package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
  169. package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
  170. package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
  171. package/dist/ui/assets/index-xBWLPOEb.js +0 -6
@@ -1,5 +1,5 @@
1
1
  export type RuntimeMode = 'local' | 'cloud';
2
- export type AuthSource = 'cloud' | 'local';
2
+ export type AuthSource = 'cloud';
3
3
  export type WorkspaceMode = 'single-local' | 'multi-cloud';
4
4
  export interface RuntimeModeContract {
5
5
  runtimeMode: RuntimeMode;
@@ -25,6 +25,7 @@ export declare function pullWorkspaceChanges(resolveCloudAuthUrl: (path: string)
25
25
  nextCursor?: string | null;
26
26
  hasMore?: boolean;
27
27
  workspaceMembers?: WorkspaceSyncWorkspaceMemberSnapshot[];
28
+ diagnostics?: Record<string, unknown>;
28
29
  }>>;
29
30
  export declare function pushWorkspaceChanges(resolveCloudAuthUrl: (path: string) => string, input: {
30
31
  workspaceId: string;
@@ -0,0 +1,17 @@
1
+ import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot } from './workspaceSyncModel.js';
2
+ import type { AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
3
+ export declare function normalizeAiProfileSyncPayload(raw: unknown, workspaceId: string, normalizeSurfaceType: (value: unknown, context: {
4
+ source: string;
5
+ profileId?: string;
6
+ workspaceId?: string;
7
+ }) => AiProfileSurfaceType | null, source: string): WorkspaceSyncAiProfileSnapshot | null;
8
+ export declare function normalizeDocumentReviewSessionSyncPayload(raw: unknown, sourceWatermark?: string): WorkspaceSyncDocumentReviewSessionSnapshot | null;
9
+ export declare function normalizeDocumentReviewCommentSyncPayload(raw: unknown, options?: {
10
+ sessionId?: string;
11
+ sourceWatermark?: string;
12
+ }): WorkspaceSyncDocumentReviewCommentSnapshot | null;
13
+ export declare function normalizeAnnotatedAttachmentSessionSyncPayload(raw: unknown, expectedWorkspaceId?: string, sourceWatermark?: string): WorkspaceSyncAnnotatedAttachmentSessionSnapshot | null;
14
+ export declare function normalizeDeleteSyncPayload(raw: unknown, idKey: 'sessionId' | 'commentId'): {
15
+ id: string;
16
+ deletedAt: string;
17
+ } | null;
@@ -0,0 +1,131 @@
1
+ function toOptionalTrimmedString(value) {
2
+ if (typeof value !== 'string')
3
+ return undefined;
4
+ const normalized = value.trim();
5
+ return normalized.length > 0 ? normalized : undefined;
6
+ }
7
+ function toNullableTrimmedString(value) {
8
+ return toOptionalTrimmedString(value) ?? null;
9
+ }
10
+ function toOptionalPositiveInteger(value) {
11
+ if (value === undefined || value === null || value === '')
12
+ return null;
13
+ const normalized = typeof value === 'number' ? value : Number(value);
14
+ if (!Number.isFinite(normalized))
15
+ return null;
16
+ return Math.max(1, Math.floor(normalized));
17
+ }
18
+ function toOptionalNonNegativeInteger(value) {
19
+ if (value === undefined || value === null || value === '')
20
+ return 0;
21
+ const normalized = typeof value === 'number' ? value : Number(value);
22
+ if (!Number.isFinite(normalized))
23
+ return 0;
24
+ return Math.max(0, Math.floor(normalized));
25
+ }
26
+ export function normalizeAiProfileSyncPayload(raw, workspaceId, normalizeSurfaceType, source) {
27
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
28
+ return null;
29
+ const profile = raw;
30
+ const profileId = String(profile.id || '').trim();
31
+ if (!profileId)
32
+ return null;
33
+ const profileWorkspaceId = String(profile.workspaceId || workspaceId).trim() || workspaceId;
34
+ return {
35
+ id: profileId,
36
+ workspaceId: profileWorkspaceId,
37
+ profileToken: String(profile.profileToken || '').trim(),
38
+ name: String(profile.name || '').trim(),
39
+ username: String(profile.username || '').trim(),
40
+ icon: String(profile.icon || 'Bot').trim() || 'Bot',
41
+ color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
42
+ surfaceType: normalizeSurfaceType(profile.surfaceType, { source, profileId, workspaceId: profileWorkspaceId }),
43
+ providerMetadata: profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
44
+ ? profile.providerMetadata
45
+ : null,
46
+ createdAt: String(profile.createdAt || '').trim(),
47
+ updatedAt: String(profile.updatedAt || profile.createdAt || '').trim(),
48
+ lastActiveAt: toNullableTrimmedString(profile.lastActiveAt),
49
+ };
50
+ }
51
+ export function normalizeDocumentReviewSessionSyncPayload(raw, sourceWatermark) {
52
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
53
+ return null;
54
+ const session = raw;
55
+ const id = String(session.id || '').trim();
56
+ const assetId = String(session.assetId || '').trim();
57
+ if (!id || !assetId)
58
+ return null;
59
+ return {
60
+ id,
61
+ assetId,
62
+ documentId: toNullableTrimmedString(session.documentId),
63
+ documentVersion: toOptionalPositiveInteger(session.documentVersion),
64
+ title: toNullableTrimmedString(session.title),
65
+ status: session.status === 'resolved' ? 'resolved' : 'open',
66
+ comments: Array.isArray(session.comments) ? session.comments : [],
67
+ createdByActorId: toNullableTrimmedString(session.createdByActorId),
68
+ updatedByActorId: toNullableTrimmedString(session.updatedByActorId),
69
+ createdAt: String(session.createdAt || sourceWatermark || '').trim(),
70
+ updatedAt: String(session.updatedAt || sourceWatermark || session.createdAt || '').trim(),
71
+ deletedAt: toNullableTrimmedString(session.deletedAt),
72
+ };
73
+ }
74
+ export function normalizeDocumentReviewCommentSyncPayload(raw, options) {
75
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
76
+ return null;
77
+ const comment = raw;
78
+ const id = String(comment.id || '').trim();
79
+ const sessionId = String(options?.sessionId || comment.sessionId || '').trim();
80
+ if (!id || !sessionId)
81
+ return null;
82
+ return {
83
+ id,
84
+ sessionId,
85
+ body: typeof comment.body === 'string' ? comment.body : '',
86
+ anchor: (comment.anchor ?? null),
87
+ order: toOptionalNonNegativeInteger(comment.order),
88
+ authorActorId: toNullableTrimmedString(comment.authorActorId),
89
+ createdAt: String(comment.createdAt || options?.sourceWatermark || '').trim(),
90
+ updatedAt: String(comment.updatedAt || options?.sourceWatermark || comment.createdAt || '').trim(),
91
+ deletedAt: toNullableTrimmedString(comment.deletedAt),
92
+ };
93
+ }
94
+ export function normalizeAnnotatedAttachmentSessionSyncPayload(raw, expectedWorkspaceId, sourceWatermark) {
95
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
96
+ return null;
97
+ const session = raw;
98
+ const id = String(session.id || '').trim();
99
+ const workspaceId = String(session.workspaceId || expectedWorkspaceId || '').trim();
100
+ const baseImageAssetId = String(session.baseImageAssetId || '').trim();
101
+ if (!id || !workspaceId || !baseImageAssetId)
102
+ return null;
103
+ if (expectedWorkspaceId && workspaceId !== expectedWorkspaceId)
104
+ return null;
105
+ return {
106
+ id,
107
+ workspaceId,
108
+ taskId: String(session.taskId || '').trim(),
109
+ baseImageAssetId,
110
+ title: toNullableTrimmedString(session.title),
111
+ globalInstruction: toNullableTrimmedString(session.globalInstruction),
112
+ annotations: Array.isArray(session.annotations) ? session.annotations : [],
113
+ createdByActorId: toNullableTrimmedString(session.createdByActorId),
114
+ updatedByActorId: toNullableTrimmedString(session.updatedByActorId),
115
+ createdAt: String(session.createdAt || sourceWatermark || '').trim(),
116
+ updatedAt: String(session.updatedAt || sourceWatermark || session.createdAt || '').trim(),
117
+ deletedAt: toNullableTrimmedString(session.deletedAt),
118
+ };
119
+ }
120
+ export function normalizeDeleteSyncPayload(raw, idKey) {
121
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
122
+ return null;
123
+ const source = raw;
124
+ const id = String(source[idKey] || source.id || '').trim();
125
+ if (!id)
126
+ return null;
127
+ return {
128
+ id,
129
+ deletedAt: String(source.deletedAt || source.watermark || new Date().toISOString()).trim(),
130
+ };
131
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,104 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload, normalizeDeleteSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, } from './collaborationSyncPayload';
3
+ describe('collaborationSyncPayload', () => {
4
+ const normalizeSurfaceType = (value) => {
5
+ if (value === undefined || value === null || value === '')
6
+ return null;
7
+ return String(value).trim();
8
+ };
9
+ it('normalizes ai profile sync payloads with workspace and surface type', () => {
10
+ const profile = normalizeAiProfileSyncPayload({
11
+ id: 'profile-1',
12
+ workspaceId: 'workspace-a',
13
+ profileToken: ' token-1 ',
14
+ name: ' Agent ',
15
+ username: ' agent ',
16
+ icon: ' Bot ',
17
+ color: ' #111111 ',
18
+ surfaceType: 'agent',
19
+ providerMetadata: { foo: 'bar' },
20
+ createdAt: '2026-04-10T10:00:00.000Z',
21
+ updatedAt: '2026-04-10T11:00:00.000Z',
22
+ lastActiveAt: '2026-04-10T12:00:00.000Z',
23
+ }, 'workspace-a', normalizeSurfaceType, 'test.ai');
24
+ expect(profile).toEqual({
25
+ id: 'profile-1',
26
+ workspaceId: 'workspace-a',
27
+ profileToken: 'token-1',
28
+ name: 'Agent',
29
+ username: 'agent',
30
+ icon: 'Bot',
31
+ color: '#111111',
32
+ surfaceType: 'agent',
33
+ providerMetadata: { foo: 'bar' },
34
+ createdAt: '2026-04-10T10:00:00.000Z',
35
+ updatedAt: '2026-04-10T11:00:00.000Z',
36
+ lastActiveAt: '2026-04-10T12:00:00.000Z',
37
+ });
38
+ });
39
+ it('normalizes document review session and comment payloads', () => {
40
+ const session = normalizeDocumentReviewSessionSyncPayload({
41
+ id: 'session-1',
42
+ assetId: 'asset-1',
43
+ documentId: ' doc-1 ',
44
+ documentVersion: '2',
45
+ title: ' Review ',
46
+ status: 'resolved',
47
+ comments: [],
48
+ createdByActorId: ' ai-1 ',
49
+ updatedByActorId: ' ai-2 ',
50
+ createdAt: '2026-04-10T10:00:00.000Z',
51
+ updatedAt: '2026-04-10T11:00:00.000Z',
52
+ });
53
+ expect(session).toEqual(expect.objectContaining({
54
+ id: 'session-1',
55
+ assetId: 'asset-1',
56
+ documentId: 'doc-1',
57
+ documentVersion: 2,
58
+ title: 'Review',
59
+ status: 'resolved',
60
+ createdByActorId: 'ai-1',
61
+ updatedByActorId: 'ai-2',
62
+ }));
63
+ const comment = normalizeDocumentReviewCommentSyncPayload({
64
+ id: 'comment-1',
65
+ body: 'Looks good',
66
+ order: '3',
67
+ authorActorId: ' ai-3 ',
68
+ createdAt: '2026-04-10T10:00:00.000Z',
69
+ updatedAt: '2026-04-10T11:00:00.000Z',
70
+ }, { sessionId: 'session-1' });
71
+ expect(comment).toEqual(expect.objectContaining({
72
+ id: 'comment-1',
73
+ sessionId: 'session-1',
74
+ body: 'Looks good',
75
+ order: 3,
76
+ authorActorId: 'ai-3',
77
+ }));
78
+ });
79
+ it('normalizes annotated attachment sessions and delete payloads', () => {
80
+ const session = normalizeAnnotatedAttachmentSessionSyncPayload({
81
+ id: 'annotated-1',
82
+ workspaceId: 'workspace-a',
83
+ taskId: 'task-1',
84
+ baseImageAssetId: 'asset-img-1',
85
+ title: ' Notes ',
86
+ globalInstruction: ' Review this ',
87
+ annotations: [{ id: 'a1' }],
88
+ createdAt: '2026-04-10T10:00:00.000Z',
89
+ updatedAt: '2026-04-10T11:00:00.000Z',
90
+ }, 'workspace-a');
91
+ expect(session).toEqual(expect.objectContaining({
92
+ id: 'annotated-1',
93
+ workspaceId: 'workspace-a',
94
+ taskId: 'task-1',
95
+ baseImageAssetId: 'asset-img-1',
96
+ title: 'Notes',
97
+ globalInstruction: 'Review this',
98
+ }));
99
+ expect(normalizeDeleteSyncPayload({ sessionId: 'annotated-1', deletedAt: '2026-04-10T12:00:00.000Z' }, 'sessionId')).toEqual({
100
+ id: 'annotated-1',
101
+ deletedAt: '2026-04-10T12:00:00.000Z',
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,15 @@
1
+ import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
2
+ import type { WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot } from './workspaceSyncModel.js';
3
+ type SyncCore = TaskforceCore & TaskforceSyncCapable;
4
+ interface CollaborationSyncStateDeps {
5
+ core: SyncCore;
6
+ }
7
+ export declare function listWorkspaceSyncDocumentReviewSessionsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncDocumentReviewSessionSnapshot[];
8
+ export declare function listWorkspaceSyncDocumentReviewCommentsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncDocumentReviewCommentSnapshot[];
9
+ export declare function listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps: CollaborationSyncStateDeps, workspaceId?: string): WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
10
+ export declare function createCollaborationSyncState(deps: CollaborationSyncStateDeps): {
11
+ listWorkspaceSyncDocumentReviewSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewSessionSnapshot[];
12
+ listWorkspaceSyncDocumentReviewCommentsSnapshot: (workspaceId?: string) => WorkspaceSyncDocumentReviewCommentSnapshot[];
13
+ listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: (workspaceId?: string) => WorkspaceSyncAnnotatedAttachmentSessionSnapshot[];
14
+ };
15
+ export {};
@@ -0,0 +1,60 @@
1
+ export function listWorkspaceSyncDocumentReviewSessionsSnapshot(deps, workspaceId = 'default') {
2
+ return deps.core.listDocumentReviewSessionsForSync({ workspaceId, includeDeleted: true })
3
+ .map((session) => ({
4
+ id: session.id,
5
+ assetId: session.assetId,
6
+ documentId: session.documentId,
7
+ documentVersion: session.documentVersion,
8
+ title: session.title,
9
+ status: session.status,
10
+ comments: session.comments,
11
+ createdByActorId: session.createdByActorId,
12
+ updatedByActorId: session.updatedByActorId,
13
+ createdAt: session.createdAt,
14
+ updatedAt: session.updatedAt,
15
+ deletedAt: session.deletedAt,
16
+ }))
17
+ .filter((session) => session.id.length > 0 && session.assetId.length > 0);
18
+ }
19
+ export function listWorkspaceSyncDocumentReviewCommentsSnapshot(deps, workspaceId = 'default') {
20
+ return deps.core.listDocumentReviewCommentsForSync({ workspaceId, includeDeleted: true })
21
+ .map((comment) => ({
22
+ id: comment.id,
23
+ sessionId: comment.sessionId,
24
+ body: comment.body,
25
+ anchor: comment.anchor,
26
+ order: comment.order,
27
+ authorActorId: comment.authorActorId,
28
+ createdAt: comment.createdAt,
29
+ updatedAt: comment.updatedAt,
30
+ deletedAt: comment.deletedAt,
31
+ }))
32
+ .filter((comment) => comment.id.length > 0 && comment.sessionId.length > 0);
33
+ }
34
+ export function listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps, workspaceId = 'default') {
35
+ return deps.core.listAnnotatedAttachmentSessionsForSync({ workspaceId, includeDeleted: true })
36
+ .map((session) => ({
37
+ id: session.id,
38
+ workspaceId: session.workspaceId,
39
+ taskId: session.taskId,
40
+ baseImageAssetId: session.baseImageAssetId,
41
+ title: session.title,
42
+ globalInstruction: session.globalInstruction,
43
+ annotations: session.annotations,
44
+ createdByActorId: session.createdByActorId,
45
+ updatedByActorId: session.updatedByActorId,
46
+ createdAt: session.createdAt,
47
+ updatedAt: session.updatedAt,
48
+ deletedAt: session.deletedAt,
49
+ }))
50
+ .filter((session) => (session.id.length > 0
51
+ && session.workspaceId.length > 0
52
+ && session.baseImageAssetId.length > 0));
53
+ }
54
+ export function createCollaborationSyncState(deps) {
55
+ return {
56
+ listWorkspaceSyncDocumentReviewSessionsSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentReviewSessionsSnapshot(deps, workspaceId),
57
+ listWorkspaceSyncDocumentReviewCommentsSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentReviewCommentsSnapshot(deps, workspaceId),
58
+ listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: (workspaceId = 'default') => listWorkspaceSyncAnnotatedAttachmentSessionsSnapshot(deps, workspaceId),
59
+ };
60
+ }
@@ -0,0 +1,45 @@
1
+ import type { WorkspaceAssetLinkRole } from '../storage/workspaceAssetStore.js';
2
+ export interface DocumentSyncPayload {
3
+ path: string;
4
+ updatedAt: string;
5
+ content: string;
6
+ assetId?: string;
7
+ documentId?: string | null;
8
+ referenceNumber?: number | null;
9
+ version?: number | null;
10
+ taskId?: string | null;
11
+ logicalName?: string | null;
12
+ caption?: string | null;
13
+ originalFilename?: string | null;
14
+ linkRole?: WorkspaceAssetLinkRole;
15
+ }
16
+ export interface DocumentDeleteSyncPayload {
17
+ path: string;
18
+ deletedAt: string;
19
+ assetId?: string;
20
+ }
21
+ export interface BinaryAssetSyncPayload {
22
+ path: string;
23
+ updatedAt: string;
24
+ contentBase64: string;
25
+ assetId?: string;
26
+ kind: 'image' | 'file';
27
+ mimeType: string;
28
+ referenceNumber?: number | null;
29
+ taskId?: string | null;
30
+ logicalName?: string | null;
31
+ caption?: string | null;
32
+ originalFilename?: string | null;
33
+ linkRole?: WorkspaceAssetLinkRole;
34
+ }
35
+ export interface BinaryAssetDeleteSyncPayload {
36
+ path: string;
37
+ deletedAt: string;
38
+ assetId?: string;
39
+ }
40
+ export declare function normalizeDocumentSyncPayload(raw: unknown, normalizePath: (rawPath: unknown) => string | null): DocumentSyncPayload | null;
41
+ export declare function normalizeDocumentDeleteSyncPayload(raw: unknown, normalizePath: (rawPath: unknown) => string | null, defaultDeletedAt?: string): DocumentDeleteSyncPayload | null;
42
+ export declare function normalizeBinaryAssetSyncPayload(raw: unknown, normalizePath: (rawPath: unknown) => string | null): BinaryAssetSyncPayload | null;
43
+ export declare function normalizeBinaryAssetDeleteSyncPayload(raw: unknown, normalizePath: (rawPath: unknown) => string | null, defaultDeletedAt?: string): BinaryAssetDeleteSyncPayload | null;
44
+ export declare function serializeDocumentSyncPayload(raw: unknown): DocumentSyncPayload | null;
45
+ export declare function serializeBinaryAssetSyncPayload(raw: unknown): BinaryAssetSyncPayload | null;
@@ -0,0 +1,104 @@
1
+ function toOptionalTrimmedString(value) {
2
+ if (typeof value !== 'string')
3
+ return undefined;
4
+ const normalized = value.trim();
5
+ return normalized.length > 0 ? normalized : undefined;
6
+ }
7
+ function toNullableTrimmedString(value) {
8
+ const normalized = toOptionalTrimmedString(value);
9
+ return normalized ?? null;
10
+ }
11
+ function toOptionalPositiveInteger(value) {
12
+ if (value === undefined || value === null || value === '')
13
+ return null;
14
+ const normalized = typeof value === 'number' ? value : Number(value);
15
+ if (!Number.isFinite(normalized))
16
+ return null;
17
+ return Math.max(1, Math.floor(normalized));
18
+ }
19
+ export function normalizeDocumentSyncPayload(raw, normalizePath) {
20
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
21
+ return null;
22
+ const source = raw;
23
+ const path = normalizePath(source.path);
24
+ if (!path)
25
+ return null;
26
+ return {
27
+ path,
28
+ updatedAt: String(source.updatedAt || source.watermark || '').trim(),
29
+ content: typeof source.content === 'string' ? source.content : '',
30
+ assetId: toOptionalTrimmedString(source.assetId),
31
+ documentId: toNullableTrimmedString(source.documentId),
32
+ referenceNumber: toOptionalPositiveInteger(source.referenceNumber),
33
+ version: toOptionalPositiveInteger(source.version),
34
+ taskId: toNullableTrimmedString(source.taskId),
35
+ logicalName: toNullableTrimmedString(source.logicalName),
36
+ caption: toNullableTrimmedString(source.caption),
37
+ originalFilename: toNullableTrimmedString(source.originalFilename),
38
+ linkRole: source.linkRole === 'reference' ? 'reference' : 'attachment',
39
+ };
40
+ }
41
+ export function normalizeDocumentDeleteSyncPayload(raw, normalizePath, defaultDeletedAt) {
42
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
43
+ return null;
44
+ const source = raw;
45
+ const path = normalizePath(source.path);
46
+ if (!path)
47
+ return null;
48
+ return {
49
+ path,
50
+ deletedAt: String(source.deletedAt || source.watermark || defaultDeletedAt || new Date().toISOString()).trim(),
51
+ assetId: toOptionalTrimmedString(source.assetId),
52
+ };
53
+ }
54
+ export function normalizeBinaryAssetSyncPayload(raw, normalizePath) {
55
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
56
+ return null;
57
+ const source = raw;
58
+ const path = normalizePath(source.path);
59
+ if (!path)
60
+ return null;
61
+ return {
62
+ path,
63
+ updatedAt: String(source.updatedAt || source.watermark || '').trim(),
64
+ contentBase64: typeof source.contentBase64 === 'string' ? source.contentBase64 : '',
65
+ assetId: toOptionalTrimmedString(source.assetId),
66
+ kind: source.kind === 'image' ? 'image' : 'file',
67
+ mimeType: toOptionalTrimmedString(source.mimeType) || 'application/octet-stream',
68
+ referenceNumber: toOptionalPositiveInteger(source.referenceNumber),
69
+ taskId: toNullableTrimmedString(source.taskId),
70
+ logicalName: toNullableTrimmedString(source.logicalName),
71
+ caption: toNullableTrimmedString(source.caption),
72
+ originalFilename: toNullableTrimmedString(source.originalFilename),
73
+ linkRole: source.linkRole === 'reference'
74
+ ? 'reference'
75
+ : source.kind === 'image'
76
+ ? 'image'
77
+ : 'attachment',
78
+ };
79
+ }
80
+ export function normalizeBinaryAssetDeleteSyncPayload(raw, normalizePath, defaultDeletedAt) {
81
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
82
+ return null;
83
+ const source = raw;
84
+ const path = normalizePath(source.path);
85
+ if (!path)
86
+ return null;
87
+ return {
88
+ path,
89
+ deletedAt: String(source.deletedAt || source.watermark || defaultDeletedAt || new Date().toISOString()).trim(),
90
+ assetId: toOptionalTrimmedString(source.assetId),
91
+ };
92
+ }
93
+ export function serializeDocumentSyncPayload(raw) {
94
+ return normalizeDocumentSyncPayload(raw, (value) => {
95
+ const normalized = typeof value === 'string' ? value.trim() : '';
96
+ return normalized.length > 0 ? normalized : null;
97
+ });
98
+ }
99
+ export function serializeBinaryAssetSyncPayload(raw) {
100
+ return normalizeBinaryAssetSyncPayload(raw, (value) => {
101
+ const normalized = typeof value === 'string' ? value.trim() : '';
102
+ return normalized.length > 0 ? normalized : null;
103
+ });
104
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,104 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeBinaryAssetDeleteSyncPayload, normalizeBinaryAssetSyncPayload, normalizeDocumentDeleteSyncPayload, normalizeDocumentSyncPayload, serializeBinaryAssetSyncPayload, serializeDocumentSyncPayload, } from './contentSyncPayload';
3
+ describe('contentSyncPayload', () => {
4
+ const normalizeDocumentPath = (rawPath) => {
5
+ const value = typeof rawPath === 'string' ? rawPath.trim() : '';
6
+ return value.startsWith('docs/') ? value : null;
7
+ };
8
+ const normalizeAssetPath = (rawPath) => {
9
+ const value = typeof rawPath === 'string' ? rawPath.trim() : '';
10
+ return value.startsWith('workspaces/') ? value : null;
11
+ };
12
+ it('normalizes document sync payloads and trims metadata', () => {
13
+ const payload = normalizeDocumentSyncPayload({
14
+ path: 'docs/spec.md',
15
+ updatedAt: '2026-04-10T17:00:00.000Z',
16
+ content: '# Spec',
17
+ documentId: ' doc-1 ',
18
+ referenceNumber: '7',
19
+ version: '2',
20
+ taskId: ' task-1 ',
21
+ logicalName: ' Spec ',
22
+ caption: ' Draft ',
23
+ originalFilename: ' spec.md ',
24
+ linkRole: 'reference',
25
+ }, normalizeDocumentPath);
26
+ expect(payload).toEqual({
27
+ path: 'docs/spec.md',
28
+ updatedAt: '2026-04-10T17:00:00.000Z',
29
+ content: '# Spec',
30
+ assetId: undefined,
31
+ documentId: 'doc-1',
32
+ referenceNumber: 7,
33
+ version: 2,
34
+ taskId: 'task-1',
35
+ logicalName: 'Spec',
36
+ caption: 'Draft',
37
+ originalFilename: 'spec.md',
38
+ linkRole: 'reference',
39
+ });
40
+ });
41
+ it('normalizes binary asset sync payloads and delete payloads', () => {
42
+ const payload = normalizeBinaryAssetSyncPayload({
43
+ path: 'workspaces/a/assets/images/test.png',
44
+ updatedAt: '2026-04-10T17:00:00.000Z',
45
+ contentBase64: 'YWJj',
46
+ kind: 'image',
47
+ mimeType: ' image/png ',
48
+ referenceNumber: '3',
49
+ taskId: ' task-2 ',
50
+ logicalName: ' Hero ',
51
+ caption: ' Screenshot ',
52
+ originalFilename: ' hero.png ',
53
+ linkRole: 'image',
54
+ }, normalizeAssetPath);
55
+ expect(payload).toEqual({
56
+ path: 'workspaces/a/assets/images/test.png',
57
+ updatedAt: '2026-04-10T17:00:00.000Z',
58
+ contentBase64: 'YWJj',
59
+ assetId: undefined,
60
+ kind: 'image',
61
+ mimeType: 'image/png',
62
+ referenceNumber: 3,
63
+ taskId: 'task-2',
64
+ logicalName: 'Hero',
65
+ caption: 'Screenshot',
66
+ originalFilename: 'hero.png',
67
+ linkRole: 'image',
68
+ });
69
+ expect(normalizeBinaryAssetDeleteSyncPayload({ path: 'workspaces/a/assets/images/test.png', deletedAt: '2026-04-10T18:00:00.000Z' }, normalizeAssetPath)).toEqual({
70
+ path: 'workspaces/a/assets/images/test.png',
71
+ deletedAt: '2026-04-10T18:00:00.000Z',
72
+ assetId: undefined,
73
+ });
74
+ });
75
+ it('serializes push-side document and asset payloads without route-local parsing', () => {
76
+ expect(serializeDocumentSyncPayload({
77
+ path: 'docs/plan.md',
78
+ updatedAt: '2026-04-10T17:00:00.000Z',
79
+ content: 'hello',
80
+ referenceNumber: '9',
81
+ linkRole: 'reference',
82
+ })).toEqual(expect.objectContaining({
83
+ path: 'docs/plan.md',
84
+ referenceNumber: 9,
85
+ linkRole: 'reference',
86
+ }));
87
+ expect(serializeBinaryAssetSyncPayload({
88
+ path: 'workspaces/a/assets/files/file.bin',
89
+ updatedAt: '2026-04-10T17:00:00.000Z',
90
+ contentBase64: 'YWJj',
91
+ kind: 'file',
92
+ mimeType: 'application/octet-stream',
93
+ })).toEqual(expect.objectContaining({
94
+ path: 'workspaces/a/assets/files/file.bin',
95
+ kind: 'file',
96
+ contentBase64: 'YWJj',
97
+ }));
98
+ expect(normalizeDocumentDeleteSyncPayload({ path: 'docs/plan.md', deletedAt: '2026-04-10T19:00:00.000Z' }, normalizeDocumentPath)).toEqual({
99
+ path: 'docs/plan.md',
100
+ deletedAt: '2026-04-10T19:00:00.000Z',
101
+ assetId: undefined,
102
+ });
103
+ });
104
+ });