@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
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { applyLocalWorkspaceChanges } from '../../sync/cloudSyncApi';
|
|
2
|
+
import { isTransientWorkspaceSyncStatus, runWorkspacePullSyncService, runWorkspacePushSyncService } from '../../sync/syncService';
|
|
3
|
+
import { logSyncDebug, savePersistedSyncWatermarks, isValidWorkspaceKey } from './orchestratorShared';
|
|
4
|
+
export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, currentWorkspaceId, workspaceSyncPhase, taxonomyStateFingerprint, isAuthenticated, checkAuthSession, resolveCloudAuthUrl, ensureCloudWorkspaceReadyForSync, ensureWorkspaceSyncReady, handleSyncAuthFailure, clearWorkspaceRetry, clearWorkspaceIssue, isWorkspaceRetryPending, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely, persistWorkspaceSyncPatchBestEffort, reportSyncEvent, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, reportWorkspaceWindowContention, refreshWorkspaceSyncMarkdownDocumentsSnapshot, refreshWorkspaceSyncAiProfilesSnapshot, refreshWorkspaceSyncAssetsSnapshot, refreshWorkspaceSyncDocumentReviewSessionsSnapshot, refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot, refreshLocalWorkspaceTaskCollections, buildWorkspaceSyncPayload, buildWorkspaceSyncSignature, seedWorkspaceSnapshotPushBaseline, shouldSuppressAttachBootstrapPush, recordSuppressedEventIds, workspaceRepairModeRef, workspaceForceFullAiProfilePushRef, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, workspaceLastPushedWatermarksRef, workspaceLastPushedInitiativeIdsRef, workspaceLastPushedInitiativeWatermarksRef, workspaceLastPushedWorkstreamIdsRef, workspaceLastPushedWorkstreamWatermarksRef, workspaceLastPushedAiProfileIdsRef, workspaceLastPushedAiProfileWatermarksRef, workspaceLastPushedDocumentPathsRef, workspaceLastPushedDocumentWatermarksRef, workspaceLastPushedAssetPathsRef, workspaceLastPushedAssetWatermarksRef, workspaceLastPushedDocumentReviewSessionIdsRef, workspaceLastPushedDocumentReviewSessionWatermarksRef, workspaceLastPushedDocumentReviewCommentIdsRef, workspaceLastPushedDocumentReviewCommentWatermarksRef, workspaceLastPushedAnnotatedAttachmentSessionIdsRef, workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef, workspaceLastPushedTaskEventWatermarksRef, workspaceCaptureAttachBootstrapBaselineRef, workspaceStartupFullReconcileRanRef, workspaceFullReconcileInFlightRef, bootstrapLastProgressAtRef, realtimeSuppressedEventQueueRef, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, setWorkspaceLastPullAt, setWorkspaceLastPushAt, setWorkspaceSyncPendingChanges, setTasks, setArchivedTasks }) {
|
|
5
|
+
const pushWorkspaceChangesToCloud = async (signature, options) => {
|
|
6
|
+
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
7
|
+
return false;
|
|
8
|
+
if (!isValidWorkspaceKey(currentWorkspaceId))
|
|
9
|
+
return false;
|
|
10
|
+
if (workspaceRepairModeRef.current === true && options?.repairMode !== true) {
|
|
11
|
+
workspacePendingSignatureRef.current = signature;
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (workspaceSyncPhase === 'attach-cloud') {
|
|
15
|
+
workspacePendingSignatureRef.current = signature;
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (!isAuthenticated) {
|
|
19
|
+
const authed = await checkAuthSession();
|
|
20
|
+
if (!authed)
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (shouldSuppressAttachBootstrapPush(signature)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (workspacePushInFlightRef.current || workspacePullInFlightRef.current) {
|
|
27
|
+
workspacePendingSignatureRef.current = signature;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const leaseAcquired = await acquireWorkspaceSyncLease(currentWorkspaceId, 'push');
|
|
31
|
+
if (!leaseAcquired) {
|
|
32
|
+
workspacePendingSignatureRef.current = signature;
|
|
33
|
+
reportWorkspaceWindowContention('push');
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
workspacePushInFlightRef.current = true;
|
|
37
|
+
setWorkspaceSyncBusy(true);
|
|
38
|
+
setUserGlobalSyncStatus('syncing');
|
|
39
|
+
setUserGlobalSyncError(null);
|
|
40
|
+
const pushStartedAtMs = Date.now();
|
|
41
|
+
const forceAllAiProfiles = options?.forceAllAiProfiles === true || workspaceForceFullAiProfilePushRef.current === true;
|
|
42
|
+
try {
|
|
43
|
+
await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
44
|
+
await refreshWorkspaceSyncAiProfilesSnapshot();
|
|
45
|
+
await refreshWorkspaceSyncAssetsSnapshot();
|
|
46
|
+
await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
47
|
+
const ready = await ensureWorkspaceSyncReady();
|
|
48
|
+
if (!ready)
|
|
49
|
+
return false;
|
|
50
|
+
const payload = buildWorkspaceSyncPayload({ forceAllAiProfiles });
|
|
51
|
+
setWorkspaceSyncPendingChanges(payload.changes.length);
|
|
52
|
+
const pushResult = await runWorkspacePushSyncService({
|
|
53
|
+
resolveCloudAuthUrl,
|
|
54
|
+
workspaceId: currentWorkspaceId,
|
|
55
|
+
payload,
|
|
56
|
+
ensureCloudWorkspaceReadyForSync,
|
|
57
|
+
repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true
|
|
58
|
+
});
|
|
59
|
+
if (!pushResult.success) {
|
|
60
|
+
if (pushResult.status === 401) {
|
|
61
|
+
await handleSyncAuthFailure('push', 401);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
let message = pushResult.error
|
|
65
|
+
? `Workspace sync push failed (${pushResult.status || 0}): ${pushResult.error}`
|
|
66
|
+
: `Workspace sync push failed (${pushResult.status || 0})`;
|
|
67
|
+
if (pushResult.status === 413) {
|
|
68
|
+
message = 'Workspace sync failed because the payload is too large. This usually happens when one or more attachments exceed the project limit.';
|
|
69
|
+
}
|
|
70
|
+
setUserGlobalSyncStatus('error');
|
|
71
|
+
setUserGlobalSyncError(message);
|
|
72
|
+
setWorkspaceLastErrorMessage(message);
|
|
73
|
+
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
74
|
+
reportSyncEvent(currentWorkspaceId, {
|
|
75
|
+
eventType: 'push',
|
|
76
|
+
status: 'error',
|
|
77
|
+
statusCode: pushResult.status,
|
|
78
|
+
errorMessage: message,
|
|
79
|
+
requestMs: pushResult.requestMs
|
|
80
|
+
});
|
|
81
|
+
workspacePendingSignatureRef.current = signature;
|
|
82
|
+
if (pushResult.transient || isTransientWorkspaceSyncStatus(pushResult.status)) {
|
|
83
|
+
scheduleWorkspaceSyncRetry(pushResult.retryAfterMs);
|
|
84
|
+
}
|
|
85
|
+
else if (workspaceSyncPhase === 'active') {
|
|
86
|
+
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
workspaceLastPushedSignatureRef.current = signature;
|
|
91
|
+
workspaceLastPushedTaskIdsRef.current = pushResult.currentTaskIds;
|
|
92
|
+
workspaceLastPushedInitiativeIdsRef.current = new Set(pushResult.currentInitiativeIds);
|
|
93
|
+
workspaceLastPushedInitiativeWatermarksRef.current = new Map(pushResult.currentInitiativeWatermarks);
|
|
94
|
+
workspaceLastPushedWorkstreamIdsRef.current = new Set(pushResult.currentWorkstreamIds);
|
|
95
|
+
workspaceLastPushedWorkstreamWatermarksRef.current = new Map(pushResult.currentWorkstreamWatermarks);
|
|
96
|
+
workspaceLastPushedAiProfileIdsRef.current = new Set(pushResult.currentAiProfileIds);
|
|
97
|
+
workspaceLastPushedAiProfileWatermarksRef.current = new Map(pushResult.currentAiProfileWatermarks);
|
|
98
|
+
workspaceLastPushedDocumentPathsRef.current = new Set(pushResult.currentDocumentPaths);
|
|
99
|
+
workspaceLastPushedDocumentWatermarksRef.current = new Map(pushResult.currentDocumentWatermarks);
|
|
100
|
+
workspaceLastPushedAssetPathsRef.current = new Set(pushResult.currentAssetPaths);
|
|
101
|
+
workspaceLastPushedAssetWatermarksRef.current = new Map(pushResult.currentAssetWatermarks);
|
|
102
|
+
workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set(pushResult.currentDocumentReviewSessionIds);
|
|
103
|
+
workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map(pushResult.currentDocumentReviewSessionWatermarks);
|
|
104
|
+
workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set(pushResult.currentDocumentReviewCommentIds);
|
|
105
|
+
workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map(pushResult.currentDocumentReviewCommentWatermarks);
|
|
106
|
+
workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set(pushResult.currentAnnotatedAttachmentSessionIds);
|
|
107
|
+
workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(pushResult.currentAnnotatedAttachmentSessionWatermarks);
|
|
108
|
+
workspaceLastPushedTaskEventWatermarksRef.current = new Map(pushResult.currentTaskEventWatermarks);
|
|
109
|
+
if (forceAllAiProfiles) {
|
|
110
|
+
workspaceForceFullAiProfilePushRef.current = false;
|
|
111
|
+
}
|
|
112
|
+
for (const [taskId, watermark] of pushResult.pushedWatermarks) {
|
|
113
|
+
workspaceLastPushedWatermarksRef.current.set(taskId, watermark);
|
|
114
|
+
}
|
|
115
|
+
if (pushResult.deleteTaskIds.size > 0) {
|
|
116
|
+
for (const taskId of pushResult.deleteTaskIds) {
|
|
117
|
+
workspaceDeletedTaskIdsRef.current.delete(taskId);
|
|
118
|
+
workspaceLastPushedWatermarksRef.current.delete(taskId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
savePersistedSyncWatermarks(currentWorkspaceId, {
|
|
122
|
+
taskWatermarks: workspaceLastPushedWatermarksRef.current,
|
|
123
|
+
initiativeWatermarks: workspaceLastPushedInitiativeWatermarksRef.current,
|
|
124
|
+
workstreamWatermarks: workspaceLastPushedWorkstreamWatermarksRef.current,
|
|
125
|
+
documentWatermarks: workspaceLastPushedDocumentWatermarksRef.current,
|
|
126
|
+
assetWatermarks: workspaceLastPushedAssetWatermarksRef.current,
|
|
127
|
+
documentReviewSessionWatermarks: workspaceLastPushedDocumentReviewSessionWatermarksRef.current,
|
|
128
|
+
documentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
|
|
129
|
+
annotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
|
|
130
|
+
taskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
|
|
131
|
+
taxonomyStateFingerprint
|
|
132
|
+
});
|
|
133
|
+
if (Array.isArray(pushResult.emittedEventIds) && pushResult.emittedEventIds.length > 0) {
|
|
134
|
+
recordSuppressedEventIds(pushResult.emittedEventIds, {
|
|
135
|
+
queueRef: realtimeSuppressedEventQueueRef,
|
|
136
|
+
maxQueueSize: 2048
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(pushResult.appliedTaskUpserts) && pushResult.appliedTaskUpserts.length > 0) {
|
|
140
|
+
const applyResult = await applyLocalWorkspaceChanges(currentWorkspaceId, pushResult.appliedTaskUpserts.map((entry) => ({
|
|
141
|
+
op: 'upsert',
|
|
142
|
+
archived: entry.archived === true,
|
|
143
|
+
task: entry.task
|
|
144
|
+
})));
|
|
145
|
+
if (applyResult.ok) {
|
|
146
|
+
for (const entry of pushResult.appliedTaskUpserts) {
|
|
147
|
+
const taskId = String(entry?.task?.id || '').trim();
|
|
148
|
+
const watermark = String(entry?.task?.updatedAt || entry?.task?.createdAt || '').trim();
|
|
149
|
+
if (!taskId || !watermark)
|
|
150
|
+
continue;
|
|
151
|
+
workspaceLastPushedWatermarksRef.current.set(taskId, watermark);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
await refreshLocalWorkspaceTaskCollections();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
logSyncDebug('push_local_task_refresh_failed', {
|
|
158
|
+
workspaceId: currentWorkspaceId,
|
|
159
|
+
taskCount: pushResult.appliedTaskUpserts.length
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
logSyncDebug('push_local_task_apply_failed', {
|
|
165
|
+
workspaceId: currentWorkspaceId,
|
|
166
|
+
taskCount: pushResult.appliedTaskUpserts.length,
|
|
167
|
+
status: applyResult.status,
|
|
168
|
+
failures: applyResult.failures
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
clearWorkspaceRetry();
|
|
173
|
+
clearWorkspaceIssue();
|
|
174
|
+
const syncedAt = pushResult.syncedAt || new Date().toISOString();
|
|
175
|
+
workspaceLastPushedSignatureRef.current = buildWorkspaceSyncSignature();
|
|
176
|
+
setWorkspaceLastPushAt(syncedAt);
|
|
177
|
+
setWorkspaceSyncPendingChanges(0);
|
|
178
|
+
setUserGlobalSyncStatus('idle');
|
|
179
|
+
persistWorkspaceSyncPatchSafely({
|
|
180
|
+
phase: 'active',
|
|
181
|
+
lastPushAt: syncedAt,
|
|
182
|
+
lastSyncedAt: syncedAt,
|
|
183
|
+
lastErrorMessage: null
|
|
184
|
+
});
|
|
185
|
+
reportSyncEvent(currentWorkspaceId, {
|
|
186
|
+
eventType: workspaceSyncPhase === 'provision-local' ? 'bootstrap' : 'push',
|
|
187
|
+
status: 'success',
|
|
188
|
+
changeCount: pushResult.changeCount,
|
|
189
|
+
requestMs: pushResult.requestMs
|
|
190
|
+
});
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
const message = 'Workspace sync push failed.';
|
|
195
|
+
setUserGlobalSyncStatus('error');
|
|
196
|
+
setUserGlobalSyncError(message);
|
|
197
|
+
setWorkspaceLastErrorMessage(message);
|
|
198
|
+
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
199
|
+
workspacePendingSignatureRef.current = signature;
|
|
200
|
+
scheduleWorkspaceSyncRetry();
|
|
201
|
+
reportSyncEvent(currentWorkspaceId, {
|
|
202
|
+
eventType: 'push',
|
|
203
|
+
status: 'error',
|
|
204
|
+
errorMessage: `${message} ${String(error?.message || '').trim()}`.trim()
|
|
205
|
+
});
|
|
206
|
+
logSyncDebug('push_failed_exception', {
|
|
207
|
+
workspaceId: currentWorkspaceId,
|
|
208
|
+
elapsedMs: Date.now() - pushStartedAtMs
|
|
209
|
+
});
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
workspacePushInFlightRef.current = false;
|
|
214
|
+
setWorkspaceSyncBusy(workspacePullInFlightRef.current);
|
|
215
|
+
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
216
|
+
const pendingSignature = workspacePendingSignatureRef.current;
|
|
217
|
+
if (pendingSignature
|
|
218
|
+
&& pendingSignature !== workspaceLastPushedSignatureRef.current
|
|
219
|
+
&& !workspacePullInFlightRef.current) {
|
|
220
|
+
workspacePendingSignatureRef.current = '';
|
|
221
|
+
window.setTimeout(() => {
|
|
222
|
+
void pushWorkspaceChangesToCloud(pendingSignature);
|
|
223
|
+
}, 120);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const pullWorkspaceChangesFromCloud = async (options) => {
|
|
228
|
+
if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
|
|
229
|
+
return false;
|
|
230
|
+
if (!isValidWorkspaceKey(currentWorkspaceId))
|
|
231
|
+
return false;
|
|
232
|
+
if (workspaceRepairModeRef.current === true && options?.repairMode !== true)
|
|
233
|
+
return false;
|
|
234
|
+
if (workspaceSyncPhase === 'provision-local')
|
|
235
|
+
return false;
|
|
236
|
+
if (isWorkspaceRetryPending())
|
|
237
|
+
return false;
|
|
238
|
+
if (!isAuthenticated) {
|
|
239
|
+
const authed = await checkAuthSession();
|
|
240
|
+
if (!authed)
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (workspacePullInFlightRef.current || workspacePushInFlightRef.current)
|
|
244
|
+
return false;
|
|
245
|
+
const leaseAcquired = await acquireWorkspaceSyncLease(currentWorkspaceId, 'pull');
|
|
246
|
+
if (!leaseAcquired) {
|
|
247
|
+
reportWorkspaceWindowContention('pull');
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
workspacePullInFlightRef.current = true;
|
|
251
|
+
setWorkspaceSyncBusy(true);
|
|
252
|
+
setUserGlobalSyncStatus('syncing');
|
|
253
|
+
setUserGlobalSyncError(null);
|
|
254
|
+
const pullStartedAtMs = Date.now();
|
|
255
|
+
try {
|
|
256
|
+
const ready = await ensureWorkspaceSyncReady();
|
|
257
|
+
if (!ready)
|
|
258
|
+
return false;
|
|
259
|
+
const bootstrap = workspaceSyncPhase === 'attach-cloud';
|
|
260
|
+
const cursor = bootstrap || options?.forceCursorNull === true ? null : workspacePullCursorRef.current;
|
|
261
|
+
const pullResult = await runWorkspacePullSyncService({
|
|
262
|
+
resolveCloudAuthUrl,
|
|
263
|
+
workspaceId: currentWorkspaceId,
|
|
264
|
+
cursor,
|
|
265
|
+
bootstrap,
|
|
266
|
+
ensureCloudWorkspaceReadyForSync,
|
|
267
|
+
maxPages: 20,
|
|
268
|
+
repairMode: options?.repairMode === true || workspaceRepairModeRef.current === true,
|
|
269
|
+
onPagePulled: () => { bootstrapLastProgressAtRef.current = Date.now(); },
|
|
270
|
+
onChangesApplied: () => { bootstrapLastProgressAtRef.current = Date.now(); }
|
|
271
|
+
});
|
|
272
|
+
if (!pullResult.success) {
|
|
273
|
+
if (pullResult.kind === 'pull_http' && pullResult.status === 401) {
|
|
274
|
+
await handleSyncAuthFailure('pull', 401);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
if (pullResult.kind === 'apply_auth') {
|
|
278
|
+
await handleSyncAuthFailure('apply', 401);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
let message = pullResult.kind === 'apply_payload' || pullResult.kind === 'apply_all_candidates' || pullResult.kind === 'exception'
|
|
282
|
+
? String(pullResult.error || 'Workspace sync apply failed.')
|
|
283
|
+
: `Workspace sync pull failed (${pullResult.status || 0})`;
|
|
284
|
+
if (pullResult.status === 413) {
|
|
285
|
+
message = 'Local sync failed because the downloaded payload is too large. This usually happens when the workspace contains massive attachments.';
|
|
286
|
+
}
|
|
287
|
+
setUserGlobalSyncStatus('error');
|
|
288
|
+
setUserGlobalSyncError(message);
|
|
289
|
+
setWorkspaceLastErrorMessage(message);
|
|
290
|
+
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
291
|
+
reportSyncEvent(currentWorkspaceId, {
|
|
292
|
+
eventType: pullResult.kind?.startsWith('apply') ? 'apply' : 'pull',
|
|
293
|
+
status: 'error',
|
|
294
|
+
statusCode: pullResult.status,
|
|
295
|
+
errorMessage: message,
|
|
296
|
+
requestMs: pullResult.pullRequestMs,
|
|
297
|
+
details: {
|
|
298
|
+
pages: pullResult.pages,
|
|
299
|
+
applyMs: pullResult.applyMs,
|
|
300
|
+
...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
if (pullResult.transient || pullResult.hasTransientApplyFailure || isTransientWorkspaceSyncStatus(pullResult.status)) {
|
|
304
|
+
scheduleWorkspaceSyncRetry(pullResult.retryAfterMs);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
persistWorkspaceSyncPatchSafely({ phase: 'error', lastErrorMessage: message });
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
if (Array.isArray(pullResult.emittedEventIds)) {
|
|
312
|
+
recordSuppressedEventIds(pullResult.emittedEventIds, { ttlMs: 60_000 });
|
|
313
|
+
}
|
|
314
|
+
if (pullResult.pulledDeleteTaskIds.size > 0) {
|
|
315
|
+
setTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
316
|
+
setArchivedTasks((prev) => prev.filter((task) => !pullResult.pulledDeleteTaskIds.has(String(task.id || ''))));
|
|
317
|
+
}
|
|
318
|
+
const decryptFailures = Array.isArray(pullResult.documentDecryptFailures)
|
|
319
|
+
? pullResult.documentDecryptFailures.filter((value) => String(value || '').trim().length > 0)
|
|
320
|
+
: [];
|
|
321
|
+
if (decryptFailures.length > 0) {
|
|
322
|
+
logSyncDebug('pull_document_decrypt_failures', {
|
|
323
|
+
workspaceId: currentWorkspaceId,
|
|
324
|
+
failureCount: decryptFailures.length
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
workspacePullCursorRef.current = pullResult.cursor || null;
|
|
328
|
+
if (pullResult.appliedChanges > 0
|
|
329
|
+
&& (pullResult.pulledActiveUpserts
|
|
330
|
+
|| pullResult.pulledArchivedUpserts
|
|
331
|
+
|| pullResult.pulledTaskEventUpserts)) {
|
|
332
|
+
try {
|
|
333
|
+
await refreshLocalWorkspaceTaskCollections();
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
logSyncDebug('pull_local_refresh_failed', {
|
|
337
|
+
workspaceId: currentWorkspaceId,
|
|
338
|
+
appliedChanges: pullResult.appliedChanges
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
clearWorkspaceRetry();
|
|
343
|
+
clearWorkspaceIssue();
|
|
344
|
+
const syncedAt = pullResult.syncedAt || new Date().toISOString();
|
|
345
|
+
setWorkspaceLastPullAt(syncedAt);
|
|
346
|
+
setWorkspaceSyncPendingChanges(0);
|
|
347
|
+
setUserGlobalSyncStatus('idle');
|
|
348
|
+
if (workspaceSyncPhase === 'attach-cloud') {
|
|
349
|
+
workspaceCaptureAttachBootstrapBaselineRef.current = true;
|
|
350
|
+
workspacePendingSignatureRef.current = '';
|
|
351
|
+
}
|
|
352
|
+
await persistWorkspaceSyncPatchBestEffort({
|
|
353
|
+
phase: 'active',
|
|
354
|
+
pullCursor: workspacePullCursorRef.current,
|
|
355
|
+
lastPullAt: syncedAt,
|
|
356
|
+
lastSyncedAt: syncedAt,
|
|
357
|
+
lastErrorMessage: null
|
|
358
|
+
});
|
|
359
|
+
reportSyncEvent(currentWorkspaceId, {
|
|
360
|
+
eventType: workspaceSyncPhase === 'attach-cloud' ? 'bootstrap' : 'pull',
|
|
361
|
+
status: 'success',
|
|
362
|
+
changeCount: pullResult.appliedChanges,
|
|
363
|
+
requestMs: pullResult.pullRequestMs,
|
|
364
|
+
details: {
|
|
365
|
+
pages: pullResult.pages,
|
|
366
|
+
applyMs: pullResult.applyMs,
|
|
367
|
+
...(pullResult.serverDiagnostics ? { serverPullDiagnostics: pullResult.serverDiagnostics } : {})
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
if (workspaceSyncPhase === 'attach-cloud') {
|
|
371
|
+
await refreshWorkspaceSyncMarkdownDocumentsSnapshot();
|
|
372
|
+
await refreshWorkspaceSyncAssetsSnapshot();
|
|
373
|
+
await refreshWorkspaceSyncDocumentReviewSessionsSnapshot();
|
|
374
|
+
await refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot();
|
|
375
|
+
seedWorkspaceSnapshotPushBaseline();
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
if (!options?.forceCursorNull && !bootstrap && !workspaceStartupFullReconcileRanRef.current) {
|
|
379
|
+
workspaceStartupFullReconcileRanRef.current = true;
|
|
380
|
+
window.setTimeout(() => {
|
|
381
|
+
if (workspaceFullReconcileInFlightRef.current)
|
|
382
|
+
return;
|
|
383
|
+
workspaceFullReconcileInFlightRef.current = true;
|
|
384
|
+
void pullWorkspaceChangesFromCloud({ forceCursorNull: true }).finally(() => {
|
|
385
|
+
workspaceFullReconcileInFlightRef.current = false;
|
|
386
|
+
});
|
|
387
|
+
}, 250);
|
|
388
|
+
}
|
|
389
|
+
const pendingSignature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
|
|
390
|
+
if (pendingSignature && pendingSignature !== workspaceLastPushedSignatureRef.current) {
|
|
391
|
+
workspacePendingSignatureRef.current = '';
|
|
392
|
+
window.setTimeout(() => {
|
|
393
|
+
void pushWorkspaceChangesToCloud(pendingSignature);
|
|
394
|
+
}, 120);
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
const detail = String(error?.message || '').trim();
|
|
400
|
+
const message = detail
|
|
401
|
+
? `Workspace sync pull failed. ${detail}`
|
|
402
|
+
: 'Workspace sync pull failed.';
|
|
403
|
+
setUserGlobalSyncStatus('error');
|
|
404
|
+
setUserGlobalSyncError(message);
|
|
405
|
+
setWorkspaceLastErrorMessage(message);
|
|
406
|
+
setWorkspaceLastErrorAt(new Date().toISOString());
|
|
407
|
+
scheduleWorkspaceSyncRetry();
|
|
408
|
+
reportSyncEvent(currentWorkspaceId, {
|
|
409
|
+
eventType: 'pull',
|
|
410
|
+
status: 'error',
|
|
411
|
+
errorMessage: message,
|
|
412
|
+
requestMs: Date.now() - pullStartedAtMs
|
|
413
|
+
});
|
|
414
|
+
logSyncDebug('pull_failed_exception', {
|
|
415
|
+
workspaceId: currentWorkspaceId,
|
|
416
|
+
elapsedMs: Date.now() - pullStartedAtMs,
|
|
417
|
+
error: detail || null
|
|
418
|
+
});
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
workspacePullInFlightRef.current = false;
|
|
423
|
+
setWorkspaceSyncBusy(workspacePushInFlightRef.current);
|
|
424
|
+
releaseWorkspaceSyncLease(currentWorkspaceId);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
return {
|
|
428
|
+
pushWorkspaceChangesToCloud,
|
|
429
|
+
pullWorkspaceChangesFromCloud
|
|
430
|
+
};
|
|
431
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type MutableRefObject } from 'react';
|
|
2
|
+
interface UseDataVersionRefreshArgs {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
authBlocked: boolean;
|
|
5
|
+
authRequiredForApi: boolean;
|
|
6
|
+
isAuthenticated: boolean;
|
|
7
|
+
canCallProtectedApi: boolean;
|
|
8
|
+
shouldGateProtectedApiCalls: boolean;
|
|
9
|
+
authSessionResolvedRef: MutableRefObject<boolean>;
|
|
10
|
+
authRequiredForApiRef: MutableRefObject<boolean>;
|
|
11
|
+
isAuthenticatedRef: MutableRefObject<boolean>;
|
|
12
|
+
dataVersionRef: MutableRefObject<number | null>;
|
|
13
|
+
refreshTaskCollectionsForDataVersion: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare function useDataVersionRefresh({ isOpen, authBlocked, authRequiredForApi, isAuthenticated, canCallProtectedApi, shouldGateProtectedApiCalls, authSessionResolvedRef, authRequiredForApiRef, isAuthenticatedRef, dataVersionRef, refreshTaskCollectionsForDataVersion }: UseDataVersionRefreshArgs): void;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { shouldSkipDataVersionPolling, shouldSkipDataVersionTick } from '../auth/useAuthGuardPolicy';
|
|
3
|
+
export function useDataVersionRefresh({ isOpen, authBlocked, authRequiredForApi, isAuthenticated, canCallProtectedApi, shouldGateProtectedApiCalls, authSessionResolvedRef, authRequiredForApiRef, isAuthenticatedRef, dataVersionRef, refreshTaskCollectionsForDataVersion }) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
6
|
+
return;
|
|
7
|
+
if (shouldSkipDataVersionPolling({
|
|
8
|
+
isOpen,
|
|
9
|
+
authBlocked,
|
|
10
|
+
authRequiredForApi,
|
|
11
|
+
isAuthenticated,
|
|
12
|
+
canCallProtectedApi
|
|
13
|
+
}))
|
|
14
|
+
return;
|
|
15
|
+
let cancelled = false;
|
|
16
|
+
const checkDataVersion = async () => {
|
|
17
|
+
if (cancelled || document.visibilityState !== 'visible')
|
|
18
|
+
return;
|
|
19
|
+
if (shouldSkipDataVersionTick({
|
|
20
|
+
shouldGateProtectedApiCalls,
|
|
21
|
+
authSessionResolved: authSessionResolvedRef.current,
|
|
22
|
+
authRequiredForApi: authRequiredForApiRef.current,
|
|
23
|
+
isAuthenticated: isAuthenticatedRef.current
|
|
24
|
+
}))
|
|
25
|
+
return;
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch('/api/taskforce/data-version');
|
|
28
|
+
if (!res.ok)
|
|
29
|
+
return;
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
const version = Number(data?.dataVersion);
|
|
32
|
+
if (!Number.isFinite(version))
|
|
33
|
+
return;
|
|
34
|
+
if (dataVersionRef.current === null) {
|
|
35
|
+
dataVersionRef.current = version;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (version !== dataVersionRef.current) {
|
|
39
|
+
dataVersionRef.current = version;
|
|
40
|
+
await refreshTaskCollectionsForDataVersion();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// best effort only
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const onVisibilityChange = () => {
|
|
48
|
+
if (document.visibilityState === 'visible') {
|
|
49
|
+
void checkDataVersion();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const intervalId = window.setInterval(() => { void checkDataVersion(); }, 3000);
|
|
53
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
54
|
+
void checkDataVersion();
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true;
|
|
57
|
+
window.clearInterval(intervalId);
|
|
58
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
59
|
+
};
|
|
60
|
+
}, [
|
|
61
|
+
isOpen,
|
|
62
|
+
authBlocked,
|
|
63
|
+
authRequiredForApi,
|
|
64
|
+
isAuthenticated,
|
|
65
|
+
canCallProtectedApi,
|
|
66
|
+
shouldGateProtectedApiCalls,
|
|
67
|
+
authSessionResolvedRef,
|
|
68
|
+
authRequiredForApiRef,
|
|
69
|
+
isAuthenticatedRef,
|
|
70
|
+
dataVersionRef,
|
|
71
|
+
refreshTaskCollectionsForDataVersion
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Environment } from '../../types';
|
|
2
2
|
interface UseResourceExportInput {
|
|
3
3
|
storagePath: string;
|
|
4
|
+
shouldDeferProtectedApiCalls: boolean;
|
|
5
|
+
shouldBlockProtectedApiCalls: boolean;
|
|
4
6
|
}
|
|
5
|
-
export declare function useResourceExport({ storagePath }: UseResourceExportInput): {
|
|
7
|
+
export declare function useResourceExport({ storagePath, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }: UseResourceExportInput): {
|
|
6
8
|
exportEnvironment: string;
|
|
7
9
|
setExportEnvironment: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
8
10
|
availableEnvironments: Environment[];
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
export function useResourceExport({ storagePath }) {
|
|
2
|
+
export function useResourceExport({ storagePath, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }) {
|
|
3
3
|
const [exportEnvironment, setExportEnvironment] = useState('antigravity');
|
|
4
4
|
const [availableEnvironments, setAvailableEnvironments] = useState([]);
|
|
5
5
|
const [exportWorkflowsPath, setExportWorkflowsPath] = useState('.agent/workflows');
|
|
6
6
|
const [exportingResource, setExportingResource] = useState(null);
|
|
7
7
|
const [exportResult, setExportResult] = useState(null);
|
|
8
8
|
const exportResultTimerRef = useRef(null);
|
|
9
|
+
const shouldSkipProtectedApiCalls = shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls;
|
|
9
10
|
useEffect(() => {
|
|
10
11
|
const env = availableEnvironments.find((item) => item.id === exportEnvironment);
|
|
11
12
|
if (env) {
|
|
@@ -23,13 +24,15 @@ export function useResourceExport({ storagePath }) {
|
|
|
23
24
|
}, 5000);
|
|
24
25
|
}, []);
|
|
25
26
|
const loadAvailableEnvironments = useCallback(async () => {
|
|
27
|
+
if (shouldSkipProtectedApiCalls)
|
|
28
|
+
return;
|
|
26
29
|
const envRes = await fetch('/api/taskforce/environments');
|
|
27
30
|
if (!envRes.ok)
|
|
28
31
|
return;
|
|
29
32
|
const envData = await envRes.json().catch(() => ({}));
|
|
30
33
|
if (envData.environments)
|
|
31
34
|
setAvailableEnvironments(envData.environments);
|
|
32
|
-
}, []);
|
|
35
|
+
}, [shouldSkipProtectedApiCalls]);
|
|
33
36
|
const handleExportWorkflows = useCallback(async (environment, selected) => {
|
|
34
37
|
setExportingResource('workflows');
|
|
35
38
|
setExportResult(null);
|
|
@@ -2,8 +2,9 @@ import { useCallback, useState } from 'react';
|
|
|
2
2
|
export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls }) {
|
|
3
3
|
const [availableWorkflows, setAvailableWorkflows] = useState([]);
|
|
4
4
|
const [initiativeTemplates, setInitiativeTemplates] = useState([]);
|
|
5
|
+
const shouldSkipProtectedApiCalls = shouldDeferProtectedApiCalls || shouldBlockProtectedApiCalls;
|
|
5
6
|
const fetchWorkflows = useCallback(async () => {
|
|
6
|
-
if (
|
|
7
|
+
if (shouldSkipProtectedApiCalls)
|
|
7
8
|
return;
|
|
8
9
|
try {
|
|
9
10
|
const res = await fetch('/api/taskforce/workflow-templates');
|
|
@@ -15,8 +16,10 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
|
|
|
15
16
|
catch {
|
|
16
17
|
// Ignore error
|
|
17
18
|
}
|
|
18
|
-
}, [
|
|
19
|
+
}, [shouldSkipProtectedApiCalls]);
|
|
19
20
|
const fetchInitiativeTemplates = useCallback(async () => {
|
|
21
|
+
if (shouldSkipProtectedApiCalls)
|
|
22
|
+
return [];
|
|
20
23
|
try {
|
|
21
24
|
const res = await fetch('/api/taskforce/initiative-templates');
|
|
22
25
|
if (!res.ok)
|
|
@@ -29,9 +32,9 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
|
|
|
29
32
|
catch {
|
|
30
33
|
return [];
|
|
31
34
|
}
|
|
32
|
-
}, []);
|
|
35
|
+
}, [shouldSkipProtectedApiCalls]);
|
|
33
36
|
const fetchWorkflowTemplate = useCallback(async (name) => {
|
|
34
|
-
if (!name)
|
|
37
|
+
if (!name || shouldSkipProtectedApiCalls)
|
|
35
38
|
return null;
|
|
36
39
|
const paths = [
|
|
37
40
|
`/api/taskforce/workflow-template/${encodeURIComponent(name)}`,
|
|
@@ -51,8 +54,10 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
|
|
|
51
54
|
catch {
|
|
52
55
|
return null;
|
|
53
56
|
}
|
|
54
|
-
}, []);
|
|
57
|
+
}, [shouldSkipProtectedApiCalls]);
|
|
55
58
|
const fetchWorkflowOverrideNames = useCallback(async () => {
|
|
59
|
+
if (shouldSkipProtectedApiCalls)
|
|
60
|
+
return [];
|
|
56
61
|
try {
|
|
57
62
|
const res = await fetch('/api/taskforce/workflow-editor/overrides');
|
|
58
63
|
if (!res.ok)
|
|
@@ -63,7 +68,7 @@ export function useWorkflowTemplates({ shouldDeferProtectedApiCalls, shouldBlock
|
|
|
63
68
|
catch {
|
|
64
69
|
return [];
|
|
65
70
|
}
|
|
66
|
-
}, []);
|
|
71
|
+
}, [shouldSkipProtectedApiCalls]);
|
|
67
72
|
const saveWorkflowTemplateDraft = useCallback(async (name, draft) => {
|
|
68
73
|
try {
|
|
69
74
|
const res = await fetch('/api/taskforce/workflow-editor/save', {
|
|
@@ -168,7 +168,8 @@ export function useRealtimeSync(input) {
|
|
|
168
168
|
if (event.type === 'taskforce:update') {
|
|
169
169
|
queueSignal({
|
|
170
170
|
type: 'taskforce:update',
|
|
171
|
-
workspaceId: normalizedWorkspaceId
|
|
171
|
+
workspaceId: normalizedWorkspaceId,
|
|
172
|
+
eventId: typeof event.eventId === 'string' ? event.eventId.trim() : undefined
|
|
172
173
|
});
|
|
173
174
|
return;
|
|
174
175
|
}
|
|
@@ -10,6 +10,10 @@ describe('normalizeWorkspaceSyncAiProfileSnapshotEntry', () => {
|
|
|
10
10
|
username: 'codex',
|
|
11
11
|
icon: 'Bot',
|
|
12
12
|
color: '#0ea5e9',
|
|
13
|
+
description: 'Terminal-first coding agent',
|
|
14
|
+
role: 'Implementer',
|
|
15
|
+
provider: 'OpenAI',
|
|
16
|
+
model: 'gpt-5.4',
|
|
13
17
|
surface_type: 'coding_tool',
|
|
14
18
|
provider_metadata: { source: 'test' },
|
|
15
19
|
created_at: '2026-03-15T18:00:00.000Z',
|
|
@@ -24,6 +28,10 @@ describe('normalizeWorkspaceSyncAiProfileSnapshotEntry', () => {
|
|
|
24
28
|
username: 'codex',
|
|
25
29
|
icon: 'Bot',
|
|
26
30
|
color: '#0ea5e9',
|
|
31
|
+
description: 'Terminal-first coding agent',
|
|
32
|
+
role: 'Implementer',
|
|
33
|
+
provider: 'OpenAI',
|
|
34
|
+
model: 'gpt-5.4',
|
|
27
35
|
surfaceType: 'coding_tool',
|
|
28
36
|
providerMetadata: { source: 'test' },
|
|
29
37
|
createdAt: '2026-03-15T18:00:00.000Z',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|