@taskforcehq/taskforce 0.3.302 → 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.
- package/dist/Taskforce.module.css +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- 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/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- 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
|
@@ -20,100 +20,21 @@
|
|
|
20
20
|
*/
|
|
21
21
|
// === SECTION: Imports & module-level constants ===
|
|
22
22
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
23
|
-
import { ensureCloudWorkspaceReadyForSyncService,
|
|
23
|
+
import { ensureCloudWorkspaceReadyForSyncService, syncUserGlobalSettingsService } from '../sync/syncService';
|
|
24
24
|
import { buildWorkspaceSyncPayloadModel, buildWorkspaceSyncSignatureModel } from '../sync/workspaceSyncModel';
|
|
25
|
-
import { parseAiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
26
25
|
import { useWorkspaceSyncController } from './useWorkspaceSyncController';
|
|
27
26
|
import { normalizeTaskforceTheme } from '../utils/theme';
|
|
28
27
|
import { useRealtimeSync } from './useRealtimeSync';
|
|
29
28
|
import { TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT } from '../utils/contextAssetEvents';
|
|
30
29
|
import { setLocale as setAppLocale } from '../localization';
|
|
31
30
|
import { normalizeTaskFromApi } from '../utils/taskNormalization.js';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
import { createSyncAuthCoordinator } from './sync/auth';
|
|
32
|
+
import { createBootstrapTimeoutMonitor, syncBootstrapPhaseTracking } from './sync/bootstrap';
|
|
33
|
+
import { buildTaxonomyStateFingerprint, flattenDocumentReviewComments, flattenTaskEvents, isValidWorkspaceKey, logSyncDebug, normalizeWorkspaceSyncAiProfileSnapshotEntry, readPersistedSyncWatermarks, readUserGlobalSyncMeta, WORKSPACE_CONCURRENT_WINDOW_MESSAGE, WORKSPACE_FULL_RECONCILE_INTERVAL_MS, writeUserGlobalSyncMeta } from './sync/orchestratorShared';
|
|
34
|
+
import { createRealtimeSyncBridge } from './sync/realtime';
|
|
35
|
+
import { createSyncRecoveryCoordinator } from './sync/recovery';
|
|
36
|
+
import { createWorkspaceTransferCoordinator } from './sync/transfers';
|
|
38
37
|
// === SECTION: Pure utility functions ===
|
|
39
|
-
function isValidWorkspaceKey(workspaceId) {
|
|
40
|
-
const workspaceKey = String(workspaceId || '').trim();
|
|
41
|
-
return workspaceKey.length > 0 && workspaceKey.toLowerCase() !== 'default';
|
|
42
|
-
}
|
|
43
|
-
function isSyncDebugLoggingEnabled() {
|
|
44
|
-
if (typeof window === 'undefined')
|
|
45
|
-
return false;
|
|
46
|
-
try {
|
|
47
|
-
const raw = String(window.localStorage.getItem(SYNC_DEBUG_LOG_KEY) || '').trim().toLowerCase();
|
|
48
|
-
return raw === '1' || raw === 'true' || raw === 'on' || raw === 'yes';
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function logSyncDebug(event, details) {
|
|
55
|
-
if (!isSyncDebugLoggingEnabled())
|
|
56
|
-
return;
|
|
57
|
-
const payload = details && typeof details === 'object' ? details : {};
|
|
58
|
-
console.info('[Taskforce Sync]', event, payload);
|
|
59
|
-
}
|
|
60
|
-
export function normalizeWorkspaceSyncAiProfileSnapshotEntry(entry) {
|
|
61
|
-
const metadata = entry?.providerMetadata ?? entry?.provider_metadata ?? null;
|
|
62
|
-
return {
|
|
63
|
-
id: String(entry?.id || '').trim(),
|
|
64
|
-
workspaceId: String(entry?.workspaceId || entry?.workspace_id || '').trim(),
|
|
65
|
-
profileToken: String(entry?.profileToken || entry?.profile_token || '').trim(),
|
|
66
|
-
name: String(entry?.name || '').trim(),
|
|
67
|
-
username: String(entry?.username || '').trim(),
|
|
68
|
-
icon: String(entry?.icon || 'Bot').trim() || 'Bot',
|
|
69
|
-
color: String(entry?.color || '#8b5cf6').trim() || '#8b5cf6',
|
|
70
|
-
surfaceType: parseAiProfileSurfaceType(entry?.surfaceType ?? entry?.surface_type),
|
|
71
|
-
providerMetadata: metadata && typeof metadata === 'object' && !Array.isArray(metadata)
|
|
72
|
-
? metadata
|
|
73
|
-
: null,
|
|
74
|
-
createdAt: String(entry?.createdAt || entry?.created_at || '').trim(),
|
|
75
|
-
updatedAt: String(entry?.updatedAt || entry?.updated_at || entry?.createdAt || entry?.created_at || '').trim(),
|
|
76
|
-
lastActiveAt: typeof entry?.lastActiveAt === 'string' && entry.lastActiveAt.trim().length > 0
|
|
77
|
-
? entry.lastActiveAt.trim()
|
|
78
|
-
: typeof entry?.last_active_at === 'string' && entry.last_active_at.trim().length > 0
|
|
79
|
-
? entry.last_active_at.trim()
|
|
80
|
-
: null
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function readUserGlobalSyncMeta() {
|
|
84
|
-
if (typeof window === 'undefined')
|
|
85
|
-
return {};
|
|
86
|
-
try {
|
|
87
|
-
const raw = window.localStorage.getItem(USER_GLOBAL_SYNC_META_KEY);
|
|
88
|
-
if (!raw)
|
|
89
|
-
return {};
|
|
90
|
-
const parsed = JSON.parse(raw);
|
|
91
|
-
return parsed && typeof parsed === 'object'
|
|
92
|
-
? parsed
|
|
93
|
-
: {};
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return {};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function writeUserGlobalSyncMeta(next) {
|
|
100
|
-
if (typeof window === 'undefined')
|
|
101
|
-
return;
|
|
102
|
-
try {
|
|
103
|
-
window.localStorage.setItem(USER_GLOBAL_SYNC_META_KEY, JSON.stringify(next));
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// best-effort only
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function buildTaxonomyStateFingerprint(taxonomyState) {
|
|
110
|
-
try {
|
|
111
|
-
return JSON.stringify(taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return 'null';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
38
|
function summarizeSyncPhase(enabled, phase, busy, retryAt, lastErrorMessage) {
|
|
118
39
|
if (!enabled)
|
|
119
40
|
return 'Sync is turned off for this workspace.';
|
|
@@ -156,84 +77,6 @@ function recommendSyncAction(enabled, phase, retryAt, lastErrorMessage) {
|
|
|
156
77
|
}
|
|
157
78
|
return 'No action needed.';
|
|
158
79
|
}
|
|
159
|
-
function flattenDocumentReviewComments(sessions) {
|
|
160
|
-
return sessions.flatMap((session) => {
|
|
161
|
-
const sessionId = String(session?.id || '').trim();
|
|
162
|
-
if (!sessionId || !Array.isArray(session?.comments))
|
|
163
|
-
return [];
|
|
164
|
-
return session.comments
|
|
165
|
-
.map((comment) => ({
|
|
166
|
-
id: String(comment?.id || '').trim(),
|
|
167
|
-
sessionId,
|
|
168
|
-
body: String(comment?.body || ''),
|
|
169
|
-
anchor: comment?.anchor ?? null,
|
|
170
|
-
order: Number.isFinite(Number(comment?.order)) ? Math.max(0, Math.floor(Number(comment.order))) : 0,
|
|
171
|
-
authorActorId: typeof comment?.authorActorId === 'string' && comment.authorActorId.trim().length > 0 ? comment.authorActorId.trim() : null,
|
|
172
|
-
createdAt: String(comment?.createdAt || '').trim(),
|
|
173
|
-
updatedAt: String(comment?.updatedAt || comment?.createdAt || '').trim(),
|
|
174
|
-
deletedAt: typeof comment?.deletedAt === 'string' && comment.deletedAt.trim().length > 0 ? comment.deletedAt.trim() : null,
|
|
175
|
-
}))
|
|
176
|
-
.filter((comment) => comment.id.length > 0 && comment.sessionId.length > 0);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
function readPersistedSyncWatermarks(workspaceId, taxonomyStateFingerprint) {
|
|
180
|
-
if (typeof window === 'undefined')
|
|
181
|
-
return null;
|
|
182
|
-
try {
|
|
183
|
-
const key = `${WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX}.${workspaceId}`;
|
|
184
|
-
const raw = window.localStorage.getItem(key);
|
|
185
|
-
if (!raw)
|
|
186
|
-
return null;
|
|
187
|
-
const parsed = JSON.parse(raw);
|
|
188
|
-
if (!parsed || (parsed.v !== 2 && parsed.v !== 3 && parsed.v !== 4 && parsed.v !== 5 && parsed.v !== 6 && parsed.v !== 7))
|
|
189
|
-
return null;
|
|
190
|
-
const savedAt = Date.parse(String(parsed.savedAt || ''));
|
|
191
|
-
if (!Number.isFinite(savedAt) || Date.now() - savedAt > WORKSPACE_SYNC_WATERMARKS_MAX_AGE_MS)
|
|
192
|
-
return null;
|
|
193
|
-
const savedTaxonomyStateFingerprint = typeof parsed.taxonomyStateFingerprint === 'string'
|
|
194
|
-
? parsed.taxonomyStateFingerprint
|
|
195
|
-
: '';
|
|
196
|
-
const taskWatermarks = (!taxonomyStateFingerprint || !savedTaxonomyStateFingerprint || savedTaxonomyStateFingerprint === taxonomyStateFingerprint)
|
|
197
|
-
? new Map(Array.isArray(parsed.taskWatermarks) ? parsed.taskWatermarks : [])
|
|
198
|
-
: new Map();
|
|
199
|
-
return {
|
|
200
|
-
taskWatermarks,
|
|
201
|
-
initiativeWatermarks: new Map(Array.isArray(parsed.initiativeWatermarks) ? parsed.initiativeWatermarks : []),
|
|
202
|
-
workstreamWatermarks: new Map(Array.isArray(parsed.workstreamWatermarks) ? parsed.workstreamWatermarks : []),
|
|
203
|
-
documentWatermarks: new Map(Array.isArray(parsed.documentWatermarks) ? parsed.documentWatermarks : []),
|
|
204
|
-
assetWatermarks: new Map(Array.isArray(parsed.assetWatermarks) ? parsed.assetWatermarks : []),
|
|
205
|
-
documentReviewSessionWatermarks: new Map(Array.isArray(parsed.documentReviewSessionWatermarks) ? parsed.documentReviewSessionWatermarks : []),
|
|
206
|
-
documentReviewCommentWatermarks: new Map(Array.isArray(parsed.documentReviewCommentWatermarks) ? parsed.documentReviewCommentWatermarks : []),
|
|
207
|
-
annotatedAttachmentSessionWatermarks: new Map(Array.isArray(parsed.annotatedAttachmentSessionWatermarks) ? parsed.annotatedAttachmentSessionWatermarks : []),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function savePersistedSyncWatermarks(workspaceId, data) {
|
|
215
|
-
if (typeof window === 'undefined')
|
|
216
|
-
return;
|
|
217
|
-
try {
|
|
218
|
-
const key = `${WORKSPACE_SYNC_WATERMARKS_KEY_PREFIX}.${workspaceId}`;
|
|
219
|
-
window.localStorage.setItem(key, JSON.stringify({
|
|
220
|
-
v: 7,
|
|
221
|
-
savedAt: new Date().toISOString(),
|
|
222
|
-
taskWatermarks: Array.from(data.taskWatermarks.entries()),
|
|
223
|
-
initiativeWatermarks: Array.from(data.initiativeWatermarks.entries()),
|
|
224
|
-
workstreamWatermarks: Array.from(data.workstreamWatermarks.entries()),
|
|
225
|
-
documentWatermarks: Array.from(data.documentWatermarks.entries()),
|
|
226
|
-
assetWatermarks: Array.from(data.assetWatermarks.entries()),
|
|
227
|
-
documentReviewSessionWatermarks: Array.from(data.documentReviewSessionWatermarks.entries()),
|
|
228
|
-
documentReviewCommentWatermarks: Array.from(data.documentReviewCommentWatermarks.entries()),
|
|
229
|
-
annotatedAttachmentSessionWatermarks: Array.from(data.annotatedAttachmentSessionWatermarks.entries()),
|
|
230
|
-
taxonomyStateFingerprint: typeof data.taxonomyStateFingerprint === 'string' ? data.taxonomyStateFingerprint : ''
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
234
|
-
// localStorage may be full or unavailable; non-fatal
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
80
|
const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
|
|
238
81
|
documents: false,
|
|
239
82
|
aiProfiles: false,
|
|
@@ -241,6 +84,7 @@ const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
|
|
|
241
84
|
documentReviewSessions: false,
|
|
242
85
|
annotatedAttachmentSessions: false
|
|
243
86
|
};
|
|
87
|
+
export { normalizeWorkspaceSyncAiProfileSnapshotEntry };
|
|
244
88
|
export function useSyncOrchestrator(input) {
|
|
245
89
|
const { currentWorkspaceId, cloudAuthConfigured, runtimeMode, authSessionResolved, isAuthenticated, authUserId, projectName, resolveCloudAuthUrl, resolveWebSocketUrl, realtimeSyncEnabled, tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState, setupState, globalTheme, locale, globalWeekStartsOn, themeUseGlobalDefault, setTasks, setArchivedTasks, setAuthBlocked, setIsAuthenticated, checkAuthSession, setGlobalTheme, setCurrentTheme, setSetupState, setLocale, setGlobalWeekStartsOn, fetchPlanningEntities } = input;
|
|
246
90
|
const taxonomyStateFingerprint = useMemo(() => buildTaxonomyStateFingerprint(taxonomyState), [taxonomyState]);
|
|
@@ -381,8 +225,12 @@ export function useSyncOrchestrator(input) {
|
|
|
381
225
|
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
|
|
382
226
|
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
|
|
383
227
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
|
|
228
|
+
workspaceLastPushedTaskEventWatermarksRef.current = new Map();
|
|
384
229
|
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
|
|
385
230
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
|
|
231
|
+
workspaceLastPushedTaskEventWatermarksRef.current = new Map();
|
|
232
|
+
workspaceStartupFullReconcileRanRef.current = false;
|
|
233
|
+
workspaceFullReconcileInFlightRef.current = false;
|
|
386
234
|
const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
|
|
387
235
|
if (persisted) {
|
|
388
236
|
workspaceLastPushedWatermarksRef.current = persisted.taskWatermarks;
|
|
@@ -393,6 +241,7 @@ export function useSyncOrchestrator(input) {
|
|
|
393
241
|
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = persisted.documentReviewSessionWatermarks;
|
|
394
242
|
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = persisted.documentReviewCommentWatermarks;
|
|
395
243
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = persisted.annotatedAttachmentSessionWatermarks;
|
|
244
|
+
workspaceLastPushedTaskEventWatermarksRef.current = persisted.taskEventWatermarks;
|
|
396
245
|
}
|
|
397
246
|
}, [currentWorkspaceId, taxonomyStateFingerprint]);
|
|
398
247
|
const markWorkspaceSyncReferenceSnapshotReady = useCallback((key) => {
|
|
@@ -404,7 +253,7 @@ export function useSyncOrchestrator(input) {
|
|
|
404
253
|
}));
|
|
405
254
|
}, []);
|
|
406
255
|
const workspaceSyncReferenceSnapshotsReady = Object.values(workspaceSyncReferenceSnapshotStatus).every(Boolean);
|
|
407
|
-
const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
|
|
256
|
+
const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
|
|
408
257
|
currentWorkspaceId,
|
|
409
258
|
setWorkspaceCloudSyncEnabled,
|
|
410
259
|
setWorkspaceSyncPhase,
|
|
@@ -429,7 +278,9 @@ export function useSyncOrchestrator(input) {
|
|
|
429
278
|
const workspaceLastPushedDocumentReviewCommentWatermarksRef = useRef(new Map());
|
|
430
279
|
const workspaceLastPushedAnnotatedAttachmentSessionIdsRef = useRef(new Set());
|
|
431
280
|
const workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef = useRef(new Map());
|
|
281
|
+
const workspaceLastPushedTaskEventWatermarksRef = useRef(new Map());
|
|
432
282
|
const workspaceStartupRepairRanRef = useRef(false);
|
|
283
|
+
const workspaceStartupFullReconcileRanRef = useRef(false);
|
|
433
284
|
const workspaceRepairModeRef = useRef(false);
|
|
434
285
|
const workspaceAttachBootstrapBaselineSignatureRef = useRef('');
|
|
435
286
|
const workspaceCaptureAttachBootstrapBaselineRef = useRef(false);
|
|
@@ -656,51 +507,44 @@ export function useSyncOrchestrator(input) {
|
|
|
656
507
|
operation
|
|
657
508
|
});
|
|
658
509
|
}, [currentWorkspaceId]);
|
|
510
|
+
const persistWorkspaceSyncPatchBestEffort = useCallback(async (patch) => {
|
|
511
|
+
try {
|
|
512
|
+
return await persistWorkspaceSyncPatch(patch);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
return applyWorkspaceSyncPatchLocally(patch);
|
|
516
|
+
}
|
|
517
|
+
}, [applyWorkspaceSyncPatchLocally, persistWorkspaceSyncPatch]);
|
|
659
518
|
const persistWorkspaceSyncPatchSafely = useCallback((patch) => {
|
|
660
|
-
void
|
|
661
|
-
}, [
|
|
519
|
+
void persistWorkspaceSyncPatchBestEffort(patch).catch(() => { });
|
|
520
|
+
}, [persistWorkspaceSyncPatchBestEffort]);
|
|
662
521
|
const scheduleWorkspaceSyncRetry = useCallback((minDelayMs) => {
|
|
663
522
|
scheduleWorkspaceRetry(() => retryWorkspaceSyncRef.current(), { minDelayMs });
|
|
664
523
|
}, [scheduleWorkspaceRetry]);
|
|
665
524
|
useEffect(() => {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
bootstrapPhaseStartedAtRef.current = null;
|
|
675
|
-
bootstrapLastProgressAtRef.current = null;
|
|
525
|
+
syncBootstrapPhaseTracking({
|
|
526
|
+
workspaceSyncPhase,
|
|
527
|
+
lastBootstrapPhaseRef,
|
|
528
|
+
bootstrapPhaseStartedAtRef,
|
|
529
|
+
bootstrapLastProgressAtRef
|
|
530
|
+
});
|
|
676
531
|
}, [workspaceSyncPhase]);
|
|
677
532
|
useEffect(() => {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
694
|
-
setUserGlobalSyncStatus('error');
|
|
695
|
-
setUserGlobalSyncError(message);
|
|
696
|
-
setWorkspaceSyncBusy(false);
|
|
697
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
698
|
-
logSyncDebug('workspace_sync_bootstrap_timeout', {
|
|
699
|
-
workspaceId: currentWorkspaceId,
|
|
700
|
-
phase: workspaceSyncPhase
|
|
701
|
-
});
|
|
702
|
-
}, 10_000);
|
|
703
|
-
return () => window.clearInterval(intervalId);
|
|
533
|
+
return createBootstrapTimeoutMonitor({
|
|
534
|
+
currentWorkspaceId,
|
|
535
|
+
workspaceCloudSyncEnabled,
|
|
536
|
+
workspaceSyncPhase,
|
|
537
|
+
workspacePullInFlightRef,
|
|
538
|
+
workspacePushInFlightRef,
|
|
539
|
+
bootstrapPhaseStartedAtRef,
|
|
540
|
+
bootstrapLastProgressAtRef,
|
|
541
|
+
setWorkspaceLastErrorMessage: (value) => setWorkspaceLastErrorMessage(value),
|
|
542
|
+
setWorkspaceLastErrorAt: (value) => setWorkspaceLastErrorAt(value),
|
|
543
|
+
setUserGlobalSyncStatus,
|
|
544
|
+
setUserGlobalSyncError: (value) => setUserGlobalSyncError(value),
|
|
545
|
+
setWorkspaceSyncBusy,
|
|
546
|
+
persistWorkspaceSyncPatchSafely
|
|
547
|
+
});
|
|
704
548
|
}, [
|
|
705
549
|
currentWorkspaceId,
|
|
706
550
|
workspaceCloudSyncEnabled,
|
|
@@ -739,6 +583,7 @@ export function useSyncOrchestrator(input) {
|
|
|
739
583
|
workspaceWorkstreamsRef.current = workstreams || [];
|
|
740
584
|
}, [workstreams]);
|
|
741
585
|
const workspaceForceFullAiProfilePushRef = useRef(false);
|
|
586
|
+
const workspaceFullReconcileInFlightRef = useRef(false);
|
|
742
587
|
const buildWorkspaceSyncPayload = useCallback((options) => {
|
|
743
588
|
const forceAllAiProfiles = options?.forceAllAiProfiles === true;
|
|
744
589
|
// Read documents and assets from refs rather than state so that the freshest data
|
|
@@ -747,6 +592,7 @@ export function useSyncOrchestrator(input) {
|
|
|
747
592
|
return buildWorkspaceSyncPayloadModel({
|
|
748
593
|
tasks: workspaceTasksRef.current,
|
|
749
594
|
archivedTasks: workspaceArchivedTasksRef.current,
|
|
595
|
+
taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
|
|
750
596
|
lastPushedTaskIds: workspaceLastPushedTaskIdsRef.current,
|
|
751
597
|
pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
|
|
752
598
|
initiatives: workspaceInitiativesRef.current,
|
|
@@ -775,6 +621,7 @@ export function useSyncOrchestrator(input) {
|
|
|
775
621
|
lastPushedDocumentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
|
|
776
622
|
lastPushedAnnotatedAttachmentSessionIds: workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current,
|
|
777
623
|
lastPushedAnnotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
|
|
624
|
+
lastPushedTaskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
|
|
778
625
|
lastPushedWatermarks: workspaceLastPushedWatermarksRef.current
|
|
779
626
|
});
|
|
780
627
|
}, [taxonomies, taxonomyState]);
|
|
@@ -802,6 +649,7 @@ export function useSyncOrchestrator(input) {
|
|
|
802
649
|
workspaceId: currentWorkspaceId,
|
|
803
650
|
tasks: workspaceTasksRef.current,
|
|
804
651
|
archivedTasks: workspaceArchivedTasksRef.current,
|
|
652
|
+
taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
|
|
805
653
|
pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
|
|
806
654
|
initiatives: workspaceInitiativesRef.current,
|
|
807
655
|
workstreams: workspaceWorkstreamsRef.current,
|
|
@@ -813,7 +661,52 @@ export function useSyncOrchestrator(input) {
|
|
|
813
661
|
documentReviewComments: workspaceSyncDocumentReviewCommentsRef.current,
|
|
814
662
|
annotatedAttachmentSessions: workspaceSyncAnnotatedAttachmentSessionsRef.current
|
|
815
663
|
});
|
|
816
|
-
}, [currentWorkspaceId, taxonomies]);
|
|
664
|
+
}, [currentWorkspaceId, taxonomies, taxonomyState]);
|
|
665
|
+
const workspaceCoreSyncFingerprint = useMemo(() => JSON.stringify({
|
|
666
|
+
tasks: (tasks || [])
|
|
667
|
+
.map((task) => ({
|
|
668
|
+
id: String(task.id || '').trim(),
|
|
669
|
+
updatedAt: String(task.updatedAt || task.createdAt || '').trim(),
|
|
670
|
+
status: String(task.status || '').trim(),
|
|
671
|
+
referenceNumber: task.referenceNumber ?? null,
|
|
672
|
+
localReferenceNumber: task.localReferenceNumber ?? null
|
|
673
|
+
}))
|
|
674
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
675
|
+
archivedTasks: (archivedTasks || [])
|
|
676
|
+
.map((task) => ({
|
|
677
|
+
id: String(task.id || '').trim(),
|
|
678
|
+
updatedAt: String(task.updatedAt || task.createdAt || '').trim(),
|
|
679
|
+
status: String(task.status || '').trim(),
|
|
680
|
+
referenceNumber: task.referenceNumber ?? null,
|
|
681
|
+
localReferenceNumber: task.localReferenceNumber ?? null
|
|
682
|
+
}))
|
|
683
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
684
|
+
initiatives: (initiatives || [])
|
|
685
|
+
.map((initiative) => ({
|
|
686
|
+
id: String(initiative.id || '').trim(),
|
|
687
|
+
updatedAt: String(initiative.updatedAt || initiative.createdAt || '').trim(),
|
|
688
|
+
isArchived: Boolean(initiative.isArchived)
|
|
689
|
+
}))
|
|
690
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
691
|
+
workstreams: (workstreams || [])
|
|
692
|
+
.map((workstream) => ({
|
|
693
|
+
id: String(workstream.id || '').trim(),
|
|
694
|
+
updatedAt: String(workstream.updatedAt || workstream.createdAt || '').trim(),
|
|
695
|
+
initiativeId: typeof workstream.initiativeId === 'string' ? workstream.initiativeId.trim() : '',
|
|
696
|
+
isArchived: Boolean(workstream.isArchived)
|
|
697
|
+
}))
|
|
698
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
699
|
+
taskEvents: flattenTaskEvents(tasks || [], archivedTasks || [])
|
|
700
|
+
.map((event) => ({
|
|
701
|
+
id: String(event.id || '').trim(),
|
|
702
|
+
taskId: String(event.taskId || '').trim(),
|
|
703
|
+
createdAt: String(event.createdAt || '').trim(),
|
|
704
|
+
action: String(event.action || '').trim(),
|
|
705
|
+
}))
|
|
706
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
707
|
+
taxonomies: Array.isArray(taxonomies) ? taxonomies : [],
|
|
708
|
+
taxonomyState: taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null
|
|
709
|
+
}), [tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
|
|
817
710
|
// Fingerprint of per-task attachment counts — changes when the MCP tool saves a new
|
|
818
711
|
// attachment and the React app polls the updated task. Used to trigger an immediate
|
|
819
712
|
// asset snapshot refresh without requiring a cross-process DOM event.
|
|
@@ -1172,6 +1065,9 @@ export function useSyncOrchestrator(input) {
|
|
|
1172
1065
|
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(workspaceSyncAnnotatedAttachmentSessionsRef.current
|
|
1173
1066
|
.map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
|
|
1174
1067
|
.filter(([id]) => id.length > 0));
|
|
1068
|
+
workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current)
|
|
1069
|
+
.map((entry) => [String(entry?.id || '').trim(), String(entry?.createdAt || '').trim()])
|
|
1070
|
+
.filter(([id]) => id.length > 0));
|
|
1175
1071
|
}, []);
|
|
1176
1072
|
const refreshLocalWorkspaceTaskCollections = useCallback(async () => {
|
|
1177
1073
|
const [tasksRes, archiveRes] = await Promise.all([
|
|
@@ -1208,543 +1104,173 @@ export function useSyncOrchestrator(input) {
|
|
|
1208
1104
|
});
|
|
1209
1105
|
}, [currentWorkspaceId, fetchPlanningEntities, setArchivedTasks, setTasks]);
|
|
1210
1106
|
// === SECTION: Auth failure handling & sync readiness ===
|
|
1211
|
-
const handleSyncAuthFailure =
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
setUserGlobalSyncStatus('error');
|
|
1215
|
-
const message = 'Session expired. Sign in again to resume cloud sync.';
|
|
1216
|
-
setUserGlobalSyncError(message);
|
|
1217
|
-
setWorkspaceLastErrorMessage(message);
|
|
1218
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1219
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1220
|
-
eventType: source === 'handshake' ? 'handshake' : source,
|
|
1221
|
-
status: 'error',
|
|
1222
|
-
statusCode,
|
|
1223
|
-
errorMessage: message
|
|
1224
|
-
});
|
|
1225
|
-
setAuthBlocked(true);
|
|
1226
|
-
setIsAuthenticated(false);
|
|
1227
|
-
await checkAuthSession();
|
|
1228
|
-
}, [
|
|
1107
|
+
const { handleSyncAuthFailure, ensureWorkspaceSyncReady } = useMemo(() => createSyncAuthCoordinator({
|
|
1108
|
+
currentWorkspaceId,
|
|
1109
|
+
workspaceSyncPhase,
|
|
1229
1110
|
clearWorkspaceRetry,
|
|
1111
|
+
setWorkspaceSyncBusy,
|
|
1112
|
+
setUserGlobalSyncStatus,
|
|
1113
|
+
setUserGlobalSyncError: (value) => setUserGlobalSyncError(value),
|
|
1114
|
+
setWorkspaceLastErrorMessage: (value) => setWorkspaceLastErrorMessage(value),
|
|
1115
|
+
setWorkspaceLastErrorAt: (value) => setWorkspaceLastErrorAt(value),
|
|
1230
1116
|
reportSyncEvent,
|
|
1231
|
-
currentWorkspaceId,
|
|
1232
1117
|
setAuthBlocked,
|
|
1233
1118
|
setIsAuthenticated,
|
|
1234
|
-
checkAuthSession
|
|
1235
|
-
]);
|
|
1236
|
-
const ensureWorkspaceSyncReady = useCallback(async () => {
|
|
1237
|
-
if (!isValidWorkspaceKey(currentWorkspaceId)) {
|
|
1238
|
-
const message = 'Workspace sync blocked: local workspace ID is unresolved.';
|
|
1239
|
-
setWorkspaceLastErrorMessage(message);
|
|
1240
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1241
|
-
setUserGlobalSyncError(message);
|
|
1242
|
-
setUserGlobalSyncStatus('error');
|
|
1243
|
-
return false;
|
|
1244
|
-
}
|
|
1245
|
-
const ensured = await ensureCloudWorkspaceReadyForSync();
|
|
1246
|
-
if (ensured.success)
|
|
1247
|
-
return true;
|
|
1248
|
-
if (ensured.statusCode === 401) {
|
|
1249
|
-
await handleSyncAuthFailure('handshake', 401);
|
|
1250
|
-
return false;
|
|
1251
|
-
}
|
|
1252
|
-
const message = ensured.error || 'Workspace sync handshake failed.';
|
|
1253
|
-
setWorkspaceLastErrorMessage(message);
|
|
1254
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1255
|
-
setUserGlobalSyncError(message);
|
|
1256
|
-
setUserGlobalSyncStatus('error');
|
|
1257
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1258
|
-
eventType: 'handshake',
|
|
1259
|
-
status: 'error',
|
|
1260
|
-
statusCode: ensured.statusCode,
|
|
1261
|
-
errorMessage: message
|
|
1262
|
-
});
|
|
1263
|
-
if (ensured.transient || isTransientWorkspaceSyncStatus(ensured.statusCode)) {
|
|
1264
|
-
scheduleWorkspaceSyncRetry(ensured.retryAfterMs);
|
|
1265
|
-
}
|
|
1266
|
-
else if (workspaceSyncPhase === 'active') {
|
|
1267
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
1268
|
-
}
|
|
1269
|
-
return false;
|
|
1270
|
-
}, [
|
|
1271
|
-
currentWorkspaceId,
|
|
1119
|
+
checkAuthSession,
|
|
1272
1120
|
ensureCloudWorkspaceReadyForSync,
|
|
1273
|
-
handleSyncAuthFailure,
|
|
1274
|
-
reportSyncEvent,
|
|
1275
1121
|
scheduleWorkspaceSyncRetry,
|
|
1122
|
+
persistWorkspaceSyncPatchSafely
|
|
1123
|
+
}), [
|
|
1124
|
+
currentWorkspaceId,
|
|
1276
1125
|
workspaceSyncPhase,
|
|
1126
|
+
clearWorkspaceRetry,
|
|
1127
|
+
reportSyncEvent,
|
|
1128
|
+
setAuthBlocked,
|
|
1129
|
+
setIsAuthenticated,
|
|
1130
|
+
checkAuthSession,
|
|
1131
|
+
ensureCloudWorkspaceReadyForSync,
|
|
1132
|
+
scheduleWorkspaceSyncRetry,
|
|
1277
1133
|
persistWorkspaceSyncPatchSafely
|
|
1278
1134
|
]);
|
|
1279
|
-
// === SECTION: Push sync ===
|
|
1280
|
-
const
|
|
1281
|
-
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
1282
|
-
return false;
|
|
1283
|
-
if (!isValidWorkspaceKey(currentWorkspaceId))
|
|
1284
|
-
return false;
|
|
1285
|
-
if (workspaceRepairModeRef.current === true && options?.repairMode !== true) {
|
|
1286
|
-
workspacePendingSignatureRef.current = signature;
|
|
1287
|
-
return false;
|
|
1288
|
-
}
|
|
1289
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1290
|
-
workspacePendingSignatureRef.current = signature;
|
|
1291
|
-
return false;
|
|
1292
|
-
}
|
|
1293
|
-
if (!isAuthenticated) {
|
|
1294
|
-
const authed = await checkAuthSession();
|
|
1295
|
-
if (!authed)
|
|
1296
|
-
return false;
|
|
1297
|
-
}
|
|
1298
|
-
if (shouldSuppressAttachBootstrapPush(signature)) {
|
|
1299
|
-
return false;
|
|
1300
|
-
}
|
|
1301
|
-
if (workspacePushInFlightRef.current || workspacePullInFlightRef.current) {
|
|
1302
|
-
workspacePendingSignatureRef.current = signature;
|
|
1303
|
-
return false;
|
|
1304
|
-
}
|
|
1305
|
-
const leaseAcquired = await acquireWorkspaceSyncLease(currentWorkspaceId, 'push');
|
|
1306
|
-
if (!leaseAcquired) {
|
|
1307
|
-
workspacePendingSignatureRef.current = signature;
|
|
1308
|
-
reportWorkspaceWindowContention('push');
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
workspacePushInFlightRef.current = true;
|
|
1312
|
-
setWorkspaceSyncBusy(true);
|
|
1313
|
-
setUserGlobalSyncStatus('syncing');
|
|
1314
|
-
setUserGlobalSyncError(null);
|
|
1315
|
-
const pushStartedAtMs = Date.now();
|
|
1316
|
-
const forceAllAiProfiles = options?.forceAllAiProfiles === true || workspaceForceFullAiProfilePushRef.current === true;
|
|
1317
|
-
try {
|
|
1318
|
-
await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1319
|
-
await refreshWorkspaceSyncAiProfilesSnapshot();
|
|
1320
|
-
await refreshWorkspaceSyncAssetsSnapshot();
|
|
1321
|
-
await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
1322
|
-
const ready = await ensureWorkspaceSyncReady();
|
|
1323
|
-
if (!ready)
|
|
1324
|
-
return false;
|
|
1325
|
-
const payload = buildWorkspaceSyncPayload({ forceAllAiProfiles });
|
|
1326
|
-
setWorkspaceSyncPendingChanges(payload.changes.length);
|
|
1327
|
-
const pushResult = await runWorkspacePushSyncService({
|
|
1328
|
-
resolveCloudAuthUrl,
|
|
1329
|
-
workspaceId: currentWorkspaceId,
|
|
1330
|
-
payload,
|
|
1331
|
-
ensureCloudWorkspaceReadyForSync,
|
|
1332
|
-
repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true
|
|
1333
|
-
});
|
|
1334
|
-
if (!pushResult.success) {
|
|
1335
|
-
if (pushResult.status === 401) {
|
|
1336
|
-
await handleSyncAuthFailure('push', 401);
|
|
1337
|
-
return false;
|
|
1338
|
-
}
|
|
1339
|
-
let message = pushResult.error
|
|
1340
|
-
? `Workspace sync push failed (${pushResult.status || 0}): ${pushResult.error}`
|
|
1341
|
-
: `Workspace sync push failed (${pushResult.status || 0})`;
|
|
1342
|
-
if (pushResult.status === 413) {
|
|
1343
|
-
message = 'Workspace sync failed because the payload is too large. This usually happens when one or more attachments exceed the project limit.';
|
|
1344
|
-
}
|
|
1345
|
-
setUserGlobalSyncStatus('error');
|
|
1346
|
-
setUserGlobalSyncError(message);
|
|
1347
|
-
setWorkspaceLastErrorMessage(message);
|
|
1348
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1349
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1350
|
-
eventType: 'push',
|
|
1351
|
-
status: 'error',
|
|
1352
|
-
statusCode: pushResult.status,
|
|
1353
|
-
errorMessage: message,
|
|
1354
|
-
requestMs: pushResult.requestMs
|
|
1355
|
-
});
|
|
1356
|
-
workspacePendingSignatureRef.current = signature;
|
|
1357
|
-
if (pushResult.transient || isTransientWorkspaceSyncStatus(pushResult.status)) {
|
|
1358
|
-
scheduleWorkspaceSyncRetry(pushResult.retryAfterMs);
|
|
1359
|
-
}
|
|
1360
|
-
else if (workspaceSyncPhase === 'active') {
|
|
1361
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
1362
|
-
}
|
|
1363
|
-
return false;
|
|
1364
|
-
}
|
|
1365
|
-
workspaceLastPushedSignatureRef.current = signature;
|
|
1366
|
-
workspaceLastPushedTaskIdsRef.current = pushResult.currentTaskIds;
|
|
1367
|
-
workspaceLastPushedInitiativeIdsRef.current = new Set(pushResult.currentInitiativeIds);
|
|
1368
|
-
workspaceLastPushedInitiativeWatermarksRef.current = new Map(pushResult.currentInitiativeWatermarks);
|
|
1369
|
-
workspaceLastPushedWorkstreamIdsRef.current = new Set(pushResult.currentWorkstreamIds);
|
|
1370
|
-
workspaceLastPushedWorkstreamWatermarksRef.current = new Map(pushResult.currentWorkstreamWatermarks);
|
|
1371
|
-
workspaceLastPushedAiProfileIdsRef.current = new Set(pushResult.currentAiProfileIds);
|
|
1372
|
-
workspaceLastPushedAiProfileWatermarksRef.current = new Map(pushResult.currentAiProfileWatermarks);
|
|
1373
|
-
workspaceLastPushedDocumentPathsRef.current = new Set(pushResult.currentDocumentPaths);
|
|
1374
|
-
workspaceLastPushedDocumentWatermarksRef.current = new Map(pushResult.currentDocumentWatermarks);
|
|
1375
|
-
workspaceLastPushedAssetPathsRef.current = new Set(pushResult.currentAssetPaths);
|
|
1376
|
-
workspaceLastPushedAssetWatermarksRef.current = new Map(pushResult.currentAssetWatermarks);
|
|
1377
|
-
workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set(pushResult.currentDocumentReviewSessionIds);
|
|
1378
|
-
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map(pushResult.currentDocumentReviewSessionWatermarks);
|
|
1379
|
-
workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set(pushResult.currentDocumentReviewCommentIds);
|
|
1380
|
-
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map(pushResult.currentDocumentReviewCommentWatermarks);
|
|
1381
|
-
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set(pushResult.currentAnnotatedAttachmentSessionIds);
|
|
1382
|
-
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(pushResult.currentAnnotatedAttachmentSessionWatermarks);
|
|
1383
|
-
if (forceAllAiProfiles) {
|
|
1384
|
-
workspaceForceFullAiProfilePushRef.current = false;
|
|
1385
|
-
}
|
|
1386
|
-
for (const [taskId, watermark] of pushResult.pushedWatermarks) {
|
|
1387
|
-
workspaceLastPushedWatermarksRef.current.set(taskId, watermark);
|
|
1388
|
-
}
|
|
1389
|
-
if (pushResult.deleteTaskIds.size > 0) {
|
|
1390
|
-
for (const taskId of pushResult.deleteTaskIds) {
|
|
1391
|
-
workspaceDeletedTaskIdsRef.current.delete(taskId);
|
|
1392
|
-
workspaceLastPushedWatermarksRef.current.delete(taskId);
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
// Persist watermarks so a page reload doesn't trigger a full resync
|
|
1396
|
-
savePersistedSyncWatermarks(currentWorkspaceId, {
|
|
1397
|
-
taskWatermarks: workspaceLastPushedWatermarksRef.current,
|
|
1398
|
-
initiativeWatermarks: workspaceLastPushedInitiativeWatermarksRef.current,
|
|
1399
|
-
workstreamWatermarks: workspaceLastPushedWorkstreamWatermarksRef.current,
|
|
1400
|
-
documentWatermarks: workspaceLastPushedDocumentWatermarksRef.current,
|
|
1401
|
-
assetWatermarks: workspaceLastPushedAssetWatermarksRef.current,
|
|
1402
|
-
documentReviewSessionWatermarks: workspaceLastPushedDocumentReviewSessionWatermarksRef.current,
|
|
1403
|
-
documentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
|
|
1404
|
-
annotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
|
|
1405
|
-
taxonomyStateFingerprint
|
|
1406
|
-
});
|
|
1407
|
-
if (Array.isArray(pushResult.emittedEventIds) && pushResult.emittedEventIds.length > 0) {
|
|
1408
|
-
for (const rawEventId of pushResult.emittedEventIds) {
|
|
1409
|
-
const eventId = String(rawEventId || '').trim();
|
|
1410
|
-
if (!eventId || realtimeSuppressedEventIdsRef.current.has(eventId))
|
|
1411
|
-
continue;
|
|
1412
|
-
realtimeSuppressedEventIdsRef.current.add(eventId);
|
|
1413
|
-
realtimeSuppressedEventQueueRef.current.push(eventId);
|
|
1414
|
-
}
|
|
1415
|
-
while (realtimeSuppressedEventQueueRef.current.length > 2048) {
|
|
1416
|
-
const stale = realtimeSuppressedEventQueueRef.current.shift();
|
|
1417
|
-
if (stale)
|
|
1418
|
-
realtimeSuppressedEventIdsRef.current.delete(stale);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
clearWorkspaceRetry();
|
|
1422
|
-
clearWorkspaceIssue();
|
|
1423
|
-
const syncedAt = pushResult.syncedAt || new Date().toISOString();
|
|
1424
|
-
setWorkspaceLastPushAt(syncedAt);
|
|
1425
|
-
setWorkspaceSyncPendingChanges(0);
|
|
1426
|
-
setUserGlobalSyncStatus('idle');
|
|
1427
|
-
persistWorkspaceSyncPatchSafely({
|
|
1428
|
-
phase: 'active',
|
|
1429
|
-
lastPushAt: syncedAt,
|
|
1430
|
-
lastSyncedAt: syncedAt,
|
|
1431
|
-
lastErrorMessage: null
|
|
1432
|
-
});
|
|
1433
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1434
|
-
eventType: workspaceSyncPhase === 'provision-local' ? 'bootstrap' : 'push',
|
|
1435
|
-
status: 'success',
|
|
1436
|
-
changeCount: pushResult.changeCount,
|
|
1437
|
-
requestMs: pushResult.requestMs
|
|
1438
|
-
});
|
|
1439
|
-
return true;
|
|
1440
|
-
}
|
|
1441
|
-
catch (error) {
|
|
1442
|
-
const message = 'Workspace sync push failed.';
|
|
1443
|
-
setUserGlobalSyncStatus('error');
|
|
1444
|
-
setUserGlobalSyncError(message);
|
|
1445
|
-
setWorkspaceLastErrorMessage(message);
|
|
1446
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1447
|
-
workspacePendingSignatureRef.current = signature;
|
|
1448
|
-
scheduleWorkspaceSyncRetry();
|
|
1449
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1450
|
-
eventType: 'push',
|
|
1451
|
-
status: 'error',
|
|
1452
|
-
errorMessage: `${message} ${String(error?.message || '').trim()}`.trim()
|
|
1453
|
-
});
|
|
1454
|
-
logSyncDebug('push_failed_exception', {
|
|
1455
|
-
workspaceId: currentWorkspaceId,
|
|
1456
|
-
elapsedMs: Date.now() - pushStartedAtMs
|
|
1457
|
-
});
|
|
1458
|
-
return false;
|
|
1459
|
-
}
|
|
1460
|
-
finally {
|
|
1461
|
-
workspacePushInFlightRef.current = false;
|
|
1462
|
-
setWorkspaceSyncBusy(workspacePullInFlightRef.current);
|
|
1463
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1464
|
-
const pendingSignature = workspacePendingSignatureRef.current;
|
|
1465
|
-
if (pendingSignature
|
|
1466
|
-
&& pendingSignature !== workspaceLastPushedSignatureRef.current
|
|
1467
|
-
&& !workspacePullInFlightRef.current) {
|
|
1468
|
-
workspacePendingSignatureRef.current = '';
|
|
1469
|
-
window.setTimeout(() => {
|
|
1470
|
-
void pushWorkspaceChangesToCloud(pendingSignature);
|
|
1471
|
-
}, 120);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
}, [
|
|
1135
|
+
// === SECTION: Push/Pull sync ===
|
|
1136
|
+
const transferCoordinator = useMemo(() => createWorkspaceTransferCoordinator({
|
|
1475
1137
|
cloudAuthConfigured,
|
|
1476
1138
|
runtimeMode,
|
|
1477
1139
|
workspaceCloudSyncEnabled,
|
|
1478
1140
|
currentWorkspaceId,
|
|
1479
1141
|
workspaceSyncPhase,
|
|
1142
|
+
taxonomyStateFingerprint,
|
|
1480
1143
|
isAuthenticated,
|
|
1481
1144
|
checkAuthSession,
|
|
1482
|
-
clearWorkspaceIssue,
|
|
1483
|
-
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1484
|
-
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
1485
|
-
refreshWorkspaceSyncAssetsSnapshot,
|
|
1486
|
-
refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
|
|
1487
|
-
ensureWorkspaceSyncReady,
|
|
1488
|
-
buildWorkspaceSyncPayload,
|
|
1489
1145
|
resolveCloudAuthUrl,
|
|
1490
1146
|
ensureCloudWorkspaceReadyForSync,
|
|
1491
|
-
|
|
1147
|
+
ensureWorkspaceSyncReady,
|
|
1492
1148
|
handleSyncAuthFailure,
|
|
1493
1149
|
clearWorkspaceRetry,
|
|
1494
|
-
|
|
1495
|
-
|
|
1150
|
+
clearWorkspaceIssue,
|
|
1151
|
+
isWorkspaceRetryPending,
|
|
1496
1152
|
scheduleWorkspaceSyncRetry,
|
|
1153
|
+
persistWorkspaceSyncPatchSafely,
|
|
1154
|
+
persistWorkspaceSyncPatchBestEffort,
|
|
1155
|
+
reportSyncEvent,
|
|
1497
1156
|
acquireWorkspaceSyncLease,
|
|
1498
1157
|
releaseWorkspaceSyncLease,
|
|
1499
1158
|
reportWorkspaceWindowContention,
|
|
1159
|
+
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1160
|
+
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
1161
|
+
refreshWorkspaceSyncAssetsSnapshot,
|
|
1162
|
+
refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
|
|
1163
|
+
refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
|
|
1164
|
+
refreshLocalWorkspaceTaskCollections,
|
|
1165
|
+
buildWorkspaceSyncPayload,
|
|
1166
|
+
buildWorkspaceSyncSignature,
|
|
1167
|
+
seedWorkspaceSnapshotPushBaseline,
|
|
1168
|
+
shouldSuppressAttachBootstrapPush,
|
|
1169
|
+
recordSuppressedEventIds: (...args) => realtimeBridgeRef.current.recordSuppressedEventIds(...args),
|
|
1170
|
+
workspaceRepairModeRef,
|
|
1171
|
+
workspaceForceFullAiProfilePushRef,
|
|
1500
1172
|
workspacePushInFlightRef,
|
|
1501
1173
|
workspacePullInFlightRef,
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
workspaceId: currentWorkspaceId,
|
|
1542
|
-
cursor,
|
|
1543
|
-
bootstrap,
|
|
1544
|
-
ensureCloudWorkspaceReadyForSync,
|
|
1545
|
-
maxPages: 20,
|
|
1546
|
-
repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true,
|
|
1547
|
-
onPagePulled: () => { bootstrapLastProgressAtRef.current = Date.now(); },
|
|
1548
|
-
onChangesApplied: () => { bootstrapLastProgressAtRef.current = Date.now(); }
|
|
1549
|
-
});
|
|
1550
|
-
if (!pullResult.success) {
|
|
1551
|
-
if (pullResult.kind === 'pull_http' && pullResult.status === 401) {
|
|
1552
|
-
await handleSyncAuthFailure('pull', 401);
|
|
1553
|
-
return false;
|
|
1554
|
-
}
|
|
1555
|
-
if (pullResult.kind === 'apply_auth') {
|
|
1556
|
-
await handleSyncAuthFailure('apply', 401);
|
|
1557
|
-
return false;
|
|
1558
|
-
}
|
|
1559
|
-
let message = pullResult.kind === 'apply_payload' || pullResult.kind === 'apply_all_candidates' || pullResult.kind === 'exception'
|
|
1560
|
-
? String(pullResult.error || 'Workspace sync apply failed.')
|
|
1561
|
-
: `Workspace sync pull failed (${pullResult.status || 0})`;
|
|
1562
|
-
if (pullResult.status === 413) {
|
|
1563
|
-
message = 'Local sync failed because the downloaded payload is too large. This usually happens when the workspace contains massive attachments.';
|
|
1564
|
-
}
|
|
1565
|
-
setUserGlobalSyncStatus('error');
|
|
1566
|
-
setUserGlobalSyncError(message);
|
|
1567
|
-
setWorkspaceLastErrorMessage(message);
|
|
1568
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1569
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1570
|
-
eventType: pullResult.kind?.startsWith('apply') ? 'apply' : 'pull',
|
|
1571
|
-
status: 'error',
|
|
1572
|
-
statusCode: pullResult.status,
|
|
1573
|
-
errorMessage: message,
|
|
1574
|
-
requestMs: pullResult.pullRequestMs
|
|
1575
|
-
});
|
|
1576
|
-
if (pullResult.transient || pullResult.hasTransientApplyFailure || isTransientWorkspaceSyncStatus(pullResult.status)) {
|
|
1577
|
-
scheduleWorkspaceSyncRetry(pullResult.retryAfterMs);
|
|
1578
|
-
}
|
|
1579
|
-
else {
|
|
1580
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
1581
|
-
}
|
|
1582
|
-
return false;
|
|
1583
|
-
}
|
|
1584
|
-
if (Array.isArray(pullResult.emittedEventIds)) {
|
|
1585
|
-
for (const eventId of pullResult.emittedEventIds) {
|
|
1586
|
-
if (eventId) {
|
|
1587
|
-
realtimeSuppressedEventIdsRef.current.add(eventId);
|
|
1588
|
-
// Optional: cleanup after a generous delay if WebSocket never arrives to clear it
|
|
1589
|
-
setTimeout(() => {
|
|
1590
|
-
realtimeSuppressedEventIdsRef.current.delete(eventId);
|
|
1591
|
-
}, 60000);
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
if (pullResult.pulledDeleteTaskIds.size > 0) {
|
|
1596
|
-
setTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
1597
|
-
setArchivedTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
1598
|
-
}
|
|
1599
|
-
const decryptFailures = Array.isArray(pullResult.documentDecryptFailures)
|
|
1600
|
-
? pullResult.documentDecryptFailures.filter((value) => String(value || '').trim().length > 0)
|
|
1601
|
-
: [];
|
|
1602
|
-
if (decryptFailures.length > 0) {
|
|
1603
|
-
logSyncDebug('pull_document_decrypt_failures', {
|
|
1604
|
-
workspaceId: currentWorkspaceId,
|
|
1605
|
-
failureCount: decryptFailures.length
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
workspacePullCursorRef.current = pullResult.cursor || null;
|
|
1609
|
-
if (pullResult.appliedChanges > 0 && (pullResult.pulledActiveUpserts || pullResult.pulledArchivedUpserts)) {
|
|
1610
|
-
try {
|
|
1611
|
-
await refreshLocalWorkspaceTaskCollections();
|
|
1612
|
-
}
|
|
1613
|
-
catch {
|
|
1614
|
-
logSyncDebug('pull_local_refresh_failed', {
|
|
1615
|
-
workspaceId: currentWorkspaceId,
|
|
1616
|
-
appliedChanges: pullResult.appliedChanges
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
clearWorkspaceRetry();
|
|
1621
|
-
clearWorkspaceIssue();
|
|
1622
|
-
const syncedAt = pullResult.syncedAt || new Date().toISOString();
|
|
1623
|
-
setWorkspaceLastPullAt(syncedAt);
|
|
1624
|
-
setWorkspaceSyncPendingChanges(0);
|
|
1625
|
-
setUserGlobalSyncStatus('idle');
|
|
1626
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1627
|
-
workspaceCaptureAttachBootstrapBaselineRef.current = true;
|
|
1628
|
-
workspacePendingSignatureRef.current = '';
|
|
1629
|
-
}
|
|
1630
|
-
await persistWorkspaceSyncPatch({
|
|
1631
|
-
phase: 'active',
|
|
1632
|
-
pullCursor: workspacePullCursorRef.current,
|
|
1633
|
-
lastPullAt: syncedAt,
|
|
1634
|
-
lastSyncedAt: syncedAt,
|
|
1635
|
-
lastErrorMessage: null
|
|
1636
|
-
});
|
|
1637
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1638
|
-
eventType: workspaceSyncPhase === 'attach-cloud' ? 'bootstrap' : 'pull',
|
|
1639
|
-
status: 'success',
|
|
1640
|
-
changeCount: pullResult.appliedChanges,
|
|
1641
|
-
requestMs: pullResult.pullRequestMs
|
|
1642
|
-
});
|
|
1643
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1644
|
-
await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1645
|
-
await refreshWorkspaceSyncAssetsSnapshot();
|
|
1646
|
-
await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
1647
|
-
await refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
1648
|
-
seedWorkspaceSnapshotPushBaseline();
|
|
1649
|
-
return true;
|
|
1650
|
-
}
|
|
1651
|
-
const pendingSignature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
|
|
1652
|
-
if (pendingSignature && pendingSignature !== workspaceLastPushedSignatureRef.current) {
|
|
1653
|
-
workspacePendingSignatureRef.current = '';
|
|
1654
|
-
window.setTimeout(() => {
|
|
1655
|
-
void pushWorkspaceChangesToCloud(pendingSignature);
|
|
1656
|
-
}, 120);
|
|
1657
|
-
}
|
|
1658
|
-
return true;
|
|
1659
|
-
}
|
|
1660
|
-
catch (error) {
|
|
1661
|
-
const detail = String(error?.message || '').trim();
|
|
1662
|
-
const message = detail
|
|
1663
|
-
? `Workspace sync pull failed. ${detail}`
|
|
1664
|
-
: 'Workspace sync pull failed.';
|
|
1665
|
-
setUserGlobalSyncStatus('error');
|
|
1666
|
-
setUserGlobalSyncError(message);
|
|
1667
|
-
setWorkspaceLastErrorMessage(message);
|
|
1668
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1669
|
-
scheduleWorkspaceSyncRetry();
|
|
1670
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1671
|
-
eventType: 'pull',
|
|
1672
|
-
status: 'error',
|
|
1673
|
-
errorMessage: message,
|
|
1674
|
-
requestMs: Date.now() - pullStartedAtMs
|
|
1675
|
-
});
|
|
1676
|
-
logSyncDebug('pull_failed_exception', {
|
|
1677
|
-
workspaceId: currentWorkspaceId,
|
|
1678
|
-
elapsedMs: Date.now() - pullStartedAtMs,
|
|
1679
|
-
error: detail || null
|
|
1680
|
-
});
|
|
1681
|
-
return false;
|
|
1682
|
-
}
|
|
1683
|
-
finally {
|
|
1684
|
-
workspacePullInFlightRef.current = false;
|
|
1685
|
-
setWorkspaceSyncBusy(workspacePushInFlightRef.current);
|
|
1686
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1687
|
-
}
|
|
1688
|
-
}, [
|
|
1174
|
+
workspaceLastPushedSignatureRef,
|
|
1175
|
+
workspacePendingSignatureRef,
|
|
1176
|
+
workspaceLastPushedTaskIdsRef,
|
|
1177
|
+
workspaceDeletedTaskIdsRef,
|
|
1178
|
+
workspacePullCursorRef,
|
|
1179
|
+
workspaceLastPushedWatermarksRef,
|
|
1180
|
+
workspaceLastPushedInitiativeIdsRef,
|
|
1181
|
+
workspaceLastPushedInitiativeWatermarksRef,
|
|
1182
|
+
workspaceLastPushedWorkstreamIdsRef,
|
|
1183
|
+
workspaceLastPushedWorkstreamWatermarksRef,
|
|
1184
|
+
workspaceLastPushedAiProfileIdsRef,
|
|
1185
|
+
workspaceLastPushedAiProfileWatermarksRef,
|
|
1186
|
+
workspaceLastPushedDocumentPathsRef,
|
|
1187
|
+
workspaceLastPushedDocumentWatermarksRef,
|
|
1188
|
+
workspaceLastPushedAssetPathsRef,
|
|
1189
|
+
workspaceLastPushedAssetWatermarksRef,
|
|
1190
|
+
workspaceLastPushedDocumentReviewSessionIdsRef,
|
|
1191
|
+
workspaceLastPushedDocumentReviewSessionWatermarksRef,
|
|
1192
|
+
workspaceLastPushedDocumentReviewCommentIdsRef,
|
|
1193
|
+
workspaceLastPushedDocumentReviewCommentWatermarksRef,
|
|
1194
|
+
workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
|
|
1195
|
+
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
|
|
1196
|
+
workspaceLastPushedTaskEventWatermarksRef,
|
|
1197
|
+
workspaceCaptureAttachBootstrapBaselineRef,
|
|
1198
|
+
workspaceStartupFullReconcileRanRef,
|
|
1199
|
+
workspaceFullReconcileInFlightRef,
|
|
1200
|
+
bootstrapLastProgressAtRef,
|
|
1201
|
+
realtimeSuppressedEventQueueRef,
|
|
1202
|
+
setWorkspaceSyncBusy,
|
|
1203
|
+
setUserGlobalSyncStatus,
|
|
1204
|
+
setUserGlobalSyncError,
|
|
1205
|
+
setWorkspaceLastErrorMessage,
|
|
1206
|
+
setWorkspaceLastErrorAt,
|
|
1207
|
+
setWorkspaceLastPullAt,
|
|
1208
|
+
setWorkspaceLastPushAt,
|
|
1209
|
+
setWorkspaceSyncPendingChanges,
|
|
1210
|
+
setTasks,
|
|
1211
|
+
setArchivedTasks
|
|
1212
|
+
}), [
|
|
1689
1213
|
cloudAuthConfigured,
|
|
1690
1214
|
runtimeMode,
|
|
1691
1215
|
workspaceCloudSyncEnabled,
|
|
1692
1216
|
currentWorkspaceId,
|
|
1693
1217
|
workspaceSyncPhase,
|
|
1694
|
-
|
|
1218
|
+
taxonomyStateFingerprint,
|
|
1695
1219
|
isAuthenticated,
|
|
1696
1220
|
checkAuthSession,
|
|
1697
|
-
clearWorkspaceIssue,
|
|
1698
|
-
ensureWorkspaceSyncReady,
|
|
1699
1221
|
resolveCloudAuthUrl,
|
|
1700
1222
|
ensureCloudWorkspaceReadyForSync,
|
|
1223
|
+
ensureWorkspaceSyncReady,
|
|
1701
1224
|
handleSyncAuthFailure,
|
|
1702
|
-
|
|
1225
|
+
clearWorkspaceRetry,
|
|
1226
|
+
clearWorkspaceIssue,
|
|
1227
|
+
scheduleWorkspaceSyncRetry,
|
|
1228
|
+
persistWorkspaceSyncPatchSafely,
|
|
1229
|
+
persistWorkspaceSyncPatchBestEffort,
|
|
1230
|
+
reportSyncEvent,
|
|
1231
|
+
acquireWorkspaceSyncLease,
|
|
1232
|
+
releaseWorkspaceSyncLease,
|
|
1233
|
+
reportWorkspaceWindowContention,
|
|
1703
1234
|
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1235
|
+
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
1704
1236
|
refreshWorkspaceSyncAssetsSnapshot,
|
|
1705
1237
|
refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
|
|
1706
1238
|
refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
reportSyncEvent,
|
|
1239
|
+
refreshLocalWorkspaceTaskCollections,
|
|
1240
|
+
buildWorkspaceSyncPayload,
|
|
1710
1241
|
buildWorkspaceSyncSignature,
|
|
1711
|
-
pushWorkspaceChangesToCloud,
|
|
1712
1242
|
seedWorkspaceSnapshotPushBaseline,
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
reportWorkspaceWindowContention,
|
|
1717
|
-
workspacePushInFlightRef,
|
|
1718
|
-
workspacePullInFlightRef,
|
|
1719
|
-
persistWorkspaceSyncPatchSafely
|
|
1243
|
+
shouldSuppressAttachBootstrapPush,
|
|
1244
|
+
setTasks,
|
|
1245
|
+
setArchivedTasks
|
|
1720
1246
|
]);
|
|
1721
|
-
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
if (eventId && realtimeSuppressedEventIdsRef.current.delete(eventId))
|
|
1729
|
-
return;
|
|
1730
|
-
if (isWorkspaceRetryPending() || workspacePullInFlightRef.current)
|
|
1731
|
-
return;
|
|
1732
|
-
if (realtimePullDebounceRef.current !== null) {
|
|
1733
|
-
window.clearTimeout(realtimePullDebounceRef.current);
|
|
1734
|
-
}
|
|
1735
|
-
realtimePullDebounceRef.current = window.setTimeout(() => {
|
|
1736
|
-
realtimePullDebounceRef.current = null;
|
|
1737
|
-
void pullWorkspaceChangesFromCloud();
|
|
1738
|
-
}, 180);
|
|
1739
|
-
}, [
|
|
1247
|
+
const { pushWorkspaceChangesToCloud, pullWorkspaceChangesFromCloud } = transferCoordinator;
|
|
1248
|
+
const realtimeBridgeRef = useRef({
|
|
1249
|
+
handleRealtimeSignal: () => { },
|
|
1250
|
+
recordSuppressedEventIds: () => { },
|
|
1251
|
+
clearRealtimePullDebounce: () => { }
|
|
1252
|
+
});
|
|
1253
|
+
const realtimeBridge = useMemo(() => createRealtimeSyncBridge({
|
|
1740
1254
|
cloudAuthConfigured,
|
|
1741
1255
|
runtimeMode,
|
|
1742
1256
|
workspaceCloudSyncEnabled,
|
|
1743
1257
|
workspaceSyncPhase,
|
|
1744
|
-
isWorkspaceRetryPending,
|
|
1745
1258
|
workspacePullInFlightRef,
|
|
1259
|
+
realtimePullDebounceRef,
|
|
1260
|
+
realtimeSuppressedEventIdsRef,
|
|
1261
|
+
clearWorkspaceRetry,
|
|
1262
|
+
isWorkspaceRetryPending,
|
|
1263
|
+
pullWorkspaceChangesFromCloud
|
|
1264
|
+
}), [
|
|
1265
|
+
cloudAuthConfigured,
|
|
1266
|
+
runtimeMode,
|
|
1267
|
+
workspaceCloudSyncEnabled,
|
|
1268
|
+
workspaceSyncPhase,
|
|
1269
|
+
clearWorkspaceRetry,
|
|
1270
|
+
isWorkspaceRetryPending,
|
|
1746
1271
|
pullWorkspaceChangesFromCloud
|
|
1747
1272
|
]);
|
|
1273
|
+
realtimeBridgeRef.current = realtimeBridge;
|
|
1748
1274
|
const realtimeSocketUrl = resolveWebSocketUrl('/taskforce-ws');
|
|
1749
1275
|
const normalizedRealtimeUserId = String(authUserId || '').trim();
|
|
1750
1276
|
const realtimeEnabled = Boolean(realtimeSyncEnabled
|
|
@@ -1760,7 +1286,7 @@ export function useSyncOrchestrator(input) {
|
|
|
1760
1286
|
enabled: realtimeEnabled,
|
|
1761
1287
|
workspaceId: currentWorkspaceId,
|
|
1762
1288
|
websocketUrl: realtimeSocketUrl,
|
|
1763
|
-
onSignal: handleRealtimeSignal,
|
|
1289
|
+
onSignal: realtimeBridge.handleRealtimeSignal,
|
|
1764
1290
|
onTelemetry: setRealtimeTelemetry,
|
|
1765
1291
|
userId: normalizedRealtimeUserId || undefined
|
|
1766
1292
|
});
|
|
@@ -1768,39 +1294,9 @@ export function useSyncOrchestrator(input) {
|
|
|
1768
1294
|
setRealtimeConnectionState(realtimeSync.connectionState);
|
|
1769
1295
|
}, [realtimeSync.connectionState]);
|
|
1770
1296
|
useEffect(() => {
|
|
1771
|
-
return () =>
|
|
1772
|
-
|
|
1773
|
-
window.clearTimeout(realtimePullDebounceRef.current);
|
|
1774
|
-
realtimePullDebounceRef.current = null;
|
|
1775
|
-
}
|
|
1776
|
-
};
|
|
1777
|
-
}, []);
|
|
1297
|
+
return () => realtimeBridge.clearRealtimePullDebounce();
|
|
1298
|
+
}, [realtimeBridge]);
|
|
1778
1299
|
// === SECTION: Repair & manual retry ===
|
|
1779
|
-
const fetchLocalWorkspaceSnapshot = useCallback(async () => {
|
|
1780
|
-
const [localTasksRes, localArchiveRes] = await Promise.all([
|
|
1781
|
-
fetch('/api/taskforce/tasks', {
|
|
1782
|
-
method: 'GET',
|
|
1783
|
-
credentials: 'include'
|
|
1784
|
-
}),
|
|
1785
|
-
fetch('/api/taskforce/archive', {
|
|
1786
|
-
method: 'GET',
|
|
1787
|
-
credentials: 'include'
|
|
1788
|
-
})
|
|
1789
|
-
]);
|
|
1790
|
-
if (!localTasksRes.ok || !localArchiveRes.ok) {
|
|
1791
|
-
return {
|
|
1792
|
-
tasks: [],
|
|
1793
|
-
archived: [],
|
|
1794
|
-
error: 'Failed to read local workspace snapshot before sync bootstrap.'
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1797
|
-
const localTasksPayload = await localTasksRes.json().catch(() => ({}));
|
|
1798
|
-
const localArchivePayload = await localArchiveRes.json().catch(() => ({}));
|
|
1799
|
-
return {
|
|
1800
|
-
tasks: Array.isArray(localTasksPayload?.tasks) ? localTasksPayload.tasks : [],
|
|
1801
|
-
archived: Array.isArray(localArchivePayload?.archived) ? localArchivePayload.archived : []
|
|
1802
|
-
};
|
|
1803
|
-
}, []);
|
|
1804
1300
|
const saveWorkspaceCloudSyncSettings = useCallback(async (settings) => {
|
|
1805
1301
|
return saveWorkspaceCloudSyncSettingsBase(settings, {
|
|
1806
1302
|
cloudAuthConfigured,
|
|
@@ -1816,144 +1312,79 @@ export function useSyncOrchestrator(input) {
|
|
|
1816
1312
|
const retryUserGlobalSettingsSync = useCallback(async () => {
|
|
1817
1313
|
await syncUserGlobalSettings({ preferCloudOnFirstSync: false });
|
|
1818
1314
|
}, [syncUserGlobalSettings]);
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
return false;
|
|
1823
|
-
if (!isAuthenticated) {
|
|
1824
|
-
const authed = await checkAuthSession();
|
|
1825
|
-
if (!authed)
|
|
1826
|
-
return false;
|
|
1827
|
-
}
|
|
1828
|
-
workspaceForceFullAiProfilePushRef.current = true;
|
|
1829
|
-
if (workspaceSyncPhase === 'provision-local') {
|
|
1830
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1831
|
-
if (!signature)
|
|
1832
|
-
return false;
|
|
1833
|
-
return pushWorkspaceChangesToCloud(signature, { forceAllAiProfiles: true });
|
|
1834
|
-
}
|
|
1835
|
-
const pulled = await pullWorkspaceChangesFromCloud();
|
|
1836
|
-
if (workspaceSyncPhase === 'attach-cloud')
|
|
1837
|
-
return pulled;
|
|
1838
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1839
|
-
const forceAllAiProfiles = workspaceForceFullAiProfilePushRef.current === true;
|
|
1840
|
-
if (!forceAllAiProfiles && shouldSuppressAttachBootstrapPush(signature)) {
|
|
1841
|
-
return pulled;
|
|
1842
|
-
}
|
|
1843
|
-
if (signature) {
|
|
1844
|
-
workspacePendingSignatureRef.current = signature;
|
|
1845
|
-
const pushed = await pushWorkspaceChangesToCloud(signature, {
|
|
1846
|
-
forceAllAiProfiles
|
|
1847
|
-
});
|
|
1848
|
-
return pulled || pushed;
|
|
1849
|
-
}
|
|
1850
|
-
return pulled;
|
|
1851
|
-
}, [
|
|
1852
|
-
clearWorkspaceRetry,
|
|
1853
|
-
cloudAuthConfigured,
|
|
1854
|
-
runtimeMode,
|
|
1315
|
+
const recoveryCoordinator = useMemo(() => createSyncRecoveryCoordinator({
|
|
1316
|
+
currentWorkspaceId,
|
|
1317
|
+
taxonomyStateFingerprint,
|
|
1855
1318
|
workspaceCloudSyncEnabled,
|
|
1856
|
-
isAuthenticated,
|
|
1857
|
-
checkAuthSession,
|
|
1858
1319
|
workspaceSyncPhase,
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
workspaceLastPushedDocumentWatermarksRef.current = new Map();
|
|
1892
|
-
workspaceLastPushedAssetPathsRef.current = new Set();
|
|
1893
|
-
workspaceLastPushedAssetWatermarksRef.current = new Map();
|
|
1894
|
-
workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
|
|
1895
|
-
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
|
|
1896
|
-
workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
|
|
1897
|
-
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
|
|
1898
|
-
workspaceForceFullAiProfilePushRef.current = false;
|
|
1899
|
-
workspaceStartupRepairRanRef.current = false;
|
|
1900
|
-
// Clear persisted watermarks so the next push is a full resync (repair intent)
|
|
1901
|
-
savePersistedSyncWatermarks(currentWorkspaceId, {
|
|
1902
|
-
taskWatermarks: new Map(),
|
|
1903
|
-
initiativeWatermarks: new Map(),
|
|
1904
|
-
workstreamWatermarks: new Map(),
|
|
1905
|
-
documentWatermarks: new Map(),
|
|
1906
|
-
assetWatermarks: new Map(),
|
|
1907
|
-
documentReviewSessionWatermarks: new Map(),
|
|
1908
|
-
documentReviewCommentWatermarks: new Map(),
|
|
1909
|
-
annotatedAttachmentSessionWatermarks: new Map(),
|
|
1910
|
-
taxonomyStateFingerprint
|
|
1911
|
-
});
|
|
1912
|
-
try {
|
|
1913
|
-
if (!workspaceCloudSyncEnabled)
|
|
1914
|
-
return;
|
|
1915
|
-
workspaceRepairModeRef.current = true;
|
|
1916
|
-
const repairPhase = workspaceSyncPhase === 'provision-local'
|
|
1917
|
-
? 'provision-local'
|
|
1918
|
-
: workspaceSyncPhase === 'attach-cloud'
|
|
1919
|
-
? 'attach-cloud'
|
|
1920
|
-
: lastBootstrapPhaseRef.current === 'provision-local' || lastBootstrapPhaseRef.current === 'attach-cloud'
|
|
1921
|
-
? lastBootstrapPhaseRef.current
|
|
1922
|
-
: (workspaceLastPullAt ? 'attach-cloud' : 'provision-local');
|
|
1923
|
-
await persistWorkspaceSyncPatch({
|
|
1924
|
-
phase: repairPhase,
|
|
1925
|
-
pullCursor: null,
|
|
1926
|
-
lastErrorMessage: null
|
|
1927
|
-
});
|
|
1928
|
-
if (repairPhase === 'provision-local') {
|
|
1929
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1930
|
-
if (!signature)
|
|
1931
|
-
return;
|
|
1932
|
-
await pushWorkspaceChangesToCloud(signature, { forceAllAiProfiles: true, repairMode: true });
|
|
1933
|
-
return;
|
|
1934
|
-
}
|
|
1935
|
-
await pullWorkspaceChangesFromCloud({ repairMode: true });
|
|
1936
|
-
}
|
|
1937
|
-
finally {
|
|
1938
|
-
workspaceRepairModeRef.current = false;
|
|
1939
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1940
|
-
}
|
|
1941
|
-
}, [
|
|
1320
|
+
workspaceLastPullAt,
|
|
1321
|
+
lastBootstrapPhaseRef,
|
|
1322
|
+
workspaceRepairModeRef,
|
|
1323
|
+
workspaceForceFullAiProfilePushRef,
|
|
1324
|
+
workspaceStartupRepairRanRef,
|
|
1325
|
+
workspaceStartupFullReconcileRanRef,
|
|
1326
|
+
workspaceFullReconcileInFlightRef,
|
|
1327
|
+
workspacePullCursorRef,
|
|
1328
|
+
workspaceAttachBootstrapBaselineSignatureRef,
|
|
1329
|
+
workspaceCaptureAttachBootstrapBaselineRef,
|
|
1330
|
+
workspacePendingSignatureRef,
|
|
1331
|
+
workspaceLastPushedSignatureRef,
|
|
1332
|
+
workspaceLastPushedWatermarksRef,
|
|
1333
|
+
workspaceLastPushedInitiativeIdsRef,
|
|
1334
|
+
workspaceLastPushedInitiativeWatermarksRef,
|
|
1335
|
+
workspaceLastPushedWorkstreamIdsRef,
|
|
1336
|
+
workspaceLastPushedWorkstreamWatermarksRef,
|
|
1337
|
+
workspaceLastPushedAiProfileIdsRef,
|
|
1338
|
+
workspaceLastPushedAiProfileWatermarksRef,
|
|
1339
|
+
workspaceLastPushedDocumentPathsRef,
|
|
1340
|
+
workspaceLastPushedDocumentWatermarksRef,
|
|
1341
|
+
workspaceLastPushedAssetPathsRef,
|
|
1342
|
+
workspaceLastPushedAssetWatermarksRef,
|
|
1343
|
+
workspaceLastPushedDocumentReviewSessionIdsRef,
|
|
1344
|
+
workspaceLastPushedDocumentReviewSessionWatermarksRef,
|
|
1345
|
+
workspaceLastPushedDocumentReviewCommentIdsRef,
|
|
1346
|
+
workspaceLastPushedDocumentReviewCommentWatermarksRef,
|
|
1347
|
+
workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
|
|
1348
|
+
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
|
|
1349
|
+
workspaceLastPushedTaskEventWatermarksRef,
|
|
1350
|
+
bootstrapPhaseStartedAtRef,
|
|
1351
|
+
bootstrapLastProgressAtRef,
|
|
1942
1352
|
acquireWorkspaceSyncLease,
|
|
1353
|
+
releaseWorkspaceSyncLease,
|
|
1354
|
+
forceClearWorkspaceSyncLease,
|
|
1943
1355
|
clearWorkspaceRetry,
|
|
1944
1356
|
clearWorkspaceIssue,
|
|
1357
|
+
reportWorkspaceWindowContention,
|
|
1358
|
+
persistWorkspaceSyncPatch,
|
|
1359
|
+
buildWorkspaceSyncSignature,
|
|
1360
|
+
pullWorkspaceChangesFromCloud,
|
|
1361
|
+
pushWorkspaceChangesToCloud,
|
|
1362
|
+
cloudAuthConfigured,
|
|
1363
|
+
runtimeMode,
|
|
1364
|
+
isAuthenticated,
|
|
1365
|
+
checkAuthSession
|
|
1366
|
+
}), [
|
|
1945
1367
|
currentWorkspaceId,
|
|
1946
|
-
|
|
1368
|
+
taxonomyStateFingerprint,
|
|
1947
1369
|
workspaceCloudSyncEnabled,
|
|
1948
1370
|
workspaceSyncPhase,
|
|
1371
|
+
workspaceLastPullAt,
|
|
1372
|
+
acquireWorkspaceSyncLease,
|
|
1373
|
+
releaseWorkspaceSyncLease,
|
|
1374
|
+
forceClearWorkspaceSyncLease,
|
|
1375
|
+
clearWorkspaceRetry,
|
|
1376
|
+
clearWorkspaceIssue,
|
|
1377
|
+
reportWorkspaceWindowContention,
|
|
1949
1378
|
persistWorkspaceSyncPatch,
|
|
1950
1379
|
buildWorkspaceSyncSignature,
|
|
1951
|
-
workspaceLastPullAt,
|
|
1952
1380
|
pullWorkspaceChangesFromCloud,
|
|
1953
1381
|
pushWorkspaceChangesToCloud,
|
|
1954
|
-
|
|
1955
|
-
|
|
1382
|
+
cloudAuthConfigured,
|
|
1383
|
+
runtimeMode,
|
|
1384
|
+
isAuthenticated,
|
|
1385
|
+
checkAuthSession
|
|
1956
1386
|
]);
|
|
1387
|
+
const { retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull } = recoveryCoordinator;
|
|
1957
1388
|
// === SECTION: Diagnostics & reactive effects ===
|
|
1958
1389
|
const getWorkspaceSyncDiagnostics = useCallback(() => {
|
|
1959
1390
|
return {
|
|
@@ -2012,6 +1443,13 @@ export function useSyncOrchestrator(input) {
|
|
|
2012
1443
|
useEffect(() => {
|
|
2013
1444
|
retryWorkspaceSyncRef.current = retryWorkspaceCloudSync;
|
|
2014
1445
|
}, [retryWorkspaceCloudSync]);
|
|
1446
|
+
const refreshWorkspaceSyncContextAssetSnapshots = useCallback(() => {
|
|
1447
|
+
void refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1448
|
+
void refreshWorkspaceSyncAssetsSnapshot();
|
|
1449
|
+
}, [
|
|
1450
|
+
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1451
|
+
refreshWorkspaceSyncAssetsSnapshot
|
|
1452
|
+
]);
|
|
2015
1453
|
useEffect(() => {
|
|
2016
1454
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
2017
1455
|
return;
|
|
@@ -2027,11 +1465,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2027
1465
|
const eventWorkspaceId = String(detail?.workspaceId || '').trim() || 'default';
|
|
2028
1466
|
if (eventWorkspaceId !== currentWorkspaceId)
|
|
2029
1467
|
return;
|
|
2030
|
-
|
|
2031
|
-
void refreshWorkspaceSyncAiProfilesSnapshot();
|
|
2032
|
-
void refreshWorkspaceSyncAssetsSnapshot();
|
|
2033
|
-
void refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
2034
|
-
void refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
1468
|
+
refreshWorkspaceSyncContextAssetSnapshots();
|
|
2035
1469
|
};
|
|
2036
1470
|
window.addEventListener(TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT, handleContextAssetsMutated);
|
|
2037
1471
|
const docsIntervalId = window.setInterval(() => {
|
|
@@ -2064,6 +1498,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2064
1498
|
authSessionResolved,
|
|
2065
1499
|
isAuthenticated,
|
|
2066
1500
|
currentWorkspaceId,
|
|
1501
|
+
refreshWorkspaceSyncContextAssetSnapshots,
|
|
2067
1502
|
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
2068
1503
|
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
2069
1504
|
refreshWorkspaceSyncAssetsSnapshot,
|
|
@@ -2082,12 +1517,8 @@ export function useSyncOrchestrator(input) {
|
|
|
2082
1517
|
return;
|
|
2083
1518
|
if (!taskAttachmentsFingerprint)
|
|
2084
1519
|
return;
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
void refreshWorkspaceSyncAiProfilesSnapshot();
|
|
2088
|
-
void refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
2089
|
-
void refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
2090
|
-
}, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot]);
|
|
1520
|
+
refreshWorkspaceSyncContextAssetSnapshots();
|
|
1521
|
+
}, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncContextAssetSnapshots]);
|
|
2091
1522
|
useEffect(() => {
|
|
2092
1523
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
|
|
2093
1524
|
return;
|
|
@@ -2120,6 +1551,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2120
1551
|
buildWorkspaceSyncSignature,
|
|
2121
1552
|
shouldSuppressAttachBootstrapPush,
|
|
2122
1553
|
buildWorkspaceSyncPayload,
|
|
1554
|
+
workspaceCoreSyncFingerprint,
|
|
2123
1555
|
workspaceSyncAiProfilesFingerprint,
|
|
2124
1556
|
workspaceSyncMarkdownDocumentsFingerprint,
|
|
2125
1557
|
workspaceSyncAssetsFingerprint,
|
|
@@ -2162,6 +1594,29 @@ export function useSyncOrchestrator(input) {
|
|
|
2162
1594
|
pullWorkspaceChangesFromCloud,
|
|
2163
1595
|
workspacePullIntervalMs
|
|
2164
1596
|
]);
|
|
1597
|
+
useEffect(() => {
|
|
1598
|
+
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved || !isAuthenticated)
|
|
1599
|
+
return;
|
|
1600
|
+
if (workspaceSyncPhase !== 'active')
|
|
1601
|
+
return;
|
|
1602
|
+
const intervalId = window.setInterval(() => {
|
|
1603
|
+
if (workspaceFullReconcileInFlightRef.current)
|
|
1604
|
+
return;
|
|
1605
|
+
workspaceFullReconcileInFlightRef.current = true;
|
|
1606
|
+
void pullWorkspaceChangesFromCloud({ forceCursorNull: true }).finally(() => {
|
|
1607
|
+
workspaceFullReconcileInFlightRef.current = false;
|
|
1608
|
+
});
|
|
1609
|
+
}, WORKSPACE_FULL_RECONCILE_INTERVAL_MS);
|
|
1610
|
+
return () => window.clearInterval(intervalId);
|
|
1611
|
+
}, [
|
|
1612
|
+
cloudAuthConfigured,
|
|
1613
|
+
runtimeMode,
|
|
1614
|
+
workspaceCloudSyncEnabled,
|
|
1615
|
+
authSessionResolved,
|
|
1616
|
+
isAuthenticated,
|
|
1617
|
+
workspaceSyncPhase,
|
|
1618
|
+
pullWorkspaceChangesFromCloud
|
|
1619
|
+
]);
|
|
2165
1620
|
useEffect(() => {
|
|
2166
1621
|
if (runtimeMode !== 'local' || !isAuthenticated)
|
|
2167
1622
|
return;
|