@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
@@ -3,6 +3,7 @@ import { Readable } from 'stream';
3
3
  import * as fs from 'fs';
4
4
  import * as os from 'os';
5
5
  import * as path from 'path';
6
+ import { createHash } from 'crypto';
6
7
  import { TaskforceCore } from '../../core/Taskforce';
7
8
  import { createRoutes, matchRoute } from '../routes';
8
9
  import { createSessionTokenCookie } from '../auth';
@@ -110,6 +111,7 @@ describe('Server Routes Workspace Sync Integration', () => {
110
111
  const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-');
111
112
  const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
112
113
  core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
114
+ const basePath = core.getPaths().basePath;
113
115
  const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
114
116
  const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
115
117
  expect(match).toBeTruthy();
@@ -344,6 +346,8 @@ describe('Server Routes Workspace Sync Integration', () => {
344
346
  username: 'codex',
345
347
  icon: 'Bot',
346
348
  color: '#0ea5e9',
349
+ avatarUrl: '/api/taskforce/context/workspaces%2Fworkspace-sync-ai%2Fassets%2Fimages%2Fai-profiles%2Fai-profile-codex%2Favatar%2Favatar.webp',
350
+ avatarSourceUrl: '/api/taskforce/context/workspaces%2Fworkspace-sync-ai%2Fassets%2Fimages%2Fai-profiles%2Fai-profile-codex%2Favatar%2Fsource.webp',
347
351
  description: 'Terminal-first coding agent',
348
352
  role: 'Implementer',
349
353
  provider: 'OpenAI',
@@ -369,7 +373,9 @@ describe('Server Routes Workspace Sync Integration', () => {
369
373
  role: 'Implementer',
370
374
  provider: 'OpenAI',
371
375
  model: 'gpt-5.4',
372
- surfaceType: 'coding_tool'
376
+ surfaceType: 'coding_tool',
377
+ avatarUrl: '/api/taskforce/context/workspaces%2Fworkspace-sync-ai%2Fassets%2Fimages%2Fai-profiles%2Fai-profile-codex%2Favatar%2Favatar.webp',
378
+ avatarSourceUrl: '/api/taskforce/context/workspaces%2Fworkspace-sync-ai%2Fassets%2Fimages%2Fai-profiles%2Fai-profile-codex%2Favatar%2Fsource.webp'
373
379
  })
374
380
  ]);
375
381
  });
@@ -1319,7 +1325,7 @@ describe('Server Routes Workspace Sync Integration', () => {
1319
1325
  })
1320
1326
  ]);
1321
1327
  });
1322
- it('lets the cloud document ref win during normal sync apply and records a diagnostic', async () => {
1328
+ it('lets the cloud document ref win during normal sync apply without recording a sync error', async () => {
1323
1329
  const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-doc-ref-drift-');
1324
1330
  const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1325
1331
  core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
@@ -1381,26 +1387,17 @@ describe('Server Routes Workspace Sync Integration', () => {
1381
1387
  const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1382
1388
  const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
1383
1389
  const asset = store.get('asset-doc-ref-drift-1', workspaceId);
1384
- const syncEvents = reloadedCore.listSyncEvents(workspaceId, 20);
1390
+ const mismatchEvents = reloadedCore.listSyncEvents(workspaceId, 20).filter((event) => event.eventType === 'apply'
1391
+ && event.status === 'error'
1392
+ && event.errorMessage === 'Detected document reference number mismatch during normal sync.');
1385
1393
  expect(asset).toEqual(expect.objectContaining({
1386
1394
  assetId: 'asset-doc-ref-drift-1',
1387
1395
  referenceNumber: 18,
1388
1396
  version: 2
1389
1397
  }));
1390
- expect(syncEvents).toEqual(expect.arrayContaining([
1391
- expect.objectContaining({
1392
- eventType: 'apply',
1393
- status: 'error',
1394
- errorMessage: 'Detected document reference number mismatch during normal sync.',
1395
- details: expect.objectContaining({
1396
- assetId: 'asset-doc-ref-drift-1',
1397
- existingReferenceNumber: 12,
1398
- incomingReferenceNumber: 18
1399
- })
1400
- })
1401
- ]));
1398
+ expect(mismatchEvents).toHaveLength(0);
1402
1399
  });
1403
- it('lets the cloud image ref win during normal sync apply and records a diagnostic', async () => {
1400
+ it('lets the cloud image ref win during normal sync apply without recording a sync error', async () => {
1404
1401
  const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-image-ref-drift-');
1405
1402
  const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1406
1403
  core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
@@ -1461,23 +1458,94 @@ describe('Server Routes Workspace Sync Integration', () => {
1461
1458
  const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1462
1459
  const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
1463
1460
  const asset = store.get('asset-image-ref-drift-1', workspaceId);
1464
- const syncEvents = reloadedCore.listSyncEvents(workspaceId, 20);
1461
+ const mismatchEvents = reloadedCore.listSyncEvents(workspaceId, 20).filter((event) => event.eventType === 'apply'
1462
+ && event.status === 'error'
1463
+ && event.errorMessage === 'Detected asset reference number mismatch during normal sync.');
1465
1464
  expect(asset).toEqual(expect.objectContaining({
1466
1465
  assetId: 'asset-image-ref-drift-1',
1467
1466
  referenceNumber: 18
1468
1467
  }));
1469
- expect(syncEvents).toEqual(expect.arrayContaining([
1470
- expect.objectContaining({
1471
- eventType: 'apply',
1472
- status: 'error',
1473
- errorMessage: 'Detected asset reference number mismatch during normal sync.',
1474
- details: expect.objectContaining({
1475
- assetId: 'asset-image-ref-drift-1',
1476
- existingReferenceNumber: 12,
1477
- incomingReferenceNumber: 18
1478
- })
1479
- })
1480
- ]));
1468
+ expect(mismatchEvents).toHaveLength(0);
1469
+ });
1470
+ it('converges unchanged image reference drift during normal sync so later pulls stop re-logging it', async () => {
1471
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-image-ref-converge-');
1472
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1473
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
1474
+ core.createTask({
1475
+ id: 'task-image-ref-converge-1',
1476
+ title: 'Task with converging synced image asset',
1477
+ status: 'task'
1478
+ }, {
1479
+ workspaceId,
1480
+ allowClientProvidedId: true
1481
+ });
1482
+ const basePath = core.getPaths().basePath;
1483
+ const relativeAssetPath = 'workspaces/workspace-local-active/assets/images/hero-converge.png';
1484
+ const absoluteAssetPath = path.join(basePath, relativeAssetPath);
1485
+ fs.mkdirSync(path.dirname(absoluteAssetPath), { recursive: true });
1486
+ fs.writeFileSync(absoluteAssetPath, 'cloud-hero');
1487
+ const seededStore = new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId());
1488
+ seededStore.upsertAsset({
1489
+ assetId: 'asset-image-ref-converge-1',
1490
+ workspaceId,
1491
+ kind: 'image',
1492
+ originalFilename: 'hero-converge.png',
1493
+ mimeType: 'image/png',
1494
+ storageProvider: 'local',
1495
+ storageKey: relativeAssetPath,
1496
+ contentSha256: createHash('sha256').update('cloud-hero').digest('hex'),
1497
+ sizeBytes: Buffer.byteLength('cloud-hero'),
1498
+ referenceNumber: 12,
1499
+ createdAt: '2026-03-17T11:55:00.000Z',
1500
+ updatedAt: '2026-03-17T12:05:00.000Z'
1501
+ });
1502
+ seededStore.upsertLink({
1503
+ workspaceId,
1504
+ assetId: 'asset-image-ref-converge-1',
1505
+ taskId: 'task-image-ref-converge-1',
1506
+ role: 'image',
1507
+ updatedAt: '2026-03-17T11:55:00.000Z',
1508
+ deletedAt: null
1509
+ });
1510
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
1511
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
1512
+ expect(match).toBeTruthy();
1513
+ const body = {
1514
+ changes: [
1515
+ {
1516
+ op: 'asset-upsert',
1517
+ path: relativeAssetPath,
1518
+ updatedAt: '2026-03-17T12:05:00.000Z',
1519
+ contentBase64: Buffer.from('cloud-hero').toString('base64'),
1520
+ assetId: 'asset-image-ref-converge-1',
1521
+ kind: 'image',
1522
+ mimeType: 'image/png',
1523
+ referenceNumber: 18,
1524
+ taskId: 'task-image-ref-converge-1',
1525
+ originalFilename: 'hero-converge.png',
1526
+ linkRole: 'image'
1527
+ }
1528
+ ]
1529
+ };
1530
+ const firstReq = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', body, { 'x-taskforce-workspace-id': workspaceId });
1531
+ const firstRes = createMockRes();
1532
+ await match?.handler(firstReq, firstRes, {});
1533
+ expect(firstRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
1534
+ const secondReq = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', body, { 'x-taskforce-workspace-id': workspaceId });
1535
+ const secondRes = createMockRes();
1536
+ await match?.handler(secondReq, secondRes, {});
1537
+ expect(secondRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
1538
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
1539
+ const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
1540
+ const asset = store.get('asset-image-ref-converge-1', workspaceId);
1541
+ const mismatchEvents = reloadedCore.listSyncEvents(workspaceId, 20).filter((event) => event.eventType === 'apply'
1542
+ && event.status === 'error'
1543
+ && event.errorMessage === 'Detected asset reference number mismatch during normal sync.');
1544
+ expect(asset).toEqual(expect.objectContaining({
1545
+ assetId: 'asset-image-ref-converge-1',
1546
+ referenceNumber: 18
1547
+ }));
1548
+ expect(mismatchEvents).toHaveLength(0);
1481
1549
  });
1482
1550
  it('round-trips cloud attachment links for canonical markdown and file assets into local apply', async () => {
1483
1551
  const cloudProjectRoot = createIsolatedProjectRoot('taskforce-sync-cloud-link-roundtrip-cloud-');
@@ -1992,9 +2060,9 @@ describe('Server Routes Workspace Sync Integration', () => {
1992
2060
  workspaceId,
1993
2061
  eventType: 'apply',
1994
2062
  status: 'error',
1995
- errorMessage: 'Detected asset reference number mismatch during normal sync.',
2063
+ errorMessage: 'Detected task reference number mismatch during normal sync.',
1996
2064
  details: {
1997
- path: 'workspaces/workspace-local-active/assets/images/hero.png',
2065
+ taskId: 'task-1',
1998
2066
  existingReferenceNumber: 12,
1999
2067
  incomingReferenceNumber: 18
2000
2068
  }
@@ -2013,15 +2081,319 @@ describe('Server Routes Workspace Sync Integration', () => {
2013
2081
  expect.objectContaining({
2014
2082
  eventType: 'apply',
2015
2083
  status: 'error',
2016
- errorMessage: 'Detected asset reference number mismatch during normal sync.',
2084
+ errorMessage: 'Detected task reference number mismatch during normal sync.',
2017
2085
  details: expect.objectContaining({
2018
- path: 'workspaces/workspace-local-active/assets/images/hero.png',
2086
+ taskId: 'task-1',
2019
2087
  existingReferenceNumber: 12,
2020
2088
  incomingReferenceNumber: 18
2021
2089
  })
2022
2090
  })
2023
2091
  ]));
2024
2092
  });
2093
+ it('repairs orphaned startup document and asset metadata and exposes the recovered metadata through snapshots', async () => {
2094
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-repair-startup-recover-');
2095
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2096
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
2097
+ const basePath = core.getPaths().basePath;
2098
+ const relativeDocPath = 'docs/recovered-startup-note.md';
2099
+ const relativeAssetPath = `workspaces/${workspaceId}/assets/images/recovered-startup-image.png`;
2100
+ const absoluteDocPath = path.join(basePath, relativeDocPath);
2101
+ const absoluteAssetPath = path.join(basePath, relativeAssetPath);
2102
+ const assetBuffer = Buffer.from('recovered-image-binary');
2103
+ fs.mkdirSync(path.dirname(absoluteDocPath), { recursive: true });
2104
+ fs.mkdirSync(path.dirname(absoluteAssetPath), { recursive: true });
2105
+ fs.writeFileSync(absoluteDocPath, '# repaired startup note\n', 'utf8');
2106
+ fs.writeFileSync(absoluteAssetPath, assetBuffer);
2107
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
2108
+ const documentsMatch = matchRoute('GET', '/api/taskforce/sync/workspace/documents', routes);
2109
+ const assetsMatch = matchRoute('GET', '/api/taskforce/sync/workspace/assets', routes);
2110
+ const repairMatch = matchRoute('POST', '/api/taskforce/sync/workspace/repair-startup', routes);
2111
+ expect(documentsMatch).toBeTruthy();
2112
+ expect(assetsMatch).toBeTruthy();
2113
+ expect(repairMatch).toBeTruthy();
2114
+ const beforeDocumentsReq = createMockReq('GET', `/api/taskforce/sync/workspace/documents?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2115
+ const beforeDocumentsRes = createMockRes();
2116
+ await documentsMatch?.handler(beforeDocumentsReq, beforeDocumentsRes, {});
2117
+ expect(beforeDocumentsRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2118
+ const beforeDocumentsBody = readJsonBody(beforeDocumentsRes);
2119
+ const beforeDoc = (Array.isArray(beforeDocumentsBody?.documents) ? beforeDocumentsBody.documents : [])
2120
+ .find((entry) => entry.path === relativeDocPath);
2121
+ expect(beforeDoc).toBeUndefined();
2122
+ const beforeAssetsReq = createMockReq('GET', `/api/taskforce/sync/workspace/assets?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2123
+ const beforeAssetsRes = createMockRes();
2124
+ await assetsMatch?.handler(beforeAssetsReq, beforeAssetsRes, {});
2125
+ expect(beforeAssetsRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2126
+ const beforeAssetsBody = readJsonBody(beforeAssetsRes);
2127
+ expect(beforeAssetsBody.assets).toEqual([]);
2128
+ const repairReq = createMockReq('POST', '/api/taskforce/sync/workspace/repair-startup', { workspaceId }, { 'x-taskforce-workspace-id': workspaceId });
2129
+ const repairRes = createMockRes();
2130
+ await repairMatch?.handler(repairReq, repairRes, {});
2131
+ expect(repairRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2132
+ expect(readJsonBody(repairRes)).toEqual(expect.objectContaining({
2133
+ success: true,
2134
+ applied: 2,
2135
+ skipped: 0,
2136
+ failed: 0
2137
+ }));
2138
+ const afterDocumentsReq = createMockReq('GET', `/api/taskforce/sync/workspace/documents?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2139
+ const afterDocumentsRes = createMockRes();
2140
+ await documentsMatch?.handler(afterDocumentsReq, afterDocumentsRes, {});
2141
+ expect(afterDocumentsRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2142
+ const afterDocumentsBody = readJsonBody(afterDocumentsRes);
2143
+ const repairedDoc = (Array.isArray(afterDocumentsBody?.documents) ? afterDocumentsBody.documents : [])
2144
+ .find((entry) => entry.path === relativeDocPath);
2145
+ expect(repairedDoc).toEqual(expect.objectContaining({
2146
+ path: relativeDocPath,
2147
+ content: '# repaired startup note\n',
2148
+ assetId: expect.stringMatching(/^asset-/),
2149
+ documentId: expect.stringMatching(/^doc-/),
2150
+ originalFilename: 'recovered-startup-note.md'
2151
+ }));
2152
+ const afterAssetsReq = createMockReq('GET', `/api/taskforce/sync/workspace/assets?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2153
+ const afterAssetsRes = createMockRes();
2154
+ await assetsMatch?.handler(afterAssetsReq, afterAssetsRes, {});
2155
+ expect(afterAssetsRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2156
+ const afterAssetsBody = readJsonBody(afterAssetsRes);
2157
+ const repairedAsset = (Array.isArray(afterAssetsBody?.assets) ? afterAssetsBody.assets : [])
2158
+ .find((entry) => entry.path === relativeAssetPath);
2159
+ expect(repairedAsset).toEqual(expect.objectContaining({
2160
+ path: relativeAssetPath,
2161
+ assetId: expect.stringMatching(/^asset-/),
2162
+ kind: 'image',
2163
+ mimeType: 'image/png',
2164
+ originalFilename: 'recovered-startup-image.png',
2165
+ linkRole: 'image',
2166
+ contentBase64: assetBuffer.toString('base64')
2167
+ }));
2168
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2169
+ const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
2170
+ expect(store.getByStorageKey(relativeDocPath, workspaceId)).toEqual(expect.objectContaining({
2171
+ storageKey: relativeDocPath,
2172
+ kind: 'document',
2173
+ mimeType: 'text/markdown',
2174
+ originalFilename: 'recovered-startup-note.md',
2175
+ documentId: expect.stringMatching(/^doc-/)
2176
+ }));
2177
+ expect(store.getByStorageKey(relativeAssetPath, workspaceId)).toEqual(expect.objectContaining({
2178
+ storageKey: relativeAssetPath,
2179
+ kind: 'image',
2180
+ mimeType: 'image/png',
2181
+ originalFilename: 'recovered-startup-image.png'
2182
+ }));
2183
+ });
2184
+ it('revives stale startup tombstones with the original metadata identifiers when local files are newer', async () => {
2185
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-repair-startup-revive-');
2186
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2187
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
2188
+ const basePath = core.getPaths().basePath;
2189
+ const relativeDocPath = 'docs/revived-startup-note.md';
2190
+ const relativeAssetPath = `workspaces/${workspaceId}/assets/images/revived-startup-image.png`;
2191
+ const absoluteDocPath = path.join(basePath, relativeDocPath);
2192
+ const absoluteAssetPath = path.join(basePath, relativeAssetPath);
2193
+ const revivedAt = new Date('2026-03-17T12:00:00.000Z');
2194
+ const deletedAt = '2026-03-10T08:00:00.000Z';
2195
+ const assetBuffer = Buffer.from('revived-image-binary');
2196
+ fs.mkdirSync(path.dirname(absoluteDocPath), { recursive: true });
2197
+ fs.mkdirSync(path.dirname(absoluteAssetPath), { recursive: true });
2198
+ fs.writeFileSync(absoluteDocPath, '# revived startup note\n', 'utf8');
2199
+ fs.writeFileSync(absoluteAssetPath, assetBuffer);
2200
+ fs.utimesSync(absoluteDocPath, revivedAt, revivedAt);
2201
+ fs.utimesSync(absoluteAssetPath, revivedAt, revivedAt);
2202
+ const seededStore = new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId());
2203
+ seededStore.upsertAsset({
2204
+ assetId: 'asset-revived-doc-1',
2205
+ workspaceId,
2206
+ kind: 'document',
2207
+ originalFilename: 'revived-startup-note.md',
2208
+ mimeType: 'text/markdown',
2209
+ storageProvider: 'local',
2210
+ storageKey: relativeDocPath,
2211
+ contentSha256: 'deleted-doc-hash',
2212
+ sizeBytes: 1,
2213
+ documentId: 'doc-revived-startup-1',
2214
+ referenceNumber: 33,
2215
+ version: 4,
2216
+ createdAt: '2026-03-01T08:00:00.000Z',
2217
+ updatedAt: deletedAt,
2218
+ deletedAt,
2219
+ purgeAfter: '2026-06-01T08:00:00.000Z'
2220
+ });
2221
+ seededStore.upsertAsset({
2222
+ assetId: 'asset-revived-image-1',
2223
+ workspaceId,
2224
+ kind: 'image',
2225
+ originalFilename: 'revived-startup-image.png',
2226
+ mimeType: 'image/png',
2227
+ storageProvider: 'local',
2228
+ storageKey: relativeAssetPath,
2229
+ contentSha256: 'deleted-image-hash',
2230
+ sizeBytes: 1,
2231
+ referenceNumber: 21,
2232
+ createdAt: '2026-03-01T08:00:00.000Z',
2233
+ updatedAt: deletedAt,
2234
+ deletedAt,
2235
+ purgeAfter: '2026-06-01T08:00:00.000Z'
2236
+ });
2237
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
2238
+ const documentsMatch = matchRoute('GET', '/api/taskforce/sync/workspace/documents', routes);
2239
+ const assetsMatch = matchRoute('GET', '/api/taskforce/sync/workspace/assets', routes);
2240
+ const repairMatch = matchRoute('POST', '/api/taskforce/sync/workspace/repair-startup', routes);
2241
+ expect(documentsMatch).toBeTruthy();
2242
+ expect(assetsMatch).toBeTruthy();
2243
+ expect(repairMatch).toBeTruthy();
2244
+ const beforeDocumentsReq = createMockReq('GET', `/api/taskforce/sync/workspace/documents?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2245
+ const beforeDocumentsRes = createMockRes();
2246
+ await documentsMatch?.handler(beforeDocumentsReq, beforeDocumentsRes, {});
2247
+ const beforeDocumentsBody = readJsonBody(beforeDocumentsRes);
2248
+ const beforeDoc = (Array.isArray(beforeDocumentsBody?.documents) ? beforeDocumentsBody.documents : [])
2249
+ .find((entry) => entry.path === relativeDocPath);
2250
+ expect(beforeDoc).toBeUndefined();
2251
+ const beforeAssetsReq = createMockReq('GET', `/api/taskforce/sync/workspace/assets?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2252
+ const beforeAssetsRes = createMockRes();
2253
+ await assetsMatch?.handler(beforeAssetsReq, beforeAssetsRes, {});
2254
+ expect(readJsonBody(beforeAssetsRes).assets).toEqual([]);
2255
+ const repairReq = createMockReq('POST', '/api/taskforce/sync/workspace/repair-startup', { workspaceId }, { 'x-taskforce-workspace-id': workspaceId });
2256
+ const repairRes = createMockRes();
2257
+ await repairMatch?.handler(repairReq, repairRes, {});
2258
+ expect(readJsonBody(repairRes)).toEqual(expect.objectContaining({
2259
+ success: true,
2260
+ applied: 2,
2261
+ skipped: 0,
2262
+ failed: 0
2263
+ }));
2264
+ const afterDocumentsReq = createMockReq('GET', `/api/taskforce/sync/workspace/documents?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2265
+ const afterDocumentsRes = createMockRes();
2266
+ await documentsMatch?.handler(afterDocumentsReq, afterDocumentsRes, {});
2267
+ const repairedDoc = (Array.isArray(readJsonBody(afterDocumentsRes)?.documents) ? readJsonBody(afterDocumentsRes).documents : [])
2268
+ .find((entry) => entry.path === relativeDocPath);
2269
+ expect(repairedDoc).toEqual(expect.objectContaining({
2270
+ path: relativeDocPath,
2271
+ assetId: 'asset-revived-doc-1',
2272
+ documentId: 'doc-revived-startup-1',
2273
+ referenceNumber: 33,
2274
+ version: 5,
2275
+ originalFilename: 'revived-startup-note.md'
2276
+ }));
2277
+ const afterAssetsReq = createMockReq('GET', `/api/taskforce/sync/workspace/assets?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2278
+ const afterAssetsRes = createMockRes();
2279
+ await assetsMatch?.handler(afterAssetsReq, afterAssetsRes, {});
2280
+ const repairedAsset = (Array.isArray(readJsonBody(afterAssetsRes)?.assets) ? readJsonBody(afterAssetsRes).assets : [])
2281
+ .find((entry) => entry.path === relativeAssetPath);
2282
+ expect(repairedAsset).toEqual(expect.objectContaining({
2283
+ path: relativeAssetPath,
2284
+ assetId: 'asset-revived-image-1',
2285
+ referenceNumber: 21,
2286
+ kind: 'image',
2287
+ mimeType: 'image/png',
2288
+ originalFilename: 'revived-startup-image.png',
2289
+ contentBase64: assetBuffer.toString('base64')
2290
+ }));
2291
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2292
+ const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
2293
+ expect(store.get('asset-revived-doc-1', workspaceId)).toEqual(expect.objectContaining({
2294
+ assetId: 'asset-revived-doc-1',
2295
+ documentId: 'doc-revived-startup-1',
2296
+ referenceNumber: 33,
2297
+ version: 5,
2298
+ deletedAt: null
2299
+ }));
2300
+ expect(store.get('asset-revived-image-1', workspaceId)).toEqual(expect.objectContaining({
2301
+ assetId: 'asset-revived-image-1',
2302
+ referenceNumber: 21,
2303
+ deletedAt: null
2304
+ }));
2305
+ });
2306
+ it('removes stale startup asset links to missing tasks while preserving the asset snapshot', async () => {
2307
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-repair-startup-stale-link-');
2308
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2309
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
2310
+ const basePath = core.getPaths().basePath;
2311
+ const relativeAssetPath = `workspaces/${workspaceId}/assets/files/stale-link-asset.txt`;
2312
+ const absoluteAssetPath = path.join(basePath, relativeAssetPath);
2313
+ const assetBuffer = Buffer.from('stale-link-asset');
2314
+ fs.mkdirSync(path.dirname(absoluteAssetPath), { recursive: true });
2315
+ fs.writeFileSync(absoluteAssetPath, assetBuffer);
2316
+ const seededStore = new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId());
2317
+ seededStore.upsertAsset({
2318
+ assetId: 'asset-stale-link-1',
2319
+ workspaceId,
2320
+ kind: 'file',
2321
+ originalFilename: 'stale-link-asset.txt',
2322
+ mimeType: 'text/plain',
2323
+ storageProvider: 'local',
2324
+ storageKey: relativeAssetPath,
2325
+ contentSha256: createHash('sha256').update(assetBuffer).digest('hex'),
2326
+ sizeBytes: assetBuffer.length,
2327
+ createdAt: '2026-03-17T11:00:00.000Z',
2328
+ updatedAt: '2026-03-17T11:00:00.000Z'
2329
+ });
2330
+ seededStore.upsertLink({
2331
+ workspaceId,
2332
+ assetId: 'asset-stale-link-1',
2333
+ taskId: 'task-missing-link-1',
2334
+ role: 'attachment',
2335
+ updatedAt: '2026-03-17T11:00:00.000Z'
2336
+ });
2337
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
2338
+ const assetsMatch = matchRoute('GET', '/api/taskforce/sync/workspace/assets', routes);
2339
+ const repairMatch = matchRoute('POST', '/api/taskforce/sync/workspace/repair-startup', routes);
2340
+ expect(assetsMatch).toBeTruthy();
2341
+ expect(repairMatch).toBeTruthy();
2342
+ const beforeAssetsReq = createMockReq('GET', `/api/taskforce/sync/workspace/assets?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2343
+ const beforeAssetsRes = createMockRes();
2344
+ await assetsMatch?.handler(beforeAssetsReq, beforeAssetsRes, {});
2345
+ expect(beforeAssetsRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2346
+ const beforeAssetsBody = readJsonBody(beforeAssetsRes);
2347
+ const beforeAsset = (Array.isArray(beforeAssetsBody?.assets) ? beforeAssetsBody.assets : [])
2348
+ .find((entry) => entry.path === relativeAssetPath);
2349
+ expect(beforeAsset).toEqual(expect.objectContaining({
2350
+ path: relativeAssetPath,
2351
+ assetId: 'asset-stale-link-1',
2352
+ taskId: 'task-missing-link-1',
2353
+ linkRole: 'attachment',
2354
+ contentBase64: assetBuffer.toString('base64')
2355
+ }));
2356
+ const repairReq = createMockReq('POST', '/api/taskforce/sync/workspace/repair-startup', { workspaceId }, { 'x-taskforce-workspace-id': workspaceId });
2357
+ const repairRes = createMockRes();
2358
+ await repairMatch?.handler(repairReq, repairRes, {});
2359
+ expect(repairRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2360
+ expect(readJsonBody(repairRes)).toEqual(expect.objectContaining({
2361
+ success: true,
2362
+ applied: 1,
2363
+ skipped: 0,
2364
+ failed: 0
2365
+ }));
2366
+ const afterAssetsReq = createMockReq('GET', `/api/taskforce/sync/workspace/assets?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2367
+ const afterAssetsRes = createMockRes();
2368
+ await assetsMatch?.handler(afterAssetsReq, afterAssetsRes, {});
2369
+ expect(afterAssetsRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2370
+ const afterAssetsBody = readJsonBody(afterAssetsRes);
2371
+ const repairedAsset = (Array.isArray(afterAssetsBody?.assets) ? afterAssetsBody.assets : [])
2372
+ .find((entry) => entry.path === relativeAssetPath);
2373
+ expect(repairedAsset).toEqual(expect.objectContaining({
2374
+ path: relativeAssetPath,
2375
+ assetId: 'asset-stale-link-1',
2376
+ taskId: null,
2377
+ linkRole: 'attachment',
2378
+ contentBase64: assetBuffer.toString('base64')
2379
+ }));
2380
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2381
+ const store = new WorkspaceAssetStore(reloadedCore.getDatabaseAdapter(), reloadedCore.getTenantId());
2382
+ expect(store.get('asset-stale-link-1', workspaceId)).toEqual(expect.objectContaining({
2383
+ assetId: 'asset-stale-link-1',
2384
+ storageKey: relativeAssetPath,
2385
+ deletedAt: null
2386
+ }));
2387
+ expect(store.listLinksForAsset('asset-stale-link-1', workspaceId)).toEqual([]);
2388
+ expect(store.listLinksForAsset('asset-stale-link-1', workspaceId, { includeDeleted: true })).toEqual([
2389
+ expect.objectContaining({
2390
+ assetId: 'asset-stale-link-1',
2391
+ taskId: 'task-missing-link-1',
2392
+ role: 'attachment',
2393
+ deletedAt: expect.any(String)
2394
+ })
2395
+ ]);
2396
+ });
2025
2397
  it('returns 400 on apply-local when workspaceId is null/empty/default', async () => {
2026
2398
  const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-wsmissing-');
2027
2399
  const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
@@ -2038,16 +2410,24 @@ describe('Server Routes Workspace Sync Integration', () => {
2038
2410
  it('keeps repeated document scans correct and invalidates cache when file content changes', async () => {
2039
2411
  const projectRoot = createIsolatedProjectRoot('taskforce-sync-doc-cache-');
2040
2412
  const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2413
+ const basePath = core.getPaths().basePath;
2041
2414
  core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
2042
- const syncRoot = core.getPaths().basePath;
2043
- const docsDir = path.join(syncRoot, 'docs');
2044
- fs.mkdirSync(docsDir, { recursive: true });
2045
- const relativeDocPath = 'docs/cache-target.md';
2046
- const absoluteDocPath = path.join(syncRoot, relativeDocPath);
2047
- fs.writeFileSync(absoluteDocPath, '# cache test\n', 'utf8');
2048
2415
  const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
2416
+ const uploadMatch = matchRoute('POST', '/api/taskforce/context-upload', routes);
2049
2417
  const match = matchRoute('GET', '/api/taskforce/sync/workspace/documents', routes);
2418
+ expect(uploadMatch).toBeTruthy();
2050
2419
  expect(match).toBeTruthy();
2420
+ const uploadReq = createMockReq('POST', '/api/taskforce/context-upload', {
2421
+ file: `data:text/markdown;base64,${Buffer.from('# cache test\n', 'utf8').toString('base64')}`,
2422
+ originalName: 'cache-target.md',
2423
+ workspaceId
2424
+ }, { 'x-taskforce-workspace-id': workspaceId });
2425
+ const uploadRes = createMockRes();
2426
+ await uploadMatch?.handler(uploadReq, uploadRes, {});
2427
+ expect(uploadRes.writeHead).toHaveBeenCalledWith(200, { 'Content-Type': 'application/json' });
2428
+ const uploadBody = JSON.parse(String(uploadRes.end.mock.calls.at(-1)?.[0] || '{}'));
2429
+ const relativeDocPath = String(uploadBody?.fsPath || '');
2430
+ const absoluteDocPath = path.join(basePath, relativeDocPath);
2051
2431
  const req1 = createMockReq('GET', `/api/taskforce/sync/workspace/documents?workspaceId=${workspaceId}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2052
2432
  const res1 = createMockRes();
2053
2433
  await match?.handler(req1, res1, {});
@@ -2102,6 +2482,24 @@ describe('Server Routes Workspace Sync Integration', () => {
2102
2482
  expect(doc).toBeTruthy();
2103
2483
  expect(doc.content).toBe('# local attachment\n');
2104
2484
  });
2485
+ it('does not sync raw docs directory markdown files because shared document sync must be canonical and workspace-scoped', async () => {
2486
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-exclude-raw-docs-');
2487
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
2488
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
2489
+ const rawDocsDir = path.join(core.getPaths().basePath, 'docs');
2490
+ fs.mkdirSync(rawDocsDir, { recursive: true });
2491
+ fs.writeFileSync(path.join(rawDocsDir, 'legacy-shared.md'), '# legacy shared doc\n', 'utf8');
2492
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
2493
+ const documentsMatch = matchRoute('GET', '/api/taskforce/sync/workspace/documents', routes);
2494
+ expect(documentsMatch).toBeTruthy();
2495
+ const req = createMockReq('GET', `/api/taskforce/sync/workspace/documents?workspaceId=${encodeURIComponent(workspaceId)}`, undefined, { 'x-taskforce-workspace-id': workspaceId });
2496
+ const res = createMockRes();
2497
+ await documentsMatch?.handler(req, res, {});
2498
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
2499
+ const body = JSON.parse(String(res.end.mock.calls.at(-1)?.[0] || '{}'));
2500
+ const docs = Array.isArray(body?.documents) ? body.documents : [];
2501
+ expect(docs.some((entry) => entry.path === 'docs/legacy-shared.md')).toBe(false);
2502
+ });
2105
2503
  it('converges task conflicts deterministically for equal timestamps regardless of apply order', async () => {
2106
2504
  const applySequence = async (prefix, titlesInOrder) => {
2107
2505
  const projectRoot = createIsolatedProjectRoot(prefix);