@taskforcehq/taskforce 0.3.305 → 0.3.307

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +603 -107
  3. package/dist/TaskforceCore.js +216 -43
  4. package/dist/TaskforceCore.test.js +611 -28
  5. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  8. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  10. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  11. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  12. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  13. package/dist/compat/workspaceSyncCompat.js +46 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  16. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  17. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  18. package/dist/components/context/ContextAttachmentManager.js +561 -0
  19. package/dist/components/features/AgentsModule.d.ts +14 -1
  20. package/dist/components/features/AgentsModule.js +366 -18
  21. package/dist/components/features/AgentsModule.test.js +507 -8
  22. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  23. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  24. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  25. package/dist/components/features/DocumentIndex.d.ts +2 -2
  26. package/dist/components/features/DocumentIndex.js +3 -4
  27. package/dist/components/features/DocumentViewer.d.ts +7 -1
  28. package/dist/components/features/DocumentViewer.js +62 -4
  29. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  30. package/dist/components/features/DocumentWorkspace.js +15 -4
  31. package/dist/components/features/DocumentWorkspace.test.js +51 -1
  32. package/dist/components/features/TaskSettings.d.ts +2 -0
  33. package/dist/components/features/TaskSettings.js +83 -78
  34. package/dist/components/features/TaskSettings.test.js +171 -82
  35. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  36. package/dist/components/task/TaskActionHeader.js +11 -5
  37. package/dist/components/task/TaskActionHeader.test.js +10 -0
  38. package/dist/components/task/TaskCard.js +24 -29
  39. package/dist/components/task/TaskCard.test.js +38 -3
  40. package/dist/components/task/TaskContextUpload.js +4 -526
  41. package/dist/components/task/TaskContextUpload.test.js +3 -3
  42. package/dist/components/task/TaskForm.js +61 -116
  43. package/dist/components/task/TaskForm.test.js +110 -8
  44. package/dist/components/task/TaskKanban.d.ts +2 -1
  45. package/dist/components/task/TaskKanban.js +94 -21
  46. package/dist/components/task/TaskKanban.test.js +36 -0
  47. package/dist/components/task/TaskList.js +5 -1
  48. package/dist/components/task/TaskList.test.js +4 -0
  49. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  50. package/dist/components/ui/EditableAvatarButton.js +18 -0
  51. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  52. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  53. package/dist/components/views/AppShellHeader.d.ts +3 -1
  54. package/dist/components/views/AppShellHeader.js +27 -11
  55. package/dist/components/views/AppShellHeader.test.js +18 -1
  56. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  57. package/dist/components/views/PlanComparisonPage.js +78 -14
  58. package/dist/components/views/PlanComparisonPage.test.js +257 -0
  59. package/dist/components/views/PlansPage.js +127 -36
  60. package/dist/components/views/PlansPage.test.js +294 -12
  61. package/dist/components/views/StandaloneLayout.js +529 -592
  62. package/dist/components/views/WidgetView.js +10 -8
  63. package/dist/components/views/panels/PlanningDrawer.d.ts +25 -5
  64. package/dist/components/views/panels/PlanningDrawer.js +270 -55
  65. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  66. package/dist/components/views/panels/PlanningDrawer.test.js +529 -0
  67. package/dist/components/views/planningScope.d.ts +20 -0
  68. package/dist/components/views/planningScope.js +25 -0
  69. package/dist/components/views/planningScope.test.d.ts +1 -0
  70. package/dist/components/views/planningScope.test.js +60 -0
  71. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  72. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  73. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  74. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  75. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  76. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  77. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  78. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  79. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  80. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  81. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  82. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  83. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  84. package/dist/config/envSchema.js +1 -0
  85. package/dist/core/AiProfileService.d.ts +46 -2
  86. package/dist/core/AiProfileService.js +1073 -82
  87. package/dist/core/AiProfileService.test.js +19 -1
  88. package/dist/core/AiProfiles.test.js +1249 -44
  89. package/dist/core/AttachmentLinkService.d.ts +3 -0
  90. package/dist/core/AttachmentLinkService.js +110 -23
  91. package/dist/core/AuthIdentityService.d.ts +107 -0
  92. package/dist/core/AuthIdentityService.js +284 -0
  93. package/dist/core/AuthTokenService.d.ts +4 -0
  94. package/dist/core/AuthTokenService.js +34 -6
  95. package/dist/core/AuthTokenService.test.js +3 -3
  96. package/dist/core/EntitlementsPolicy.test.js +2 -2
  97. package/dist/core/GlobalSettingsService.js +127 -6
  98. package/dist/core/McpTokenService.d.ts +12 -0
  99. package/dist/core/McpTokenService.js +44 -1
  100. package/dist/core/PlanEntitlementService.d.ts +23 -2
  101. package/dist/core/PlanEntitlementService.js +343 -57
  102. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  103. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  104. package/dist/core/PlanVersionPolicy.test.js +35 -2
  105. package/dist/core/PlanningEntities.test.js +418 -4
  106. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  107. package/dist/core/SyncReconciliationService.js +2 -65
  108. package/dist/core/SystemAdmin.test.js +42 -42
  109. package/dist/core/TaskActivityService.d.ts +28 -1
  110. package/dist/core/TaskActivityService.js +227 -46
  111. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  112. package/dist/core/TaskActivitySync.test.js +90 -0
  113. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  114. package/dist/core/TaskAuditTimeline.test.js +96 -0
  115. package/dist/core/Taskforce.d.ts +415 -40
  116. package/dist/core/Taskforce.ids.test.js +58 -0
  117. package/dist/core/Taskforce.js +1543 -266
  118. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  119. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  120. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  121. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  122. package/dist/core/WorkspaceLifecycleService.js +0 -5
  123. package/dist/core/WorkspacePermissions.test.js +25 -1
  124. package/dist/core/WorkspacePlanMode.test.js +117 -16
  125. package/dist/core/shared.d.ts +1 -1
  126. package/dist/core/shared.js +5 -1
  127. package/dist/core/types.d.ts +75 -2
  128. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +28 -2
  129. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  130. package/dist/hooks/bootstrapTimeouts.js +8 -0
  131. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  132. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  133. package/dist/hooks/sync/bootstrap.js +2 -0
  134. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  135. package/dist/hooks/sync/controlPlane.js +78 -0
  136. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  137. package/dist/hooks/sync/controlPlane.test.js +121 -0
  138. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  139. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  140. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  141. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  142. package/dist/hooks/sync/orchestratorShared.d.ts +13 -4
  143. package/dist/hooks/sync/orchestratorShared.js +129 -48
  144. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  145. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  146. package/dist/hooks/sync/pullLifecycle.js +24 -0
  147. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  148. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  149. package/dist/hooks/sync/recovery.d.ts +16 -2
  150. package/dist/hooks/sync/recovery.js +179 -53
  151. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  152. package/dist/hooks/sync/recovery.test.js +292 -0
  153. package/dist/hooks/sync/transfers.d.ts +16 -3
  154. package/dist/hooks/sync/transfers.js +210 -66
  155. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  156. package/dist/hooks/sync/transfers.test.js +928 -0
  157. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  158. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  159. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  160. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  161. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  162. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  163. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  164. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  165. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  166. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  167. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  168. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  169. package/dist/hooks/useFilterSort.js +13 -13
  170. package/dist/hooks/useFilterSort.test.js +87 -1
  171. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  172. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  173. package/dist/hooks/useSyncOrchestrator.js +409 -183
  174. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  175. package/dist/hooks/useTaskData.js +8 -3
  176. package/dist/hooks/useTaskData.test.js +45 -0
  177. package/dist/hooks/useTaskforce.d.ts +145 -52
  178. package/dist/hooks/useTaskforce.js +592 -253
  179. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  180. package/dist/hooks/useTaskforce.runtime-routing.test.js +246 -0
  181. package/dist/hooks/useTaskforce.sync-behavior.test.js +2468 -201
  182. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  183. package/dist/hooks/useWorkspaceSyncController.js +13 -2
  184. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  185. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  186. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +328 -40
  187. package/dist/localization/locales/en-US.d.ts +0 -1
  188. package/dist/localization/locales/en-US.js +0 -1
  189. package/dist/localization/locales/es-419.js +0 -1
  190. package/dist/localization/locales/pt-BR.js +0 -1
  191. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  192. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  193. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  194. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  195. package/dist/mcp/clientIntegrations.d.ts +1 -0
  196. package/dist/mcp/clientIntegrations.js +25 -0
  197. package/dist/mcp/clientIntegrations.test.js +14 -3
  198. package/dist/mcp/clientProfileDefaults.js +3 -3
  199. package/dist/mcp/collaborationRegistrar.js +7 -7
  200. package/dist/mcp/documentAssetRegistrar.js +116 -10
  201. package/dist/mcp/runtime.d.ts +3 -0
  202. package/dist/mcp/runtime.js +1099 -185
  203. package/dist/mcp/runtime.test.js +1491 -140
  204. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  205. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  206. package/dist/mcp/toolRegistry.d.ts +56 -0
  207. package/dist/mcp/toolRegistry.js +8 -0
  208. package/dist/mcp/toolRegistry.test.js +4 -0
  209. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  210. package/dist/migrations/taskSchemaMigrations.js +192 -61
  211. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  212. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  213. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  214. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  215. package/dist/resources/templates/workflows/plan.yaml +1 -1
  216. package/dist/resources/templates/workflows/review.yaml +2 -2
  217. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  218. package/dist/server/aiProfileAvatarAssets.js +319 -0
  219. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  220. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  221. package/dist/server/annotatedAttachmentsRoutes.test.js +69 -10
  222. package/dist/server/auth/providers/apple.d.ts +33 -0
  223. package/dist/server/auth/providers/apple.js +82 -0
  224. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  225. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  226. package/dist/server/auth/providers/github.d.ts +26 -0
  227. package/dist/server/auth/providers/github.js +86 -0
  228. package/dist/server/auth/providers/google.d.ts +21 -0
  229. package/dist/server/auth/providers/google.js +56 -0
  230. package/dist/server/auth/providers/types.d.ts +21 -0
  231. package/dist/server/auth/providers/types.js +1 -0
  232. package/dist/server/auth.d.ts +2 -0
  233. package/dist/server/auth.js +6 -3
  234. package/dist/server/documentReviewRoutes.test.js +36 -3
  235. package/dist/server/index.cookieProxy.test.js +1 -0
  236. package/dist/server/index.d.ts +12 -0
  237. package/dist/server/index.js +124 -9
  238. package/dist/server/index.rateLimit.test.js +3 -0
  239. package/dist/server/index.test.js +111 -3
  240. package/dist/server/routes/admin.d.ts +3 -0
  241. package/dist/server/routes/admin.js +459 -28
  242. package/dist/server/routes/annotatedAttachments.js +51 -0
  243. package/dist/server/routes/auth.d.ts +26 -2
  244. package/dist/server/routes/auth.js +1170 -71
  245. package/dist/server/routes/authSupport.d.ts +5 -1
  246. package/dist/server/routes/authSupport.js +13 -2
  247. package/dist/server/routes/billing.js +372 -35
  248. package/dist/server/routes/billing.test.js +595 -12
  249. package/dist/server/routes/documents.d.ts +1 -0
  250. package/dist/server/routes/documents.js +182 -70
  251. package/dist/server/routes/shared.d.ts +28 -2
  252. package/dist/server/routes/shared.js +69 -14
  253. package/dist/server/routes/sync.integration.test.js +658 -39
  254. package/dist/server/routes/sync.js +74 -608
  255. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  256. package/dist/server/routes/syncAuxRoutes.js +91 -0
  257. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  258. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  259. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  260. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  261. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  262. package/dist/server/routes/syncPushRoutes.js +212 -0
  263. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  264. package/dist/server/routes/syncRouteGuards.js +67 -0
  265. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  266. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  267. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  268. package/dist/server/routes/syncRouteTypes.js +1 -0
  269. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  270. package/dist/server/routes/syncSnapshotRoutes.js +182 -0
  271. package/dist/server/routes/tasks.js +55 -10
  272. package/dist/server/routes/workspaces.js +36 -5
  273. package/dist/server/routes.js +147 -11
  274. package/dist/server/routes.test.js +2704 -183
  275. package/dist/services/workflowExportSnapshots.test.js +2 -0
  276. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  277. package/dist/shared/aiProfileSeatScope.js +21 -0
  278. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  279. package/dist/shared/aiProfileSurfaceType.js +10 -8
  280. package/dist/shared/passwordPolicy.d.ts +13 -0
  281. package/dist/shared/passwordPolicy.js +84 -0
  282. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  283. package/dist/shared/passwordPolicy.test.js +34 -0
  284. package/dist/storage/objectStorage.d.ts +1 -0
  285. package/dist/storage/objectStorage.js +2 -1
  286. package/dist/storage/objectStorageClient.d.ts +1 -0
  287. package/dist/storage/objectStorageClient.js +41 -8
  288. package/dist/storage/objectStorageClient.test.js +26 -1
  289. package/dist/storage/objectStorageConfig.js +3 -1
  290. package/dist/storage/objectStorageConfig.test.js +13 -1
  291. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  292. package/dist/storage/workspaceAssetStore.js +132 -44
  293. package/dist/storage/workspaceAssetStore.test.js +93 -6
  294. package/dist/styles/fonts.css +70 -0
  295. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  296. package/dist/sync/collaborationSyncPayload.js +19 -11
  297. package/dist/sync/collaborationSyncPayload.test.js +14 -1
  298. package/dist/sync/collaborationSyncState.d.ts +3 -3
  299. package/dist/sync/collaborationSyncState.js +19 -12
  300. package/dist/sync/contentSyncPayload.d.ts +5 -1
  301. package/dist/sync/contentSyncPayload.js +9 -0
  302. package/dist/sync/contentSyncPayload.test.js +39 -0
  303. package/dist/sync/contentSyncState.d.ts +9 -9
  304. package/dist/sync/contentSyncState.js +248 -107
  305. package/dist/sync/planningSyncPayload.js +5 -0
  306. package/dist/sync/planningSyncPayload.test.js +24 -0
  307. package/dist/sync/syncApplyHandlers.d.ts +11 -4
  308. package/dist/sync/syncApplyHandlers.js +115 -42
  309. package/dist/sync/syncApplyHandlers.test.js +251 -4
  310. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  311. package/dist/sync/syncFieldSemantics.js +55 -0
  312. package/dist/sync/syncResourceAdapters.d.ts +46 -0
  313. package/dist/sync/syncResourceAdapters.js +79 -0
  314. package/dist/sync/syncService.d.ts +6 -0
  315. package/dist/sync/syncService.js +65 -1
  316. package/dist/sync/syncService.test.js +96 -3
  317. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  318. package/dist/sync/taskSyncChangeKey.js +143 -0
  319. package/dist/sync/taskSyncPayload.js +3 -8
  320. package/dist/sync/workspacePullFeed.d.ts +4 -0
  321. package/dist/sync/workspacePullFeed.js +132 -72
  322. package/dist/sync/workspacePullFeed.test.js +92 -0
  323. package/dist/sync/workspaceRepair.js +5 -2
  324. package/dist/sync/workspaceSyncModel.d.ts +73 -6
  325. package/dist/sync/workspaceSyncModel.js +342 -73
  326. package/dist/sync/workspaceSyncModel.test.js +559 -14
  327. package/dist/sync/workspaceSyncState.d.ts +17 -0
  328. package/dist/sync/workspaceSyncState.js +2 -1
  329. package/dist/sync/workspaceSyncSurface.js +3 -8
  330. package/dist/types.d.ts +50 -2
  331. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  332. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  333. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  334. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  335. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  336. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  337. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  338. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  339. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  340. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  341. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  342. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  343. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-D4XseTwW.js} +1 -1
  344. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  345. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  346. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  347. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  348. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  349. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  350. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  351. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  352. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  353. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  354. package/dist/ui/assets/index-607WPo_X.js +4 -0
  355. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  356. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-B32hGuF2.js} +1 -1
  357. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  358. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  359. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  360. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  361. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  362. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  363. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  364. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  365. package/dist/ui/fonts/Noir_medium.otf +0 -0
  366. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  367. package/dist/ui/fonts/Noir_regular.otf +0 -0
  368. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  369. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  370. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  371. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  372. package/dist/ui/index.html +3 -3
  373. package/dist/ui/product/agent manager.png +0 -0
  374. package/dist/ui/product/agent_manager_02.png +0 -0
  375. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  376. package/dist/utils/aiProfileEvents.d.ts +7 -0
  377. package/dist/utils/aiProfileEvents.js +6 -0
  378. package/dist/utils/assignees.d.ts +8 -0
  379. package/dist/utils/assignees.js +10 -1
  380. package/dist/utils/assignees.test.d.ts +1 -0
  381. package/dist/utils/assignees.test.js +25 -0
  382. package/dist/utils/avatarDisplay.d.ts +1 -0
  383. package/dist/utils/avatarDisplay.js +15 -0
  384. package/dist/utils/avatarUpload.d.ts +27 -0
  385. package/dist/utils/avatarUpload.js +132 -0
  386. package/dist/utils/avatarUpload.test.d.ts +1 -0
  387. package/dist/utils/avatarUpload.test.js +40 -0
  388. package/dist/utils/bootstrapTrace.d.ts +7 -0
  389. package/dist/utils/bootstrapTrace.js +29 -0
  390. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  391. package/dist/utils/contextAssetEvents.d.ts +2 -0
  392. package/dist/utils/contextFiles.d.ts +3 -1
  393. package/dist/utils/contextFiles.js +2 -1
  394. package/dist/utils/syncEventDetails.d.ts +3 -0
  395. package/dist/utils/syncEventDetails.js +64 -0
  396. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  397. package/dist/utils/syncEventDetails.test.js +23 -0
  398. package/dist/utils/syncEventPresentation.d.ts +15 -0
  399. package/dist/utils/syncEventPresentation.js +129 -0
  400. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  401. package/dist/utils/syncEventPresentation.test.js +64 -0
  402. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  403. package/dist/utils/syncStatusPresentation.js +217 -0
  404. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  405. package/dist/utils/syncStatusPresentation.test.js +164 -0
  406. package/dist/utils/taskActivity.d.ts +9 -1
  407. package/dist/utils/taskActivity.js +290 -22
  408. package/dist/utils/taskActivity.test.js +141 -8
  409. package/dist/utils/taskSearch.d.ts +22 -0
  410. package/dist/utils/taskSearch.js +87 -0
  411. package/dist/utils/taskSearch.test.d.ts +1 -0
  412. package/dist/utils/taskSearch.test.js +69 -0
  413. package/dist/utils/taskUtils.js +10 -3
  414. package/dist/utils/taskUtils.test.d.ts +1 -0
  415. package/dist/utils/taskUtils.test.js +19 -0
  416. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  417. package/dist/utils/workspaceSyncPresentation.js +68 -2
  418. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  419. package/package.json +3 -2
  420. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  421. package/scripts/run-e2e-realtime.sh +17 -3
  422. package/scripts/run-smoke.sh +17 -5
  423. package/scripts/run-soak.sh +17 -5
  424. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  425. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  426. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  427. package/src/resources/templates/workflows/plan.yaml +1 -1
  428. package/src/resources/templates/workflows/review.yaml +2 -2
  429. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  430. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BG6P_GVW.js +0 -3
  431. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  432. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  433. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  434. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  435. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  436. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  437. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +0 -9
  438. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  439. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  440. package/scripts/migrate-planning-model.ts +0 -233
@@ -102,7 +102,7 @@ content:
102
102
  - `### Next Status`
103
103
  3. Ask whether to save as `{artifacts.reviewReport.path}`.
104
104
  4. If user confirms, persist task-specific content via `{{MCP_PREFIX}}save_task_attachment` using `filename: "review.md"`.
105
- - Use `{{MCP_PREFIX}}link_task_context` only for linking existing files/URLs (for example, shared docs under `docs/`).
105
+ - Use `{{MCP_PREFIX}}link_task_context` only for external http(s) URLs that should stay as references.
106
106
  5. Keep status within allowed statuses (`task`, `on-hold`, `in-progress`, `review`, `done`) unless user explicitly instructs otherwise.
107
107
  - If setting status to `task` or `on-hold`, add explicit comment justification using `{{MCP_PREFIX}}add_comment`.
108
108
  6. Never set `cancelled`, and never call `{{MCP_PREFIX}}archive_task`, unless explicitly instructed by the user.
@@ -111,5 +111,5 @@ content:
111
111
 
112
112
  1. Use report files only when user opts in.
113
113
  2. Save to `task attachment "review.md"` and attach to task context when approved.
114
- - If the document is not task-specific, store it under `docs/` and optionally link it with `{{MCP_PREFIX}}link_task_context`.
114
+ - If the document should become a shared durable document, save it through the canonical document/document-manager flow instead of a raw `docs/` file.
115
115
  3. Stop.
@@ -0,0 +1,49 @@
1
+ import type { TaskforceCore } from '../core/Taskforce.js';
2
+ import type { ObjectStorageClient } from '../storage/objectStorageClient.js';
3
+ import type { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
4
+ type AiAvatarSlot = 'display' | 'source';
5
+ export interface AiProfileAvatarPayload {
6
+ buffer: Buffer;
7
+ mimeType: string;
8
+ originalName?: string | null;
9
+ }
10
+ export interface AiProfileAvatarAssetDeps {
11
+ core: TaskforceCore;
12
+ workspaceAssetStore: WorkspaceAssetStore | null;
13
+ getObjectStorageClient: () => ObjectStorageClient | null;
14
+ ensureWithinDir: (candidatePath: string, rootDir: string) => string | null;
15
+ markAssetDeleted: (pathValue: string) => unknown;
16
+ appendStorageTelemetry: (event: string, payload?: {
17
+ path?: string;
18
+ status?: string;
19
+ provider?: string;
20
+ }) => void;
21
+ }
22
+ export interface SaveAiProfileAvatarInput {
23
+ workspaceId: string;
24
+ profileId: string;
25
+ display: AiProfileAvatarPayload;
26
+ source?: AiProfileAvatarPayload | null;
27
+ actorId?: string | null;
28
+ }
29
+ export declare function buildAiProfileAvatarStorageKey(input: {
30
+ workspaceId: string;
31
+ profileId: string;
32
+ slot: AiAvatarSlot;
33
+ mimeType: string;
34
+ }): string;
35
+ export declare function storageKeyFromContextUrl(url: unknown): string | null;
36
+ export declare function saveAiProfileAvatarAssets(deps: AiProfileAvatarAssetDeps, input: SaveAiProfileAvatarInput): Promise<{
37
+ profile: import("../core/Taskforce.js").AiProfileRecord;
38
+ displayAsset: import("../storage/workspaceAssetStore.js").WorkspaceAssetEntry;
39
+ sourceAsset: import("../storage/workspaceAssetStore.js").WorkspaceAssetEntry;
40
+ displayStorageKey: string;
41
+ sourceStorageKey: string;
42
+ }>;
43
+ export declare function clearAiProfileAvatarAssets(deps: AiProfileAvatarAssetDeps, input: {
44
+ workspaceId: string;
45
+ profileId: string;
46
+ }): Promise<{
47
+ profile: import("../core/Taskforce.js").AiProfileRecord;
48
+ }>;
49
+ export {};
@@ -0,0 +1,319 @@
1
+ import { createHash } from 'crypto';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { normalizeTaskDataRelativePath } from '../storage/objectStorageClient.js';
5
+ const SUPPORTED_IMAGE_MIME_TYPES = new Set([
6
+ 'image/gif',
7
+ 'image/jpeg',
8
+ 'image/png',
9
+ 'image/webp',
10
+ ]);
11
+ const AVATAR_MAX_BYTES = 5 * 1024 * 1024;
12
+ function normalizeId(value) {
13
+ return String(value || '').trim();
14
+ }
15
+ function sanitizeStorageSegment(value, fallback) {
16
+ const normalized = normalizeId(value)
17
+ .replace(/[^a-zA-Z0-9._-]/g, '-')
18
+ .replace(/-+/g, '-')
19
+ .replace(/^-+|-+$/g, '');
20
+ return normalized || fallback;
21
+ }
22
+ function normalizeImageMimeType(value) {
23
+ return normalizeId(value).toLowerCase();
24
+ }
25
+ function extensionForMimeType(mimeType) {
26
+ if (mimeType === 'image/gif')
27
+ return 'gif';
28
+ if (mimeType === 'image/jpeg')
29
+ return 'jpg';
30
+ if (mimeType === 'image/png')
31
+ return 'png';
32
+ return 'webp';
33
+ }
34
+ function stableAssetId(workspaceId, profileId, slot) {
35
+ const digest = createHash('sha256')
36
+ .update(`${workspaceId}:${profileId}:${slot}`)
37
+ .digest('hex')
38
+ .slice(0, 32);
39
+ return `asset-${digest}`;
40
+ }
41
+ export function buildAiProfileAvatarStorageKey(input) {
42
+ const workspaceId = sanitizeStorageSegment(input.workspaceId, 'default');
43
+ const profileId = sanitizeStorageSegment(input.profileId, 'ai-profile');
44
+ const ext = extensionForMimeType(input.mimeType);
45
+ return `workspaces/${workspaceId}/assets/images/ai-profiles/${profileId}/avatar/${input.slot}.${ext}`;
46
+ }
47
+ function contextUrlForStorageKey(storageKey) {
48
+ return `/api/taskforce/context/${encodeURIComponent(storageKey)}`;
49
+ }
50
+ export function storageKeyFromContextUrl(url) {
51
+ const raw = normalizeId(url);
52
+ if (!raw)
53
+ return null;
54
+ const contextPrefix = '/api/taskforce/context/';
55
+ if (!raw.startsWith(contextPrefix))
56
+ return null;
57
+ const encodedPath = raw.slice(contextPrefix.length).split('?')[0] || '';
58
+ try {
59
+ return normalizeTaskDataRelativePath(decodeURIComponent(encodedPath));
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ function validatePayload(payload, label) {
66
+ const mimeType = normalizeImageMimeType(payload.mimeType);
67
+ if (!SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) {
68
+ throw new Error(`${label} must be a PNG, JPEG, WebP, or GIF image.`);
69
+ }
70
+ if (!Buffer.isBuffer(payload.buffer) || payload.buffer.length === 0) {
71
+ throw new Error(`${label} is empty.`);
72
+ }
73
+ if (payload.buffer.length > AVATAR_MAX_BYTES) {
74
+ throw new Error(`${label} must be 5 MB or smaller.`);
75
+ }
76
+ return mimeType;
77
+ }
78
+ function storageKeyBelongsToAiProfileAvatar(workspaceId, profileId, storageKey) {
79
+ const workspaceIdSafe = sanitizeStorageSegment(workspaceId, 'default');
80
+ const profileIdSafe = sanitizeStorageSegment(profileId, 'ai-profile');
81
+ return storageKey.startsWith(`workspaces/${workspaceIdSafe}/assets/images/ai-profiles/${profileIdSafe}/avatar/`);
82
+ }
83
+ async function writeAvatarObject(deps, storageKey, payload, provider) {
84
+ if (provider === 'r2') {
85
+ const objectStorageClient = deps.getObjectStorageClient();
86
+ if (!objectStorageClient)
87
+ throw new Error('Object storage is not configured.');
88
+ await objectStorageClient.putObject(storageKey, payload.buffer, payload.mimeType);
89
+ return;
90
+ }
91
+ const { basePath } = deps.core.getPaths();
92
+ const destination = deps.ensureWithinDir(path.join(basePath, storageKey), basePath);
93
+ if (!destination)
94
+ throw new Error('Invalid AI profile avatar storage path.');
95
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
96
+ fs.writeFileSync(destination, payload.buffer);
97
+ }
98
+ async function deleteAvatarObject(deps, storageKey, provider) {
99
+ try {
100
+ deps.markAssetDeleted(storageKey);
101
+ }
102
+ catch {
103
+ // Best-effort legacy asset index cleanup should not make the canonical
104
+ // workspace asset/profile update fail after the new slots are written.
105
+ }
106
+ if (provider === 'r2') {
107
+ const objectStorageClient = deps.getObjectStorageClient();
108
+ if (objectStorageClient) {
109
+ await objectStorageClient.deleteObject(storageKey).catch(() => false);
110
+ }
111
+ return;
112
+ }
113
+ const { basePath } = deps.core.getPaths();
114
+ const destination = deps.ensureWithinDir(path.join(basePath, storageKey), basePath);
115
+ if (destination && fs.existsSync(destination)) {
116
+ fs.rmSync(destination, { force: true });
117
+ }
118
+ }
119
+ function upsertAvatarAsset(input) {
120
+ if (!input.deps.workspaceAssetStore) {
121
+ throw new Error('Workspace asset storage is required for AI profile avatars.');
122
+ }
123
+ const contentSha256 = createHash('sha256').update(input.payload.buffer).digest('hex');
124
+ const existing = input.deps.workspaceAssetStore.getByStorageKey(input.storageKey, input.workspaceId);
125
+ return input.deps.workspaceAssetStore.upsertAsset({
126
+ assetId: existing?.assetId || stableAssetId(input.workspaceId, input.profileId, input.slot),
127
+ workspaceId: input.workspaceId,
128
+ kind: 'image',
129
+ logicalName: `AI profile avatar ${input.slot}`,
130
+ originalFilename: normalizeId(input.payload.originalName) || `${input.slot}.${extensionForMimeType(input.payload.mimeType)}`,
131
+ mimeType: input.payload.mimeType,
132
+ storageProvider: input.provider,
133
+ storageKey: input.storageKey,
134
+ contentSha256,
135
+ sizeBytes: input.payload.buffer.length,
136
+ documentId: null,
137
+ version: null,
138
+ isLatest: true,
139
+ scanStatus: 'clean',
140
+ scanEngine: null,
141
+ scanDetails: null,
142
+ createdByUserId: existing?.createdByUserId || normalizeId(input.actorId) || null,
143
+ createdAt: existing?.createdAt || input.nowIso,
144
+ updatedAt: input.nowIso,
145
+ deletedAt: null,
146
+ purgeAfter: null,
147
+ });
148
+ }
149
+ async function cleanupAiProfileAvatarAssets(input) {
150
+ const keep = new Set(Array.from(input.keepStorageKeys || []).filter(Boolean));
151
+ const candidates = new Map();
152
+ for (const storageKey of input.explicitStorageKeys || []) {
153
+ const normalized = normalizeTaskDataRelativePath(normalizeId(storageKey));
154
+ if (!normalized || keep.has(normalized))
155
+ continue;
156
+ if (storageKeyBelongsToAiProfileAvatar(input.workspaceId, input.profileId, normalized)) {
157
+ candidates.set(normalized, input.providerFallback);
158
+ }
159
+ }
160
+ if (input.deps.workspaceAssetStore) {
161
+ const assets = input.deps.workspaceAssetStore.listAllByWorkspace(input.workspaceId, {
162
+ includeDeleted: false,
163
+ kind: 'image',
164
+ });
165
+ for (const asset of assets) {
166
+ if (!storageKeyBelongsToAiProfileAvatar(input.workspaceId, input.profileId, asset.storageKey))
167
+ continue;
168
+ if (keep.has(asset.storageKey))
169
+ continue;
170
+ candidates.set(asset.storageKey, asset.storageProvider === 'r2' ? 'r2' : 'local');
171
+ }
172
+ }
173
+ for (const [storageKey, provider] of candidates) {
174
+ try {
175
+ const asset = input.deps.workspaceAssetStore?.getByStorageKey(storageKey, input.workspaceId);
176
+ if (asset && !asset.deletedAt) {
177
+ input.deps.workspaceAssetStore?.markDeleted(asset.assetId, input.workspaceId);
178
+ }
179
+ }
180
+ catch {
181
+ input.deps.appendStorageTelemetry('ai_avatar.cleanup', {
182
+ path: storageKey,
183
+ provider,
184
+ status: 'metadata-delete-failed',
185
+ });
186
+ }
187
+ try {
188
+ await deleteAvatarObject(input.deps, storageKey, provider);
189
+ input.deps.appendStorageTelemetry('ai_avatar.cleanup', {
190
+ path: storageKey,
191
+ provider,
192
+ status: 'deleted',
193
+ });
194
+ }
195
+ catch {
196
+ input.deps.appendStorageTelemetry('ai_avatar.cleanup', {
197
+ path: storageKey,
198
+ provider,
199
+ status: 'object-delete-failed',
200
+ });
201
+ }
202
+ }
203
+ }
204
+ export async function saveAiProfileAvatarAssets(deps, input) {
205
+ const workspaceId = normalizeId(input.workspaceId);
206
+ const profileId = normalizeId(input.profileId);
207
+ if (!workspaceId)
208
+ throw new Error('workspaceId is required.');
209
+ if (!profileId)
210
+ throw new Error('profileId is required.');
211
+ const existingProfile = deps.core.listAiProfiles(workspaceId)
212
+ .find((profile) => profile.id === profileId);
213
+ if (!existingProfile)
214
+ throw new Error(`Profile ${profileId} not found.`);
215
+ const displayMimeType = validatePayload(input.display, 'Display avatar');
216
+ const sourcePayload = input.source || input.display;
217
+ const sourceMimeType = validatePayload(sourcePayload, 'Source avatar');
218
+ const displayPayload = { ...input.display, mimeType: displayMimeType };
219
+ const normalizedSourcePayload = { ...sourcePayload, mimeType: sourceMimeType };
220
+ const displayStorageKey = buildAiProfileAvatarStorageKey({
221
+ workspaceId,
222
+ profileId,
223
+ slot: 'display',
224
+ mimeType: displayMimeType,
225
+ });
226
+ const sourceStorageKey = buildAiProfileAvatarStorageKey({
227
+ workspaceId,
228
+ profileId,
229
+ slot: 'source',
230
+ mimeType: sourceMimeType,
231
+ });
232
+ const objectStorageClient = deps.getObjectStorageClient();
233
+ const provider = objectStorageClient ? 'r2' : 'local';
234
+ const nowIso = new Date().toISOString();
235
+ await writeAvatarObject(deps, displayStorageKey, displayPayload, provider);
236
+ await writeAvatarObject(deps, sourceStorageKey, normalizedSourcePayload, provider);
237
+ const displayAsset = upsertAvatarAsset({
238
+ deps,
239
+ workspaceId,
240
+ profileId,
241
+ slot: 'display',
242
+ storageKey: displayStorageKey,
243
+ payload: displayPayload,
244
+ provider,
245
+ actorId: input.actorId,
246
+ nowIso,
247
+ });
248
+ const sourceAsset = upsertAvatarAsset({
249
+ deps,
250
+ workspaceId,
251
+ profileId,
252
+ slot: 'source',
253
+ storageKey: sourceStorageKey,
254
+ payload: normalizedSourcePayload,
255
+ provider,
256
+ actorId: input.actorId,
257
+ nowIso,
258
+ });
259
+ deps.core.notifyDocumentMetadataMutation(workspaceId, [displayStorageKey, sourceStorageKey], 'upsert');
260
+ deps.appendStorageTelemetry('ai_avatar.upload', {
261
+ path: displayStorageKey,
262
+ provider,
263
+ status: 'stored',
264
+ });
265
+ const profile = deps.core.updateAiProfileAvatar({
266
+ workspaceId,
267
+ profileId,
268
+ avatarUrl: contextUrlForStorageKey(displayStorageKey),
269
+ avatarSourceUrl: contextUrlForStorageKey(sourceStorageKey),
270
+ });
271
+ await cleanupAiProfileAvatarAssets({
272
+ deps,
273
+ workspaceId,
274
+ profileId,
275
+ keepStorageKeys: [displayStorageKey, sourceStorageKey],
276
+ explicitStorageKeys: [
277
+ storageKeyFromContextUrl(existingProfile.avatarUrl),
278
+ storageKeyFromContextUrl(existingProfile.avatarSourceUrl),
279
+ ],
280
+ providerFallback: provider,
281
+ });
282
+ return {
283
+ profile,
284
+ displayAsset,
285
+ sourceAsset,
286
+ displayStorageKey,
287
+ sourceStorageKey,
288
+ };
289
+ }
290
+ export async function clearAiProfileAvatarAssets(deps, input) {
291
+ const workspaceId = normalizeId(input.workspaceId);
292
+ const profileId = normalizeId(input.profileId);
293
+ if (!workspaceId)
294
+ throw new Error('workspaceId is required.');
295
+ if (!profileId)
296
+ throw new Error('profileId is required.');
297
+ const existingProfile = deps.core.listAiProfiles(workspaceId)
298
+ .find((profile) => profile.id === profileId);
299
+ const objectStorageClient = deps.getObjectStorageClient();
300
+ const provider = objectStorageClient ? 'r2' : 'local';
301
+ const profile = deps.core.updateAiProfileAvatar({
302
+ workspaceId,
303
+ profileId,
304
+ avatarUrl: null,
305
+ avatarSourceUrl: null,
306
+ });
307
+ await cleanupAiProfileAvatarAssets({
308
+ deps,
309
+ workspaceId,
310
+ profileId,
311
+ keepStorageKeys: [],
312
+ explicitStorageKeys: [
313
+ storageKeyFromContextUrl(existingProfile?.avatarUrl),
314
+ storageKeyFromContextUrl(existingProfile?.avatarSourceUrl),
315
+ ],
316
+ providerFallback: provider,
317
+ });
318
+ return { profile };
319
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,213 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { TaskforceCore } from '../core/Taskforce';
6
+ import { WorkspaceAssetStore } from '../storage/workspaceAssetStore';
7
+ import { buildAiProfileAvatarStorageKey, saveAiProfileAvatarAssets, } from './aiProfileAvatarAssets';
8
+ const roots = [];
9
+ function createFixture() {
10
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-ai-avatar-assets-'));
11
+ roots.push(projectRoot);
12
+ const core = new TaskforceCore({ projectRoot });
13
+ const workspaceAssetStore = new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId());
14
+ const ensureWithinDir = (candidatePath, rootDir) => {
15
+ const resolvedRoot = path.resolve(rootDir);
16
+ const resolvedCandidate = path.resolve(candidatePath);
17
+ return resolvedCandidate.startsWith(resolvedRoot) ? resolvedCandidate : null;
18
+ };
19
+ return {
20
+ core,
21
+ workspaceAssetStore,
22
+ deps: {
23
+ core,
24
+ workspaceAssetStore,
25
+ getObjectStorageClient: () => null,
26
+ ensureWithinDir,
27
+ markAssetDeleted: vi.fn(),
28
+ appendStorageTelemetry: vi.fn(),
29
+ }
30
+ };
31
+ }
32
+ afterEach(() => {
33
+ while (roots.length > 0) {
34
+ const root = roots.pop();
35
+ if (root)
36
+ fs.rmSync(root, { recursive: true, force: true });
37
+ }
38
+ });
39
+ describe('AI profile avatar stable assets', () => {
40
+ it('builds deterministic stable slot paths', () => {
41
+ expect(buildAiProfileAvatarStorageKey({
42
+ workspaceId: 'workspace-1',
43
+ profileId: 'ai-profile-1',
44
+ slot: 'display',
45
+ mimeType: 'image/webp',
46
+ })).toBe('workspaces/workspace-1/assets/images/ai-profiles/ai-profile-1/avatar/display.webp');
47
+ expect(buildAiProfileAvatarStorageKey({
48
+ workspaceId: 'workspace-1',
49
+ profileId: 'ai-profile-1',
50
+ slot: 'source',
51
+ mimeType: 'image/gif',
52
+ })).toBe('workspaces/workspace-1/assets/images/ai-profiles/ai-profile-1/avatar/source.gif');
53
+ });
54
+ it('overwrites the same display/source assets and advances profile revision', async () => {
55
+ const { core, workspaceAssetStore, deps } = createFixture();
56
+ const workspaceId = 'workspace-1';
57
+ const resolved = core.resolveAiProfile({
58
+ workspaceId,
59
+ name: 'Codex',
60
+ provider: 'OpenAI',
61
+ model: 'gpt-5',
62
+ });
63
+ const profileId = resolved.profile.id;
64
+ const first = await saveAiProfileAvatarAssets(deps, {
65
+ workspaceId,
66
+ profileId,
67
+ display: {
68
+ buffer: Buffer.from('display-one'),
69
+ mimeType: 'image/webp',
70
+ originalName: 'display.webp',
71
+ },
72
+ source: {
73
+ buffer: Buffer.from('source-one'),
74
+ mimeType: 'image/jpeg',
75
+ originalName: 'source.jpg',
76
+ },
77
+ actorId: 'user-1',
78
+ });
79
+ const second = await saveAiProfileAvatarAssets(deps, {
80
+ workspaceId,
81
+ profileId,
82
+ display: {
83
+ buffer: Buffer.from('display-two'),
84
+ mimeType: 'image/webp',
85
+ originalName: 'display.webp',
86
+ },
87
+ source: {
88
+ buffer: Buffer.from('source-two'),
89
+ mimeType: 'image/jpeg',
90
+ originalName: 'source.jpg',
91
+ },
92
+ actorId: 'user-1',
93
+ });
94
+ expect(second.displayStorageKey).toBe(first.displayStorageKey);
95
+ expect(second.sourceStorageKey).toBe(first.sourceStorageKey);
96
+ expect(second.profile.avatarRevision).toBe(2);
97
+ expect(second.profile.avatarUrl).toBe(`/api/taskforce/context/${encodeURIComponent(second.displayStorageKey)}`);
98
+ expect(second.profile.avatarSourceUrl).toBe(`/api/taskforce/context/${encodeURIComponent(second.sourceStorageKey)}`);
99
+ const assets = workspaceAssetStore.listAllByWorkspace(workspaceId, { kind: 'image' });
100
+ expect(assets.map((asset) => asset.storageKey).sort()).toEqual([
101
+ second.displayStorageKey,
102
+ second.sourceStorageKey,
103
+ ].sort());
104
+ const displayAsset = workspaceAssetStore.getByStorageKey(second.displayStorageKey, workspaceId);
105
+ expect(displayAsset?.contentSha256).not.toBe(first.displayAsset.contentSha256);
106
+ const displayPath = path.join(core.getPaths().basePath, second.displayStorageKey);
107
+ expect(fs.readFileSync(displayPath, 'utf8')).toBe('display-two');
108
+ core.close();
109
+ });
110
+ it('tombstones old generated avatar assets after a stable replacement succeeds', async () => {
111
+ const { core, workspaceAssetStore, deps } = createFixture();
112
+ const workspaceId = 'workspace-1';
113
+ const resolved = core.resolveAiProfile({
114
+ workspaceId,
115
+ name: 'Harry Palmer',
116
+ provider: 'OpenClaw',
117
+ model: 'gpt-5.4',
118
+ });
119
+ const profileId = resolved.profile.id;
120
+ const oldStorageKey = `workspaces/${workspaceId}/assets/images/ai-profiles/${profileId}/avatar/asset-old-cropped-harry.webp`;
121
+ fs.mkdirSync(path.dirname(path.join(core.getPaths().basePath, oldStorageKey)), { recursive: true });
122
+ fs.writeFileSync(path.join(core.getPaths().basePath, oldStorageKey), 'old-avatar');
123
+ workspaceAssetStore.upsertAsset({
124
+ assetId: 'asset-old-harry-avatar-000000000001',
125
+ workspaceId,
126
+ kind: 'image',
127
+ logicalName: 'old avatar',
128
+ originalFilename: 'cropped-harry.webp',
129
+ mimeType: 'image/webp',
130
+ storageProvider: 'local',
131
+ storageKey: oldStorageKey,
132
+ contentSha256: 'old-sha',
133
+ sizeBytes: 10,
134
+ });
135
+ core.updateAiProfileAvatar({
136
+ workspaceId,
137
+ profileId,
138
+ avatarUrl: `/api/taskforce/context/${encodeURIComponent(oldStorageKey)}`,
139
+ avatarSourceUrl: null,
140
+ });
141
+ await saveAiProfileAvatarAssets(deps, {
142
+ workspaceId,
143
+ profileId,
144
+ display: {
145
+ buffer: Buffer.from('display'),
146
+ mimeType: 'image/webp',
147
+ originalName: 'display.webp',
148
+ },
149
+ source: {
150
+ buffer: Buffer.from('source'),
151
+ mimeType: 'image/webp',
152
+ originalName: 'source.webp',
153
+ },
154
+ });
155
+ expect(workspaceAssetStore.getByStorageKey(oldStorageKey, workspaceId)?.deletedAt).toBeTruthy();
156
+ expect(fs.existsSync(path.join(core.getPaths().basePath, oldStorageKey))).toBe(false);
157
+ expect(deps.markAssetDeleted).toHaveBeenCalledWith(oldStorageKey);
158
+ core.close();
159
+ });
160
+ it('does not fail a stable replacement when legacy cleanup markers fail', async () => {
161
+ const { core, workspaceAssetStore, deps } = createFixture();
162
+ const workspaceId = 'workspace-1';
163
+ const resolved = core.resolveAiProfile({
164
+ workspaceId,
165
+ name: 'Control',
166
+ provider: 'OpenAI',
167
+ model: 'gpt-5',
168
+ });
169
+ const profileId = resolved.profile.id;
170
+ const oldStorageKey = `workspaces/${workspaceId}/assets/images/ai-profiles/${profileId}/avatar/asset-old-control.webp`;
171
+ fs.mkdirSync(path.dirname(path.join(core.getPaths().basePath, oldStorageKey)), { recursive: true });
172
+ fs.writeFileSync(path.join(core.getPaths().basePath, oldStorageKey), 'old-avatar');
173
+ workspaceAssetStore.upsertAsset({
174
+ assetId: 'asset-old-control-avatar-0000001',
175
+ workspaceId,
176
+ kind: 'image',
177
+ logicalName: 'old avatar',
178
+ originalFilename: 'control.webp',
179
+ mimeType: 'image/webp',
180
+ storageProvider: 'local',
181
+ storageKey: oldStorageKey,
182
+ contentSha256: 'old-sha',
183
+ sizeBytes: 10,
184
+ });
185
+ core.updateAiProfileAvatar({
186
+ workspaceId,
187
+ profileId,
188
+ avatarUrl: `/api/taskforce/context/${encodeURIComponent(oldStorageKey)}`,
189
+ avatarSourceUrl: null,
190
+ });
191
+ deps.markAssetDeleted.mockImplementation(() => {
192
+ throw new Error('legacy marker failed');
193
+ });
194
+ const result = await saveAiProfileAvatarAssets(deps, {
195
+ workspaceId,
196
+ profileId,
197
+ display: {
198
+ buffer: Buffer.from('display'),
199
+ mimeType: 'image/webp',
200
+ originalName: 'display.webp',
201
+ },
202
+ source: {
203
+ buffer: Buffer.from('source'),
204
+ mimeType: 'image/webp',
205
+ originalName: 'source.webp',
206
+ },
207
+ });
208
+ expect(result.profile.avatarUrl).toBe(`/api/taskforce/context/${encodeURIComponent(result.displayStorageKey)}`);
209
+ expect(workspaceAssetStore.getByStorageKey(oldStorageKey, workspaceId)?.deletedAt).toBeTruthy();
210
+ expect(fs.existsSync(path.join(core.getPaths().basePath, oldStorageKey))).toBe(false);
211
+ core.close();
212
+ });
213
+ });