@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
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Apple OAuth client-secret JWT minter.
3
+ *
4
+ * Apple Sign In requires a short-lived JWT as the OAuth `client_secret` rather
5
+ * than a static secret. The JWT must be:
6
+ * - Signed with ES256 (ECDSA P-256) using the private key registered in App Store Connect
7
+ * - Valid for at most 6 months (Apple enforces this; we use 10 min for freshness)
8
+ * - Cached so callers don't re-sign on every request
9
+ *
10
+ * Reference: https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
11
+ */
12
+ import { sign } from 'crypto';
13
+ // ~9 min — well under Apple's 10-min max, allows safe margin for clock skew
14
+ const CACHE_TTL_MS = 9 * 60 * 1000;
15
+ // Apple max is 6 months; we use 10 min so tokens stay fresh
16
+ const TOKEN_LIFETIME_SECONDS = 600;
17
+ const secretCache = new Map();
18
+ function configCacheKey(cfg) {
19
+ return `${cfg.teamId}:${cfg.servicesId}:${cfg.keyId}`;
20
+ }
21
+ function buildJwt(cfg) {
22
+ const now = Math.floor(Date.now() / 1000);
23
+ const header = Buffer.from(JSON.stringify({ alg: 'ES256', kid: cfg.keyId })).toString('base64url');
24
+ const payload = Buffer.from(JSON.stringify({
25
+ iss: cfg.teamId,
26
+ iat: now,
27
+ exp: now + TOKEN_LIFETIME_SECONDS,
28
+ aud: 'https://appleid.apple.com',
29
+ sub: cfg.servicesId
30
+ })).toString('base64url');
31
+ const signingInput = `${header}.${payload}`;
32
+ // sign() with dsaEncoding: 'ieee-p1363' produces raw r||s (64 bytes) required by JWT ES256
33
+ const signature = sign('SHA256', Buffer.from(signingInput), {
34
+ key: cfg.privateKeyPem,
35
+ dsaEncoding: 'ieee-p1363'
36
+ }).toString('base64url');
37
+ return `${signingInput}.${signature}`;
38
+ }
39
+ /**
40
+ * Returns a cached client-secret JWT for the given Apple config, minting a
41
+ * fresh one if the cache entry is absent or within 60 s of expiry.
42
+ */
43
+ export function getAppleClientSecret(cfg) {
44
+ const key = configCacheKey(cfg);
45
+ const cached = secretCache.get(key);
46
+ if (cached && cached.expiresAt > Date.now() + 60_000) {
47
+ return cached.token;
48
+ }
49
+ const token = buildJwt(cfg);
50
+ secretCache.set(key, { token, expiresAt: Date.now() + CACHE_TTL_MS });
51
+ return token;
52
+ }
53
+ /**
54
+ * Force-mints a fresh JWT (bypasses cache) for health-check purposes.
55
+ * Returns success/error without updating the cache.
56
+ */
57
+ export function testMintAppleClientSecret(cfg) {
58
+ try {
59
+ const token = buildJwt(cfg);
60
+ return { ok: true, token };
61
+ }
62
+ catch (err) {
63
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
64
+ }
65
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * GitHub OAuth adapter.
3
+ *
4
+ * Flow:
5
+ * 1. Redirect user to buildGithubAuthUrl() (includes PKCE S256 for GitHub Apps).
6
+ * 2. Receive code on callback; call exchangeGithubCode() to get profile.
7
+ *
8
+ * Email selection rules:
9
+ * - Prefer the primary + verified email from /user/emails.
10
+ * - Fall back to any verified email, then to user.email (public profile field).
11
+ * - If none is available, email is null.
12
+ */
13
+ import type { NormalizedOAuthProfile } from './types.js';
14
+ export declare function buildGithubAuthUrl(input: {
15
+ clientId: string;
16
+ redirectUri: string;
17
+ state: string;
18
+ codeChallenge: string;
19
+ }): string;
20
+ export declare function exchangeGithubCode(input: {
21
+ code: string;
22
+ codeVerifier: string;
23
+ redirectUri: string;
24
+ clientId: string;
25
+ clientSecret: string;
26
+ }): Promise<NormalizedOAuthProfile>;
@@ -0,0 +1,86 @@
1
+ export function buildGithubAuthUrl(input) {
2
+ const params = new URLSearchParams({
3
+ client_id: input.clientId,
4
+ redirect_uri: input.redirectUri,
5
+ state: input.state,
6
+ scope: 'read:user user:email',
7
+ code_challenge: input.codeChallenge,
8
+ code_challenge_method: 'S256',
9
+ });
10
+ return `https://github.com/login/oauth/authorize?${params.toString()}`;
11
+ }
12
+ export async function exchangeGithubCode(input) {
13
+ // Step 1: exchange authorisation code for access token
14
+ const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
15
+ method: 'POST',
16
+ headers: {
17
+ 'Content-Type': 'application/x-www-form-urlencoded',
18
+ Accept: 'application/json',
19
+ },
20
+ body: new URLSearchParams({
21
+ client_id: input.clientId,
22
+ client_secret: input.clientSecret,
23
+ code: input.code,
24
+ redirect_uri: input.redirectUri,
25
+ code_verifier: input.codeVerifier,
26
+ }).toString(),
27
+ });
28
+ if (!tokenRes.ok) {
29
+ const body = await tokenRes.text().catch(() => '');
30
+ throw new Error(`GitHub token exchange failed (${tokenRes.status}): ${body}`);
31
+ }
32
+ const tokens = await tokenRes.json();
33
+ if (tokens.error) {
34
+ throw new Error(`GitHub token exchange error: ${tokens.error}${tokens.error_description ? ` — ${tokens.error_description}` : ''}`);
35
+ }
36
+ const accessToken = String(tokens.access_token || '').trim();
37
+ if (!accessToken)
38
+ throw new Error('GitHub token exchange: missing access_token');
39
+ // Step 2: fetch user + emails in parallel
40
+ const ghHeaders = {
41
+ Authorization: `Bearer ${accessToken}`,
42
+ 'User-Agent': 'Taskforce/1.0',
43
+ Accept: 'application/vnd.github+json',
44
+ };
45
+ const [userRes, emailsRes] = await Promise.all([
46
+ fetch('https://api.github.com/user', { headers: ghHeaders }),
47
+ fetch('https://api.github.com/user/emails', { headers: ghHeaders }),
48
+ ]);
49
+ if (!userRes.ok) {
50
+ throw new Error(`GitHub /user API failed (${userRes.status})`);
51
+ }
52
+ const user = await userRes.json();
53
+ const numericId = String(user.id || '').trim();
54
+ if (!numericId)
55
+ throw new Error('GitHub /user API: missing id');
56
+ // Step 3: resolve best email
57
+ let email = null;
58
+ let emailVerified = false;
59
+ if (emailsRes.ok) {
60
+ const emails = await emailsRes.json().catch(() => []);
61
+ const primaryVerified = emails.find(e => e.primary === true && e.verified === true);
62
+ if (primaryVerified) {
63
+ email = String(primaryVerified.email || '').trim().toLowerCase() || null;
64
+ emailVerified = true;
65
+ }
66
+ else {
67
+ const anyVerified = emails.find(e => e.verified === true);
68
+ if (anyVerified) {
69
+ email = String(anyVerified.email || '').trim().toLowerCase() || null;
70
+ emailVerified = true;
71
+ }
72
+ }
73
+ }
74
+ if (!email) {
75
+ // Last resort: public profile email (may be null / not set)
76
+ email = String(user.email || '').trim().toLowerCase() || null;
77
+ }
78
+ return {
79
+ provider: 'github',
80
+ providerSubject: numericId,
81
+ email,
82
+ emailVerified,
83
+ displayName: String(user.name || user.login || '').trim() || null,
84
+ avatarUrl: String(user.avatar_url || '').trim() || null,
85
+ };
86
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Google OAuth 2.0 / OpenID Connect adapter.
3
+ *
4
+ * Flow:
5
+ * 1. Redirect user to buildGoogleAuthUrl() (PKCE + S256).
6
+ * 2. Receive code on callback; call exchangeGoogleCode() to get profile.
7
+ */
8
+ import type { NormalizedOAuthProfile } from './types.js';
9
+ export declare function buildGoogleAuthUrl(input: {
10
+ clientId: string;
11
+ redirectUri: string;
12
+ state: string;
13
+ codeChallenge: string;
14
+ }): string;
15
+ export declare function exchangeGoogleCode(input: {
16
+ code: string;
17
+ codeVerifier: string;
18
+ redirectUri: string;
19
+ clientId: string;
20
+ clientSecret: string;
21
+ }): Promise<NormalizedOAuthProfile>;
@@ -0,0 +1,56 @@
1
+ export function buildGoogleAuthUrl(input) {
2
+ const params = new URLSearchParams({
3
+ client_id: input.clientId,
4
+ redirect_uri: input.redirectUri,
5
+ response_type: 'code',
6
+ scope: 'openid email profile',
7
+ state: input.state,
8
+ code_challenge: input.codeChallenge,
9
+ code_challenge_method: 'S256',
10
+ access_type: 'offline',
11
+ prompt: 'select_account',
12
+ });
13
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
14
+ }
15
+ export async function exchangeGoogleCode(input) {
16
+ // Step 1: exchange authorisation code for tokens
17
+ const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
18
+ method: 'POST',
19
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
20
+ body: new URLSearchParams({
21
+ grant_type: 'authorization_code',
22
+ code: input.code,
23
+ redirect_uri: input.redirectUri,
24
+ client_id: input.clientId,
25
+ client_secret: input.clientSecret,
26
+ code_verifier: input.codeVerifier,
27
+ }).toString(),
28
+ });
29
+ if (!tokenRes.ok) {
30
+ const body = await tokenRes.text().catch(() => '');
31
+ throw new Error(`Google token exchange failed (${tokenRes.status}): ${body}`);
32
+ }
33
+ const tokens = await tokenRes.json();
34
+ const accessToken = String(tokens.access_token || '').trim();
35
+ if (!accessToken)
36
+ throw new Error('Google token exchange: missing access_token');
37
+ // Step 2: fetch userinfo
38
+ const userinfoRes = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
39
+ headers: { Authorization: `Bearer ${accessToken}` },
40
+ });
41
+ if (!userinfoRes.ok) {
42
+ throw new Error(`Google userinfo failed (${userinfoRes.status})`);
43
+ }
44
+ const info = await userinfoRes.json();
45
+ const sub = String(info.sub || '').trim();
46
+ if (!sub)
47
+ throw new Error('Google userinfo: missing sub');
48
+ return {
49
+ provider: 'google',
50
+ providerSubject: sub,
51
+ email: String(info.email || '').trim().toLowerCase() || null,
52
+ emailVerified: info.email_verified === true,
53
+ displayName: String(info.name || '').trim() || null,
54
+ avatarUrl: String(info.picture || '').trim() || null,
55
+ };
56
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Normalised OAuth profile returned by every provider adapter.
3
+ * The callback route uses this to resolve/create the Taskforce identity.
4
+ */
5
+ export type OAuthProviderName = 'google' | 'github' | 'apple';
6
+ export interface NormalizedOAuthProfile {
7
+ /** The provider that authenticated the user. */
8
+ provider: OAuthProviderName;
9
+ /** Stable, provider-assigned user ID (Google `sub`, GitHub numeric `id`, Apple `sub`). */
10
+ providerSubject: string;
11
+ /** Primary email address, or null if the provider withheld it (e.g. Apple private relay). */
12
+ email: string | null;
13
+ /** Whether the provider has verified ownership of the email address. */
14
+ emailVerified: boolean;
15
+ /** Human-readable display name, or null if not provided. */
16
+ displayName: string | null;
17
+ /** Avatar / profile picture URL, or null if not provided. */
18
+ avatarUrl: string | null;
19
+ /** Apple-specific: true when `email` is a private relay address. */
20
+ isPrivateEmail?: boolean;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -30,6 +30,7 @@ export declare function createSessionTokenCookie(sessionId: string, config: Task
30
30
  role?: TaskforceRole;
31
31
  workspaceId?: string;
32
32
  userId?: string;
33
+ identityId?: string;
33
34
  secure?: boolean;
34
35
  sameSite?: 'Lax' | 'Strict' | 'None';
35
36
  }): string;
@@ -42,6 +43,7 @@ export interface RequestAccessContext {
42
43
  systemRole: TaskforceSystemRole;
43
44
  workspaceId: string;
44
45
  userId: string;
46
+ identityId: string | null;
45
47
  }
46
48
  export declare function resolveRequestAccess(req: IncomingMessage, config: TaskforceAuthConfig): RequestAccessContext;
47
49
  export declare function isRequestAuthorized(req: IncomingMessage, config: TaskforceAuthConfig): {
@@ -176,7 +176,8 @@ export function createSessionTokenCookie(sessionId, config, options) {
176
176
  exp,
177
177
  role: options?.role,
178
178
  workspaceId: options?.workspaceId,
179
- userId: options?.userId
179
+ userId: options?.userId,
180
+ identityId: options?.identityId
180
181
  }));
181
182
  const sig = signPayload(payload, config.sessionSecret);
182
183
  const value = `${payload}.${sig}`;
@@ -240,7 +241,8 @@ export function resolveRequestAccess(req, config) {
240
241
  role: config.defaultRole,
241
242
  systemRole: 'user',
242
243
  workspaceId: fallbackWorkspaceId,
243
- userId: fallbackUserId
244
+ userId: fallbackUserId,
245
+ identityId: null
244
246
  };
245
247
  const sessionClaims = readSessionClaimsFromRequest(req, config);
246
248
  if (sessionClaims?.sessionId) {
@@ -251,7 +253,8 @@ export function resolveRequestAccess(req, config) {
251
253
  role,
252
254
  systemRole: 'user',
253
255
  workspaceId: String(sessionClaims.workspaceId || config.defaultWorkspaceId).trim() || config.defaultWorkspaceId,
254
- userId: String(sessionClaims.userId || fallbackUserId).trim() || fallbackUserId
256
+ userId: String(sessionClaims.userId || fallbackUserId).trim() || fallbackUserId,
257
+ identityId: String(sessionClaims.identityId || '').trim() || null
255
258
  };
256
259
  }
257
260
  return base;
@@ -70,9 +70,9 @@ describe('document review routes', () => {
70
70
  core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
71
71
  const now = new Date().toISOString();
72
72
  core.db.prepare(`
73
- INSERT INTO users (id, tenant_id, email, display_name, auth_provider, external_auth_id, beta_access, is_disabled, created_at, updated_at)
74
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
75
- `).run('user-local-1', core.getTenantId(), 'local@example.com', 'Local User', 'local', 'user-local-1', 1, 0, now, now);
73
+ INSERT INTO users (id, tenant_id, email, display_name, beta_access, is_disabled, created_at, updated_at)
74
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
75
+ `).run('user-local-1', core.getTenantId(), 'local@example.com', 'Local User', 1, 0, now, now);
76
76
  core.db.prepare(`
77
77
  INSERT INTO workspace_memberships (id, tenant_id, workspace_id, user_id, role, status, created_at, updated_at)
78
78
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
@@ -154,4 +154,37 @@ describe('document review routes', () => {
154
154
  fs.rmSync(projectRoot, { recursive: true, force: true });
155
155
  }
156
156
  });
157
+ it('blocks anonymous cloud access before reading document review sessions', async () => {
158
+ const projectRoot = createProjectRoot();
159
+ const core = new TaskforceCore({
160
+ projectRoot,
161
+ storagePath: path.join(projectRoot, '.taskforce'),
162
+ });
163
+ try {
164
+ const routes = createRoutes(core, {
165
+ authConfig: {
166
+ runtimeMode: 'cloud',
167
+ enabled: true,
168
+ requiredForApi: true,
169
+ excludedPaths: ['/api/taskforce/health', '/api/taskforce/auth'],
170
+ defaultRole: 'member',
171
+ defaultWorkspaceId: '',
172
+ sessionCookieName: 'taskforce_session',
173
+ sessionSecret: 'test-secret',
174
+ sessionTtlSeconds: 3600,
175
+ sessionCookieSameSite: 'None',
176
+ sessionCookieSecure: true,
177
+ },
178
+ });
179
+ const req = createMockReq('GET', '/api/taskforce/document-reviews/sessions?assetId=asset-doc-1', undefined, { 'x-taskforce-runtime-mode': 'cloud' });
180
+ const res = createMockRes();
181
+ await matchRoute('GET', '/api/taskforce/document-reviews/sessions', routes)?.handler(req, res, {});
182
+ expect(res.statusCode).toBe(401);
183
+ expect(res.__getBody()).toContain('Authentication required.');
184
+ }
185
+ finally {
186
+ core.close();
187
+ fs.rmSync(projectRoot, { recursive: true, force: true });
188
+ }
189
+ });
157
190
  });
@@ -29,6 +29,7 @@ describe('proxy cookie rewrite helpers', () => {
29
29
  expect(shouldProxyCloudPath('/api/taskforce/billing/checkout-session')).toBe(true);
30
30
  expect(shouldProxyCloudPath('/api/taskforce/auth/session')).toBe(true);
31
31
  expect(shouldProxyCloudPath('/api/taskforce/workspace/assignee-options')).toBe(false);
32
+ expect(shouldProxyCloudPath('/api/taskforce/workspace/ai-profiles/avatar')).toBe(false);
32
33
  expect(shouldProxyCloudPath('/api/taskforce/settings/mcp/tokens')).toBe(true);
33
34
  expect(shouldProxyCloudPath('/api/taskforce/settings/mcp/test-connection')).toBe(true);
34
35
  expect(shouldProxyCloudPath('/api/platform/plans')).toBe(true);
@@ -11,6 +11,13 @@ export interface StandaloneServerOptions {
11
11
  projectRoot: string;
12
12
  port: number;
13
13
  }
14
+ export type HttpServerTimeoutSettings = {
15
+ keepAliveTimeoutMs: number;
16
+ headersTimeoutMs: number;
17
+ requestTimeoutMs: number;
18
+ };
19
+ export declare function resolveHttpServerTimeoutSettings(env?: NodeJS.ProcessEnv): HttpServerTimeoutSettings;
20
+ export declare function configureHttpServerTimeouts(server: Pick<http.Server, 'keepAliveTimeout' | 'headersTimeout' | 'requestTimeout'>, env?: NodeJS.ProcessEnv): HttpServerTimeoutSettings;
14
21
  export declare const TASKFORCE_PROXY_ORIGIN_HOST_HEADER = "x-taskforce-origin-host";
15
22
  export declare const TASKFORCE_PROXY_ORIGIN_PROTO_HEADER = "x-taskforce-origin-proto";
16
23
  export declare const TASKFORCE_PROXY_ORIGIN_BASE_URL_HEADER = "x-taskforce-origin-base-url";
@@ -18,10 +25,15 @@ export declare const TASKFORCE_PROXY_ORIGIN_RUNTIME_HEADER = "x-taskforce-origin
18
25
  export declare const TASKFORCE_PROXY_CLOUD_ENVIRONMENT_HEADER = "x-taskforce-cloud-environment";
19
26
  interface AuthorizedRequestAccess extends RequestAccessContext {
20
27
  disabled?: boolean;
28
+ accountDisabled?: boolean;
29
+ workspaceDisabled?: boolean;
21
30
  }
31
+ export declare function isWorkspaceAccessRecoveryRoute(pathname: string): boolean;
32
+ export declare function resolveDisabledAccessError(access: Pick<AuthorizedRequestAccess, 'disabled' | 'accountDisabled' | 'workspaceDisabled'>, pathname: string): string | null;
22
33
  export declare function shouldProxyCloudPath(pathname: string): boolean;
23
34
  export declare function resolveCloudProxyUpstreamBaseUrl(req: http.IncomingMessage, fallbackBaseUrl: string): string;
24
35
  export declare function resolveAuthorizedWorkspaceAccess(core: Pick<TaskforceCore, 'resolveUserAccess' | 'getSystemRole'>, access: AuthorizedRequestAccess, requestedWorkspaceId: string): AuthorizedRequestAccess;
36
+ export declare function shouldApplyReadOnlyMutationGuard(methodRaw: string | undefined, pathname: string): boolean;
25
37
  export declare function resolveEffectiveRequestUserId(accessUserId: string, explicitUserId: string): string;
26
38
  export declare function resolveMaxPayloadBytesForPath(pathname: string, defaultMaxBytes: number): number;
27
39
  export declare function shouldRewriteSetCookieForLocalDevHost(hostHeader: string | undefined): boolean;
@@ -84,11 +84,85 @@ const CORS_RESPONSE_HEADERS = new Set([
84
84
  'access-control-max-age',
85
85
  'vary'
86
86
  ]);
87
+ const REFRESH_DIAGNOSTIC_PATH_PREFIXES = [
88
+ '/api/taskforce/auth/session',
89
+ '/api/taskforce/config',
90
+ '/api/taskforce/tasks',
91
+ '/api/taskforce/archive',
92
+ '/api/taskforce/workspaces',
93
+ '/api/taskforce/workspace/assignee-options',
94
+ '/api/taskforce/auth/workspace-members',
95
+ '/api/taskforce/taxonomy-state',
96
+ '/api/taskforce/categories',
97
+ '/api/taskforce/types',
98
+ '/api/taskforce/priorities',
99
+ '/api/taskforce/taxonomies',
100
+ '/api/taskforce/initiative-templates',
101
+ '/api/taskforce/workflow-templates',
102
+ '/api/taskforce/workflow-editor/overrides',
103
+ '/api/taskforce/environments',
104
+ '/api/taskforce/sync/'
105
+ ];
106
+ const REFRESH_DIAGNOSTIC_DURATION_MS = 100;
107
+ const DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT_MS = 65_000;
108
+ const DEFAULT_HTTP_HEADERS_TIMEOUT_MS = 70_000;
109
+ const DEFAULT_HTTP_REQUEST_TIMEOUT_MS = 120_000;
110
+ export function resolveHttpServerTimeoutSettings(env = process.env) {
111
+ const keepAliveTimeoutMs = readPositiveInteger(env.TASKFORCE_HTTP_KEEP_ALIVE_TIMEOUT_MS, DEFAULT_HTTP_KEEP_ALIVE_TIMEOUT_MS);
112
+ const configuredHeadersTimeoutMs = readPositiveInteger(env.TASKFORCE_HTTP_HEADERS_TIMEOUT_MS, DEFAULT_HTTP_HEADERS_TIMEOUT_MS);
113
+ const headersTimeoutMs = Math.max(configuredHeadersTimeoutMs, keepAliveTimeoutMs + 5_000);
114
+ const requestTimeoutMs = readPositiveInteger(env.TASKFORCE_HTTP_REQUEST_TIMEOUT_MS, DEFAULT_HTTP_REQUEST_TIMEOUT_MS);
115
+ return {
116
+ keepAliveTimeoutMs,
117
+ headersTimeoutMs,
118
+ requestTimeoutMs
119
+ };
120
+ }
121
+ export function configureHttpServerTimeouts(server, env = process.env) {
122
+ const settings = resolveHttpServerTimeoutSettings(env);
123
+ server.keepAliveTimeout = settings.keepAliveTimeoutMs;
124
+ server.headersTimeout = settings.headersTimeoutMs;
125
+ server.requestTimeout = settings.requestTimeoutMs;
126
+ return settings;
127
+ }
87
128
  export const TASKFORCE_PROXY_ORIGIN_HOST_HEADER = 'x-taskforce-origin-host';
88
129
  export const TASKFORCE_PROXY_ORIGIN_PROTO_HEADER = 'x-taskforce-origin-proto';
89
130
  export const TASKFORCE_PROXY_ORIGIN_BASE_URL_HEADER = 'x-taskforce-origin-base-url';
90
131
  export const TASKFORCE_PROXY_ORIGIN_RUNTIME_HEADER = 'x-taskforce-origin-runtime';
91
132
  export const TASKFORCE_PROXY_CLOUD_ENVIRONMENT_HEADER = 'x-taskforce-cloud-environment';
133
+ export function isWorkspaceAccessRecoveryRoute(pathname) {
134
+ return pathname === '/api/taskforce/workspaces'
135
+ || pathname === '/api/taskforce/session/workspace';
136
+ }
137
+ export function resolveDisabledAccessError(access, pathname) {
138
+ if (pathname.startsWith('/api/taskforce/auth/'))
139
+ return null;
140
+ if (access.accountDisabled ?? access.disabled) {
141
+ return 'Forbidden: user account is disabled.';
142
+ }
143
+ if (access.workspaceDisabled && !isWorkspaceAccessRecoveryRoute(pathname)) {
144
+ return 'Forbidden: workspace access is not active for this user.';
145
+ }
146
+ return null;
147
+ }
148
+ function shouldLogRefreshDiagnostic(pathname) {
149
+ return REFRESH_DIAGNOSTIC_PATH_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(prefix));
150
+ }
151
+ function getRequestTimingNow() {
152
+ return Date.now();
153
+ }
154
+ function isCloudOwnedUserAvatarContextPath(pathname) {
155
+ const prefix = '/api/taskforce/context/';
156
+ if (!pathname.startsWith(prefix))
157
+ return false;
158
+ try {
159
+ const decodedPath = decodeURIComponent(pathname.slice(prefix.length)).replace(/\\/g, '/');
160
+ return /^users\/[^/]+\/profile\/avatar\/[^/]+$/i.test(decodedPath);
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ }
92
166
  export function shouldProxyCloudPath(pathname) {
93
167
  if (!pathname)
94
168
  return false;
@@ -114,6 +188,7 @@ export function shouldProxyCloudPath(pathname) {
114
188
  || pathname.startsWith('/api/taskforce/admin/')
115
189
  || pathname.startsWith('/api/taskforce/billing/')
116
190
  || pathname.startsWith('/api/taskforce/auth/')
191
+ || isCloudOwnedUserAvatarContextPath(pathname)
117
192
  || pathname.startsWith('/api/taskforce/settings/mcp/')
118
193
  || pathname.startsWith('/api/platform/')
119
194
  || pathname.startsWith('/api/taskforce/sync/');
@@ -142,11 +217,27 @@ export function resolveAuthorizedWorkspaceAccess(core, access, requestedWorkspac
142
217
  workspaceId: resolved.workspaceId,
143
218
  userId: resolved.userId,
144
219
  systemRole: core.getSystemRole(resolved.userId),
145
- disabled: resolved.disabled
220
+ disabled: resolved.disabled,
221
+ accountDisabled: resolved.accountDisabled ?? resolved.disabled,
222
+ workspaceDisabled: resolved.workspaceDisabled ?? false
146
223
  };
147
224
  }
148
225
  return access;
149
226
  }
227
+ export function shouldApplyReadOnlyMutationGuard(methodRaw, pathname) {
228
+ const method = String(methodRaw || 'GET').toUpperCase();
229
+ if (method !== 'POST' && method !== 'PATCH' && method !== 'DELETE')
230
+ return false;
231
+ if (!pathname.startsWith('/api/taskforce/'))
232
+ return false;
233
+ if (pathname.startsWith('/api/taskforce/auth/'))
234
+ return false;
235
+ if (pathname === '/api/taskforce/health')
236
+ return false;
237
+ if (method === 'POST' && pathname === '/api/taskforce/session/workspace')
238
+ return false;
239
+ return true;
240
+ }
150
241
  export function resolveEffectiveRequestUserId(accessUserId, explicitUserId) {
151
242
  const normalizedAccessUserId = String(accessUserId || '').trim();
152
243
  if (normalizedAccessUserId && normalizedAccessUserId !== 'anonymous') {
@@ -259,6 +350,7 @@ export function shouldApplyAuthRateLimitForPath(pathname) {
259
350
  const normalizedPath = String(pathname || '').trim();
260
351
  return normalizedPath.startsWith('/api/taskforce/auth/')
261
352
  && normalizedPath !== '/api/taskforce/auth/session'
353
+ && normalizedPath !== '/api/taskforce/auth/providers'
262
354
  && normalizedPath !== '/api/taskforce/auth/runtime-config'
263
355
  && normalizedPath !== '/api/taskforce/auth/admin-session';
264
356
  }
@@ -458,6 +550,17 @@ export function createStandaloneServer(options) {
458
550
  statusCode
459
551
  }, counterKeys);
460
552
  };
553
+ const logRefreshDiagnostic = (input) => {
554
+ const { pathname, method, statusCode, durationMs, req } = input;
555
+ if (!shouldLogRefreshDiagnostic(pathname))
556
+ return;
557
+ if (durationMs < REFRESH_DIAGNOSTIC_DURATION_MS && statusCode < 400)
558
+ return;
559
+ const requestId = String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || '-';
560
+ const workspaceId = String(req.headers['x-taskforce-workspace-id'] || '').trim() || '-';
561
+ const userId = String(req.headers['x-taskforce-user-id'] || '').trim() || '-';
562
+ console.info(`[Taskforce RefreshDiag] method=${method} path=${pathname} status=${statusCode} durationMs=${durationMs} requestId=${requestId} workspaceId=${workspaceId} userId=${userId}`);
563
+ };
461
564
  // Resolve static files directory at runtime
462
565
  // Standalone UI source now lives in apps/app and builds to dist/ui.
463
566
  // When built: dist/server/index.js -> dist/ui/
@@ -514,6 +617,7 @@ export function createStandaloneServer(options) {
514
617
  const integritySchedulePollMinutes = readPositiveInteger(process.env.TASKFORCE_STORAGE_INTEGRITY_SCHEDULE_POLL_MINUTES, 1);
515
618
  console.log(`[Taskforce] Storage integrity scheduler poll: every ${integritySchedulePollMinutes} minute(s)`);
516
619
  const server = http.createServer(async (req, res) => {
620
+ const requestStartedAt = getRequestTimingNow();
517
621
  if (!distUiPath) {
518
622
  uiResolution = resolveStaticUiPath();
519
623
  distUiPath = uiResolution.uiPath;
@@ -568,11 +672,12 @@ export function createStandaloneServer(options) {
568
672
  const hasBody = method !== 'GET' && method !== 'HEAD';
569
673
  const requestMaxPayload = resolveMaxPayloadBytesForPath(url.pathname, maxPayloadBytes);
570
674
  const body = hasBody ? await readRequestBody(req, requestMaxPayload) : undefined;
675
+ const shouldStreamCloudAvatar = method === 'GET' && isCloudOwnedUserAvatarContextPath(url.pathname);
571
676
  const upstream = await fetch(upstreamUrl, {
572
677
  method,
573
678
  headers,
574
679
  body: body && body.length > 0 ? new Uint8Array(body) : undefined,
575
- redirect: 'manual'
680
+ redirect: shouldStreamCloudAvatar ? 'follow' : 'manual'
576
681
  });
577
682
  const outgoingHeaders = {};
578
683
  for (const [key, value] of upstream.headers.entries()) {
@@ -714,19 +819,16 @@ export function createStandaloneServer(options) {
714
819
  return;
715
820
  }
716
821
  const pathname = url.pathname;
717
- const isMutation = (req.method === 'POST' || req.method === 'PATCH' || req.method === 'DELETE')
718
- && pathname.startsWith('/api/taskforce/')
719
- && !pathname.startsWith('/api/taskforce/auth/')
720
- && pathname !== '/api/taskforce/health';
822
+ const isMutation = shouldApplyReadOnlyMutationGuard(req.method, pathname);
721
823
  let access = accessForRateLimit;
722
- const isAuthRoute = pathname.startsWith('/api/taskforce/auth/');
723
824
  if (access.authenticated) {
724
825
  access = resolveAuthorizedWorkspaceAccess(core, access, Array.isArray(req.headers['x-taskforce-workspace-id'])
725
826
  ? String(req.headers['x-taskforce-workspace-id'][0] || '')
726
827
  : String(req.headers['x-taskforce-workspace-id'] || ''));
727
- if (access.disabled && !isAuthRoute) {
828
+ const disabledAccessError = resolveDisabledAccessError(access, pathname);
829
+ if (disabledAccessError) {
728
830
  res.writeHead(403, { 'Content-Type': 'application/json' });
729
- res.end(JSON.stringify({ error: 'Forbidden: user account is disabled.' }));
831
+ res.end(JSON.stringify({ error: disabledAccessError }));
730
832
  return;
731
833
  }
732
834
  }
@@ -756,6 +858,13 @@ export function createStandaloneServer(options) {
756
858
  res.writeHead = originalWriteHead;
757
859
  const finalStatus = Number.isFinite(Number(observedStatusCode)) ? Number(observedStatusCode) : 200;
758
860
  recordCollaborationRouteTelemetry(url.pathname, req.method || 'GET', finalStatus);
861
+ logRefreshDiagnostic({
862
+ pathname: url.pathname,
863
+ method: String(req.method || 'GET').toUpperCase(),
864
+ statusCode: finalStatus,
865
+ durationMs: Math.max(0, Math.round(getRequestTimingNow() - requestStartedAt)),
866
+ req
867
+ });
759
868
  }
760
869
  return;
761
870
  }
@@ -789,6 +898,8 @@ export function createStandaloneServer(options) {
789
898
  res.writeHead(404);
790
899
  res.end('Not Found');
791
900
  });
901
+ const timeoutSettings = configureHttpServerTimeouts(server);
902
+ console.log(`[Taskforce] HTTP timeouts: keepAlive=${timeoutSettings.keepAliveTimeoutMs}ms headers=${timeoutSettings.headersTimeoutMs}ms request=${timeoutSettings.requestTimeoutMs}ms`);
792
903
  // WebSocket server at /Taskforce-ws path
793
904
  const wss = new WebSocketServer({ server, path: '/taskforce-ws' });
794
905
  const realtimeWsManager = createRealtimeSyncWsManager(core, authConfig);
@@ -7,6 +7,9 @@ describe('shouldApplyAuthRateLimitForPath', () => {
7
7
  it('excludes auth session endpoint', () => {
8
8
  expect(shouldApplyAuthRateLimitForPath('/api/taskforce/auth/session')).toBe(false);
9
9
  });
10
+ it('excludes auth providers metadata endpoint', () => {
11
+ expect(shouldApplyAuthRateLimitForPath('/api/taskforce/auth/providers')).toBe(false);
12
+ });
10
13
  it('excludes runtime config bootstrap endpoint', () => {
11
14
  expect(shouldApplyAuthRateLimitForPath('/api/taskforce/auth/runtime-config')).toBe(false);
12
15
  });