@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.
Files changed (192) hide show
  1. package/dist/Taskforce.module.css +113 -5
  2. package/dist/TaskforceCore.js +560 -208
  3. package/dist/TaskforceCore.test.js +1833 -91
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskKanban.test.js +17 -0
  21. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  22. package/dist/components/task/TaskStatusActions.js +8 -3
  23. package/dist/components/task/TaskStatusActions.test.js +9 -0
  24. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  25. package/dist/components/task/taskKanbanDropRules.js +187 -0
  26. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  27. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  28. package/dist/components/ui/Modal.d.ts +1 -1
  29. package/dist/components/ui/Modal.js +1 -0
  30. package/dist/components/views/AppShellHeader.js +5 -1
  31. package/dist/components/views/AppShellHeader.test.js +4 -0
  32. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  33. package/dist/components/views/PlanComparisonPage.js +4 -2
  34. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  35. package/dist/components/views/PlansPage.d.ts +7 -1
  36. package/dist/components/views/PlansPage.js +139 -53
  37. package/dist/components/views/PlansPage.test.d.ts +1 -0
  38. package/dist/components/views/PlansPage.test.js +157 -0
  39. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  40. package/dist/components/views/StandaloneLayout.js +216 -186
  41. package/dist/components/views/WidgetView.js +11 -2
  42. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  43. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  44. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  45. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  46. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  47. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  48. package/dist/core/EntitlementsPolicy.test.js +2 -2
  49. package/dist/core/GlobalSettingsService.js +78 -18
  50. package/dist/core/PlanEntitlementService.d.ts +31 -5
  51. package/dist/core/PlanEntitlementService.js +350 -222
  52. package/dist/core/SignupMonetizationSettings.test.js +208 -49
  53. package/dist/core/SystemAdmin.test.js +186 -96
  54. package/dist/core/TaskActivityService.d.ts +4 -1
  55. package/dist/core/TaskActivityService.js +45 -0
  56. package/dist/core/TaskAuditTimeline.test.js +52 -1
  57. package/dist/core/Taskforce.d.ts +65 -20
  58. package/dist/core/Taskforce.js +261 -51
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  61. package/dist/core/WorkspacePlanMode.test.js +65 -0
  62. package/dist/core/types.d.ts +11 -12
  63. package/dist/documentReviews/service.d.ts +1 -1
  64. package/dist/documentReviews/types.d.ts +1 -1
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  66. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  67. package/dist/hooks/sync/auth.d.ts +37 -0
  68. package/dist/hooks/sync/auth.js +61 -0
  69. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  70. package/dist/hooks/sync/bootstrap.js +52 -0
  71. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  72. package/dist/hooks/sync/orchestratorShared.js +209 -0
  73. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  74. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  75. package/dist/hooks/sync/realtime.d.ts +25 -0
  76. package/dist/hooks/sync/realtime.js +60 -0
  77. package/dist/hooks/sync/recovery.d.ts +69 -0
  78. package/dist/hooks/sync/recovery.js +118 -0
  79. package/dist/hooks/sync/transfers.d.ts +122 -0
  80. package/dist/hooks/sync/transfers.js +431 -0
  81. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  82. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  83. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  84. package/dist/hooks/ui/useResourceExport.js +5 -2
  85. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  86. package/dist/hooks/useRealtimeSync.js +2 -1
  87. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  88. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  89. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  90. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  91. package/dist/hooks/useSyncOrchestrator.js +315 -860
  92. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
  93. package/dist/hooks/useTaskMutations.js +45 -6
  94. package/dist/hooks/useTaskMutations.test.js +147 -0
  95. package/dist/hooks/useTaskforce.d.ts +11 -2
  96. package/dist/hooks/useTaskforce.js +290 -812
  97. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  98. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  99. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  100. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  101. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  102. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  103. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  104. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  105. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  106. package/dist/localization/locales/en-US.js +2 -2
  107. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  108. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  109. package/dist/mcp/clientIntegrations.d.ts +12 -1
  110. package/dist/mcp/clientIntegrations.js +161 -6
  111. package/dist/mcp/clientIntegrations.test.js +76 -4
  112. package/dist/mcp/documentHelpers.d.ts +100 -0
  113. package/dist/mcp/documentHelpers.js +161 -0
  114. package/dist/mcp/runtime.js +264 -505
  115. package/dist/mcp/runtime.test.js +353 -89
  116. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  117. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  118. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  119. package/dist/mcp/taskAttachmentTypes.js +1 -0
  120. package/dist/migrations/billingSchemaParity.test.js +3 -0
  121. package/dist/migrations/taskSchemaMigrations.js +15 -0
  122. package/dist/plugins/vite.js +1 -1
  123. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  124. package/dist/runtime/appGateDefinitions.js +1 -1
  125. package/dist/server/index.cookieProxy.test.js +1 -0
  126. package/dist/server/index.d.ts +1 -0
  127. package/dist/server/index.js +48 -3
  128. package/dist/server/index.test.js +14 -2
  129. package/dist/server/mockStripe.d.ts +1 -0
  130. package/dist/server/mockStripe.js +2 -0
  131. package/dist/server/routes/admin.js +91 -40
  132. package/dist/server/routes/auth.d.ts +1 -0
  133. package/dist/server/routes/auth.js +231 -55
  134. package/dist/server/routes/billing.js +473 -39
  135. package/dist/server/routes/billing.test.js +972 -37
  136. package/dist/server/routes/shared.d.ts +30 -0
  137. package/dist/server/routes/shared.js +31 -3
  138. package/dist/server/routes/sync.integration.test.js +180 -0
  139. package/dist/server/routes/sync.js +125 -242
  140. package/dist/server/routes/tasks.js +49 -7
  141. package/dist/server/routes.js +11 -22
  142. package/dist/server/routes.test.js +530 -81
  143. package/dist/shared/taskActor.d.ts +10 -0
  144. package/dist/shared/taskActor.js +1 -0
  145. package/dist/sync/cloudSyncApi.d.ts +1 -0
  146. package/dist/sync/collaborationSyncPayload.js +4 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -1
  149. package/dist/sync/collaborationSyncState.js +19 -0
  150. package/dist/sync/contentSyncState.d.ts +10 -0
  151. package/dist/sync/contentSyncState.js +277 -14
  152. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  153. package/dist/sync/syncApplyHandlers.js +370 -8
  154. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  155. package/dist/sync/syncApplyHandlers.test.js +377 -0
  156. package/dist/sync/syncService.d.ts +9 -1
  157. package/dist/sync/syncService.js +57 -10
  158. package/dist/sync/syncService.test.js +48 -0
  159. package/dist/sync/workspacePullFeed.d.ts +11 -0
  160. package/dist/sync/workspacePullFeed.js +123 -68
  161. package/dist/sync/workspacePullFeed.test.js +180 -0
  162. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  163. package/dist/sync/workspaceSyncModel.js +65 -0
  164. package/dist/sync/workspaceSyncModel.test.js +119 -0
  165. package/dist/types.d.ts +2 -10
  166. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  167. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  168. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  169. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
  170. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  171. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  172. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  173. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
  174. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  175. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  176. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
  177. package/dist/ui/index.html +4 -4
  178. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  179. package/dist/utils/accountProfileSummaryCache.js +97 -0
  180. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  181. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  182. package/dist/utils/taskActivity.js +4 -0
  183. package/dist/utils/taskActivity.test.js +25 -1
  184. package/package.json +5 -3
  185. package/scripts/playwright-auth.sh +4 -0
  186. package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
  187. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  188. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
  189. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  190. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  191. package/dist/utils/billingStatusCache.d.ts +0 -21
  192. 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 { getTaskMutationTieBreak, getTaskMutationWatermark } from '../../sync/workspaceSyncSurface.js';
9
- import { normalizeTaskSyncPayload, taskSyncPayloadToCoreTask } from '../../sync/taskSyncPayload.js';
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, normalizeDeleteSyncPayload, normalizeDocumentReviewCommentSyncPayload, normalizeDocumentReviewSessionSyncPayload, } from '../../sync/collaborationSyncPayload.js';
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, resolveSyncConflictOrdering, } from '../../sync/syncApplyHandlers.js';
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
- for (const change of incomingChanges) {
246
- const raw = change;
247
- const op = String(raw.op || '').trim().toLowerCase();
248
- if (op === 'taxonomy-upsert') {
249
- const taxonomy = normalizeTaxonomySyncPayload(raw);
250
- if (taxonomy?.taxonomyState)
251
- incomingTaxonomyState = taxonomy.taxonomyState;
252
- if (Array.isArray(taxonomy?.taxonomies))
253
- incomingTaxonomies = taxonomy.taxonomies;
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
- validateTaskActorAssignments(workspaceId, tasks, archived);
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 result = core.applyWorkspaceSyncSnapshot({
467
- tasks: tasks.map(taskSyncPayloadToCoreTask),
468
- archived: archived.map(taskSyncPayloadToCoreTask)
469
- }, workspaceId);
470
- return { result, deleteCount: incomingTaskDeletes.size, emittedEventIds: Array.from(emittedEventIds) };
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
- ? core.createWorkspace({
163
- workspaceId: incomingWorkspaceId === 'default' ? undefined : incomingWorkspaceId,
164
- name,
165
- description,
166
- slug
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
- skipTaskEvents: saveSource === 'autosave'
443
+ saveSource
402
444
  });
403
445
  if (!updated) {
404
446
  res.writeHead(404, { 'Content-Type': 'application/json' });
@@ -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 signupSettings = core.getSignupMonetizationSettings();
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
- signupMonetizationStrategy: signupSettings.strategy,
361
- defaultSignupPlanVersionId: signupSettings.defaultSignupPlanVersionId
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\nVerify your email by opening this link:\n${link}\n\nIf you did not create an account, you can ignore this message.`
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
- scopes: authScopes,
1463
- userId: auth.authenticatedViaToken?.userId || auth.authenticatedViaOAuth?.userId || auth.userId || null,
1464
- tokenId: auth.authenticatedViaToken?.tokenId || auth.authenticatedViaOAuth?.grantId || null,
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
- ip: req.socket?.remoteAddress || null,
1477
- userAgent: requestHeader(req, 'user-agent', '') || null,
1478
- },
1466
+ authScopes,
1467
+ }),
1479
1468
  });
1480
1469
  const transport = new StreamableHTTPServerTransport({
1481
1470
  sessionIdGenerator: undefined,