@taskforcehq/taskforce 0.3.303 → 0.3.304
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +514 -212
- package/dist/TaskforceCore.test.js +1172 -133
- 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/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 +5 -1
- package/dist/components/views/PlansPage.js +39 -12
- package/dist/components/views/PlansPage.test.js +59 -2
- package/dist/components/views/StandaloneLayout.js +113 -38
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/PlanEntitlementService.d.ts +3 -1
- package/dist/core/PlanEntitlementService.js +61 -11
- package/dist/core/SignupMonetizationSettings.test.js +37 -2
- 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 +15 -11
- package/dist/core/Taskforce.js +43 -2
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- 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 +314 -873
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +8 -2
- package/dist/hooks/useTaskforce.js +285 -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 +174 -476
- package/dist/mcp/runtime.test.js +81 -8
- 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/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.js +13 -19
- package/dist/server/routes/auth.js +37 -2
- package/dist/server/routes/billing.js +18 -67
- package/dist/server/routes/billing.test.js +43 -0
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +110 -237
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +8 -19
- package/dist/server/routes.test.js +243 -5
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +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.js +12 -10
- 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 +8 -1
- package/dist/sync/syncService.js +36 -2
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- 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-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.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-CGk9s3NJ.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-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +3 -1
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -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,557 +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
|
-
details: {
|
|
1576
|
-
pages: pullResult.pages,
|
|
1577
|
-
applyMs: pullResult.applyMs,
|
|
1578
|
-
...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
|
|
1579
|
-
}
|
|
1580
|
-
});
|
|
1581
|
-
if (pullResult.transient || pullResult.hasTransientApplyFailure || isTransientWorkspaceSyncStatus(pullResult.status)) {
|
|
1582
|
-
scheduleWorkspaceSyncRetry(pullResult.retryAfterMs);
|
|
1583
|
-
}
|
|
1584
|
-
else {
|
|
1585
|
-
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
1586
|
-
}
|
|
1587
|
-
return false;
|
|
1588
|
-
}
|
|
1589
|
-
if (Array.isArray(pullResult.emittedEventIds)) {
|
|
1590
|
-
for (const eventId of pullResult.emittedEventIds) {
|
|
1591
|
-
if (eventId) {
|
|
1592
|
-
realtimeSuppressedEventIdsRef.current.add(eventId);
|
|
1593
|
-
// Optional: cleanup after a generous delay if WebSocket never arrives to clear it
|
|
1594
|
-
setTimeout(() => {
|
|
1595
|
-
realtimeSuppressedEventIdsRef.current.delete(eventId);
|
|
1596
|
-
}, 60000);
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
if (pullResult.pulledDeleteTaskIds.size > 0) {
|
|
1601
|
-
setTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
1602
|
-
setArchivedTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
1603
|
-
}
|
|
1604
|
-
const decryptFailures = Array.isArray(pullResult.documentDecryptFailures)
|
|
1605
|
-
? pullResult.documentDecryptFailures.filter((value) => String(value || '').trim().length > 0)
|
|
1606
|
-
: [];
|
|
1607
|
-
if (decryptFailures.length > 0) {
|
|
1608
|
-
logSyncDebug('pull_document_decrypt_failures', {
|
|
1609
|
-
workspaceId: currentWorkspaceId,
|
|
1610
|
-
failureCount: decryptFailures.length
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
workspacePullCursorRef.current = pullResult.cursor || null;
|
|
1614
|
-
if (pullResult.appliedChanges > 0 && (pullResult.pulledActiveUpserts || pullResult.pulledArchivedUpserts)) {
|
|
1615
|
-
try {
|
|
1616
|
-
await refreshLocalWorkspaceTaskCollections();
|
|
1617
|
-
}
|
|
1618
|
-
catch {
|
|
1619
|
-
logSyncDebug('pull_local_refresh_failed', {
|
|
1620
|
-
workspaceId: currentWorkspaceId,
|
|
1621
|
-
appliedChanges: pullResult.appliedChanges
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
clearWorkspaceRetry();
|
|
1626
|
-
clearWorkspaceIssue();
|
|
1627
|
-
const syncedAt = pullResult.syncedAt || new Date().toISOString();
|
|
1628
|
-
setWorkspaceLastPullAt(syncedAt);
|
|
1629
|
-
setWorkspaceSyncPendingChanges(0);
|
|
1630
|
-
setUserGlobalSyncStatus('idle');
|
|
1631
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1632
|
-
workspaceCaptureAttachBootstrapBaselineRef.current = true;
|
|
1633
|
-
workspacePendingSignatureRef.current = '';
|
|
1634
|
-
}
|
|
1635
|
-
await persistWorkspaceSyncPatch({
|
|
1636
|
-
phase: 'active',
|
|
1637
|
-
pullCursor: workspacePullCursorRef.current,
|
|
1638
|
-
lastPullAt: syncedAt,
|
|
1639
|
-
lastSyncedAt: syncedAt,
|
|
1640
|
-
lastErrorMessage: null
|
|
1641
|
-
});
|
|
1642
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1643
|
-
eventType: workspaceSyncPhase === 'attach-cloud' ? 'bootstrap' : 'pull',
|
|
1644
|
-
status: 'success',
|
|
1645
|
-
changeCount: pullResult.appliedChanges,
|
|
1646
|
-
requestMs: pullResult.pullRequestMs,
|
|
1647
|
-
details: {
|
|
1648
|
-
pages: pullResult.pages,
|
|
1649
|
-
applyMs: pullResult.applyMs,
|
|
1650
|
-
...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
|
|
1651
|
-
}
|
|
1652
|
-
});
|
|
1653
|
-
if (workspaceSyncPhase === 'attach-cloud') {
|
|
1654
|
-
await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1655
|
-
await refreshWorkspaceSyncAssetsSnapshot();
|
|
1656
|
-
await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
1657
|
-
await refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
1658
|
-
seedWorkspaceSnapshotPushBaseline();
|
|
1659
|
-
return true;
|
|
1660
|
-
}
|
|
1661
|
-
const pendingSignature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
|
|
1662
|
-
if (pendingSignature && pendingSignature !== workspaceLastPushedSignatureRef.current) {
|
|
1663
|
-
workspacePendingSignatureRef.current = '';
|
|
1664
|
-
window.setTimeout(() => {
|
|
1665
|
-
void pushWorkspaceChangesToCloud(pendingSignature);
|
|
1666
|
-
}, 120);
|
|
1667
|
-
}
|
|
1668
|
-
return true;
|
|
1669
|
-
}
|
|
1670
|
-
catch (error) {
|
|
1671
|
-
const detail = String(error?.message || '').trim();
|
|
1672
|
-
const message = detail
|
|
1673
|
-
? `Workspace sync pull failed. ${detail}`
|
|
1674
|
-
: 'Workspace sync pull failed.';
|
|
1675
|
-
setUserGlobalSyncStatus('error');
|
|
1676
|
-
setUserGlobalSyncError(message);
|
|
1677
|
-
setWorkspaceLastErrorMessage(message);
|
|
1678
|
-
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
1679
|
-
scheduleWorkspaceSyncRetry();
|
|
1680
|
-
reportSyncEvent(currentWorkspaceId, {
|
|
1681
|
-
eventType: 'pull',
|
|
1682
|
-
status: 'error',
|
|
1683
|
-
errorMessage: message,
|
|
1684
|
-
requestMs: Date.now() - pullStartedAtMs
|
|
1685
|
-
});
|
|
1686
|
-
logSyncDebug('pull_failed_exception', {
|
|
1687
|
-
workspaceId: currentWorkspaceId,
|
|
1688
|
-
elapsedMs: Date.now() - pullStartedAtMs,
|
|
1689
|
-
error: detail || null
|
|
1690
|
-
});
|
|
1691
|
-
return false;
|
|
1692
|
-
}
|
|
1693
|
-
finally {
|
|
1694
|
-
workspacePullInFlightRef.current = false;
|
|
1695
|
-
setWorkspaceSyncBusy(workspacePushInFlightRef.current);
|
|
1696
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1697
|
-
}
|
|
1698
|
-
}, [
|
|
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
|
+
}), [
|
|
1699
1213
|
cloudAuthConfigured,
|
|
1700
1214
|
runtimeMode,
|
|
1701
1215
|
workspaceCloudSyncEnabled,
|
|
1702
1216
|
currentWorkspaceId,
|
|
1703
1217
|
workspaceSyncPhase,
|
|
1704
|
-
|
|
1218
|
+
taxonomyStateFingerprint,
|
|
1705
1219
|
isAuthenticated,
|
|
1706
1220
|
checkAuthSession,
|
|
1707
|
-
clearWorkspaceIssue,
|
|
1708
|
-
ensureWorkspaceSyncReady,
|
|
1709
1221
|
resolveCloudAuthUrl,
|
|
1710
1222
|
ensureCloudWorkspaceReadyForSync,
|
|
1223
|
+
ensureWorkspaceSyncReady,
|
|
1711
1224
|
handleSyncAuthFailure,
|
|
1712
|
-
|
|
1225
|
+
clearWorkspaceRetry,
|
|
1226
|
+
clearWorkspaceIssue,
|
|
1227
|
+
scheduleWorkspaceSyncRetry,
|
|
1228
|
+
persistWorkspaceSyncPatchSafely,
|
|
1229
|
+
persistWorkspaceSyncPatchBestEffort,
|
|
1230
|
+
reportSyncEvent,
|
|
1231
|
+
acquireWorkspaceSyncLease,
|
|
1232
|
+
releaseWorkspaceSyncLease,
|
|
1233
|
+
reportWorkspaceWindowContention,
|
|
1713
1234
|
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1235
|
+
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
1714
1236
|
refreshWorkspaceSyncAssetsSnapshot,
|
|
1715
1237
|
refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
|
|
1716
1238
|
refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
reportSyncEvent,
|
|
1239
|
+
refreshLocalWorkspaceTaskCollections,
|
|
1240
|
+
buildWorkspaceSyncPayload,
|
|
1720
1241
|
buildWorkspaceSyncSignature,
|
|
1721
|
-
pushWorkspaceChangesToCloud,
|
|
1722
1242
|
seedWorkspaceSnapshotPushBaseline,
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
reportWorkspaceWindowContention,
|
|
1727
|
-
workspacePushInFlightRef,
|
|
1728
|
-
workspacePullInFlightRef,
|
|
1729
|
-
persistWorkspaceSyncPatchSafely
|
|
1243
|
+
shouldSuppressAttachBootstrapPush,
|
|
1244
|
+
setTasks,
|
|
1245
|
+
setArchivedTasks
|
|
1730
1246
|
]);
|
|
1731
|
-
|
|
1732
|
-
const
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
if (eventId && realtimeSuppressedEventIdsRef.current.delete(eventId))
|
|
1739
|
-
return;
|
|
1740
|
-
if (workspacePullInFlightRef.current)
|
|
1741
|
-
return;
|
|
1742
|
-
if (isWorkspaceRetryPending()) {
|
|
1743
|
-
clearWorkspaceRetry();
|
|
1744
|
-
}
|
|
1745
|
-
if (realtimePullDebounceRef.current !== null) {
|
|
1746
|
-
window.clearTimeout(realtimePullDebounceRef.current);
|
|
1747
|
-
}
|
|
1748
|
-
realtimePullDebounceRef.current = window.setTimeout(() => {
|
|
1749
|
-
realtimePullDebounceRef.current = null;
|
|
1750
|
-
void pullWorkspaceChangesFromCloud();
|
|
1751
|
-
}, 180);
|
|
1752
|
-
}, [
|
|
1247
|
+
const { pushWorkspaceChangesToCloud, pullWorkspaceChangesFromCloud } = transferCoordinator;
|
|
1248
|
+
const realtimeBridgeRef = useRef({
|
|
1249
|
+
handleRealtimeSignal: () => { },
|
|
1250
|
+
recordSuppressedEventIds: () => { },
|
|
1251
|
+
clearRealtimePullDebounce: () => { }
|
|
1252
|
+
});
|
|
1253
|
+
const realtimeBridge = useMemo(() => createRealtimeSyncBridge({
|
|
1753
1254
|
cloudAuthConfigured,
|
|
1754
1255
|
runtimeMode,
|
|
1755
1256
|
workspaceCloudSyncEnabled,
|
|
1756
1257
|
workspaceSyncPhase,
|
|
1258
|
+
workspacePullInFlightRef,
|
|
1259
|
+
realtimePullDebounceRef,
|
|
1260
|
+
realtimeSuppressedEventIdsRef,
|
|
1261
|
+
clearWorkspaceRetry,
|
|
1757
1262
|
isWorkspaceRetryPending,
|
|
1263
|
+
pullWorkspaceChangesFromCloud
|
|
1264
|
+
}), [
|
|
1265
|
+
cloudAuthConfigured,
|
|
1266
|
+
runtimeMode,
|
|
1267
|
+
workspaceCloudSyncEnabled,
|
|
1268
|
+
workspaceSyncPhase,
|
|
1758
1269
|
clearWorkspaceRetry,
|
|
1759
|
-
|
|
1270
|
+
isWorkspaceRetryPending,
|
|
1760
1271
|
pullWorkspaceChangesFromCloud
|
|
1761
1272
|
]);
|
|
1273
|
+
realtimeBridgeRef.current = realtimeBridge;
|
|
1762
1274
|
const realtimeSocketUrl = resolveWebSocketUrl('/taskforce-ws');
|
|
1763
1275
|
const normalizedRealtimeUserId = String(authUserId || '').trim();
|
|
1764
1276
|
const realtimeEnabled = Boolean(realtimeSyncEnabled
|
|
@@ -1774,7 +1286,7 @@ export function useSyncOrchestrator(input) {
|
|
|
1774
1286
|
enabled: realtimeEnabled,
|
|
1775
1287
|
workspaceId: currentWorkspaceId,
|
|
1776
1288
|
websocketUrl: realtimeSocketUrl,
|
|
1777
|
-
onSignal: handleRealtimeSignal,
|
|
1289
|
+
onSignal: realtimeBridge.handleRealtimeSignal,
|
|
1778
1290
|
onTelemetry: setRealtimeTelemetry,
|
|
1779
1291
|
userId: normalizedRealtimeUserId || undefined
|
|
1780
1292
|
});
|
|
@@ -1782,39 +1294,9 @@ export function useSyncOrchestrator(input) {
|
|
|
1782
1294
|
setRealtimeConnectionState(realtimeSync.connectionState);
|
|
1783
1295
|
}, [realtimeSync.connectionState]);
|
|
1784
1296
|
useEffect(() => {
|
|
1785
|
-
return () =>
|
|
1786
|
-
|
|
1787
|
-
window.clearTimeout(realtimePullDebounceRef.current);
|
|
1788
|
-
realtimePullDebounceRef.current = null;
|
|
1789
|
-
}
|
|
1790
|
-
};
|
|
1791
|
-
}, []);
|
|
1297
|
+
return () => realtimeBridge.clearRealtimePullDebounce();
|
|
1298
|
+
}, [realtimeBridge]);
|
|
1792
1299
|
// === SECTION: Repair & manual retry ===
|
|
1793
|
-
const fetchLocalWorkspaceSnapshot = useCallback(async () => {
|
|
1794
|
-
const [localTasksRes, localArchiveRes] = await Promise.all([
|
|
1795
|
-
fetch('/api/taskforce/tasks', {
|
|
1796
|
-
method: 'GET',
|
|
1797
|
-
credentials: 'include'
|
|
1798
|
-
}),
|
|
1799
|
-
fetch('/api/taskforce/archive', {
|
|
1800
|
-
method: 'GET',
|
|
1801
|
-
credentials: 'include'
|
|
1802
|
-
})
|
|
1803
|
-
]);
|
|
1804
|
-
if (!localTasksRes.ok || !localArchiveRes.ok) {
|
|
1805
|
-
return {
|
|
1806
|
-
tasks: [],
|
|
1807
|
-
archived: [],
|
|
1808
|
-
error: 'Failed to read local workspace snapshot before sync bootstrap.'
|
|
1809
|
-
};
|
|
1810
|
-
}
|
|
1811
|
-
const localTasksPayload = await localTasksRes.json().catch(() => ({}));
|
|
1812
|
-
const localArchivePayload = await localArchiveRes.json().catch(() => ({}));
|
|
1813
|
-
return {
|
|
1814
|
-
tasks: Array.isArray(localTasksPayload?.tasks) ? localTasksPayload.tasks : [],
|
|
1815
|
-
archived: Array.isArray(localArchivePayload?.archived) ? localArchivePayload.archived : []
|
|
1816
|
-
};
|
|
1817
|
-
}, []);
|
|
1818
1300
|
const saveWorkspaceCloudSyncSettings = useCallback(async (settings) => {
|
|
1819
1301
|
return saveWorkspaceCloudSyncSettingsBase(settings, {
|
|
1820
1302
|
cloudAuthConfigured,
|
|
@@ -1830,144 +1312,79 @@ export function useSyncOrchestrator(input) {
|
|
|
1830
1312
|
const retryUserGlobalSettingsSync = useCallback(async () => {
|
|
1831
1313
|
await syncUserGlobalSettings({ preferCloudOnFirstSync: false });
|
|
1832
1314
|
}, [syncUserGlobalSettings]);
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
return false;
|
|
1837
|
-
if (!isAuthenticated) {
|
|
1838
|
-
const authed = await checkAuthSession();
|
|
1839
|
-
if (!authed)
|
|
1840
|
-
return false;
|
|
1841
|
-
}
|
|
1842
|
-
workspaceForceFullAiProfilePushRef.current = true;
|
|
1843
|
-
if (workspaceSyncPhase === 'provision-local') {
|
|
1844
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1845
|
-
if (!signature)
|
|
1846
|
-
return false;
|
|
1847
|
-
return pushWorkspaceChangesToCloud(signature, { forceAllAiProfiles: true });
|
|
1848
|
-
}
|
|
1849
|
-
const pulled = await pullWorkspaceChangesFromCloud();
|
|
1850
|
-
if (workspaceSyncPhase === 'attach-cloud')
|
|
1851
|
-
return pulled;
|
|
1852
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1853
|
-
const forceAllAiProfiles = workspaceForceFullAiProfilePushRef.current === true;
|
|
1854
|
-
if (!forceAllAiProfiles && shouldSuppressAttachBootstrapPush(signature)) {
|
|
1855
|
-
return pulled;
|
|
1856
|
-
}
|
|
1857
|
-
if (signature) {
|
|
1858
|
-
workspacePendingSignatureRef.current = signature;
|
|
1859
|
-
const pushed = await pushWorkspaceChangesToCloud(signature, {
|
|
1860
|
-
forceAllAiProfiles
|
|
1861
|
-
});
|
|
1862
|
-
return pulled || pushed;
|
|
1863
|
-
}
|
|
1864
|
-
return pulled;
|
|
1865
|
-
}, [
|
|
1866
|
-
clearWorkspaceRetry,
|
|
1867
|
-
cloudAuthConfigured,
|
|
1868
|
-
runtimeMode,
|
|
1315
|
+
const recoveryCoordinator = useMemo(() => createSyncRecoveryCoordinator({
|
|
1316
|
+
currentWorkspaceId,
|
|
1317
|
+
taxonomyStateFingerprint,
|
|
1869
1318
|
workspaceCloudSyncEnabled,
|
|
1870
|
-
isAuthenticated,
|
|
1871
|
-
checkAuthSession,
|
|
1872
1319
|
workspaceSyncPhase,
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
workspaceLastPushedDocumentWatermarksRef.current = new Map();
|
|
1906
|
-
workspaceLastPushedAssetPathsRef.current = new Set();
|
|
1907
|
-
workspaceLastPushedAssetWatermarksRef.current = new Map();
|
|
1908
|
-
workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
|
|
1909
|
-
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
|
|
1910
|
-
workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
|
|
1911
|
-
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
|
|
1912
|
-
workspaceForceFullAiProfilePushRef.current = false;
|
|
1913
|
-
workspaceStartupRepairRanRef.current = false;
|
|
1914
|
-
// Clear persisted watermarks so the next push is a full resync (repair intent)
|
|
1915
|
-
savePersistedSyncWatermarks(currentWorkspaceId, {
|
|
1916
|
-
taskWatermarks: new Map(),
|
|
1917
|
-
initiativeWatermarks: new Map(),
|
|
1918
|
-
workstreamWatermarks: new Map(),
|
|
1919
|
-
documentWatermarks: new Map(),
|
|
1920
|
-
assetWatermarks: new Map(),
|
|
1921
|
-
documentReviewSessionWatermarks: new Map(),
|
|
1922
|
-
documentReviewCommentWatermarks: new Map(),
|
|
1923
|
-
annotatedAttachmentSessionWatermarks: new Map(),
|
|
1924
|
-
taxonomyStateFingerprint
|
|
1925
|
-
});
|
|
1926
|
-
try {
|
|
1927
|
-
if (!workspaceCloudSyncEnabled)
|
|
1928
|
-
return;
|
|
1929
|
-
workspaceRepairModeRef.current = true;
|
|
1930
|
-
const repairPhase = workspaceSyncPhase === 'provision-local'
|
|
1931
|
-
? 'provision-local'
|
|
1932
|
-
: workspaceSyncPhase === 'attach-cloud'
|
|
1933
|
-
? 'attach-cloud'
|
|
1934
|
-
: lastBootstrapPhaseRef.current === 'provision-local' || lastBootstrapPhaseRef.current === 'attach-cloud'
|
|
1935
|
-
? lastBootstrapPhaseRef.current
|
|
1936
|
-
: (workspaceLastPullAt ? 'attach-cloud' : 'provision-local');
|
|
1937
|
-
await persistWorkspaceSyncPatch({
|
|
1938
|
-
phase: repairPhase,
|
|
1939
|
-
pullCursor: null,
|
|
1940
|
-
lastErrorMessage: null
|
|
1941
|
-
});
|
|
1942
|
-
if (repairPhase === 'provision-local') {
|
|
1943
|
-
const signature = buildWorkspaceSyncSignature();
|
|
1944
|
-
if (!signature)
|
|
1945
|
-
return;
|
|
1946
|
-
await pushWorkspaceChangesToCloud(signature, { forceAllAiProfiles: true, repairMode: true });
|
|
1947
|
-
return;
|
|
1948
|
-
}
|
|
1949
|
-
await pullWorkspaceChangesFromCloud({ repairMode: true });
|
|
1950
|
-
}
|
|
1951
|
-
finally {
|
|
1952
|
-
workspaceRepairModeRef.current = false;
|
|
1953
|
-
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
1954
|
-
}
|
|
1955
|
-
}, [
|
|
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,
|
|
1956
1352
|
acquireWorkspaceSyncLease,
|
|
1353
|
+
releaseWorkspaceSyncLease,
|
|
1354
|
+
forceClearWorkspaceSyncLease,
|
|
1957
1355
|
clearWorkspaceRetry,
|
|
1958
1356
|
clearWorkspaceIssue,
|
|
1357
|
+
reportWorkspaceWindowContention,
|
|
1358
|
+
persistWorkspaceSyncPatch,
|
|
1359
|
+
buildWorkspaceSyncSignature,
|
|
1360
|
+
pullWorkspaceChangesFromCloud,
|
|
1361
|
+
pushWorkspaceChangesToCloud,
|
|
1362
|
+
cloudAuthConfigured,
|
|
1363
|
+
runtimeMode,
|
|
1364
|
+
isAuthenticated,
|
|
1365
|
+
checkAuthSession
|
|
1366
|
+
}), [
|
|
1959
1367
|
currentWorkspaceId,
|
|
1960
|
-
|
|
1368
|
+
taxonomyStateFingerprint,
|
|
1961
1369
|
workspaceCloudSyncEnabled,
|
|
1962
1370
|
workspaceSyncPhase,
|
|
1371
|
+
workspaceLastPullAt,
|
|
1372
|
+
acquireWorkspaceSyncLease,
|
|
1373
|
+
releaseWorkspaceSyncLease,
|
|
1374
|
+
forceClearWorkspaceSyncLease,
|
|
1375
|
+
clearWorkspaceRetry,
|
|
1376
|
+
clearWorkspaceIssue,
|
|
1377
|
+
reportWorkspaceWindowContention,
|
|
1963
1378
|
persistWorkspaceSyncPatch,
|
|
1964
1379
|
buildWorkspaceSyncSignature,
|
|
1965
|
-
workspaceLastPullAt,
|
|
1966
1380
|
pullWorkspaceChangesFromCloud,
|
|
1967
1381
|
pushWorkspaceChangesToCloud,
|
|
1968
|
-
|
|
1969
|
-
|
|
1382
|
+
cloudAuthConfigured,
|
|
1383
|
+
runtimeMode,
|
|
1384
|
+
isAuthenticated,
|
|
1385
|
+
checkAuthSession
|
|
1970
1386
|
]);
|
|
1387
|
+
const { retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull } = recoveryCoordinator;
|
|
1971
1388
|
// === SECTION: Diagnostics & reactive effects ===
|
|
1972
1389
|
const getWorkspaceSyncDiagnostics = useCallback(() => {
|
|
1973
1390
|
return {
|
|
@@ -2026,6 +1443,13 @@ export function useSyncOrchestrator(input) {
|
|
|
2026
1443
|
useEffect(() => {
|
|
2027
1444
|
retryWorkspaceSyncRef.current = retryWorkspaceCloudSync;
|
|
2028
1445
|
}, [retryWorkspaceCloudSync]);
|
|
1446
|
+
const refreshWorkspaceSyncContextAssetSnapshots = useCallback(() => {
|
|
1447
|
+
void refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
1448
|
+
void refreshWorkspaceSyncAssetsSnapshot();
|
|
1449
|
+
}, [
|
|
1450
|
+
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
1451
|
+
refreshWorkspaceSyncAssetsSnapshot
|
|
1452
|
+
]);
|
|
2029
1453
|
useEffect(() => {
|
|
2030
1454
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
2031
1455
|
return;
|
|
@@ -2041,11 +1465,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2041
1465
|
const eventWorkspaceId = String(detail?.workspaceId || '').trim() || 'default';
|
|
2042
1466
|
if (eventWorkspaceId !== currentWorkspaceId)
|
|
2043
1467
|
return;
|
|
2044
|
-
|
|
2045
|
-
void refreshWorkspaceSyncAiProfilesSnapshot();
|
|
2046
|
-
void refreshWorkspaceSyncAssetsSnapshot();
|
|
2047
|
-
void refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
2048
|
-
void refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
1468
|
+
refreshWorkspaceSyncContextAssetSnapshots();
|
|
2049
1469
|
};
|
|
2050
1470
|
window.addEventListener(TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT, handleContextAssetsMutated);
|
|
2051
1471
|
const docsIntervalId = window.setInterval(() => {
|
|
@@ -2078,6 +1498,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2078
1498
|
authSessionResolved,
|
|
2079
1499
|
isAuthenticated,
|
|
2080
1500
|
currentWorkspaceId,
|
|
1501
|
+
refreshWorkspaceSyncContextAssetSnapshots,
|
|
2081
1502
|
refreshWorkspaceSyncMarkdownDocumentsSnapshot,
|
|
2082
1503
|
refreshWorkspaceSyncAiProfilesSnapshot,
|
|
2083
1504
|
refreshWorkspaceSyncAssetsSnapshot,
|
|
@@ -2096,12 +1517,8 @@ export function useSyncOrchestrator(input) {
|
|
|
2096
1517
|
return;
|
|
2097
1518
|
if (!taskAttachmentsFingerprint)
|
|
2098
1519
|
return;
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
void refreshWorkspaceSyncAiProfilesSnapshot();
|
|
2102
|
-
void refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
2103
|
-
void refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
2104
|
-
}, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot]);
|
|
1520
|
+
refreshWorkspaceSyncContextAssetSnapshots();
|
|
1521
|
+
}, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncContextAssetSnapshots]);
|
|
2105
1522
|
useEffect(() => {
|
|
2106
1523
|
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
|
|
2107
1524
|
return;
|
|
@@ -2134,6 +1551,7 @@ export function useSyncOrchestrator(input) {
|
|
|
2134
1551
|
buildWorkspaceSyncSignature,
|
|
2135
1552
|
shouldSuppressAttachBootstrapPush,
|
|
2136
1553
|
buildWorkspaceSyncPayload,
|
|
1554
|
+
workspaceCoreSyncFingerprint,
|
|
2137
1555
|
workspaceSyncAiProfilesFingerprint,
|
|
2138
1556
|
workspaceSyncMarkdownDocumentsFingerprint,
|
|
2139
1557
|
workspaceSyncAssetsFingerprint,
|
|
@@ -2176,6 +1594,29 @@ export function useSyncOrchestrator(input) {
|
|
|
2176
1594
|
pullWorkspaceChangesFromCloud,
|
|
2177
1595
|
workspacePullIntervalMs
|
|
2178
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
|
+
]);
|
|
2179
1620
|
useEffect(() => {
|
|
2180
1621
|
if (runtimeMode !== 'local' || !isAuthenticated)
|
|
2181
1622
|
return;
|