@taskforcehq/taskforce 0.3.304 → 0.3.306

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 (383) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +295 -89
  3. package/dist/TaskforceCore.js +378 -111
  4. package/dist/TaskforceCore.test.js +1221 -107
  5. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  8. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  10. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  11. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  12. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  13. package/dist/compat/workspaceSyncCompat.js +52 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  16. package/dist/components/context/ContextAttachmentManager.js +549 -0
  17. package/dist/components/features/AgentsModule.d.ts +10 -1
  18. package/dist/components/features/AgentsModule.js +260 -15
  19. package/dist/components/features/AgentsModule.test.js +255 -5
  20. package/dist/components/features/DocumentIndex.d.ts +1 -1
  21. package/dist/components/features/DocumentIndex.js +1 -1
  22. package/dist/components/features/DocumentViewer.d.ts +6 -2
  23. package/dist/components/features/DocumentViewer.js +90 -10
  24. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  25. package/dist/components/features/DocumentWorkspace.js +12 -155
  26. package/dist/components/features/DocumentWorkspace.test.js +226 -6
  27. package/dist/components/features/TaskSettings.js +29 -3
  28. package/dist/components/features/TaskSettings.test.js +42 -1
  29. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  30. package/dist/components/features/documentWorkspaceModel.js +57 -0
  31. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  32. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  33. package/dist/components/task/TaskCard.js +19 -28
  34. package/dist/components/task/TaskCard.test.js +38 -3
  35. package/dist/components/task/TaskContextUpload.js +4 -526
  36. package/dist/components/task/TaskForm.d.ts +8 -8
  37. package/dist/components/task/TaskForm.js +362 -230
  38. package/dist/components/task/TaskForm.test.js +558 -381
  39. package/dist/components/task/TaskKanban.d.ts +2 -1
  40. package/dist/components/task/TaskKanban.js +96 -14
  41. package/dist/components/task/TaskKanban.test.js +36 -0
  42. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  43. package/dist/components/ui/EditableAvatarButton.js +18 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  45. package/dist/components/views/PlanComparisonPage.js +70 -17
  46. package/dist/components/views/PlanComparisonPage.test.js +289 -41
  47. package/dist/components/views/PlansPage.js +239 -106
  48. package/dist/components/views/PlansPage.test.js +593 -42
  49. package/dist/components/views/StandaloneLayout.js +310 -554
  50. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  51. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  52. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  53. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  54. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  55. package/dist/components/views/planningScope.d.ts +20 -0
  56. package/dist/components/views/planningScope.js +25 -0
  57. package/dist/components/views/planningScope.test.d.ts +1 -0
  58. package/dist/components/views/planningScope.test.js +60 -0
  59. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
  60. package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
  61. package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
  62. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  63. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  64. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  65. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  66. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  67. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  68. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  69. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  70. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  71. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  72. package/dist/config/envSchema.js +1 -1
  73. package/dist/core/AiProfileService.d.ts +45 -3
  74. package/dist/core/AiProfileService.js +774 -63
  75. package/dist/core/AiProfileService.test.js +22 -2
  76. package/dist/core/AiProfiles.test.js +761 -2
  77. package/dist/core/AttachmentLinkService.js +57 -15
  78. package/dist/core/AuthIdentityService.d.ts +107 -0
  79. package/dist/core/AuthIdentityService.js +284 -0
  80. package/dist/core/AuthTokenService.d.ts +4 -0
  81. package/dist/core/AuthTokenService.js +34 -6
  82. package/dist/core/AuthTokenService.test.d.ts +1 -0
  83. package/dist/core/AuthTokenService.test.js +78 -0
  84. package/dist/core/EntitlementsPolicy.test.js +77 -15
  85. package/dist/core/GlobalSettingsService.js +115 -6
  86. package/dist/core/PlanEntitlementService.d.ts +30 -3
  87. package/dist/core/PlanEntitlementService.js +454 -93
  88. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  89. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  90. package/dist/core/PlanVersionPolicy.test.js +54 -28
  91. package/dist/core/PlanningEntities.test.js +52 -0
  92. package/dist/core/SignupMonetizationSettings.test.js +241 -44
  93. package/dist/core/SyncReconciliationService.js +2 -65
  94. package/dist/core/SystemAdmin.test.js +244 -88
  95. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  96. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  97. package/dist/core/Taskforce.d.ts +228 -14
  98. package/dist/core/Taskforce.js +814 -201
  99. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  100. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  101. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  102. package/dist/core/WorkspaceLifecycleService.js +0 -5
  103. package/dist/core/WorkspacePermissions.test.js +25 -1
  104. package/dist/core/WorkspacePlanMode.test.js +128 -27
  105. package/dist/core/shared.d.ts +3 -1
  106. package/dist/core/shared.js +16 -1
  107. package/dist/core/types.d.ts +68 -2
  108. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  109. package/dist/hooks/sync/auth.js +3 -1
  110. package/dist/hooks/sync/bootstrap.js +2 -0
  111. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  112. package/dist/hooks/sync/controlPlane.js +78 -0
  113. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  114. package/dist/hooks/sync/controlPlane.test.js +121 -0
  115. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  116. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  117. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  118. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  119. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  120. package/dist/hooks/sync/orchestratorShared.js +68 -47
  121. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  122. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  123. package/dist/hooks/sync/pullLifecycle.js +24 -0
  124. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  125. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  126. package/dist/hooks/sync/recovery.d.ts +16 -2
  127. package/dist/hooks/sync/recovery.js +171 -53
  128. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  129. package/dist/hooks/sync/recovery.test.js +292 -0
  130. package/dist/hooks/sync/transfers.d.ts +16 -3
  131. package/dist/hooks/sync/transfers.js +186 -65
  132. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  133. package/dist/hooks/sync/transfers.test.js +396 -0
  134. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  135. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  136. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  137. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  138. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  139. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  140. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  141. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  142. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  143. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  144. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  145. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  146. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  147. package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
  148. package/dist/hooks/useSyncOrchestrator.js +424 -175
  149. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
  150. package/dist/hooks/useTaskData.js +8 -3
  151. package/dist/hooks/useTaskData.test.js +45 -0
  152. package/dist/hooks/useTaskMutations.d.ts +2 -1
  153. package/dist/hooks/useTaskMutations.js +12 -1
  154. package/dist/hooks/useTaskMutations.test.js +30 -1
  155. package/dist/hooks/useTaskforce.d.ts +65 -4
  156. package/dist/hooks/useTaskforce.js +564 -243
  157. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  158. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  159. package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
  160. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  161. package/dist/hooks/useWorkspaceSyncController.js +17 -2
  162. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  163. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  164. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  165. package/dist/localization/locales/en-US.d.ts +0 -1
  166. package/dist/localization/locales/en-US.js +0 -1
  167. package/dist/localization/locales/es-419.js +0 -1
  168. package/dist/localization/locales/pt-BR.js +0 -1
  169. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  170. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  171. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  172. package/dist/mcp/collaborationRegistrar.js +71 -0
  173. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  174. package/dist/mcp/documentAssetRegistrar.js +346 -0
  175. package/dist/mcp/runtime.d.ts +2 -0
  176. package/dist/mcp/runtime.js +2433 -3534
  177. package/dist/mcp/runtime.test.js +440 -2
  178. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  179. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  180. package/dist/mcp/toolCatalog.d.ts +35 -0
  181. package/dist/mcp/toolCatalog.js +3 -0
  182. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  183. package/dist/migrations/taskSchemaMigrations.js +171 -61
  184. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  185. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  186. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  187. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  188. package/dist/resources/templates/workflows/plan.yaml +1 -1
  189. package/dist/resources/templates/workflows/review.yaml +2 -2
  190. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  191. package/dist/server/auth/providers/apple.d.ts +33 -0
  192. package/dist/server/auth/providers/apple.js +82 -0
  193. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  194. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  195. package/dist/server/auth/providers/github.d.ts +26 -0
  196. package/dist/server/auth/providers/github.js +86 -0
  197. package/dist/server/auth/providers/google.d.ts +21 -0
  198. package/dist/server/auth/providers/google.js +56 -0
  199. package/dist/server/auth/providers/types.d.ts +21 -0
  200. package/dist/server/auth/providers/types.js +1 -0
  201. package/dist/server/auth.d.ts +2 -0
  202. package/dist/server/auth.js +6 -3
  203. package/dist/server/documentReviewRoutes.test.js +36 -3
  204. package/dist/server/index.cookieProxy.test.js +1 -0
  205. package/dist/server/index.d.ts +12 -0
  206. package/dist/server/index.js +120 -9
  207. package/dist/server/index.rateLimit.test.js +3 -0
  208. package/dist/server/index.test.js +106 -3
  209. package/dist/server/routes/admin.d.ts +4 -5
  210. package/dist/server/routes/admin.js +410 -80
  211. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  212. package/dist/server/routes/annotatedAttachments.js +1 -1
  213. package/dist/server/routes/auth.d.ts +16 -6
  214. package/dist/server/routes/auth.js +1015 -118
  215. package/dist/server/routes/authSupport.d.ts +30 -0
  216. package/dist/server/routes/authSupport.js +81 -0
  217. package/dist/server/routes/billing.d.ts +1 -1
  218. package/dist/server/routes/billing.js +276 -34
  219. package/dist/server/routes/billing.test.js +760 -52
  220. package/dist/server/routes/documentReviews.d.ts +1 -1
  221. package/dist/server/routes/documentReviews.js +1 -1
  222. package/dist/server/routes/documents.d.ts +5 -2
  223. package/dist/server/routes/documents.js +242 -74
  224. package/dist/server/routes/primitives.d.ts +11 -0
  225. package/dist/server/routes/primitives.js +73 -0
  226. package/dist/server/routes/resources.d.ts +1 -1
  227. package/dist/server/routes/resources.js +1 -1
  228. package/dist/server/routes/shared.d.ts +29 -3
  229. package/dist/server/routes/shared.js +69 -4
  230. package/dist/server/routes/sync.d.ts +1 -1
  231. package/dist/server/routes/sync.integration.test.js +437 -39
  232. package/dist/server/routes/sync.js +62 -606
  233. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  234. package/dist/server/routes/syncAuxRoutes.js +91 -0
  235. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  236. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  237. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  238. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  239. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  240. package/dist/server/routes/syncPushRoutes.js +212 -0
  241. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  242. package/dist/server/routes/syncRouteGuards.js +67 -0
  243. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  244. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  245. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  246. package/dist/server/routes/syncRouteTypes.js +1 -0
  247. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  248. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  249. package/dist/server/routes/tasks.d.ts +1 -1
  250. package/dist/server/routes/tasks.js +44 -2
  251. package/dist/server/routes/workspaces.d.ts +1 -1
  252. package/dist/server/routes/workspaces.js +37 -6
  253. package/dist/server/routes.d.ts +2 -9
  254. package/dist/server/routes.js +136 -165
  255. package/dist/server/routes.test.js +2457 -162
  256. package/dist/services/workflowExportSnapshots.test.js +2 -0
  257. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  258. package/dist/shared/aiProfileSeatScope.js +21 -0
  259. package/dist/shared/passwordPolicy.d.ts +13 -0
  260. package/dist/shared/passwordPolicy.js +84 -0
  261. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  262. package/dist/shared/passwordPolicy.test.js +34 -0
  263. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  264. package/dist/storage/workspaceAssetStore.js +132 -44
  265. package/dist/storage/workspaceAssetStore.test.js +93 -6
  266. package/dist/styles/fonts.css +70 -0
  267. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  268. package/dist/sync/collaborationSyncPayload.js +17 -11
  269. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  270. package/dist/sync/contentSyncState.d.ts +4 -8
  271. package/dist/sync/contentSyncState.js +33 -77
  272. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  273. package/dist/sync/syncApplyHandlers.js +28 -13
  274. package/dist/sync/syncApplyHandlers.test.js +72 -3
  275. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  276. package/dist/sync/syncFieldSemantics.js +55 -0
  277. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  278. package/dist/sync/syncResourceAdapters.js +77 -0
  279. package/dist/sync/syncService.d.ts +6 -0
  280. package/dist/sync/syncService.js +60 -0
  281. package/dist/sync/syncService.test.js +71 -3
  282. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  283. package/dist/sync/taskSyncChangeKey.js +143 -0
  284. package/dist/sync/taskSyncPayload.js +3 -8
  285. package/dist/sync/workspacePullFeed.js +4 -3
  286. package/dist/sync/workspacePullFeed.test.js +43 -0
  287. package/dist/sync/workspaceRepair.js +5 -2
  288. package/dist/sync/workspaceSyncModel.d.ts +48 -0
  289. package/dist/sync/workspaceSyncModel.js +195 -31
  290. package/dist/sync/workspaceSyncModel.test.js +325 -12
  291. package/dist/sync/workspaceSyncState.d.ts +19 -0
  292. package/dist/sync/workspaceSyncState.js +3 -1
  293. package/dist/sync/workspaceSyncSurface.js +3 -8
  294. package/dist/types.d.ts +17 -0
  295. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  296. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  297. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  298. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  299. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  300. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  301. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  302. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  303. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
  304. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  305. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  306. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  307. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  308. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  309. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  310. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
  311. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  312. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  313. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  314. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
  315. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  316. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  317. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  318. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  319. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  320. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  321. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  322. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  323. package/dist/ui/fonts/Noir_medium.otf +0 -0
  324. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  325. package/dist/ui/fonts/Noir_regular.otf +0 -0
  326. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  327. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  328. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  329. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  330. package/dist/ui/index.html +3 -3
  331. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  332. package/dist/utils/assignees.d.ts +2 -0
  333. package/dist/utils/assignees.js +2 -0
  334. package/dist/utils/avatarUpload.d.ts +27 -0
  335. package/dist/utils/avatarUpload.js +132 -0
  336. package/dist/utils/avatarUpload.test.d.ts +1 -0
  337. package/dist/utils/avatarUpload.test.js +40 -0
  338. package/dist/utils/bootstrapTrace.d.ts +7 -0
  339. package/dist/utils/bootstrapTrace.js +29 -0
  340. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  341. package/dist/utils/commercialLifecyclePresentation.js +199 -0
  342. package/dist/utils/contextAssetEvents.d.ts +2 -0
  343. package/dist/utils/syncEventDetails.d.ts +3 -0
  344. package/dist/utils/syncEventDetails.js +64 -0
  345. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  346. package/dist/utils/syncEventDetails.test.js +23 -0
  347. package/dist/utils/syncEventPresentation.d.ts +15 -0
  348. package/dist/utils/syncEventPresentation.js +129 -0
  349. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  350. package/dist/utils/syncEventPresentation.test.js +64 -0
  351. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  352. package/dist/utils/syncStatusPresentation.js +217 -0
  353. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  354. package/dist/utils/syncStatusPresentation.test.js +164 -0
  355. package/dist/utils/taskActivity.d.ts +2 -0
  356. package/dist/utils/taskActivity.js +228 -22
  357. package/dist/utils/taskActivity.test.js +119 -8
  358. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  359. package/dist/utils/workspaceSyncPresentation.js +68 -2
  360. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  361. package/package.json +10 -2
  362. package/scripts/check-cloud-mcp.mjs +83 -0
  363. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  364. package/scripts/playwright-auth.sh +5 -1
  365. package/scripts/run-billing-performance-smoke.sh +24 -0
  366. package/scripts/run-e2e-realtime.sh +17 -3
  367. package/scripts/run-smoke.sh +17 -5
  368. package/scripts/run-soak.sh +17 -5
  369. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  370. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  371. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  372. package/src/resources/templates/workflows/plan.yaml +1 -1
  373. package/src/resources/templates/workflows/review.yaml +2 -2
  374. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
  375. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  376. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  377. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  378. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  379. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  380. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  381. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
  382. package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
  383. package/scripts/migrate-planning-model.ts +0 -233
@@ -52,7 +52,7 @@
52
52
  import { IncomingMessage, ServerResponse } from 'http';
53
53
  import { TaskforceCore } from '../../core/Taskforce.js';
54
54
  import { type ResolvedObjectStorageConfig } from '../../storage/objectStorage.js';
55
- import { type RouteHandler, type RouteContext } from '../routes.js';
55
+ import { type RouteHandler, type RouteContext } from './primitives.js';
56
56
  export interface TaskRouteDependencies {
57
57
  addRoute: (method: string, path: string, handler: RouteHandler) => void;
58
58
  core: TaskforceCore;
@@ -54,9 +54,9 @@ import { toPublicObjectStorageConfig } from '../../storage/objectStorage.js';
54
54
  import { getRuntimeCapabilities } from '../../runtime/capabilities.js';
55
55
  import { resolveBuildInfo } from '../buildInfo.js';
56
56
  import { resolveRealtimeSyncFlag } from '../../sync/realtimeFeature.js';
57
- import { parseJsonBody } from '../routes.js';
57
+ import { parseJsonBody } from './primitives.js';
58
58
  import { resolveRequestAccess } from '../auth.js';
59
- import { resolveErrorStatusCode, requestHeader, requestRuntimeMode } from './shared.js';
59
+ import { getRouteTimingNow, logSlowBootstrapRoute, requestBootstrapTraceId, requestHeader, requestRuntimeMode, resolveErrorStatusCode, serializeJsonWithTiming, setServerTimingHeader } from './shared.js';
60
60
  import { listBuiltInCustomTaxonomyLibraryPacks } from '../../shared/customTaxonomyLibrary.js';
61
61
  import { listBuiltInTaxonomyLibraryPacks } from '../../shared/taxonomyLibrary.js';
62
62
  import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
@@ -506,6 +506,48 @@ export function registerTaskRoutes(deps) {
506
506
  res.writeHead(200, { 'Content-Type': 'application/json' });
507
507
  res.end(JSON.stringify(data));
508
508
  });
509
+ // GET /api/taskforce/planning/bootstrap - List planning entities plus lightweight task summaries
510
+ addRoute('GET', '/api/taskforce/planning/bootstrap', async (req, res) => {
511
+ if (!ensureAppAccess(req, res))
512
+ return;
513
+ const startedAt = getRouteTimingNow();
514
+ const workspaceId = requestWorkspaceId(req);
515
+ const traceId = requestBootstrapTraceId(req);
516
+ const diagnostics = {};
517
+ const data = core.listPlanningBootstrap(workspaceId, diagnostics);
518
+ const serializeStartedAt = getRouteTimingNow();
519
+ const serialized = serializeJsonWithTiming(data, serializeStartedAt);
520
+ const totalDurationMs = Math.max(0, getRouteTimingNow() - startedAt);
521
+ const details = diagnostics;
522
+ setServerTimingHeader(res, [
523
+ { name: 'planning-bootstrap', durationMs: totalDurationMs },
524
+ { name: 'planning-bootstrap-initiatives', durationMs: details.initiativeLoadMs ?? 0 },
525
+ { name: 'planning-bootstrap-workstreams', durationMs: details.workstreamLoadMs ?? 0 },
526
+ { name: 'planning-bootstrap-tasks', durationMs: details.taskSummaryQueryMs ?? 0 },
527
+ { name: 'planning-bootstrap-build', durationMs: details.taskSummaryBuildMs ?? 0 },
528
+ { name: 'planning-bootstrap-json', durationMs: serialized.durationMs },
529
+ ]);
530
+ logSlowBootstrapRoute({
531
+ route: '/api/taskforce/planning/bootstrap',
532
+ workspaceId,
533
+ traceId,
534
+ durationMs: totalDurationMs,
535
+ details: {
536
+ initiativeCount: details.initiativeCount ?? 0,
537
+ workstreamCount: details.workstreamCount ?? 0,
538
+ workstreamLinkedTaskCount: details.workstreamLinkedTaskCount ?? 0,
539
+ workstreamTaskSummaryCount: details.workstreamTaskSummaryCount ?? 0,
540
+ responseBytes: serialized.byteLength,
541
+ initiativeLoadMs: details.initiativeLoadMs ?? 0,
542
+ workstreamLoadMs: details.workstreamLoadMs ?? 0,
543
+ taskSummaryQueryMs: details.taskSummaryQueryMs ?? 0,
544
+ taskSummaryBuildMs: details.taskSummaryBuildMs ?? 0,
545
+ jsonSerializeMs: serialized.durationMs,
546
+ },
547
+ });
548
+ res.writeHead(200, { 'Content-Type': 'application/json' });
549
+ res.end(serialized.body);
550
+ });
509
551
  // POST /api/taskforce/initiative - Create initiative
510
552
  addRoute('POST', '/api/taskforce/initiative', async (req, res) => {
511
553
  if (!ensureAppAccess(req, res))
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import type { IncomingMessage, ServerResponse } from 'http';
15
15
  import type { TaskforceCore } from '../../core/Taskforce.js';
16
- import type { RouteHandler, RouteContext } from '../routes.js';
16
+ import { type RouteHandler, type RouteContext } from './primitives.js';
17
17
  import { ObjectStorageClient } from '../../storage/objectStorageClient.js';
18
18
  export interface WorkspaceRouteDependencies {
19
19
  addRoute: (method: string, path: string, handler: RouteHandler) => void;
@@ -13,10 +13,10 @@
13
13
  */
14
14
  import * as fs from 'fs';
15
15
  import * as path from 'path';
16
- import { parseJsonBody } from '../routes.js';
16
+ import { parseJsonBody } from './primitives.js';
17
17
  import { resolveRequestAccess, createSessionTokenCookie, resolveSessionCookieAttributesForRequest, } from '../auth.js';
18
18
  import { isWorkspaceSwitchingEnabledForRuntime } from '../../shared/runtimeContract.js';
19
- import { resolveErrorStatusCode } from './shared.js';
19
+ import { getRouteTimingNow, logSlowBootstrapRoute, requestBootstrapTraceId, resolveErrorStatusCode, serializeJsonWithTiming, setServerTimingHeader } from './shared.js';
20
20
  // === SECTION: Registration ===
21
21
  export function registerWorkspaceRoutes(deps) {
22
22
  const { addRoute, core, context, ensureAppAccess, auditAuthAction, getObjectStorageClient, ensureWithinDir, sanitizeStorageSegment, sanitizeTaskIdForPath, normalizeDocumentPathInput, } = deps;
@@ -94,6 +94,8 @@ export function registerWorkspaceRoutes(deps) {
94
94
  // === SECTION: Workspace CRUD routes ===
95
95
  // GET /api/taskforce/workspaces - List authenticated user's accessible workspaces
96
96
  addRoute('GET', '/api/taskforce/workspaces', async (req, res) => {
97
+ const routeStartedAt = getRouteTimingNow();
98
+ const traceId = requestBootstrapTraceId(req);
97
99
  const authConfig = context.authConfig;
98
100
  if (!isWorkspaceSwitchingEnabledForRuntime(authConfig?.runtimeMode)) {
99
101
  res.writeHead(501, { 'Content-Type': 'application/json' });
@@ -106,6 +108,7 @@ export function registerWorkspaceRoutes(deps) {
106
108
  return;
107
109
  }
108
110
  const access = resolveRequestAccess(req, authConfig);
111
+ const authResolvedAt = getRouteTimingNow();
109
112
  if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
110
113
  res.writeHead(401, { 'Content-Type': 'application/json' });
111
114
  res.end(JSON.stringify({ success: false, error: 'Authentication required.' }));
@@ -113,17 +116,45 @@ export function registerWorkspaceRoutes(deps) {
113
116
  }
114
117
  if (!ensureAppAccess(req, res))
115
118
  return;
119
+ const appAccessCheckedAt = getRouteTimingNow();
116
120
  const workspaces = core.listUserWorkspaces(access.userId);
121
+ const workspacesLoadedAt = getRouteTimingNow();
117
122
  const requestedCurrentWorkspaceId = String(access.workspaceId || '').trim();
118
123
  const currentWorkspaceId = workspaces.some((workspace) => workspace.id === requestedCurrentWorkspaceId)
119
124
  ? requestedCurrentWorkspaceId
120
125
  : (workspaces[0]?.id || 'default');
121
- res.writeHead(200, { 'Content-Type': 'application/json' });
122
- res.end(JSON.stringify({
126
+ const currentWorkspaceResolvedAt = getRouteTimingNow();
127
+ const serialized = serializeJsonWithTiming({
123
128
  success: true,
124
129
  workspaces,
125
130
  currentWorkspaceId: currentWorkspaceId || access.workspaceId
126
- }));
131
+ }, currentWorkspaceResolvedAt);
132
+ const totalDurationMs = getRouteTimingNow() - routeStartedAt;
133
+ setServerTimingHeader(res, [
134
+ { name: 'workspaces', durationMs: totalDurationMs },
135
+ { name: 'workspaces-auth', durationMs: authResolvedAt - routeStartedAt },
136
+ { name: 'workspaces-app-access', durationMs: appAccessCheckedAt - authResolvedAt },
137
+ { name: 'workspaces-list', durationMs: workspacesLoadedAt - appAccessCheckedAt },
138
+ { name: 'workspaces-current', durationMs: currentWorkspaceResolvedAt - workspacesLoadedAt },
139
+ { name: 'workspaces-json', durationMs: serialized.durationMs },
140
+ ]);
141
+ logSlowBootstrapRoute({
142
+ route: '/api/taskforce/workspaces',
143
+ workspaceId: currentWorkspaceId || access.workspaceId || 'default',
144
+ traceId,
145
+ durationMs: totalDurationMs,
146
+ details: {
147
+ workspaceCount: workspaces.length,
148
+ payloadBytes: serialized.byteLength,
149
+ authMs: authResolvedAt - routeStartedAt,
150
+ appAccessMs: appAccessCheckedAt - authResolvedAt,
151
+ listWorkspacesMs: workspacesLoadedAt - appAccessCheckedAt,
152
+ currentWorkspaceMs: currentWorkspaceResolvedAt - workspacesLoadedAt,
153
+ serializeMs: serialized.durationMs,
154
+ }
155
+ });
156
+ res.writeHead(200, { 'Content-Type': 'application/json' });
157
+ res.end(serialized.body);
127
158
  });
128
159
  // POST /api/taskforce/workspaces - Create workspace for current authenticated user
129
160
  addRoute('POST', '/api/taskforce/workspaces', async (req, res) => {
@@ -281,7 +312,7 @@ export function registerWorkspaceRoutes(deps) {
281
312
  return;
282
313
  }
283
314
  const resolved = core.resolveUserAccess(access.userId, workspaceId, access.role);
284
- if (!resolved || resolved.role === 'read-only') {
315
+ if (!resolved || resolved.disabled) {
285
316
  res.writeHead(403, { 'Content-Type': 'application/json' });
286
317
  res.end(JSON.stringify({ success: false, error: 'Workspace access is not active for this user.' }));
287
318
  return;
@@ -4,17 +4,14 @@
4
4
  * Extracted from vite.ts plugin to be reusable in both Vite plugin and standalone server.
5
5
  * This is a minimal implementation with core routes - additional routes can be added incrementally.
6
6
  */
7
- import { IncomingMessage, ServerResponse } from 'http';
8
7
  import { TaskforceCore } from '../core/Taskforce.js';
9
- import { type TaskforceAuthConfig } from './auth.js';
10
- import { type ResolvedObjectStorageConfig } from '../storage/objectStorage.js';
11
- export type RouteHandler = (req: IncomingMessage, res: ServerResponse, params: Record<string, string>) => Promise<void>;
8
+ import { type RouteContext, type RouteHandler } from './routes/primitives.js';
9
+ export { parseJsonBody } from './routes/primitives.js';
12
10
  export interface Route {
13
11
  pattern: RegExp;
14
12
  keys: string[];
15
13
  handler: RouteHandler;
16
14
  }
17
- export declare function parseJsonBody(req: IncomingMessage, maxBytes?: number): Promise<any>;
18
15
  /**
19
16
  * Create API routes for Taskforce
20
17
  *
@@ -60,10 +57,6 @@ export declare function parseJsonBody(req: IncomingMessage, maxBytes?: number):
60
57
  * - POST /api/taskforce/export-resources
61
58
  * - GET /api/taskforce/health
62
59
  */
63
- export interface RouteContext {
64
- authConfig?: TaskforceAuthConfig;
65
- objectStorage?: ResolvedObjectStorageConfig;
66
- }
67
60
  export declare function createRoutes(core: TaskforceCore, context?: RouteContext): Route[];
68
61
  export declare function matchRoute(method: string, pathname: string, routes: Route[]): {
69
62
  handler: RouteHandler;
@@ -14,7 +14,7 @@ import { parseAiProfileBootstrapHeaders } from '../mcp/aiProfileBootstrap.js';
14
14
  import { createTaskforceMcpServer, isMcpWriteToolName } from '../mcp/runtime.js';
15
15
  import { applyPrefixedRateLimitHeaders } from './rateLimit.js';
16
16
  import { createSessionTokenCookie, resolveRequestAccess } from './auth.js';
17
- import { resolveEffectiveEmailConfig, sendEmail } from './email.js';
17
+ import { sendEmail } from './email.js';
18
18
  import { normalizeTaskDataRelativePath, ObjectStorageClient } from '../storage/objectStorageClient.js';
19
19
  import { DocumentRegistry, createDocumentHash } from '../storage/documentRegistry.js';
20
20
  import { TaskAssetStore } from '../storage/taskAssetStore.js';
@@ -34,12 +34,35 @@ 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 { buildCloudMcpRuntimeAuth, requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, ensureWorkspaceAdminRole, } from './routes/shared.js';
37
+ import { escapeHtml, parseJsonBody } from './routes/primitives.js';
38
+ import { getRouteEmailConfig, resolvePublicAuthBaseUrl, resolveRuntimeConfigBaseUrl } from './routes/authSupport.js';
39
+ import { buildCloudMcpRuntimeAuth, requestHeader, requestRole, requestRuntimeMode, requestUserId, makeAuditAdminAction, makeAuditAuthAction, makeEnsureAdminRole, makeEnsureAppAccess, makeEnsureAuthenticatedUser, makeEnsureCloudPlanData, makeRequestWorkspaceId, makeRequireCloudRuntime, makeResolveAdminRequestActor, makeResolveAuthenticatedUserId, makeResolveAuthenticatedMutationUserId, } from './routes/shared.js';
38
40
  import { resolveDeploymentConfig } from '../config/deployment.js';
39
41
  import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../config/cloudEnvironment.js';
40
42
  import { getCanonicalDocumentName } from '../utils/documentNames.js';
41
43
  import { MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS } from '../mcp/toolRegistry.js';
42
- const DEFAULT_MAX_JSON_BODY_BYTES = 1024 * 1024; // 1MB
44
+ export { parseJsonBody } from './routes/primitives.js';
45
+ function pathMatchesRouteAuthExclusion(pathname, excludedPaths) {
46
+ if (!Array.isArray(excludedPaths))
47
+ return false;
48
+ return excludedPaths.some((path) => pathname === path || pathname.startsWith(`${path}/`));
49
+ }
50
+ function shouldRequireAuthenticatedCloudApiRoute(pathname, context) {
51
+ const authConfig = context.authConfig;
52
+ if (!authConfig?.enabled)
53
+ return false;
54
+ if (!pathname.startsWith('/api/taskforce/'))
55
+ return false;
56
+ if (pathname.startsWith('/api/taskforce/sync/'))
57
+ return false;
58
+ if (pathMatchesRouteAuthExclusion(pathname, authConfig.excludedPaths))
59
+ return false;
60
+ return true;
61
+ }
62
+ function writeRouteAuthenticationRequired(res) {
63
+ res.writeHead(401, { 'Content-Type': 'application/json' });
64
+ res.end(JSON.stringify({ error: 'Authentication required.' }));
65
+ }
43
66
  const DEFAULT_DOCUMENT_RETENTION_DAYS = 7;
44
67
  const STORAGE_INTEGRITY_HISTORY_LIMIT = 50;
45
68
  const MCP_TOKEN_SCOPE_VALUES = new Set(['tasks:read', 'tasks:write', 'workspace:read']);
@@ -70,20 +93,6 @@ function readPositiveInteger(value, fallback) {
70
93
  return fallback;
71
94
  return parsed;
72
95
  }
73
- function resolveBooleanSetting(value, fallback) {
74
- if (typeof value === 'boolean')
75
- return value;
76
- if (typeof value === 'number')
77
- return value !== 0;
78
- const normalized = String(value || '').trim().toLowerCase();
79
- if (!normalized)
80
- return fallback;
81
- if (['1', 'true', 'yes', 'on'].includes(normalized))
82
- return true;
83
- if (['0', 'false', 'no', 'off'].includes(normalized))
84
- return false;
85
- return fallback;
86
- }
87
96
  // Convert route pattern like '/api/taskforce/tasks/:id' to regex
88
97
  function createRoutePattern(path) {
89
98
  const keys = [];
@@ -93,74 +102,6 @@ function createRoutePattern(path) {
93
102
  });
94
103
  return { pattern: new RegExp(`^${pattern}$`), keys };
95
104
  }
96
- // Helper to parse JSON body from request with size guard.
97
- export async function parseJsonBody(req, maxBytes = DEFAULT_MAX_JSON_BODY_BYTES) {
98
- const safeMaxBytes = Number.isFinite(maxBytes) && maxBytes > 0
99
- ? Math.floor(maxBytes)
100
- : DEFAULT_MAX_JSON_BODY_BYTES;
101
- return new Promise((resolve, reject) => {
102
- let body = '';
103
- let receivedBytes = 0;
104
- req.on('data', chunk => {
105
- receivedBytes += chunk.length;
106
- if (receivedBytes > safeMaxBytes) {
107
- req.pause();
108
- reject(createHttpError(`Payload too large (max ${safeMaxBytes} bytes)`, 413));
109
- return;
110
- }
111
- body += chunk;
112
- });
113
- req.on('end', () => {
114
- try {
115
- resolve(body ? JSON.parse(body) : {});
116
- }
117
- catch (e) {
118
- reject(createHttpError('Invalid JSON', 400));
119
- }
120
- });
121
- req.on('error', reject);
122
- });
123
- }
124
- async function parseFormBody(req, maxBytes = DEFAULT_MAX_JSON_BODY_BYTES) {
125
- const safeMaxBytes = Number.isFinite(maxBytes) && maxBytes > 0
126
- ? Math.floor(maxBytes)
127
- : DEFAULT_MAX_JSON_BODY_BYTES;
128
- return new Promise((resolve, reject) => {
129
- let body = '';
130
- let receivedBytes = 0;
131
- req.on('data', chunk => {
132
- receivedBytes += chunk.length;
133
- if (receivedBytes > safeMaxBytes) {
134
- req.pause();
135
- reject(createHttpError(`Payload too large (max ${safeMaxBytes} bytes)`, 413));
136
- return;
137
- }
138
- body += chunk;
139
- });
140
- req.on('end', () => {
141
- try {
142
- const params = new URLSearchParams(body);
143
- const parsed = {};
144
- params.forEach((value, key) => {
145
- parsed[String(key)] = String(value);
146
- });
147
- resolve(parsed);
148
- }
149
- catch {
150
- reject(createHttpError('Invalid form body', 400));
151
- }
152
- });
153
- req.on('error', reject);
154
- });
155
- }
156
- function escapeHtml(value) {
157
- return String(value || '')
158
- .replace(/&/g, '&amp;')
159
- .replace(/</g, '&lt;')
160
- .replace(/>/g, '&gt;')
161
- .replace(/"/g, '&quot;')
162
- .replace(/'/g, '&#39;');
163
- }
164
105
  function resolveErrorStatusCode(error, fallback = 400) {
165
106
  if (error instanceof TaskforceRuleError)
166
107
  return error.statusCode;
@@ -206,12 +147,73 @@ function inferMcpOauthProviderFromRedirectUris(redirectUris) {
206
147
  }
207
148
  return MCP_OAUTH_DEFAULT_PROVIDER;
208
149
  }
150
+ /**
151
+ * Create API routes for Taskforce
152
+ *
153
+ * Core routes (29 total):
154
+ *
155
+ * **Task Management (12 routes)**
156
+ * - GET /api/taskforce/config
157
+ * - GET /api/taskforce/tasks
158
+ * - POST /api/taskforce/task
159
+ * - PATCH /api/taskforce/task/:id
160
+ * - DELETE /api/taskforce/task/:id
161
+ * - GET /api/taskforce/archive
162
+ * - POST /api/taskforce/task/:id/complete
163
+ * - POST /api/taskforce/task/:id/cancel
164
+ * - POST /api/taskforce/task/:id/comment
165
+ * - POST /api/taskforce/task/:id/unarchive
166
+ * - POST /api/taskforce/bulk-create
167
+ * - POST /api/taskforce/bulk-archive
168
+ * - POST /api/taskforce/bulk-update-fields
169
+ * - POST /api/taskforce/bulk-update-fields/preview
170
+ *
171
+ * **Configuration Management**
172
+ * - GET /api/taskforce/categories
173
+ * - POST /api/taskforce/categories
174
+ * - GET /api/taskforce/types
175
+ * - POST /api/taskforce/types
176
+ * - GET /api/taskforce/priorities
177
+ * - POST /api/taskforce/priorities
178
+ * - GET /api/taskforce/taxonomies
179
+ * - POST /api/taskforce/taxonomies
180
+ *
181
+ * **Workflow Settings**
182
+ * - GET /api/taskforce/approaches
183
+ * - POST /api/taskforce/approaches
184
+ *
185
+ * **Templates & Setup**
186
+ * - GET /api/taskforce/initiative-templates
187
+ * - POST /api/taskforce/initiative-templates/create
188
+ *
189
+ * **Advanced Features (3 routes)**
190
+ * - GET /api/taskforce/folders
191
+ * - POST /api/taskforce/validate-paths
192
+ * - POST /api/taskforce/export-resources
193
+ * - GET /api/taskforce/health
194
+ */
209
195
  export function createRoutes(core, context = {}) {
210
196
  const routes = [];
211
197
  const uiStateCache = new Map();
212
198
  const addRoute = (method, path, handler) => {
199
+ const wrappedHandler = async (req, res, params) => {
200
+ const runtimeHeader = requestHeader(req, 'x-taskforce-runtime-mode', '').trim().toLowerCase();
201
+ const runtimeMode = runtimeHeader === 'cloud'
202
+ ? 'cloud'
203
+ : runtimeHeader === 'local'
204
+ ? 'local'
205
+ : (context.authConfig?.runtimeMode || requestRuntimeMode(req));
206
+ if (runtimeMode === 'cloud' && shouldRequireAuthenticatedCloudApiRoute(path, context)) {
207
+ const access = resolveRequestAccess(req, context.authConfig);
208
+ if (!access.authenticated || !access.userId || access.userId === 'anonymous') {
209
+ writeRouteAuthenticationRequired(res);
210
+ return;
211
+ }
212
+ }
213
+ await handler(req, res, params);
214
+ };
213
215
  const { pattern, keys } = createRoutePattern(`${method}:${path}`);
214
- routes.push({ pattern, keys, handler });
216
+ routes.push({ pattern, keys, handler: wrappedHandler });
215
217
  };
216
218
  const buildUiStateCacheKey = (workspaceId, userId, stateKey) => `${workspaceId}::${userId}::${stateKey}`;
217
219
  const readCachedUiState = (workspaceId, userId, stateKey) => {
@@ -299,33 +301,6 @@ export function createRoutes(core, context = {}) {
299
301
  return null;
300
302
  return resolved;
301
303
  };
302
- const shouldExposeDevAuthTokens = () => {
303
- const explicit = String(process.env.TASKFORCE_AUTH_EXPOSE_DEV_TOKENS || '').trim().toLowerCase();
304
- if (explicit === '1' || explicit === 'true' || explicit === 'yes')
305
- return true;
306
- if (explicit === '0' || explicit === 'false' || explicit === 'no')
307
- return false;
308
- return String(process.env.NODE_ENV || '').trim().toLowerCase() !== 'production';
309
- };
310
- const resolveRuntimeConfigBaseUrl = (req) => {
311
- const host = String(req.headers['x-forwarded-host'] || req.headers.host || '').trim();
312
- if (!host)
313
- return '';
314
- const proto = String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https'
315
- ? 'https'
316
- : (req.socket?.encrypted ? 'https' : 'http');
317
- return `${proto}://${host}`.replace(/\/+$/, '');
318
- };
319
- const resolvePublicAuthBaseUrl = (req) => {
320
- const configured = String(process.env.TASKFORCE_AUTH_PUBLIC_BASE_URL || '').trim().replace(/\/+$/, '');
321
- if (configured)
322
- return configured;
323
- const host = String(req.headers['x-forwarded-host'] || req.headers.host || '').trim();
324
- const proto = String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'http' ? 'http' : 'https';
325
- if (!host)
326
- return resolveDeploymentConfig(process.env).cloudBaseUrl || 'https://app.taskforcehq.ai';
327
- return `${proto}://${host}`;
328
- };
329
304
  const logAuthLink = (kind, email, token, req) => {
330
305
  const base = resolvePublicAuthBaseUrl(req);
331
306
  const path = kind === 'verify-email'
@@ -333,34 +308,6 @@ export function createRoutes(core, context = {}) {
333
308
  : `/login?mode=reset&token=${encodeURIComponent(token)}`;
334
309
  console.info(`[Taskforce Auth] ${kind} email=${email} link=${base}${path}`);
335
310
  };
336
- const getEmailConfig = () => {
337
- if (context.authConfig?.runtimeMode !== 'cloud') {
338
- return resolveEffectiveEmailConfig(process.env, { provider: 'console' });
339
- }
340
- const globalSettings = core.getGlobalSettings();
341
- return resolveEffectiveEmailConfig(process.env, globalSettings.email || null);
342
- };
343
- const getAuthBehavior = () => {
344
- const globalSettings = core.getGlobalSettings();
345
- const authSettings = globalSettings.auth || {};
346
- const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
347
- const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
348
- const onboardingPolicy = core.getOnboardingPolicy();
349
- return {
350
- allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
351
- requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
352
- defaultNewUserBetaAccess: resolveBooleanSetting(authSettings.defaultNewUserBetaAccess, false),
353
- authRateLimitEnabled: resolveBooleanSetting(authSettings.authRateLimitEnabled, true),
354
- authRateLimitMaxRequests: Number.isFinite(authRateLimitMaxRequestsRaw) && authRateLimitMaxRequestsRaw >= 1
355
- ? Math.floor(authRateLimitMaxRequestsRaw)
356
- : 10,
357
- authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
358
- ? Math.floor(authRateLimitWindowMsRaw)
359
- : 15 * 60_000,
360
- onboardingPolicyMode: onboardingPolicy.mode,
361
- onboardingPlanVersionId: onboardingPolicy.planVersionId
362
- };
363
- };
364
311
  const deliverAuthEmail = async (kind, email, token, req) => {
365
312
  const base = resolvePublicAuthBaseUrl(req);
366
313
  const path = kind === 'verify-email'
@@ -378,7 +325,7 @@ export function createRoutes(core, context = {}) {
378
325
  to: email,
379
326
  subject,
380
327
  text
381
- }, getEmailConfig());
328
+ }, getRouteEmailConfig(core, context));
382
329
  if (result.ok) {
383
330
  console.info(`[Taskforce Auth Email] kind=${kind} provider=${result.provider} to=${email} messageId=${result.messageId || '-'}`);
384
331
  }
@@ -396,7 +343,7 @@ export function createRoutes(core, context = {}) {
396
343
  to: email,
397
344
  subject,
398
345
  text
399
- }, getEmailConfig());
346
+ }, getRouteEmailConfig(core, context));
400
347
  if (result.ok) {
401
348
  console.info(`[Taskforce Auth Email] kind=invite provider=${result.provider} to=${email} messageId=${result.messageId || '-'}`);
402
349
  }
@@ -799,7 +746,10 @@ export function createRoutes(core, context = {}) {
799
746
  const contentDisposition = params.forceDownload
800
747
  ? `attachment; filename="${resolvedDownloadName.replace(/"/g, '')}"`
801
748
  : 'inline';
802
- if (params.asset.storageProvider === 'r2' && objectStorageClient && resolvedObjectStorage.provider === 'r2' && resolvedObjectStorage.r2) {
749
+ const readFromObjectStorage = async () => {
750
+ if (!objectStorageClient || resolvedObjectStorage.provider !== 'r2' || !resolvedObjectStorage.r2) {
751
+ return { statusCode: 404 };
752
+ }
803
753
  if (!params.forceDownload) {
804
754
  const object = await objectStorageClient.getObject(params.asset.storageKey);
805
755
  if (!object) {
@@ -820,19 +770,22 @@ export function createRoutes(core, context = {}) {
820
770
  Location: objectStorageClient.createSignedGetUrl(params.asset.storageKey, resolvedObjectStorage.r2.signedDownloadTtlSeconds, { responseContentDisposition: contentDisposition })
821
771
  }
822
772
  };
773
+ };
774
+ if (params.asset.storageProvider === 'r2' && objectStorageClient && resolvedObjectStorage.provider === 'r2' && resolvedObjectStorage.r2) {
775
+ return readFromObjectStorage();
823
776
  }
824
777
  const filePath = ensureWithinDir(path.join(basePath, params.asset.storageKey), basePath);
825
- if (!filePath || !fs.existsSync(filePath)) {
826
- return { statusCode: 404 };
778
+ if (filePath && fs.existsSync(filePath)) {
779
+ return {
780
+ statusCode: 200,
781
+ headers: {
782
+ 'Content-Type': params.asset.mimeType || contentTypeForExt(ext),
783
+ 'Content-Disposition': contentDisposition
784
+ },
785
+ body: fs.readFileSync(filePath)
786
+ };
827
787
  }
828
- return {
829
- statusCode: 200,
830
- headers: {
831
- 'Content-Type': params.asset.mimeType || contentTypeForExt(ext),
832
- 'Content-Disposition': contentDisposition
833
- },
834
- body: fs.readFileSync(filePath)
835
- };
788
+ return readFromObjectStorage();
836
789
  };
837
790
  const getAssetMetadata = (pathValue) => {
838
791
  const canonical = getCanonicalAssetMetadata(pathValue);
@@ -1109,7 +1062,7 @@ export function createRoutes(core, context = {}) {
1109
1062
  return null;
1110
1063
  return workspaceId;
1111
1064
  };
1112
- const ensureContextUploadAllowed = (req, res, workspaceId) => {
1065
+ const ensureContextUploadAllowed = (req, res, workspaceId, options) => {
1113
1066
  if (requestRuntimeMode(req) !== 'cloud')
1114
1067
  return true;
1115
1068
  if (!ensureAuthenticatedUser(req)) {
@@ -1120,12 +1073,17 @@ export function createRoutes(core, context = {}) {
1120
1073
  const gate = core.evaluateFeatureAccess({
1121
1074
  userId: requestUserId(req),
1122
1075
  workspaceId,
1123
- featureKey: 'context.uploads'
1076
+ featureKey: 'context.uploads',
1077
+ context: {
1078
+ requestedBytes: options?.requestedBytes
1079
+ }
1124
1080
  });
1125
1081
  if (!gate.allowed) {
1126
1082
  res.writeHead(403, { 'Content-Type': 'application/json' });
1127
1083
  res.end(JSON.stringify({
1128
- error: 'Document cloud storage is unavailable for this account entitlement.',
1084
+ error: gate.reasonCode === 'UPLOAD_STORAGE_LIMIT_REACHED'
1085
+ ? 'This upload exceeds the document cloud storage limit for the current plan.'
1086
+ : 'Document cloud storage is unavailable for this account entitlement.',
1129
1087
  code: gate.reasonCode
1130
1088
  }));
1131
1089
  return false;
@@ -1491,7 +1449,26 @@ export function createRoutes(core, context = {}) {
1491
1449
  const auditAdminAction = makeAuditAdminAction(core, context);
1492
1450
  const auditAuthAction = makeAuditAuthAction(core, requestWorkspaceId, context);
1493
1451
  const ensureAdminRole = makeEnsureAdminRole(core, context);
1494
- const ensureAuthenticatedUser = makeEnsureAuthenticatedUser();
1452
+ const ensureAuthenticatedUser = makeEnsureAuthenticatedUser(context.authConfig);
1453
+ const ensureWorkspaceAdminRole = (req, workspaceIdOverride) => {
1454
+ const hasWorkspaceOverride = typeof workspaceIdOverride === 'string' && workspaceIdOverride.trim().length > 0;
1455
+ const workspaceId = String(hasWorkspaceOverride ? workspaceIdOverride : requestWorkspaceId(req) || '').trim() || 'default';
1456
+ const userId = String(resolveAuthenticatedUserId(req) || '').trim();
1457
+ if (userId && userId !== 'anonymous') {
1458
+ const membership = core.listWorkspaceUsers(workspaceId).find((user) => String(user.userId || '').trim() === userId);
1459
+ if (membership) {
1460
+ const role = String(membership.role || '').trim().toLowerCase();
1461
+ const status = String(membership.status || '').trim().toLowerCase();
1462
+ return membership.disabled !== true
1463
+ && status === 'active'
1464
+ && (role === 'owner' || role === 'admin');
1465
+ }
1466
+ if (hasWorkspaceOverride)
1467
+ return false;
1468
+ }
1469
+ const fallbackRole = String(requestRole(req) || '').trim().toLowerCase();
1470
+ return fallbackRole === 'owner' || fallbackRole === 'admin';
1471
+ };
1495
1472
  const isCollaborationPolicyEnabled = () => {
1496
1473
  const rawDefault = String(process.env.TASKFORCE_COLLAB_POLICY_V1 || 'true').trim().toLowerCase();
1497
1474
  const envDefaultEnabled = !(rawDefault === '0' || rawDefault === 'false' || rawDefault === 'off' || rawDefault === 'no');
@@ -1543,6 +1520,7 @@ export function createRoutes(core, context = {}) {
1543
1520
  context,
1544
1521
  requestWorkspaceId,
1545
1522
  requestUserId,
1523
+ resolveAuthenticatedUserId,
1546
1524
  requestRole,
1547
1525
  ensureAppAccess,
1548
1526
  ensureAuthenticatedUser,
@@ -1551,9 +1529,6 @@ export function createRoutes(core, context = {}) {
1551
1529
  requireCloudRuntime,
1552
1530
  auditAuthAction,
1553
1531
  resolveAdminRequestActor,
1554
- shouldExposeDevAuthTokens,
1555
- resolveRuntimeConfigBaseUrl,
1556
- resolvePublicAuthBaseUrl,
1557
1532
  resolveEffectiveObjectStorageConfig,
1558
1533
  getObjectStorageClient,
1559
1534
  });
@@ -1604,10 +1579,6 @@ export function createRoutes(core, context = {}) {
1604
1579
  appendDocumentPurgeHistory,
1605
1580
  getCollaborationTelemetry,
1606
1581
  isCollaborationPolicyEnabled,
1607
- resolveRuntimeConfigBaseUrl,
1608
- resolvePublicAuthBaseUrl,
1609
- parseFormBody,
1610
- escapeHtml,
1611
1582
  normalizeMcpOauthScopeString,
1612
1583
  MCP_TOKEN_SCOPE_VALUES,
1613
1584
  MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS,