@taskforcehq/taskforce 0.3.305 → 0.3.307

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 (440) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +603 -107
  3. package/dist/TaskforceCore.js +216 -43
  4. package/dist/TaskforceCore.test.js +611 -28
  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 +46 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  16. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  17. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  18. package/dist/components/context/ContextAttachmentManager.js +561 -0
  19. package/dist/components/features/AgentsModule.d.ts +14 -1
  20. package/dist/components/features/AgentsModule.js +366 -18
  21. package/dist/components/features/AgentsModule.test.js +507 -8
  22. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  23. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  24. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  25. package/dist/components/features/DocumentIndex.d.ts +2 -2
  26. package/dist/components/features/DocumentIndex.js +3 -4
  27. package/dist/components/features/DocumentViewer.d.ts +7 -1
  28. package/dist/components/features/DocumentViewer.js +62 -4
  29. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  30. package/dist/components/features/DocumentWorkspace.js +15 -4
  31. package/dist/components/features/DocumentWorkspace.test.js +51 -1
  32. package/dist/components/features/TaskSettings.d.ts +2 -0
  33. package/dist/components/features/TaskSettings.js +83 -78
  34. package/dist/components/features/TaskSettings.test.js +171 -82
  35. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  36. package/dist/components/task/TaskActionHeader.js +11 -5
  37. package/dist/components/task/TaskActionHeader.test.js +10 -0
  38. package/dist/components/task/TaskCard.js +24 -29
  39. package/dist/components/task/TaskCard.test.js +38 -3
  40. package/dist/components/task/TaskContextUpload.js +4 -526
  41. package/dist/components/task/TaskContextUpload.test.js +3 -3
  42. package/dist/components/task/TaskForm.js +61 -116
  43. package/dist/components/task/TaskForm.test.js +110 -8
  44. package/dist/components/task/TaskKanban.d.ts +2 -1
  45. package/dist/components/task/TaskKanban.js +94 -21
  46. package/dist/components/task/TaskKanban.test.js +36 -0
  47. package/dist/components/task/TaskList.js +5 -1
  48. package/dist/components/task/TaskList.test.js +4 -0
  49. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  50. package/dist/components/ui/EditableAvatarButton.js +18 -0
  51. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  52. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  53. package/dist/components/views/AppShellHeader.d.ts +3 -1
  54. package/dist/components/views/AppShellHeader.js +27 -11
  55. package/dist/components/views/AppShellHeader.test.js +18 -1
  56. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  57. package/dist/components/views/PlanComparisonPage.js +78 -14
  58. package/dist/components/views/PlanComparisonPage.test.js +257 -0
  59. package/dist/components/views/PlansPage.js +127 -36
  60. package/dist/components/views/PlansPage.test.js +294 -12
  61. package/dist/components/views/StandaloneLayout.js +529 -592
  62. package/dist/components/views/WidgetView.js +10 -8
  63. package/dist/components/views/panels/PlanningDrawer.d.ts +25 -5
  64. package/dist/components/views/panels/PlanningDrawer.js +270 -55
  65. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  66. package/dist/components/views/panels/PlanningDrawer.test.js +529 -0
  67. package/dist/components/views/planningScope.d.ts +20 -0
  68. package/dist/components/views/planningScope.js +25 -0
  69. package/dist/components/views/planningScope.test.d.ts +1 -0
  70. package/dist/components/views/planningScope.test.js +60 -0
  71. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  72. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  73. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  74. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  75. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  76. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  77. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  78. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  79. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  80. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  81. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  82. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  83. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  84. package/dist/config/envSchema.js +1 -0
  85. package/dist/core/AiProfileService.d.ts +46 -2
  86. package/dist/core/AiProfileService.js +1073 -82
  87. package/dist/core/AiProfileService.test.js +19 -1
  88. package/dist/core/AiProfiles.test.js +1249 -44
  89. package/dist/core/AttachmentLinkService.d.ts +3 -0
  90. package/dist/core/AttachmentLinkService.js +110 -23
  91. package/dist/core/AuthIdentityService.d.ts +107 -0
  92. package/dist/core/AuthIdentityService.js +284 -0
  93. package/dist/core/AuthTokenService.d.ts +4 -0
  94. package/dist/core/AuthTokenService.js +34 -6
  95. package/dist/core/AuthTokenService.test.js +3 -3
  96. package/dist/core/EntitlementsPolicy.test.js +2 -2
  97. package/dist/core/GlobalSettingsService.js +127 -6
  98. package/dist/core/McpTokenService.d.ts +12 -0
  99. package/dist/core/McpTokenService.js +44 -1
  100. package/dist/core/PlanEntitlementService.d.ts +23 -2
  101. package/dist/core/PlanEntitlementService.js +343 -57
  102. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  103. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  104. package/dist/core/PlanVersionPolicy.test.js +35 -2
  105. package/dist/core/PlanningEntities.test.js +418 -4
  106. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  107. package/dist/core/SyncReconciliationService.js +2 -65
  108. package/dist/core/SystemAdmin.test.js +42 -42
  109. package/dist/core/TaskActivityService.d.ts +28 -1
  110. package/dist/core/TaskActivityService.js +227 -46
  111. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  112. package/dist/core/TaskActivitySync.test.js +90 -0
  113. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  114. package/dist/core/TaskAuditTimeline.test.js +96 -0
  115. package/dist/core/Taskforce.d.ts +415 -40
  116. package/dist/core/Taskforce.ids.test.js +58 -0
  117. package/dist/core/Taskforce.js +1543 -266
  118. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  119. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  120. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  121. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  122. package/dist/core/WorkspaceLifecycleService.js +0 -5
  123. package/dist/core/WorkspacePermissions.test.js +25 -1
  124. package/dist/core/WorkspacePlanMode.test.js +117 -16
  125. package/dist/core/shared.d.ts +1 -1
  126. package/dist/core/shared.js +5 -1
  127. package/dist/core/types.d.ts +75 -2
  128. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +28 -2
  129. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  130. package/dist/hooks/bootstrapTimeouts.js +8 -0
  131. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  132. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  133. package/dist/hooks/sync/bootstrap.js +2 -0
  134. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  135. package/dist/hooks/sync/controlPlane.js +78 -0
  136. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  137. package/dist/hooks/sync/controlPlane.test.js +121 -0
  138. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  139. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  140. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  141. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  142. package/dist/hooks/sync/orchestratorShared.d.ts +13 -4
  143. package/dist/hooks/sync/orchestratorShared.js +129 -48
  144. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  145. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  146. package/dist/hooks/sync/pullLifecycle.js +24 -0
  147. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  148. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  149. package/dist/hooks/sync/recovery.d.ts +16 -2
  150. package/dist/hooks/sync/recovery.js +179 -53
  151. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  152. package/dist/hooks/sync/recovery.test.js +292 -0
  153. package/dist/hooks/sync/transfers.d.ts +16 -3
  154. package/dist/hooks/sync/transfers.js +210 -66
  155. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  156. package/dist/hooks/sync/transfers.test.js +928 -0
  157. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  158. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  159. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  160. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  161. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  162. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  163. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  164. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  165. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  166. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  167. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  168. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  169. package/dist/hooks/useFilterSort.js +13 -13
  170. package/dist/hooks/useFilterSort.test.js +87 -1
  171. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  172. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  173. package/dist/hooks/useSyncOrchestrator.js +409 -183
  174. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  175. package/dist/hooks/useTaskData.js +8 -3
  176. package/dist/hooks/useTaskData.test.js +45 -0
  177. package/dist/hooks/useTaskforce.d.ts +145 -52
  178. package/dist/hooks/useTaskforce.js +592 -253
  179. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  180. package/dist/hooks/useTaskforce.runtime-routing.test.js +246 -0
  181. package/dist/hooks/useTaskforce.sync-behavior.test.js +2468 -201
  182. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  183. package/dist/hooks/useWorkspaceSyncController.js +13 -2
  184. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  185. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  186. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +328 -40
  187. package/dist/localization/locales/en-US.d.ts +0 -1
  188. package/dist/localization/locales/en-US.js +0 -1
  189. package/dist/localization/locales/es-419.js +0 -1
  190. package/dist/localization/locales/pt-BR.js +0 -1
  191. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  192. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  193. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  194. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  195. package/dist/mcp/clientIntegrations.d.ts +1 -0
  196. package/dist/mcp/clientIntegrations.js +25 -0
  197. package/dist/mcp/clientIntegrations.test.js +14 -3
  198. package/dist/mcp/clientProfileDefaults.js +3 -3
  199. package/dist/mcp/collaborationRegistrar.js +7 -7
  200. package/dist/mcp/documentAssetRegistrar.js +116 -10
  201. package/dist/mcp/runtime.d.ts +3 -0
  202. package/dist/mcp/runtime.js +1099 -185
  203. package/dist/mcp/runtime.test.js +1491 -140
  204. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  205. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  206. package/dist/mcp/toolRegistry.d.ts +56 -0
  207. package/dist/mcp/toolRegistry.js +8 -0
  208. package/dist/mcp/toolRegistry.test.js +4 -0
  209. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  210. package/dist/migrations/taskSchemaMigrations.js +192 -61
  211. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  212. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  213. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  214. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  215. package/dist/resources/templates/workflows/plan.yaml +1 -1
  216. package/dist/resources/templates/workflows/review.yaml +2 -2
  217. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  218. package/dist/server/aiProfileAvatarAssets.js +319 -0
  219. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  220. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  221. package/dist/server/annotatedAttachmentsRoutes.test.js +69 -10
  222. package/dist/server/auth/providers/apple.d.ts +33 -0
  223. package/dist/server/auth/providers/apple.js +82 -0
  224. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  225. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  226. package/dist/server/auth/providers/github.d.ts +26 -0
  227. package/dist/server/auth/providers/github.js +86 -0
  228. package/dist/server/auth/providers/google.d.ts +21 -0
  229. package/dist/server/auth/providers/google.js +56 -0
  230. package/dist/server/auth/providers/types.d.ts +21 -0
  231. package/dist/server/auth/providers/types.js +1 -0
  232. package/dist/server/auth.d.ts +2 -0
  233. package/dist/server/auth.js +6 -3
  234. package/dist/server/documentReviewRoutes.test.js +36 -3
  235. package/dist/server/index.cookieProxy.test.js +1 -0
  236. package/dist/server/index.d.ts +12 -0
  237. package/dist/server/index.js +124 -9
  238. package/dist/server/index.rateLimit.test.js +3 -0
  239. package/dist/server/index.test.js +111 -3
  240. package/dist/server/routes/admin.d.ts +3 -0
  241. package/dist/server/routes/admin.js +459 -28
  242. package/dist/server/routes/annotatedAttachments.js +51 -0
  243. package/dist/server/routes/auth.d.ts +26 -2
  244. package/dist/server/routes/auth.js +1170 -71
  245. package/dist/server/routes/authSupport.d.ts +5 -1
  246. package/dist/server/routes/authSupport.js +13 -2
  247. package/dist/server/routes/billing.js +372 -35
  248. package/dist/server/routes/billing.test.js +595 -12
  249. package/dist/server/routes/documents.d.ts +1 -0
  250. package/dist/server/routes/documents.js +182 -70
  251. package/dist/server/routes/shared.d.ts +28 -2
  252. package/dist/server/routes/shared.js +69 -14
  253. package/dist/server/routes/sync.integration.test.js +658 -39
  254. package/dist/server/routes/sync.js +74 -608
  255. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  256. package/dist/server/routes/syncAuxRoutes.js +91 -0
  257. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  258. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  259. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  260. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  261. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  262. package/dist/server/routes/syncPushRoutes.js +212 -0
  263. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  264. package/dist/server/routes/syncRouteGuards.js +67 -0
  265. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  266. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  267. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  268. package/dist/server/routes/syncRouteTypes.js +1 -0
  269. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  270. package/dist/server/routes/syncSnapshotRoutes.js +182 -0
  271. package/dist/server/routes/tasks.js +55 -10
  272. package/dist/server/routes/workspaces.js +36 -5
  273. package/dist/server/routes.js +147 -11
  274. package/dist/server/routes.test.js +2704 -183
  275. package/dist/services/workflowExportSnapshots.test.js +2 -0
  276. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  277. package/dist/shared/aiProfileSeatScope.js +21 -0
  278. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  279. package/dist/shared/aiProfileSurfaceType.js +10 -8
  280. package/dist/shared/passwordPolicy.d.ts +13 -0
  281. package/dist/shared/passwordPolicy.js +84 -0
  282. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  283. package/dist/shared/passwordPolicy.test.js +34 -0
  284. package/dist/storage/objectStorage.d.ts +1 -0
  285. package/dist/storage/objectStorage.js +2 -1
  286. package/dist/storage/objectStorageClient.d.ts +1 -0
  287. package/dist/storage/objectStorageClient.js +41 -8
  288. package/dist/storage/objectStorageClient.test.js +26 -1
  289. package/dist/storage/objectStorageConfig.js +3 -1
  290. package/dist/storage/objectStorageConfig.test.js +13 -1
  291. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  292. package/dist/storage/workspaceAssetStore.js +132 -44
  293. package/dist/storage/workspaceAssetStore.test.js +93 -6
  294. package/dist/styles/fonts.css +70 -0
  295. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  296. package/dist/sync/collaborationSyncPayload.js +19 -11
  297. package/dist/sync/collaborationSyncPayload.test.js +14 -1
  298. package/dist/sync/collaborationSyncState.d.ts +3 -3
  299. package/dist/sync/collaborationSyncState.js +19 -12
  300. package/dist/sync/contentSyncPayload.d.ts +5 -1
  301. package/dist/sync/contentSyncPayload.js +9 -0
  302. package/dist/sync/contentSyncPayload.test.js +39 -0
  303. package/dist/sync/contentSyncState.d.ts +9 -9
  304. package/dist/sync/contentSyncState.js +248 -107
  305. package/dist/sync/planningSyncPayload.js +5 -0
  306. package/dist/sync/planningSyncPayload.test.js +24 -0
  307. package/dist/sync/syncApplyHandlers.d.ts +11 -4
  308. package/dist/sync/syncApplyHandlers.js +115 -42
  309. package/dist/sync/syncApplyHandlers.test.js +251 -4
  310. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  311. package/dist/sync/syncFieldSemantics.js +55 -0
  312. package/dist/sync/syncResourceAdapters.d.ts +46 -0
  313. package/dist/sync/syncResourceAdapters.js +79 -0
  314. package/dist/sync/syncService.d.ts +6 -0
  315. package/dist/sync/syncService.js +65 -1
  316. package/dist/sync/syncService.test.js +96 -3
  317. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  318. package/dist/sync/taskSyncChangeKey.js +143 -0
  319. package/dist/sync/taskSyncPayload.js +3 -8
  320. package/dist/sync/workspacePullFeed.d.ts +4 -0
  321. package/dist/sync/workspacePullFeed.js +132 -72
  322. package/dist/sync/workspacePullFeed.test.js +92 -0
  323. package/dist/sync/workspaceRepair.js +5 -2
  324. package/dist/sync/workspaceSyncModel.d.ts +73 -6
  325. package/dist/sync/workspaceSyncModel.js +342 -73
  326. package/dist/sync/workspaceSyncModel.test.js +559 -14
  327. package/dist/sync/workspaceSyncState.d.ts +17 -0
  328. package/dist/sync/workspaceSyncState.js +2 -1
  329. package/dist/sync/workspaceSyncSurface.js +3 -8
  330. package/dist/types.d.ts +50 -2
  331. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  332. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  333. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  334. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  335. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  336. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  337. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  338. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  339. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  340. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  341. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  342. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  343. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-D4XseTwW.js} +1 -1
  344. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  345. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  346. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  347. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  348. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  349. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  350. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  351. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  352. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  353. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  354. package/dist/ui/assets/index-607WPo_X.js +4 -0
  355. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  356. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-B32hGuF2.js} +1 -1
  357. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  358. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  359. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  360. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  361. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  362. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  363. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  364. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  365. package/dist/ui/fonts/Noir_medium.otf +0 -0
  366. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  367. package/dist/ui/fonts/Noir_regular.otf +0 -0
  368. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  369. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  370. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  371. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  372. package/dist/ui/index.html +3 -3
  373. package/dist/ui/product/agent manager.png +0 -0
  374. package/dist/ui/product/agent_manager_02.png +0 -0
  375. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  376. package/dist/utils/aiProfileEvents.d.ts +7 -0
  377. package/dist/utils/aiProfileEvents.js +6 -0
  378. package/dist/utils/assignees.d.ts +8 -0
  379. package/dist/utils/assignees.js +10 -1
  380. package/dist/utils/assignees.test.d.ts +1 -0
  381. package/dist/utils/assignees.test.js +25 -0
  382. package/dist/utils/avatarDisplay.d.ts +1 -0
  383. package/dist/utils/avatarDisplay.js +15 -0
  384. package/dist/utils/avatarUpload.d.ts +27 -0
  385. package/dist/utils/avatarUpload.js +132 -0
  386. package/dist/utils/avatarUpload.test.d.ts +1 -0
  387. package/dist/utils/avatarUpload.test.js +40 -0
  388. package/dist/utils/bootstrapTrace.d.ts +7 -0
  389. package/dist/utils/bootstrapTrace.js +29 -0
  390. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  391. package/dist/utils/contextAssetEvents.d.ts +2 -0
  392. package/dist/utils/contextFiles.d.ts +3 -1
  393. package/dist/utils/contextFiles.js +2 -1
  394. package/dist/utils/syncEventDetails.d.ts +3 -0
  395. package/dist/utils/syncEventDetails.js +64 -0
  396. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  397. package/dist/utils/syncEventDetails.test.js +23 -0
  398. package/dist/utils/syncEventPresentation.d.ts +15 -0
  399. package/dist/utils/syncEventPresentation.js +129 -0
  400. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  401. package/dist/utils/syncEventPresentation.test.js +64 -0
  402. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  403. package/dist/utils/syncStatusPresentation.js +217 -0
  404. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  405. package/dist/utils/syncStatusPresentation.test.js +164 -0
  406. package/dist/utils/taskActivity.d.ts +9 -1
  407. package/dist/utils/taskActivity.js +290 -22
  408. package/dist/utils/taskActivity.test.js +141 -8
  409. package/dist/utils/taskSearch.d.ts +22 -0
  410. package/dist/utils/taskSearch.js +87 -0
  411. package/dist/utils/taskSearch.test.d.ts +1 -0
  412. package/dist/utils/taskSearch.test.js +69 -0
  413. package/dist/utils/taskUtils.js +10 -3
  414. package/dist/utils/taskUtils.test.d.ts +1 -0
  415. package/dist/utils/taskUtils.test.js +19 -0
  416. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  417. package/dist/utils/workspaceSyncPresentation.js +68 -2
  418. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  419. package/package.json +3 -2
  420. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  421. package/scripts/run-e2e-realtime.sh +17 -3
  422. package/scripts/run-smoke.sh +17 -5
  423. package/scripts/run-soak.sh +17 -5
  424. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  425. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  426. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  427. package/src/resources/templates/workflows/plan.yaml +1 -1
  428. package/src/resources/templates/workflows/review.yaml +2 -2
  429. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  430. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BG6P_GVW.js +0 -3
  431. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  432. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  433. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  434. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  435. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  436. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  437. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +0 -9
  438. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  439. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  440. package/scripts/migrate-planning-model.ts +0 -233
@@ -199,7 +199,69 @@ describe('annotated attachment routes', () => {
199
199
  fs.rmSync(projectRoot, { recursive: true, force: true });
200
200
  }
201
201
  });
202
- it('allows local mutation attribution fallback when local runtime has an active user even if auth is enabled', async () => {
202
+ it('lists canonical image assets for the Image Notes tray', async () => {
203
+ const projectRoot = createProjectRoot();
204
+ const core = new TaskforceCore({
205
+ projectRoot,
206
+ storagePath: path.join(projectRoot, '.taskforce'),
207
+ });
208
+ try {
209
+ core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
210
+ const category = core.getCategories()[0]?.value;
211
+ const type = core.getTypes()[0]?.value;
212
+ const task = core.createTask({ title: 'Review image', category, type }, { workspaceId: 'workspace-1', actorRef: 'owner-1' });
213
+ const assetStore = core.getWorkspaceAssetStore();
214
+ const asset = assetStore.upsertAsset({
215
+ assetId: 'asset-image-1',
216
+ workspaceId: 'workspace-1',
217
+ kind: 'image',
218
+ originalFilename: 'ui-review.png',
219
+ logicalName: 'UI Review',
220
+ mimeType: 'image/png',
221
+ storageProvider: 'local',
222
+ storageKey: 'workspaces/workspace-1/assets/images/asset-image-1-ui-review.png',
223
+ contentSha256: 'sha-image-1',
224
+ sizeBytes: 256,
225
+ });
226
+ assetStore.upsertLink({
227
+ workspaceId: 'workspace-1',
228
+ assetId: asset.assetId,
229
+ taskId: task.id,
230
+ role: 'attachment',
231
+ });
232
+ core.createAnnotatedAttachmentSession({
233
+ workspaceId: 'workspace-1',
234
+ taskId: '',
235
+ baseImageAssetId: asset.assetId,
236
+ title: 'UI Review session',
237
+ actorId: 'owner-1',
238
+ });
239
+ const routes = createRoutes(core);
240
+ const listImagesMatch = matchRoute('GET', '/api/taskforce/annotated-attachments/images', routes);
241
+ const listImagesReq = createMockReq('GET', '/api/taskforce/annotated-attachments/images?workspaceId=workspace-1');
242
+ const listImagesRes = createMockRes();
243
+ await listImagesMatch?.handler(listImagesReq, listImagesRes, {});
244
+ expect(listImagesRes.statusCode).toBe(200);
245
+ expect(listImagesRes.getHeader('cache-control')).toBe('no-store');
246
+ const listed = JSON.parse(listImagesRes.__getBody());
247
+ expect(listed.images).toHaveLength(1);
248
+ expect(listed.images[0]).toMatchObject({
249
+ assetId: asset.assetId,
250
+ displayName: 'UI Review',
251
+ path: '/api/taskforce/context/workspaces%2Fworkspace-1%2Fassets%2Fimages%2Fasset-image-1-ui-review.png',
252
+ attachmentCount: 1,
253
+ sessionCount: 1,
254
+ taskId: task.id,
255
+ taskReferenceLabel: task.referenceLabel,
256
+ });
257
+ expect(listed.images[0].imageReferenceLabel).toMatch(/^I-\d+$/);
258
+ }
259
+ finally {
260
+ core.close();
261
+ fs.rmSync(projectRoot, { recursive: true, force: true });
262
+ }
263
+ });
264
+ it('rejects local mutation attribution when only an active workspace user fallback is available', async () => {
203
265
  const projectRoot = createProjectRoot();
204
266
  const core = new TaskforceCore({
205
267
  projectRoot,
@@ -209,9 +271,9 @@ describe('annotated attachment routes', () => {
209
271
  core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
210
272
  const now = new Date().toISOString();
211
273
  core.db.prepare(`
212
- INSERT INTO users (id, tenant_id, email, display_name, auth_provider, external_auth_id, beta_access, is_disabled, created_at, updated_at)
213
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
214
- `).run('user-local-1', core.getTenantId(), 'local@example.com', 'Local User', 'local', 'user-local-1', 1, 0, now, now);
274
+ INSERT INTO users (id, tenant_id, email, display_name, beta_access, is_disabled, created_at, updated_at)
275
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
276
+ `).run('user-local-1', core.getTenantId(), 'local@example.com', 'Local User', 1, 0, now, now);
215
277
  core.db.prepare(`
216
278
  INSERT INTO workspace_memberships (id, tenant_id, workspace_id, user_id, role, status, created_at, updated_at)
217
279
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
@@ -252,13 +314,10 @@ describe('annotated attachment routes', () => {
252
314
  });
253
315
  const createRes = createMockRes();
254
316
  await createMatch?.handler(createReq, createRes, {});
255
- expect(createRes.statusCode).toBe(200);
317
+ expect(createRes.statusCode).toBe(401);
256
318
  expect(JSON.parse(createRes.__getBody())).toMatchObject({
257
- success: true,
258
- session: expect.objectContaining({
259
- createdByActorId: 'user-local-1',
260
- updatedByActorId: 'user-local-1',
261
- }),
319
+ success: false,
320
+ code: 'MUTATION_AUTH_REQUIRED',
262
321
  });
263
322
  }
264
323
  finally {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Apple Sign In adapter.
3
+ *
4
+ * Flow:
5
+ * 1. Redirect user to buildAppleAuthUrl() with response_mode=form_post.
6
+ * 2. Apple POSTs `code`, `state`, `id_token` and optional `user` JSON to the callback.
7
+ * 3. Call exchangeAppleCode() which mints the client-secret JWT, exchanges the code,
8
+ * and decodes the id_token payload for the canonical profile.
9
+ *
10
+ * Notes:
11
+ * - Apple never re-sends the user's name after the very first authorisation.
12
+ * If the user JSON arrives on first login it is captured; subsequent logins
13
+ * use the snapshot stored in user_auth_identities.
14
+ * - Private-relay email addresses contain `.privaterelay.appleid.com`; the
15
+ * `is_private_email` claim is set to `true` in that case.
16
+ */
17
+ import type { NormalizedOAuthProfile } from './types.js';
18
+ export declare function buildAppleAuthUrl(input: {
19
+ servicesId: string;
20
+ redirectUri: string;
21
+ state: string;
22
+ nonce: string;
23
+ }): string;
24
+ export declare function exchangeAppleCode(input: {
25
+ code: string;
26
+ redirectUri: string;
27
+ servicesId: string;
28
+ teamId: string;
29
+ keyId: string;
30
+ privateKeyPem: string;
31
+ /** Optional: JSON string Apple sends on first authorisation containing { name }. */
32
+ userJson?: string | null;
33
+ }): Promise<NormalizedOAuthProfile>;
@@ -0,0 +1,82 @@
1
+ import { getAppleClientSecret } from './appleClientSecret.js';
2
+ export function buildAppleAuthUrl(input) {
3
+ const params = new URLSearchParams({
4
+ client_id: input.servicesId,
5
+ redirect_uri: input.redirectUri,
6
+ response_type: 'code id_token',
7
+ scope: 'name email',
8
+ state: input.state,
9
+ nonce: input.nonce,
10
+ response_mode: 'form_post',
11
+ });
12
+ return `https://appleid.apple.com/auth/authorize?${params.toString()}`;
13
+ }
14
+ export async function exchangeAppleCode(input) {
15
+ const clientSecret = getAppleClientSecret({
16
+ servicesId: input.servicesId,
17
+ teamId: input.teamId,
18
+ keyId: input.keyId,
19
+ privateKeyPem: input.privateKeyPem,
20
+ });
21
+ // Step 1: exchange authorisation code for tokens
22
+ const tokenRes = await fetch('https://appleid.apple.com/auth/token', {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
25
+ body: new URLSearchParams({
26
+ grant_type: 'authorization_code',
27
+ code: input.code,
28
+ redirect_uri: input.redirectUri,
29
+ client_id: input.servicesId,
30
+ client_secret: clientSecret,
31
+ }).toString(),
32
+ });
33
+ if (!tokenRes.ok) {
34
+ const body = await tokenRes.text().catch(() => '');
35
+ throw new Error(`Apple token exchange failed (${tokenRes.status}): ${body}`);
36
+ }
37
+ const tokens = await tokenRes.json();
38
+ const idToken = String(tokens.id_token || '').trim();
39
+ if (!idToken)
40
+ throw new Error('Apple token exchange: missing id_token');
41
+ // Step 2: decode id_token payload (trust Apple — skip signature verification here)
42
+ const parts = idToken.split('.');
43
+ if (parts.length !== 3)
44
+ throw new Error('Apple id_token: not a valid JWT');
45
+ let payload;
46
+ try {
47
+ payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
48
+ }
49
+ catch {
50
+ throw new Error('Apple id_token: could not decode payload');
51
+ }
52
+ const sub = String(payload.sub || '').trim();
53
+ if (!sub)
54
+ throw new Error('Apple id_token: missing sub');
55
+ const email = String(payload.email || '').trim().toLowerCase() || null;
56
+ const emailVerified = payload.email_verified === true || payload.email_verified === 'true';
57
+ const isPrivateEmail = payload.is_private_email === true || payload.is_private_email === 'true';
58
+ // Step 3: extract display name from one-time `user` JSON (first-login only)
59
+ let displayName = null;
60
+ if (input.userJson) {
61
+ try {
62
+ const userObj = JSON.parse(input.userJson);
63
+ const firstName = String(userObj?.name?.firstName || '').trim();
64
+ const lastName = String(userObj?.name?.lastName || '').trim();
65
+ const full = [firstName, lastName].filter(Boolean).join(' ');
66
+ if (full)
67
+ displayName = full;
68
+ }
69
+ catch {
70
+ // malformed — ignore
71
+ }
72
+ }
73
+ return {
74
+ provider: 'apple',
75
+ providerSubject: sub,
76
+ email,
77
+ emailVerified,
78
+ displayName,
79
+ avatarUrl: null, // Apple never provides an avatar
80
+ isPrivateEmail,
81
+ };
82
+ }
@@ -0,0 +1,37 @@
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
+ export interface AppleClientSecretConfig {
13
+ /** 10-character Apple Team ID */
14
+ teamId: string;
15
+ /** Services ID (bundle ID) registered in App Store Connect */
16
+ servicesId: string;
17
+ /** Key ID of the registered Sign In with Apple key */
18
+ keyId: string;
19
+ /** PEM-encoded EC P-256 private key downloaded from App Store Connect */
20
+ privateKeyPem: string;
21
+ }
22
+ /**
23
+ * Returns a cached client-secret JWT for the given Apple config, minting a
24
+ * fresh one if the cache entry is absent or within 60 s of expiry.
25
+ */
26
+ export declare function getAppleClientSecret(cfg: AppleClientSecretConfig): string;
27
+ /**
28
+ * Force-mints a fresh JWT (bypasses cache) for health-check purposes.
29
+ * Returns success/error without updating the cache.
30
+ */
31
+ export declare function testMintAppleClientSecret(cfg: AppleClientSecretConfig): {
32
+ ok: true;
33
+ token: string;
34
+ } | {
35
+ ok: false;
36
+ error: string;
37
+ };
@@ -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);