@taskforcehq/taskforce 0.3.302 → 0.3.304
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -5,15 +5,14 @@ import { WorkspaceAssetStore, } from '../../storage/workspaceAssetStore.js';
|
|
|
5
5
|
import { resolveSessionCookieAttributesForRequest } from '../auth.js';
|
|
6
6
|
import { decryptDocument, ENCRYPTION_PREFIX } from '../../sync/encryptionService.js';
|
|
7
7
|
import { isAiProfileSurfaceType } from '../../shared/aiProfileSurfaceType.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { normalizeBinaryAssetDeleteSyncPayload, normalizeBinaryAssetSyncPayload, normalizeDocumentDeleteSyncPayload, normalizeDocumentSyncPayload, serializeBinaryAssetSyncPayload, serializeDocumentSyncPayload } from '../../sync/contentSyncPayload.js';
|
|
8
|
+
import { serializeTaskForSync, } from '../../sync/taskSyncPayload.js';
|
|
9
|
+
import { serializeBinaryAssetSyncPayload, serializeDocumentSyncPayload, } from '../../sync/contentSyncPayload.js';
|
|
11
10
|
import { createContentSyncState } from '../../sync/contentSyncState.js';
|
|
12
|
-
import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload,
|
|
11
|
+
import { normalizeAiProfileSyncPayload, normalizeAnnotatedAttachmentSessionSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, } from '../../sync/collaborationSyncPayload.js';
|
|
13
12
|
import { createCollaborationSyncState } from '../../sync/collaborationSyncState.js';
|
|
14
13
|
import { buildWorkspacePullFeed } from '../../sync/workspacePullFeed.js';
|
|
15
14
|
import { normalizeInitiativeSyncPayload, normalizeTaxonomySyncPayload, normalizeWorkstreamSyncPayload, } from '../../sync/planningSyncPayload.js';
|
|
16
|
-
import { applyAiProfileAndMemberSyncChanges, applyCollaborationSyncChanges, applyContentSyncChanges, applyPlanningSyncChanges,
|
|
15
|
+
import { applyAiProfileAndMemberSyncChanges, applyCollaborationSyncChanges, applyContentSyncChanges, applyPlanningSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskActivitySyncChanges, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from '../../sync/syncApplyHandlers.js';
|
|
17
16
|
import { applyWorkspaceRepair } from '../../sync/workspaceRepair.js';
|
|
18
17
|
function decodeWorkspaceSyncCursor(raw) {
|
|
19
18
|
const value = String(raw || '').trim();
|
|
@@ -60,6 +59,40 @@ function normalizeIncomingAiProfileSurfaceType(value, context) {
|
|
|
60
59
|
}
|
|
61
60
|
return normalized;
|
|
62
61
|
}
|
|
62
|
+
function normalizeTaskEventSyncPayload(raw, workspaceId, sourceWatermark) {
|
|
63
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
64
|
+
return null;
|
|
65
|
+
const source = raw;
|
|
66
|
+
const id = String(source.id || '').trim();
|
|
67
|
+
const taskId = String(source.taskId || source.task_id || '').trim();
|
|
68
|
+
const action = String(source.action || '').trim();
|
|
69
|
+
const actor = String(source.actor || 'system').trim() || 'system';
|
|
70
|
+
const createdAt = String(source.createdAt || source.created_at || sourceWatermark || '').trim();
|
|
71
|
+
if (!id || !taskId || !action || !Number.isFinite(Date.parse(createdAt)))
|
|
72
|
+
return null;
|
|
73
|
+
const actorType = source.actorType === 'ai' || source.actorType === 'human' || source.actorType === 'system'
|
|
74
|
+
? source.actorType
|
|
75
|
+
: source.actor_type === 'ai' || source.actor_type === 'human' || source.actor_type === 'system'
|
|
76
|
+
? source.actor_type
|
|
77
|
+
: actor.startsWith('ai-profile-')
|
|
78
|
+
? 'ai'
|
|
79
|
+
: actor === 'system'
|
|
80
|
+
? 'system'
|
|
81
|
+
: 'human';
|
|
82
|
+
const details = source.details && typeof source.details === 'object' && !Array.isArray(source.details)
|
|
83
|
+
? source.details
|
|
84
|
+
: {};
|
|
85
|
+
return {
|
|
86
|
+
id,
|
|
87
|
+
taskId,
|
|
88
|
+
workspaceId,
|
|
89
|
+
action,
|
|
90
|
+
actor,
|
|
91
|
+
actorType,
|
|
92
|
+
details,
|
|
93
|
+
createdAt
|
|
94
|
+
};
|
|
95
|
+
}
|
|
63
96
|
export function registerSyncRoutes(deps) {
|
|
64
97
|
const { addRoute, core, context, auditAuthAction, resolveErrorStatusCode, requestWorkspaceId, resolveRequestAccess, isReservedWorkspaceId, createSessionTokenCookie } = deps;
|
|
65
98
|
const readPushIdempotency = (workspaceId, key) => {
|
|
@@ -190,32 +223,7 @@ export function registerSyncRoutes(deps) {
|
|
|
190
223
|
workspaceDocumentSnapshotCache,
|
|
191
224
|
});
|
|
192
225
|
const collaborationSyncState = createCollaborationSyncState({ core });
|
|
193
|
-
const hasTaskReferenceNumberDrift = (workspaceId, task) => {
|
|
194
|
-
const taskId = String(task?.id || '').trim();
|
|
195
|
-
const incomingReferenceNumber = Number.isFinite(Number(task?.referenceNumber))
|
|
196
|
-
? Math.max(1, Math.floor(Number(task?.referenceNumber)))
|
|
197
|
-
: null;
|
|
198
|
-
if (!incomingReferenceNumber)
|
|
199
|
-
return false;
|
|
200
|
-
const existingTask = core.resolveTaskIdentifier(`T-${incomingReferenceNumber}`, workspaceId);
|
|
201
|
-
if (!existingTask || existingTask.id === taskId)
|
|
202
|
-
return false;
|
|
203
|
-
recordSyncDiagnostic(workspaceId, 'apply', 'Detected task reference number mismatch during normal sync.', {
|
|
204
|
-
source: 'tasks.referenceNumber.mismatch',
|
|
205
|
-
taskId: taskId || null,
|
|
206
|
-
taskTitle: typeof task?.title === 'string' ? task.title.trim() || null : null,
|
|
207
|
-
existingTaskId: existingTask.id,
|
|
208
|
-
existingTaskTitle: existingTask.title || null,
|
|
209
|
-
referenceLabel: `T-${incomingReferenceNumber}`,
|
|
210
|
-
existingReferenceNumber: existingTask.referenceNumber ?? incomingReferenceNumber,
|
|
211
|
-
incomingReferenceNumber
|
|
212
|
-
});
|
|
213
|
-
return true;
|
|
214
|
-
};
|
|
215
226
|
const dispatchWorkspaceSyncChanges = async (workspaceId, incomingChanges, options) => {
|
|
216
|
-
const tasks = [];
|
|
217
|
-
const archived = [];
|
|
218
|
-
const incomingTaskDeletes = new Map();
|
|
219
227
|
const emittedEventIds = new Set();
|
|
220
228
|
const unsubscribeRealtime = core.onRealtimeMutation((event) => {
|
|
221
229
|
const eventWorkspaceId = String(event?.workspaceId || '').trim();
|
|
@@ -225,224 +233,63 @@ export function registerSyncRoutes(deps) {
|
|
|
225
233
|
if (eventId)
|
|
226
234
|
emittedEventIds.add(eventId);
|
|
227
235
|
});
|
|
228
|
-
let incomingTaxonomies = null;
|
|
229
|
-
let incomingTaxonomyState = null;
|
|
230
|
-
let incomingTaxonomiesUpdatedAt = '';
|
|
231
|
-
const incomingInitiatives = new Map();
|
|
232
|
-
const incomingWorkstreams = new Map();
|
|
233
|
-
const incomingAiProfiles = new Map();
|
|
234
|
-
const incomingDocuments = [];
|
|
235
|
-
const incomingDocumentDeletes = new Map();
|
|
236
|
-
const incomingDocumentReviewSessions = new Map();
|
|
237
|
-
const incomingDocumentReviewSessionDeletes = new Map();
|
|
238
|
-
const incomingDocumentReviewComments = new Map();
|
|
239
|
-
const incomingDocumentReviewCommentDeletes = new Map();
|
|
240
|
-
const incomingAnnotatedAttachmentSessions = new Map();
|
|
241
|
-
const incomingAnnotatedAttachmentSessionDeletes = new Map();
|
|
242
|
-
const incomingAssets = [];
|
|
243
|
-
const incomingAssetDeletes = new Map();
|
|
244
236
|
try {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (taxonomy?.updatedAt)
|
|
255
|
-
incomingTaxonomiesUpdatedAt = taxonomy.updatedAt;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (op === 'initiative-upsert') {
|
|
259
|
-
const initiative = normalizeInitiativeSyncPayload(raw.initiative, String(raw.updatedAt || raw.watermark || '').trim());
|
|
260
|
-
if (initiative)
|
|
261
|
-
incomingInitiatives.set(initiative.id, initiative);
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (op === 'workstream-upsert') {
|
|
265
|
-
const workstream = normalizeWorkstreamSyncPayload(raw.workstream, String(raw.updatedAt || raw.watermark || '').trim());
|
|
266
|
-
if (workstream)
|
|
267
|
-
incomingWorkstreams.set(workstream.id, workstream);
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
if (op === 'ai-profile-upsert') {
|
|
271
|
-
const profile = normalizeAiProfileSyncPayload(raw.profile, workspaceId, normalizeIncomingAiProfileSurfaceType, 'apply.aiProfileUpsert');
|
|
272
|
-
const profileId = String(profile?.id || '').trim();
|
|
273
|
-
if (profileId && profile) {
|
|
274
|
-
const profileWorkspaceId = String(profile.workspaceId || '').trim();
|
|
275
|
-
if (profileWorkspaceId && profileWorkspaceId !== workspaceId) {
|
|
276
|
-
recordSyncDiagnostic(workspaceId, 'apply', `Rejected ai-profile-upsert for profile ${profileId}: workspace mismatch (expected ${workspaceId}, got ${profileWorkspaceId}).`, {
|
|
277
|
-
source: 'apply.aiProfileWorkspaceMismatch',
|
|
278
|
-
workspaceId,
|
|
279
|
-
profileId,
|
|
280
|
-
expectedWorkspaceId: workspaceId,
|
|
281
|
-
receivedWorkspaceId: profileWorkspaceId
|
|
282
|
-
});
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
incomingAiProfiles.set(profileId, {
|
|
286
|
-
...profile,
|
|
287
|
-
workspaceId,
|
|
288
|
-
createdAt: profile.createdAt || String(raw.updatedAt || raw.watermark || '').trim(),
|
|
289
|
-
updatedAt: profile.updatedAt || String(raw.updatedAt || raw.watermark || '').trim(),
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (op === 'document-upsert') {
|
|
295
|
-
const document = normalizeDocumentSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
|
|
296
|
-
if (document)
|
|
297
|
-
incomingDocuments.push(document);
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
if (op === 'document-delete') {
|
|
301
|
-
const deletedDocument = normalizeDocumentDeleteSyncPayload(raw, normalizeWorkspaceSyncDocumentPath);
|
|
302
|
-
if (deletedDocument)
|
|
303
|
-
incomingDocumentDeletes.set(deletedDocument.path, deletedDocument.deletedAt);
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
if (op === 'asset-upsert') {
|
|
307
|
-
const asset = normalizeBinaryAssetSyncPayload(raw, (rawPath) => normalizeTaskDataRelativePath(String(rawPath || '').trim()));
|
|
308
|
-
if (asset)
|
|
309
|
-
incomingAssets.push(asset);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (op === 'asset-delete') {
|
|
313
|
-
const deletedAsset = normalizeBinaryAssetDeleteSyncPayload(raw, (rawPath) => normalizeTaskDataRelativePath(String(rawPath || '').trim()));
|
|
314
|
-
if (deletedAsset)
|
|
315
|
-
incomingAssetDeletes.set(deletedAsset.path, deletedAsset.deletedAt);
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
if (op === 'document-review-session-upsert') {
|
|
319
|
-
const session = normalizeDocumentReviewSessionSyncPayload(raw.session, String(raw.updatedAt || raw.watermark || '').trim());
|
|
320
|
-
if (session)
|
|
321
|
-
incomingDocumentReviewSessions.set(session.id, session);
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (op === 'document-review-session-delete') {
|
|
325
|
-
const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
|
|
326
|
-
if (deletedSession)
|
|
327
|
-
incomingDocumentReviewSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (op === 'document-review-comment-upsert') {
|
|
331
|
-
const comment = normalizeDocumentReviewCommentSyncPayload(raw.comment, { sourceWatermark: String(raw.updatedAt || raw.watermark || '').trim() });
|
|
332
|
-
if (comment)
|
|
333
|
-
incomingDocumentReviewComments.set(comment.id, comment);
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (op === 'document-review-comment-delete') {
|
|
337
|
-
const deletedComment = normalizeDeleteSyncPayload(raw, 'commentId');
|
|
338
|
-
if (deletedComment)
|
|
339
|
-
incomingDocumentReviewCommentDeletes.set(deletedComment.id, deletedComment.deletedAt);
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
if (op === 'annotated-attachment-session-upsert') {
|
|
343
|
-
const session = normalizeAnnotatedAttachmentSessionSyncPayload(raw.session, workspaceId, String(raw.updatedAt || raw.watermark || '').trim());
|
|
344
|
-
if (session)
|
|
345
|
-
incomingAnnotatedAttachmentSessions.set(session.id, session);
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
if (op === 'annotated-attachment-session-delete') {
|
|
349
|
-
const deletedSession = normalizeDeleteSyncPayload(raw, 'sessionId');
|
|
350
|
-
if (deletedSession)
|
|
351
|
-
incomingAnnotatedAttachmentSessionDeletes.set(deletedSession.id, deletedSession.deletedAt);
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
if (op !== 'delete')
|
|
355
|
-
continue;
|
|
356
|
-
const task = normalizeTaskSyncPayload(raw.task);
|
|
357
|
-
const deleteTaskId = String(raw.taskId || raw.id || task?.id || '').trim();
|
|
358
|
-
if (deleteTaskId) {
|
|
359
|
-
incomingTaskDeletes.set(deleteTaskId, String(raw.deletedAt || raw.watermark || new Date().toISOString()).trim());
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
for (const change of incomingChanges) {
|
|
363
|
-
const raw = change;
|
|
364
|
-
const op = String(raw.op || '').trim().toLowerCase();
|
|
365
|
-
if (op === 'delete'
|
|
366
|
-
|| op === 'initiative-upsert'
|
|
367
|
-
|| op === 'workstream-upsert'
|
|
368
|
-
|| op === 'document-upsert'
|
|
369
|
-
|| op === 'document-delete'
|
|
370
|
-
|| op === 'asset-upsert'
|
|
371
|
-
|| op === 'asset-delete'
|
|
372
|
-
|| op === 'document-review-session-upsert'
|
|
373
|
-
|| op === 'document-review-session-delete'
|
|
374
|
-
|| op === 'document-review-comment-upsert'
|
|
375
|
-
|| op === 'document-review-comment-delete'
|
|
376
|
-
|| op === 'annotated-attachment-session-upsert'
|
|
377
|
-
|| op === 'annotated-attachment-session-delete')
|
|
378
|
-
continue;
|
|
379
|
-
const task = normalizeTaskSyncPayload(raw.task);
|
|
380
|
-
if (!task)
|
|
381
|
-
continue;
|
|
382
|
-
const taskId = String(task.id || '').trim();
|
|
383
|
-
hasTaskReferenceNumberDrift(workspaceId, task);
|
|
384
|
-
const deleteWatermark = taskId ? String(incomingTaskDeletes.get(taskId) || '').trim() : '';
|
|
385
|
-
if (taskId && deleteWatermark) {
|
|
386
|
-
const compareAgainstDelete = resolveSyncConflictOrdering(deleteWatermark, String(task.updatedAt || task.createdAt || '').trim(), `delete:${taskId}`, getTaskMutationTieBreak(task, raw.archived === true || task?.isArchived === true));
|
|
387
|
-
if (compareAgainstDelete >= 0)
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
const markedArchived = raw.archived === true || task?.isArchived === true;
|
|
391
|
-
if (markedArchived)
|
|
392
|
-
archived.push(task);
|
|
393
|
-
else
|
|
394
|
-
tasks.push(task);
|
|
395
|
-
}
|
|
396
|
-
for (const [taskId, deleteWatermark] of incomingTaskDeletes.entries()) {
|
|
397
|
-
try {
|
|
398
|
-
const existingTask = core.getTask(taskId, workspaceId);
|
|
399
|
-
if (existingTask) {
|
|
400
|
-
const compareAgainstExisting = resolveSyncConflictOrdering(deleteWatermark, getTaskMutationWatermark(existingTask), `delete:${taskId}`, getTaskMutationTieBreak(existingTask, Boolean(existingTask.isArchived)));
|
|
401
|
-
if (compareAgainstExisting < 0)
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
core.deleteTaskForSync(taskId, workspaceId, { persistBackup: false });
|
|
405
|
-
}
|
|
406
|
-
catch (error) {
|
|
407
|
-
recordSyncDiagnostic(workspaceId, 'apply', 'Failed to delete task during sync apply.', {
|
|
408
|
-
source: 'apply.deleteTask',
|
|
409
|
-
taskId,
|
|
410
|
-
error: String(error?.message || error || '')
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
}
|
|
237
|
+
const applyModel = classifyWorkspaceSyncChanges({
|
|
238
|
+
workspaceId,
|
|
239
|
+
incomingChanges,
|
|
240
|
+
normalizeWorkspaceSyncDocumentPath,
|
|
241
|
+
normalizeTaskAssetPath: (rawPath) => normalizeTaskDataRelativePath(String(rawPath || '').trim()),
|
|
242
|
+
normalizeIncomingAiProfileSurfaceType,
|
|
243
|
+
normalizeTaskEventSyncPayload,
|
|
244
|
+
recordSyncDiagnostic,
|
|
245
|
+
});
|
|
414
246
|
applyPlanningSyncChanges({
|
|
415
247
|
core,
|
|
416
248
|
workspaceId,
|
|
417
|
-
incomingInitiatives,
|
|
418
|
-
incomingWorkstreams,
|
|
419
|
-
incomingTaxonomies,
|
|
420
|
-
incomingTaxonomyState,
|
|
421
|
-
incomingTaxonomiesUpdatedAt,
|
|
249
|
+
incomingInitiatives: applyModel.planning.incomingInitiatives,
|
|
250
|
+
incomingWorkstreams: applyModel.planning.incomingWorkstreams,
|
|
251
|
+
incomingTaxonomies: applyModel.planning.incomingTaxonomies,
|
|
252
|
+
incomingTaxonomyState: applyModel.planning.incomingTaxonomyState,
|
|
253
|
+
incomingTaxonomiesUpdatedAt: applyModel.planning.incomingTaxonomiesUpdatedAt,
|
|
422
254
|
workspaceTaxonomySyncMeta,
|
|
423
255
|
recordSyncDiagnostic,
|
|
424
256
|
});
|
|
425
257
|
applyAiProfileAndMemberSyncChanges({
|
|
426
258
|
core,
|
|
427
259
|
workspaceId,
|
|
428
|
-
incomingAiProfiles,
|
|
260
|
+
incomingAiProfiles: applyModel.profiles.incomingAiProfiles,
|
|
429
261
|
workspaceMembers: options?.workspaceMembers,
|
|
430
262
|
recordSyncDiagnostic,
|
|
431
263
|
assertSyncedAiProfilesPersisted,
|
|
432
264
|
createSyncRouteError,
|
|
433
265
|
});
|
|
434
|
-
|
|
266
|
+
const preparedTaskSnapshot = prepareTaskSyncSnapshotApply({
|
|
267
|
+
core,
|
|
268
|
+
workspaceId,
|
|
269
|
+
taskMutations: applyModel.taskMutations,
|
|
270
|
+
recordSyncDiagnostic,
|
|
271
|
+
});
|
|
272
|
+
validateTaskActorAssignments(workspaceId, preparedTaskSnapshot.tasks, preparedTaskSnapshot.archived);
|
|
435
273
|
await applyContentSyncChanges({
|
|
436
274
|
core,
|
|
437
275
|
workspaceId,
|
|
438
|
-
incomingDocuments,
|
|
439
|
-
incomingDocumentDeletes,
|
|
440
|
-
incomingAssets,
|
|
441
|
-
incomingAssetDeletes,
|
|
276
|
+
incomingDocuments: applyModel.content.incomingDocuments,
|
|
277
|
+
incomingDocumentDeletes: applyModel.content.incomingDocumentDeletes,
|
|
278
|
+
incomingAssets: applyModel.content.incomingAssets,
|
|
279
|
+
incomingAssetDeletes: applyModel.content.incomingAssetDeletes,
|
|
442
280
|
basePath: core.getPaths().basePath,
|
|
443
281
|
hasObjectStorageClient: () => Boolean(getObjectStorageClient()),
|
|
444
282
|
hasDocumentSyncMetadataDrift: contentSyncState.hasDocumentSyncMetadataDrift,
|
|
445
283
|
hasBinaryAssetSyncMetadataDrift: contentSyncState.hasBinaryAssetSyncMetadataDrift,
|
|
284
|
+
getExistingDocument: (docPath, targetWorkspaceId) => {
|
|
285
|
+
const asset = workspaceAssetStore?.getByStorageKey(docPath, targetWorkspaceId) || null;
|
|
286
|
+
return asset
|
|
287
|
+
? {
|
|
288
|
+
updatedAt: asset.updatedAt,
|
|
289
|
+
contentHash: asset.contentSha256
|
|
290
|
+
}
|
|
291
|
+
: null;
|
|
292
|
+
},
|
|
446
293
|
writeWorkspaceSyncDocument: contentSyncState.writeWorkspaceSyncDocument,
|
|
447
294
|
deleteWorkspaceSyncDocument: contentSyncState.deleteWorkspaceSyncDocument,
|
|
448
295
|
writeWorkspaceSyncBinaryAsset: contentSyncState.writeWorkspaceSyncBinaryAsset,
|
|
@@ -454,20 +301,34 @@ export function registerSyncRoutes(deps) {
|
|
|
454
301
|
applyCollaborationSyncChanges({
|
|
455
302
|
core,
|
|
456
303
|
workspaceId,
|
|
457
|
-
incomingDocumentReviewSessions,
|
|
458
|
-
incomingDocumentReviewSessionDeletes,
|
|
459
|
-
incomingDocumentReviewComments,
|
|
460
|
-
incomingDocumentReviewCommentDeletes,
|
|
461
|
-
incomingAnnotatedAttachmentSessions,
|
|
462
|
-
incomingAnnotatedAttachmentSessionDeletes,
|
|
304
|
+
incomingDocumentReviewSessions: applyModel.collaboration.incomingDocumentReviewSessions,
|
|
305
|
+
incomingDocumentReviewSessionDeletes: applyModel.collaboration.incomingDocumentReviewSessionDeletes,
|
|
306
|
+
incomingDocumentReviewComments: applyModel.collaboration.incomingDocumentReviewComments,
|
|
307
|
+
incomingDocumentReviewCommentDeletes: applyModel.collaboration.incomingDocumentReviewCommentDeletes,
|
|
308
|
+
incomingAnnotatedAttachmentSessions: applyModel.collaboration.incomingAnnotatedAttachmentSessions,
|
|
309
|
+
incomingAnnotatedAttachmentSessionDeletes: applyModel.collaboration.incomingAnnotatedAttachmentSessionDeletes,
|
|
463
310
|
recordSyncDiagnostic,
|
|
464
311
|
createSyncRouteError,
|
|
465
312
|
});
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
313
|
+
const deleteCount = applyTaskSyncDeletes({
|
|
314
|
+
core,
|
|
315
|
+
workspaceId,
|
|
316
|
+
taskMutations: applyModel.taskMutations,
|
|
317
|
+
recordSyncDiagnostic,
|
|
318
|
+
});
|
|
319
|
+
const result = applyPreparedTaskSyncSnapshot({
|
|
320
|
+
core,
|
|
321
|
+
workspaceId,
|
|
322
|
+
prepared: preparedTaskSnapshot,
|
|
323
|
+
});
|
|
324
|
+
applyTaskActivitySyncChanges({
|
|
325
|
+
core,
|
|
326
|
+
workspaceId,
|
|
327
|
+
incomingTaskEvents: applyModel.taskActivity.incomingTaskEvents,
|
|
328
|
+
recordSyncDiagnostic,
|
|
329
|
+
createSyncRouteError,
|
|
330
|
+
});
|
|
331
|
+
return { result, deleteCount, emittedEventIds: Array.from(emittedEventIds) };
|
|
471
332
|
}
|
|
472
333
|
finally {
|
|
473
334
|
unsubscribeRealtime();
|
|
@@ -771,10 +632,18 @@ export function registerSyncRoutes(deps) {
|
|
|
771
632
|
})),
|
|
772
633
|
...incomingDocuments
|
|
773
634
|
.map((doc) => serializeDocumentSyncPayload(doc))
|
|
774
|
-
.filter((doc) => Boolean(doc && doc.path.length > 0))
|
|
635
|
+
.filter((doc) => Boolean(doc && doc.path.length > 0))
|
|
636
|
+
.map((doc) => ({
|
|
637
|
+
op: 'document-upsert',
|
|
638
|
+
...doc
|
|
639
|
+
})),
|
|
775
640
|
...incomingAssets
|
|
776
641
|
.map((asset) => serializeBinaryAssetSyncPayload(asset))
|
|
777
|
-
.filter((asset) => Boolean(asset && asset.path.length > 0 && asset.contentBase64.length > 0))
|
|
642
|
+
.filter((asset) => Boolean(asset && asset.path.length > 0 && asset.contentBase64.length > 0))
|
|
643
|
+
.map((asset) => ({
|
|
644
|
+
op: 'asset-upsert',
|
|
645
|
+
...asset
|
|
646
|
+
})),
|
|
778
647
|
...incomingDocumentReviewSessions
|
|
779
648
|
.map((session) => normalizeDocumentReviewSessionSyncPayload(session))
|
|
780
649
|
.filter((session) => Boolean(session && session.id.length > 0 && session.assetId.length > 0))
|
|
@@ -1060,7 +929,7 @@ export function registerSyncRoutes(deps) {
|
|
|
1060
929
|
const pageSize = Number.isFinite(requestedLimit) && requestedLimit > 0
|
|
1061
930
|
? Math.max(1, Math.min(500, Math.floor(requestedLimit)))
|
|
1062
931
|
: 200;
|
|
1063
|
-
const { workspaceMembers, changes, nextCursor, hasMore } = await buildWorkspacePullFeed({
|
|
932
|
+
const { workspaceMembers, changes, nextCursor, hasMore, diagnostics } = await buildWorkspacePullFeed({
|
|
1064
933
|
core,
|
|
1065
934
|
workspaceId: targetWorkspaceId,
|
|
1066
935
|
cursor,
|
|
@@ -1076,12 +945,14 @@ export function registerSyncRoutes(deps) {
|
|
|
1076
945
|
workspaceMembers,
|
|
1077
946
|
changes,
|
|
1078
947
|
nextCursor,
|
|
1079
|
-
hasMore
|
|
948
|
+
hasMore,
|
|
949
|
+
diagnostics
|
|
1080
950
|
}));
|
|
1081
951
|
auditAuthAction(req, 'sync-workspace-pull-success', {
|
|
1082
952
|
workspaceId: targetWorkspaceId,
|
|
1083
953
|
returnedChanges: changes.length,
|
|
1084
|
-
hasMore
|
|
954
|
+
hasMore,
|
|
955
|
+
diagnostics
|
|
1085
956
|
});
|
|
1086
957
|
});
|
|
1087
958
|
// POST /api/taskforce/sync/workspace/apply-local - Apply pulled cloud deltas into local workspace store
|
|
@@ -1247,6 +1118,10 @@ export function registerSyncRoutes(deps) {
|
|
|
1247
1118
|
}
|
|
1248
1119
|
}
|
|
1249
1120
|
let incomingChanges = Array.isArray(body?.changes) ? body.changes : [];
|
|
1121
|
+
const incomingTaskUpsertIds = incomingChanges
|
|
1122
|
+
.filter((change) => change?.op === 'upsert')
|
|
1123
|
+
.map((change) => String(change.task?.id || '').trim())
|
|
1124
|
+
.filter((taskId) => taskId.length > 0);
|
|
1250
1125
|
const allowReferenceNumberReassignment = body?.repair === true;
|
|
1251
1126
|
// Decrypt asset and document content if TASKFORCE_SYNC_KEY is configured on this server.
|
|
1252
1127
|
// The local runtime encrypts content before pushing; the cloud must decrypt before writing to storage.
|
|
@@ -1301,12 +1176,20 @@ export function registerSyncRoutes(deps) {
|
|
|
1301
1176
|
const { result, deleteCount, emittedEventIds } = await dispatchWorkspaceSyncChanges(targetWorkspaceId, incomingChanges, {
|
|
1302
1177
|
allowReferenceNumberReassignment
|
|
1303
1178
|
});
|
|
1179
|
+
const appliedTaskUpserts = incomingTaskUpsertIds
|
|
1180
|
+
.map((taskId) => core.getTask(taskId, targetWorkspaceId))
|
|
1181
|
+
.filter((task) => Boolean(task))
|
|
1182
|
+
.map((task) => ({
|
|
1183
|
+
archived: Boolean(task.isArchived),
|
|
1184
|
+
task: serializeTaskForSync(task)
|
|
1185
|
+
}));
|
|
1304
1186
|
const durationMs = Date.now() - startedAtMs;
|
|
1305
1187
|
const payload = JSON.stringify({
|
|
1306
1188
|
success: true,
|
|
1307
1189
|
workspaceId: targetWorkspaceId,
|
|
1308
1190
|
idempotencyKey: idempotencyKey || null,
|
|
1309
1191
|
emittedEventIds,
|
|
1192
|
+
appliedTaskUpserts,
|
|
1310
1193
|
result,
|
|
1311
1194
|
durationMs
|
|
1312
1195
|
});
|
|
@@ -55,10 +55,18 @@ import { getRuntimeCapabilities } from '../../runtime/capabilities.js';
|
|
|
55
55
|
import { resolveBuildInfo } from '../buildInfo.js';
|
|
56
56
|
import { resolveRealtimeSyncFlag } from '../../sync/realtimeFeature.js';
|
|
57
57
|
import { parseJsonBody } from '../routes.js';
|
|
58
|
+
import { resolveRequestAccess } from '../auth.js';
|
|
58
59
|
import { resolveErrorStatusCode, requestHeader, requestRuntimeMode } from './shared.js';
|
|
59
60
|
import { listBuiltInCustomTaxonomyLibraryPacks } from '../../shared/customTaxonomyLibrary.js';
|
|
60
61
|
import { listBuiltInTaxonomyLibraryPacks } from '../../shared/taxonomyLibrary.js';
|
|
61
62
|
import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
|
|
63
|
+
function createUiStateRequestId() {
|
|
64
|
+
return `ui-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
65
|
+
}
|
|
66
|
+
function attachUiStateDebugHeaders(res, requestId) {
|
|
67
|
+
res.setHeader('x-taskforce-ui-state-route', 'tasks');
|
|
68
|
+
res.setHeader('x-taskforce-ui-state-request-id', requestId);
|
|
69
|
+
}
|
|
62
70
|
// === SECTION: Registration ===
|
|
63
71
|
export function registerTaskRoutes(deps) {
|
|
64
72
|
const { addRoute, core, context, requestWorkspaceId, ensureAppAccess, resolveEffectiveObjectStorageConfig, buildBatchSummary, CLOUD_SAFE_CONFIG_PATHS, } = deps;
|
|
@@ -155,16 +163,31 @@ export function registerTaskRoutes(deps) {
|
|
|
155
163
|
|| ((isLocalRuntime && allowCreate && !explicitWorkspaceId) ? '' : headerWorkspaceId)).trim() || 'default';
|
|
156
164
|
try {
|
|
157
165
|
const workspaceExists = Boolean(core.getWorkspaceProfile(incomingWorkspaceId));
|
|
166
|
+
const authConfig = context.authConfig;
|
|
167
|
+
const requestAccess = authConfig?.enabled
|
|
168
|
+
? resolveRequestAccess(req, authConfig)
|
|
169
|
+
: null;
|
|
158
170
|
const shouldCreateWorkspace = Boolean(name
|
|
159
171
|
&& (incomingWorkspaceId === 'default' || !workspaceExists)
|
|
160
172
|
&& (isLocalRuntime || allowCreate));
|
|
161
173
|
const workspace = shouldCreateWorkspace
|
|
162
|
-
?
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
? (!isLocalRuntime
|
|
175
|
+
&& requestAccess?.authenticated
|
|
176
|
+
&& requestAccess.userId
|
|
177
|
+
&& requestAccess.userId !== 'anonymous'
|
|
178
|
+
? core.createWorkspaceForUser({
|
|
179
|
+
userId: requestAccess.userId,
|
|
180
|
+
preferredWorkspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
|
|
181
|
+
name,
|
|
182
|
+
description,
|
|
183
|
+
role: 'owner'
|
|
184
|
+
})
|
|
185
|
+
: core.createWorkspace({
|
|
186
|
+
workspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
|
|
187
|
+
name,
|
|
188
|
+
description,
|
|
189
|
+
slug
|
|
190
|
+
}))
|
|
168
191
|
: core.updateWorkspaceProfile(incomingWorkspaceId, { name, description, slug });
|
|
169
192
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
170
193
|
res.end(JSON.stringify({ success: true, workspace }));
|
|
@@ -179,6 +202,9 @@ export function registerTaskRoutes(deps) {
|
|
|
179
202
|
addRoute('GET', '/api/taskforce/ui-state', async (req, res) => {
|
|
180
203
|
if (!ensureAppAccess(req, res))
|
|
181
204
|
return;
|
|
205
|
+
const requestId = createUiStateRequestId();
|
|
206
|
+
const startedAt = Date.now();
|
|
207
|
+
attachUiStateDebugHeaders(res, requestId);
|
|
182
208
|
try {
|
|
183
209
|
const url = new URL(req.url || '/', 'http://localhost');
|
|
184
210
|
const requestedWorkspaceId = String(url.searchParams.get('workspaceId') || '').trim();
|
|
@@ -194,6 +220,12 @@ export function registerTaskRoutes(deps) {
|
|
|
194
220
|
}
|
|
195
221
|
catch (error) {
|
|
196
222
|
const statusCode = resolveErrorStatusCode(error, 400);
|
|
223
|
+
console.warn('[Taskforce][ui-state] GET failed', {
|
|
224
|
+
requestId,
|
|
225
|
+
durationMs: Date.now() - startedAt,
|
|
226
|
+
error: String(error?.message || 'Failed to read UI state'),
|
|
227
|
+
path: req.url || '/api/taskforce/ui-state'
|
|
228
|
+
});
|
|
197
229
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
198
230
|
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Failed to read UI state') }));
|
|
199
231
|
}
|
|
@@ -202,6 +234,9 @@ export function registerTaskRoutes(deps) {
|
|
|
202
234
|
addRoute('POST', '/api/taskforce/ui-state', async (req, res) => {
|
|
203
235
|
if (!ensureAppAccess(req, res))
|
|
204
236
|
return;
|
|
237
|
+
const requestId = createUiStateRequestId();
|
|
238
|
+
const startedAt = Date.now();
|
|
239
|
+
attachUiStateDebugHeaders(res, requestId);
|
|
205
240
|
const body = await parseJsonBody(req);
|
|
206
241
|
try {
|
|
207
242
|
const requestedWorkspaceId = String(body?.workspaceId || '').trim();
|
|
@@ -222,6 +257,13 @@ export function registerTaskRoutes(deps) {
|
|
|
222
257
|
}
|
|
223
258
|
catch (error) {
|
|
224
259
|
const statusCode = resolveErrorStatusCode(error, 400);
|
|
260
|
+
console.warn('[Taskforce][ui-state] POST failed', {
|
|
261
|
+
requestId,
|
|
262
|
+
durationMs: Date.now() - startedAt,
|
|
263
|
+
error: String(error?.message || 'Failed to merge UI state'),
|
|
264
|
+
path: req.url || '/api/taskforce/ui-state',
|
|
265
|
+
stateKey: String(body?.stateKey || body?.key || '').trim() || 'default'
|
|
266
|
+
});
|
|
225
267
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
226
268
|
res.end(JSON.stringify({ success: false, error: String(error?.message || 'Failed to merge UI state') }));
|
|
227
269
|
}
|
|
@@ -398,7 +440,7 @@ export function registerTaskRoutes(deps) {
|
|
|
398
440
|
: body;
|
|
399
441
|
const updated = core.updateTask(params.id, updates, workspaceId, {
|
|
400
442
|
actorRef: 'user',
|
|
401
|
-
|
|
443
|
+
saveSource
|
|
402
444
|
});
|
|
403
445
|
if (!updated) {
|
|
404
446
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
package/dist/server/routes.js
CHANGED
|
@@ -34,7 +34,7 @@ import { registerResourceRoutes } from './routes/resources.js';
|
|
|
34
34
|
import { registerDocumentRoutes } from './routes/documents.js';
|
|
35
35
|
import { registerDocumentReviewRoutes } from './routes/documentReviews.js';
|
|
36
36
|
import { registerAnnotatedAttachmentRoutes } from './routes/annotatedAttachments.js';
|
|
37
|
-
import { requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, ensureWorkspaceAdminRole, } from './routes/shared.js';
|
|
37
|
+
import { buildCloudMcpRuntimeAuth, requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, ensureWorkspaceAdminRole, } from './routes/shared.js';
|
|
38
38
|
import { resolveDeploymentConfig } from '../config/deployment.js';
|
|
39
39
|
import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../config/cloudEnvironment.js';
|
|
40
40
|
import { getCanonicalDocumentName } from '../utils/documentNames.js';
|
|
@@ -345,7 +345,7 @@ export function createRoutes(core, context = {}) {
|
|
|
345
345
|
const authSettings = globalSettings.auth || {};
|
|
346
346
|
const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
|
|
347
347
|
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
348
|
-
const
|
|
348
|
+
const onboardingPolicy = core.getOnboardingPolicy();
|
|
349
349
|
return {
|
|
350
350
|
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
351
351
|
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
@@ -357,8 +357,8 @@ export function createRoutes(core, context = {}) {
|
|
|
357
357
|
authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
|
|
358
358
|
? Math.floor(authRateLimitWindowMsRaw)
|
|
359
359
|
: 15 * 60_000,
|
|
360
|
-
|
|
361
|
-
|
|
360
|
+
onboardingPolicyMode: onboardingPolicy.mode,
|
|
361
|
+
onboardingPlanVersionId: onboardingPolicy.planVersionId
|
|
362
362
|
};
|
|
363
363
|
};
|
|
364
364
|
const deliverAuthEmail = async (kind, email, token, req) => {
|
|
@@ -372,7 +372,7 @@ export function createRoutes(core, context = {}) {
|
|
|
372
372
|
? 'Verify your Taskforce account'
|
|
373
373
|
: 'Reset your Taskforce password';
|
|
374
374
|
const text = kind === 'verify-email'
|
|
375
|
-
? `Welcome to Taskforce.\n\
|
|
375
|
+
? `Welcome to Taskforce.\n\nYou're almost in. Enter this verification token on the verification screen to activate your account:\n\n${token}\n\nPrefer the faster route? Open the link below to verify your email automatically:\n${link}\n\nIf you didn't create a Taskforce account, you can safely ignore this email.`
|
|
376
376
|
: `Reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
|
|
377
377
|
const result = await sendEmail({
|
|
378
378
|
to: email,
|
|
@@ -1458,24 +1458,13 @@ export function createRoutes(core, context = {}) {
|
|
|
1458
1458
|
serverName: 'taskforce-cloud',
|
|
1459
1459
|
logStartup: false,
|
|
1460
1460
|
runtimeMode: 'cloud',
|
|
1461
|
-
auth: {
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
aiProfileToken: auth.aiProfileToken,
|
|
1466
|
-
aiProfileName: auth.aiProfileName,
|
|
1467
|
-
aiProfileIcon: auth.aiProfileIcon,
|
|
1468
|
-
aiProfileColor: auth.aiProfileColor,
|
|
1469
|
-
aiProfileDescription: auth.aiProfileDescription,
|
|
1470
|
-
aiProfileRole: auth.aiProfileRole,
|
|
1471
|
-
aiProfileProvider: auth.aiProfileProvider,
|
|
1472
|
-
aiProfileModel: auth.aiProfileModel,
|
|
1473
|
-
aiProfileSurfaceType: auth.aiProfileSurfaceType,
|
|
1474
|
-
actorType: auth.authenticatedViaToken ? 'token' : auth.authenticatedViaOAuth ? 'oauth' : 'user',
|
|
1461
|
+
auth: buildCloudMcpRuntimeAuth({
|
|
1462
|
+
req,
|
|
1463
|
+
auth,
|
|
1464
|
+
requestHeader,
|
|
1475
1465
|
requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
},
|
|
1466
|
+
authScopes,
|
|
1467
|
+
}),
|
|
1479
1468
|
});
|
|
1480
1469
|
const transport = new StreamableHTTPServerTransport({
|
|
1481
1470
|
sessionIdGenerator: undefined,
|