@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
@@ -17,6 +17,8 @@ describe('Workflow export snapshots', () => {
17
17
  for (const workflowName of workflowNames) {
18
18
  it(`${envId}/${workflowName} export snapshot`, () => {
19
19
  const result = engine.processTemplate(workflowName, envId);
20
+ expect(result.content).not.toContain('shared docs under `docs/`');
21
+ expect(result.content).not.toContain('store it under `docs/`');
20
22
  expect({
21
23
  filename: result.filename,
22
24
  strategy: result.strategy,
@@ -0,0 +1,5 @@
1
+ export declare const AI_PROFILE_SEAT_SCOPES: readonly ["cloud_metered", "local_unmetered"];
2
+ export type AiProfileSeatScope = typeof AI_PROFILE_SEAT_SCOPES[number];
3
+ export declare function isAiProfileSeatScope(value: unknown): value is AiProfileSeatScope;
4
+ export declare function parseAiProfileSeatScope(value: unknown): AiProfileSeatScope | null;
5
+ export declare function formatAiProfileSeatScopeLabel(value: AiProfileSeatScope): string;
@@ -0,0 +1,21 @@
1
+ export const AI_PROFILE_SEAT_SCOPES = [
2
+ 'cloud_metered',
3
+ 'local_unmetered',
4
+ ];
5
+ const AI_PROFILE_SEAT_SCOPE_LABELS = {
6
+ cloud_metered: 'Cloud MCP',
7
+ local_unmetered: 'Local MCP',
8
+ };
9
+ export function isAiProfileSeatScope(value) {
10
+ return typeof value === 'string'
11
+ && AI_PROFILE_SEAT_SCOPES.includes(value);
12
+ }
13
+ export function parseAiProfileSeatScope(value) {
14
+ if (typeof value !== 'string')
15
+ return null;
16
+ const normalized = value.trim();
17
+ return isAiProfileSeatScope(normalized) ? normalized : null;
18
+ }
19
+ export function formatAiProfileSeatScopeLabel(value) {
20
+ return AI_PROFILE_SEAT_SCOPE_LABELS[value];
21
+ }
@@ -0,0 +1,13 @@
1
+ export declare const MIN_PASSWORD_LENGTH = 12;
2
+ export type PasswordPolicyCode = 'PASSWORD_REQUIRED' | 'PASSWORD_TOO_SHORT' | 'PASSWORD_TOO_COMMON' | 'PASSWORD_CONTAINS_PERSONAL_INFO';
3
+ export interface PasswordPolicyResult {
4
+ ok: boolean;
5
+ code?: PasswordPolicyCode;
6
+ message?: string;
7
+ }
8
+ export interface PasswordPolicyContext {
9
+ email?: string | null;
10
+ displayName?: string | null;
11
+ }
12
+ export declare function getPasswordPolicyGuidance(): string[];
13
+ export declare function validatePasswordPolicy(password: string, context?: PasswordPolicyContext): PasswordPolicyResult;
@@ -0,0 +1,84 @@
1
+ export const MIN_PASSWORD_LENGTH = 12;
2
+ const COMMON_PASSWORDS = new Set([
3
+ '12345678',
4
+ '123456789',
5
+ '1234567890',
6
+ 'admin123',
7
+ 'changeme',
8
+ 'letmein',
9
+ 'letmein123',
10
+ 'password',
11
+ 'password1',
12
+ 'password12',
13
+ 'password123',
14
+ 'password1234',
15
+ 'qwerty',
16
+ 'qwerty123',
17
+ 'welcome',
18
+ 'welcome123'
19
+ ]);
20
+ const normalizeForComparison = (value) => (String(value || '')
21
+ .trim()
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]/g, ''));
24
+ function extractPersonalInfoTokens(context) {
25
+ const tokens = new Set();
26
+ const email = String(context?.email || '').trim().toLowerCase();
27
+ const displayName = String(context?.displayName || '').trim().toLowerCase();
28
+ if (email.includes('@')) {
29
+ const localPart = email.split('@')[0] || '';
30
+ const compactLocalPart = localPart.replace(/[^a-z0-9]/g, '');
31
+ if (compactLocalPart.length >= 4)
32
+ tokens.add(compactLocalPart);
33
+ for (const part of localPart.split(/[^a-z0-9]+/)) {
34
+ if (part.length >= 4)
35
+ tokens.add(part);
36
+ }
37
+ }
38
+ for (const part of displayName.split(/[^a-z0-9]+/)) {
39
+ if (part.length >= 4)
40
+ tokens.add(part);
41
+ }
42
+ return [...tokens];
43
+ }
44
+ export function getPasswordPolicyGuidance() {
45
+ return [
46
+ `Use at least ${MIN_PASSWORD_LENGTH} characters.`,
47
+ 'Avoid common passwords or obvious patterns.',
48
+ 'Do not include your email or display name.'
49
+ ];
50
+ }
51
+ export function validatePasswordPolicy(password, context) {
52
+ const rawPassword = String(password || '');
53
+ if (!rawPassword) {
54
+ return {
55
+ ok: false,
56
+ code: 'PASSWORD_REQUIRED',
57
+ message: 'Password is required.'
58
+ };
59
+ }
60
+ if (rawPassword.length < MIN_PASSWORD_LENGTH) {
61
+ return {
62
+ ok: false,
63
+ code: 'PASSWORD_TOO_SHORT',
64
+ message: `Password must be at least ${MIN_PASSWORD_LENGTH} characters.`
65
+ };
66
+ }
67
+ const normalizedPassword = normalizeForComparison(rawPassword);
68
+ if (COMMON_PASSWORDS.has(normalizedPassword)) {
69
+ return {
70
+ ok: false,
71
+ code: 'PASSWORD_TOO_COMMON',
72
+ message: 'Choose a less common password.'
73
+ };
74
+ }
75
+ const personalTokens = extractPersonalInfoTokens(context);
76
+ if (personalTokens.some((token) => normalizedPassword.includes(token))) {
77
+ return {
78
+ ok: false,
79
+ code: 'PASSWORD_CONTAINS_PERSONAL_INFO',
80
+ message: 'Password cannot contain your email or display name.'
81
+ };
82
+ }
83
+ return { ok: true };
84
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getPasswordPolicyGuidance, MIN_PASSWORD_LENGTH, validatePasswordPolicy } from './passwordPolicy';
3
+ describe('passwordPolicy', () => {
4
+ it('describes the active password policy guidance', () => {
5
+ expect(getPasswordPolicyGuidance()).toEqual([
6
+ `Use at least ${MIN_PASSWORD_LENGTH} characters.`,
7
+ 'Avoid common passwords or obvious patterns.',
8
+ 'Do not include your email or display name.'
9
+ ]);
10
+ });
11
+ it('rejects common passwords even when padded with punctuation', () => {
12
+ expect(validatePasswordPolicy('Password123!')).toEqual({
13
+ ok: false,
14
+ code: 'PASSWORD_TOO_COMMON',
15
+ message: 'Choose a less common password.'
16
+ });
17
+ });
18
+ it('rejects passwords containing personal information', () => {
19
+ expect(validatePasswordPolicy('RossBurkeSecure!2026', {
20
+ email: 'ross@example.com',
21
+ displayName: 'Ross Burke'
22
+ })).toEqual({
23
+ ok: false,
24
+ code: 'PASSWORD_CONTAINS_PERSONAL_INFO',
25
+ message: 'Password cannot contain your email or display name.'
26
+ });
27
+ });
28
+ it('accepts long passwords without common or personal tokens', () => {
29
+ expect(validatePasswordPolicy('MapleHarborOrbit!2026', {
30
+ email: 'ross@example.com',
31
+ displayName: 'Ross Burke'
32
+ })).toEqual({ ok: true });
33
+ });
34
+ });
@@ -3,6 +3,7 @@ import type { ObjectStorageProvider } from './objectStorage.js';
3
3
  export type WorkspaceAssetKind = 'document' | 'image' | 'file';
4
4
  export type WorkspaceAssetScanStatus = 'pending' | 'clean' | 'infected' | 'failed';
5
5
  export type WorkspaceAssetLinkRole = 'attachment' | 'image' | 'reference';
6
+ export type WorkspaceAssetLinkTargetType = 'task' | 'initiative' | 'workstream';
6
7
  export interface WorkspaceAssetEntry {
7
8
  assetId: string;
8
9
  tenantId: string;
@@ -32,7 +33,9 @@ export interface WorkspaceAssetLinkEntry {
32
33
  tenantId: string;
33
34
  workspaceId: string;
34
35
  assetId: string;
35
- taskId: string;
36
+ targetType: WorkspaceAssetLinkTargetType;
37
+ targetId: string;
38
+ taskId: string | null;
36
39
  role: WorkspaceAssetLinkRole;
37
40
  displayName: string | null;
38
41
  sortOrder: number | null;
@@ -43,7 +46,9 @@ export interface WorkspaceAssetLinkEntry {
43
46
  export declare class WorkspaceAssetStore {
44
47
  private readonly db;
45
48
  private readonly tenantId;
49
+ private supportsTargetTypeCache;
46
50
  constructor(db: DatabaseAdapter, tenantId: string);
51
+ private supportsTargetType;
47
52
  private normalizePersistedAsset;
48
53
  get(assetIdRaw: string, workspaceIdRaw?: string): WorkspaceAssetEntry | null;
49
54
  getByStorageKey(storageKeyRaw: string, workspaceIdRaw: string): WorkspaceAssetEntry | null;
@@ -98,7 +103,9 @@ export declare class WorkspaceAssetStore {
98
103
  upsertLink(input: {
99
104
  workspaceId: string;
100
105
  assetId: string;
101
- taskId: string;
106
+ taskId?: string;
107
+ targetType?: WorkspaceAssetLinkTargetType;
108
+ targetId?: string;
102
109
  role: WorkspaceAssetLinkRole;
103
110
  displayName?: string | null;
104
111
  sortOrder?: number | null;
@@ -109,13 +116,18 @@ export declare class WorkspaceAssetStore {
109
116
  listLinksForTask(taskIdRaw: string, workspaceIdRaw: string, options?: {
110
117
  includeDeleted?: boolean;
111
118
  }): WorkspaceAssetLinkEntry[];
119
+ listLinksForTarget(targetTypeRaw: WorkspaceAssetLinkTargetType, targetIdRaw: string, workspaceIdRaw: string, options?: {
120
+ includeDeleted?: boolean;
121
+ }): WorkspaceAssetLinkEntry[];
112
122
  listLinksForAsset(assetIdRaw: string, workspaceIdRaw: string, options?: {
113
123
  includeDeleted?: boolean;
114
124
  }): WorkspaceAssetLinkEntry[];
115
125
  deleteLink(input: {
116
126
  workspaceId: string;
117
127
  assetId: string;
118
- taskId: string;
128
+ taskId?: string;
129
+ targetType?: WorkspaceAssetLinkTargetType;
130
+ targetId?: string;
119
131
  role: WorkspaceAssetLinkRole;
120
132
  hardDelete?: boolean;
121
133
  }): boolean;
@@ -47,11 +47,15 @@ function mapAssetRow(row) {
47
47
  };
48
48
  }
49
49
  function mapLinkRow(row) {
50
+ const targetType = String(row.target_type || 'task').trim();
51
+ const targetId = String(row.task_id || '').trim();
50
52
  return {
51
53
  tenantId: String(row.tenant_id || 'default'),
52
54
  workspaceId: String(row.workspace_id || 'default'),
53
55
  assetId: String(row.asset_id || ''),
54
- taskId: String(row.task_id || ''),
56
+ targetType,
57
+ targetId,
58
+ taskId: targetType === 'task' ? targetId : null,
55
59
  role: String(row.role || 'attachment'),
56
60
  displayName: row.display_name == null ? null : String(row.display_name),
57
61
  sortOrder: row.sort_order == null ? null : Number(row.sort_order),
@@ -73,10 +77,36 @@ function usesReadableAssetReference(kind) {
73
77
  export class WorkspaceAssetStore {
74
78
  db;
75
79
  tenantId;
80
+ supportsTargetTypeCache = null;
76
81
  constructor(db, tenantId) {
77
82
  this.db = db;
78
83
  this.tenantId = tenantId;
79
84
  }
85
+ supportsTargetType() {
86
+ if (this.supportsTargetTypeCache !== null)
87
+ return this.supportsTargetTypeCache;
88
+ try {
89
+ if (this.db.provider === 'postgres') {
90
+ const row = this.db.prepare(`
91
+ SELECT 1
92
+ FROM information_schema.columns
93
+ WHERE table_schema = current_schema()
94
+ AND table_name = 'workspace_asset_links'
95
+ AND column_name = 'target_type'
96
+ LIMIT 1
97
+ `).get();
98
+ this.supportsTargetTypeCache = Boolean(row);
99
+ return this.supportsTargetTypeCache;
100
+ }
101
+ const columns = this.db.prepare(`PRAGMA table_info(workspace_asset_links)`).all();
102
+ this.supportsTargetTypeCache = columns.some((column) => String(column.name || '').trim() === 'target_type');
103
+ return this.supportsTargetTypeCache;
104
+ }
105
+ catch {
106
+ this.supportsTargetTypeCache = false;
107
+ return false;
108
+ }
109
+ }
80
110
  normalizePersistedAsset(entry) {
81
111
  const normalizedKind = normalizeCanonicalAssetKind({
82
112
  kind: entry.kind,
@@ -411,47 +441,93 @@ export class WorkspaceAssetStore {
411
441
  upsertLink(input) {
412
442
  const workspaceId = normalizeWorkspaceId(input.workspaceId);
413
443
  const assetId = normalizeId(input.assetId);
414
- const taskId = normalizeId(input.taskId);
444
+ const targetType = normalizeId(input.targetType, input.taskId ? 'task' : 'task');
445
+ const targetId = normalizeId(input.targetId, input.taskId || '');
415
446
  const role = normalizeId(input.role, 'attachment');
416
447
  const createdAt = normalizeId(input.createdAt, new Date().toISOString());
417
448
  const updatedAt = normalizeId(input.updatedAt, createdAt);
418
- this.db.prepare(`
419
- INSERT INTO workspace_asset_links (
420
- tenant_id,
421
- workspace_id,
422
- asset_id,
423
- task_id,
424
- role,
425
- display_name,
426
- sort_order,
427
- created_at,
428
- updated_at,
429
- deleted_at
430
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
431
- ON CONFLICT(tenant_id, workspace_id, asset_id, task_id, role) DO UPDATE SET
432
- display_name = excluded.display_name,
433
- sort_order = excluded.sort_order,
434
- updated_at = excluded.updated_at,
435
- deleted_at = excluded.deleted_at
436
- `).run(this.tenantId, workspaceId, assetId, taskId, role, normalizeNullable(input.displayName), input.sortOrder == null ? null : Math.floor(Number(input.sortOrder)), createdAt, updatedAt, normalizeNullable(input.deletedAt));
449
+ if (!targetId) {
450
+ throw new Error('Workspace asset links require a targetId.');
451
+ }
452
+ if (this.supportsTargetType()) {
453
+ this.db.prepare(`
454
+ INSERT INTO workspace_asset_links (
455
+ tenant_id,
456
+ workspace_id,
457
+ asset_id,
458
+ target_type,
459
+ task_id,
460
+ role,
461
+ display_name,
462
+ sort_order,
463
+ created_at,
464
+ updated_at,
465
+ deleted_at
466
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
467
+ ON CONFLICT(tenant_id, workspace_id, asset_id, task_id, role) DO UPDATE SET
468
+ target_type = excluded.target_type,
469
+ display_name = excluded.display_name,
470
+ sort_order = excluded.sort_order,
471
+ updated_at = excluded.updated_at,
472
+ deleted_at = excluded.deleted_at
473
+ `).run(this.tenantId, workspaceId, assetId, targetType, targetId, role, normalizeNullable(input.displayName), input.sortOrder == null ? null : Math.floor(Number(input.sortOrder)), createdAt, updatedAt, normalizeNullable(input.deletedAt));
474
+ }
475
+ else {
476
+ this.db.prepare(`
477
+ INSERT INTO workspace_asset_links (
478
+ tenant_id,
479
+ workspace_id,
480
+ asset_id,
481
+ task_id,
482
+ role,
483
+ display_name,
484
+ sort_order,
485
+ created_at,
486
+ updated_at,
487
+ deleted_at
488
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
489
+ ON CONFLICT(tenant_id, workspace_id, asset_id, task_id, role) DO UPDATE SET
490
+ display_name = excluded.display_name,
491
+ sort_order = excluded.sort_order,
492
+ updated_at = excluded.updated_at,
493
+ deleted_at = excluded.deleted_at
494
+ `).run(this.tenantId, workspaceId, assetId, targetId, role, normalizeNullable(input.displayName), input.sortOrder == null ? null : Math.floor(Number(input.sortOrder)), createdAt, updatedAt, normalizeNullable(input.deletedAt));
495
+ }
437
496
  return this.listLinksForAsset(assetId, workspaceId, { includeDeleted: true })
438
- .find((entry) => entry.taskId === taskId && entry.role === role);
497
+ .find((entry) => entry.targetType === targetType && entry.targetId === targetId && entry.role === role);
439
498
  }
440
499
  listLinksForTask(taskIdRaw, workspaceIdRaw, options) {
500
+ return this.listLinksForTarget('task', taskIdRaw, workspaceIdRaw, options);
501
+ }
502
+ listLinksForTarget(targetTypeRaw, targetIdRaw, workspaceIdRaw, options) {
441
503
  const workspaceId = normalizeWorkspaceId(workspaceIdRaw);
442
- const taskId = normalizeId(taskIdRaw);
443
- if (!taskId)
504
+ const targetType = normalizeId(targetTypeRaw, 'task');
505
+ const targetId = normalizeId(targetIdRaw);
506
+ if (!targetId)
444
507
  return [];
445
508
  const includeDeleted = options?.includeDeleted === true;
446
- const rows = this.db.prepare(`
447
- SELECT *
448
- FROM workspace_asset_links
449
- WHERE tenant_id = ?
450
- AND workspace_id = ?
451
- AND task_id = ?
452
- ${includeDeleted ? '' : 'AND deleted_at IS NULL'}
453
- ORDER BY COALESCE(sort_order, 2147483647) ASC, created_at ASC, asset_id ASC
454
- `).all(this.tenantId, workspaceId, taskId);
509
+ const rows = this.supportsTargetType()
510
+ ? this.db.prepare(`
511
+ SELECT *
512
+ FROM workspace_asset_links
513
+ WHERE tenant_id = ?
514
+ AND workspace_id = ?
515
+ AND target_type = ?
516
+ AND task_id = ?
517
+ ${includeDeleted ? '' : 'AND deleted_at IS NULL'}
518
+ ORDER BY COALESCE(sort_order, 2147483647) ASC, created_at ASC, asset_id ASC
519
+ `).all(this.tenantId, workspaceId, targetType, targetId)
520
+ : (targetType === 'task'
521
+ ? this.db.prepare(`
522
+ SELECT *
523
+ FROM workspace_asset_links
524
+ WHERE tenant_id = ?
525
+ AND workspace_id = ?
526
+ AND task_id = ?
527
+ ${includeDeleted ? '' : 'AND deleted_at IS NULL'}
528
+ ORDER BY COALESCE(sort_order, 2147483647) ASC, created_at ASC, asset_id ASC
529
+ `).all(this.tenantId, workspaceId, targetId)
530
+ : []);
455
531
  return rows.map(mapLinkRow);
456
532
  }
457
533
  listLinksForAsset(assetIdRaw, workspaceIdRaw, options) {
@@ -474,23 +550,35 @@ export class WorkspaceAssetStore {
474
550
  deleteLink(input) {
475
551
  const workspaceId = normalizeWorkspaceId(input.workspaceId);
476
552
  const assetId = normalizeId(input.assetId);
477
- const taskId = normalizeId(input.taskId);
553
+ const targetType = normalizeId(input.targetType, input.taskId ? 'task' : 'task');
554
+ const targetId = normalizeId(input.targetId, input.taskId || '');
478
555
  const role = normalizeId(input.role);
479
- if (!assetId || !taskId || !role)
556
+ if (!assetId || !targetId || !role)
480
557
  return false;
481
558
  if (input.hardDelete) {
482
- const result = this.db.prepare(`
483
- DELETE FROM workspace_asset_links
484
- WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND task_id = ? AND role = ?
485
- `).run(this.tenantId, workspaceId, assetId, taskId, role);
559
+ const result = this.supportsTargetType()
560
+ ? this.db.prepare(`
561
+ DELETE FROM workspace_asset_links
562
+ WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND target_type = ? AND task_id = ? AND role = ?
563
+ `).run(this.tenantId, workspaceId, assetId, targetType, targetId, role)
564
+ : this.db.prepare(`
565
+ DELETE FROM workspace_asset_links
566
+ WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND task_id = ? AND role = ?
567
+ `).run(this.tenantId, workspaceId, assetId, targetId, role);
486
568
  return Number(result?.changes || 0) > 0;
487
569
  }
488
570
  const nowIso = new Date().toISOString();
489
- const result = this.db.prepare(`
490
- UPDATE workspace_asset_links
491
- SET deleted_at = ?, updated_at = ?
492
- WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND task_id = ? AND role = ?
493
- `).run(nowIso, nowIso, this.tenantId, workspaceId, assetId, taskId, role);
571
+ const result = this.supportsTargetType()
572
+ ? this.db.prepare(`
573
+ UPDATE workspace_asset_links
574
+ SET deleted_at = ?, updated_at = ?
575
+ WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND target_type = ? AND task_id = ? AND role = ?
576
+ `).run(nowIso, nowIso, this.tenantId, workspaceId, assetId, targetType, targetId, role)
577
+ : this.db.prepare(`
578
+ UPDATE workspace_asset_links
579
+ SET deleted_at = ?, updated_at = ?
580
+ WHERE tenant_id = ? AND workspace_id = ? AND asset_id = ? AND task_id = ? AND role = ?
581
+ `).run(nowIso, nowIso, this.tenantId, workspaceId, assetId, targetId, role);
494
582
  return Number(result?.changes || 0) > 0;
495
583
  }
496
584
  }
@@ -23,6 +23,40 @@ function makeStore() {
23
23
  }
24
24
  };
25
25
  }
26
+ function makeLegacyLinkStore() {
27
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-workspace-assets-legacy-'));
28
+ const dbPath = path.join(root, 'taskforce.db');
29
+ const db = createDatabaseAdapter({ sqliteDbPath: dbPath });
30
+ ensureTaskAssetsSchema(db);
31
+ ensureWorkspaceAssetsSchema(db);
32
+ ensureDocumentReferenceSchema(db);
33
+ db.exec(`DROP TABLE IF EXISTS workspace_asset_links`);
34
+ db.exec(`
35
+ CREATE TABLE IF NOT EXISTS workspace_asset_links (
36
+ tenant_id TEXT NOT NULL DEFAULT 'default',
37
+ workspace_id TEXT NOT NULL,
38
+ asset_id TEXT NOT NULL,
39
+ task_id TEXT NOT NULL,
40
+ role TEXT NOT NULL,
41
+ display_name TEXT,
42
+ sort_order INTEGER,
43
+ created_at TEXT NOT NULL,
44
+ updated_at TEXT NOT NULL,
45
+ deleted_at TEXT,
46
+ PRIMARY KEY (tenant_id, workspace_id, asset_id, task_id, role)
47
+ );
48
+ `);
49
+ const store = new WorkspaceAssetStore(db, 'default');
50
+ return {
51
+ root,
52
+ db,
53
+ store,
54
+ cleanup() {
55
+ db.close();
56
+ fs.rmSync(root, { recursive: true, force: true });
57
+ }
58
+ };
59
+ }
26
60
  describe('WorkspaceAssetStore', () => {
27
61
  const cleanups = [];
28
62
  afterEach(() => {
@@ -64,6 +98,50 @@ describe('WorkspaceAssetStore', () => {
64
98
  referenceNumber: 1
65
99
  });
66
100
  });
101
+ it('supports legacy workspace asset links without target_type', () => {
102
+ const ctx = makeLegacyLinkStore();
103
+ cleanups.push(() => ctx.cleanup());
104
+ const now = new Date().toISOString();
105
+ ctx.store.upsertAsset({
106
+ assetId: 'asset-legacy-1',
107
+ workspaceId: 'workspace-1',
108
+ kind: 'file',
109
+ originalFilename: 'legacy.pdf',
110
+ mimeType: 'application/pdf',
111
+ storageProvider: 'local',
112
+ storageKey: 'workspaces/workspace-1/assets/files/asset-legacy-1-legacy.pdf',
113
+ contentSha256: 'sha-legacy',
114
+ sizeBytes: 42,
115
+ createdAt: now,
116
+ updatedAt: now
117
+ });
118
+ const created = ctx.store.upsertLink({
119
+ workspaceId: 'workspace-1',
120
+ assetId: 'asset-legacy-1',
121
+ taskId: 'task-1',
122
+ role: 'attachment',
123
+ displayName: 'Legacy attachment',
124
+ createdAt: now,
125
+ updatedAt: now
126
+ });
127
+ expect(created.targetType).toBe('task');
128
+ expect(created.targetId).toBe('task-1');
129
+ expect(ctx.store.listLinksForTask('task-1', 'workspace-1')).toEqual([
130
+ expect.objectContaining({
131
+ assetId: 'asset-legacy-1',
132
+ targetType: 'task',
133
+ targetId: 'task-1',
134
+ role: 'attachment'
135
+ })
136
+ ]);
137
+ expect(ctx.store.deleteLink({
138
+ workspaceId: 'workspace-1',
139
+ assetId: 'asset-legacy-1',
140
+ taskId: 'task-1',
141
+ role: 'attachment'
142
+ })).toBe(true);
143
+ expect(ctx.store.listLinksForTask('task-1', 'workspace-1')).toEqual([]);
144
+ });
67
145
  it('assigns sequential document reference numbers per workspace', () => {
68
146
  const ctx = makeStore();
69
147
  cleanups.push(() => ctx.cleanup());
@@ -382,7 +460,7 @@ describe('WorkspaceAssetStore', () => {
382
460
  const all = ctx.store.listByWorkspace('workspace-1', { includeDeleted: true });
383
461
  expect(all.map((entry) => entry.assetId).sort()).toEqual(['asset-a', 'asset-b']);
384
462
  });
385
- it('creates, lists, and soft-deletes task links separately from asset identity', () => {
463
+ it('creates, lists, and soft-deletes generic target links while preserving task compatibility helpers', () => {
386
464
  const ctx = makeStore();
387
465
  cleanups.push(() => ctx.cleanup());
388
466
  ctx.store.upsertAsset({
@@ -399,27 +477,36 @@ describe('WorkspaceAssetStore', () => {
399
477
  ctx.store.upsertLink({
400
478
  workspaceId: 'workspace-1',
401
479
  assetId: 'asset-link-1',
402
- taskId: 'task-1',
480
+ targetType: 'task',
481
+ targetId: 'task-1',
403
482
  role: 'attachment',
404
483
  sortOrder: 2
405
484
  });
406
485
  ctx.store.upsertLink({
407
486
  workspaceId: 'workspace-1',
408
487
  assetId: 'asset-link-1',
409
- taskId: 'task-2',
488
+ targetType: 'initiative',
489
+ targetId: 'initiative-2',
410
490
  role: 'reference',
411
491
  sortOrder: 1
412
492
  });
413
493
  const taskLinks = ctx.store.listLinksForTask('task-1', 'workspace-1');
414
494
  expect(taskLinks).toHaveLength(1);
415
- expect(taskLinks[0]).toMatchObject({ assetId: 'asset-link-1', role: 'attachment' });
495
+ expect(taskLinks[0]).toMatchObject({ assetId: 'asset-link-1', role: 'attachment', targetType: 'task', targetId: 'task-1', taskId: 'task-1' });
496
+ const initiativeLinks = ctx.store.listLinksForTarget('initiative', 'initiative-2', 'workspace-1');
497
+ expect(initiativeLinks).toHaveLength(1);
498
+ expect(initiativeLinks[0]).toMatchObject({ assetId: 'asset-link-1', role: 'reference', targetType: 'initiative', targetId: 'initiative-2', taskId: null });
416
499
  const assetLinks = ctx.store.listLinksForAsset('asset-link-1', 'workspace-1');
417
500
  expect(assetLinks).toHaveLength(2);
418
- expect(assetLinks.map((entry) => entry.taskId)).toEqual(['task-2', 'task-1']);
501
+ expect(assetLinks.map((entry) => [entry.targetType, entry.targetId])).toEqual([
502
+ ['initiative', 'initiative-2'],
503
+ ['task', 'task-1'],
504
+ ]);
419
505
  expect(ctx.store.deleteLink({
420
506
  workspaceId: 'workspace-1',
421
507
  assetId: 'asset-link-1',
422
- taskId: 'task-1',
508
+ targetType: 'task',
509
+ targetId: 'task-1',
423
510
  role: 'attachment'
424
511
  })).toBe(true);
425
512
  expect(ctx.store.listLinksForTask('task-1', 'workspace-1')).toHaveLength(0);