@taskforcehq/taskforce 0.3.305 → 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 (337) hide show
  1. package/dist/Taskforce.module.css +295 -89
  2. package/dist/TaskforceCore.js +215 -43
  3. package/dist/TaskforceCore.test.js +433 -28
  4. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  5. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  8. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  10. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  11. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  12. package/dist/compat/workspaceSyncCompat.js +46 -2
  13. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  14. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  15. package/dist/components/context/ContextAttachmentManager.js +549 -0
  16. package/dist/components/features/AgentsModule.d.ts +10 -1
  17. package/dist/components/features/AgentsModule.js +260 -15
  18. package/dist/components/features/AgentsModule.test.js +255 -5
  19. package/dist/components/features/DocumentViewer.d.ts +5 -1
  20. package/dist/components/features/DocumentViewer.js +60 -3
  21. package/dist/components/features/DocumentWorkspace.js +1 -1
  22. package/dist/components/features/DocumentWorkspace.test.js +33 -0
  23. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  24. package/dist/components/task/TaskCard.js +19 -28
  25. package/dist/components/task/TaskCard.test.js +38 -3
  26. package/dist/components/task/TaskContextUpload.js +4 -526
  27. package/dist/components/task/TaskForm.js +85 -90
  28. package/dist/components/task/TaskForm.test.js +110 -8
  29. package/dist/components/task/TaskKanban.d.ts +2 -1
  30. package/dist/components/task/TaskKanban.js +94 -13
  31. package/dist/components/task/TaskKanban.test.js +36 -0
  32. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  33. package/dist/components/ui/EditableAvatarButton.js +18 -0
  34. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  35. package/dist/components/views/PlanComparisonPage.js +70 -14
  36. package/dist/components/views/PlanComparisonPage.test.js +238 -0
  37. package/dist/components/views/PlansPage.js +127 -36
  38. package/dist/components/views/PlansPage.test.js +294 -12
  39. package/dist/components/views/StandaloneLayout.js +283 -533
  40. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  41. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  42. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  43. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  44. package/dist/components/views/planningScope.d.ts +20 -0
  45. package/dist/components/views/planningScope.js +25 -0
  46. package/dist/components/views/planningScope.test.d.ts +1 -0
  47. package/dist/components/views/planningScope.test.js +60 -0
  48. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  49. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  50. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  51. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  52. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  53. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  54. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  55. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  56. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  57. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  58. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  59. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  60. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  61. package/dist/core/AiProfileService.d.ts +39 -2
  62. package/dist/core/AiProfileService.js +639 -73
  63. package/dist/core/AiProfileService.test.js +19 -1
  64. package/dist/core/AiProfiles.test.js +566 -7
  65. package/dist/core/AttachmentLinkService.js +57 -15
  66. package/dist/core/AuthIdentityService.d.ts +107 -0
  67. package/dist/core/AuthIdentityService.js +284 -0
  68. package/dist/core/AuthTokenService.d.ts +4 -0
  69. package/dist/core/AuthTokenService.js +34 -6
  70. package/dist/core/AuthTokenService.test.js +3 -3
  71. package/dist/core/EntitlementsPolicy.test.js +2 -2
  72. package/dist/core/GlobalSettingsService.js +115 -6
  73. package/dist/core/PlanEntitlementService.d.ts +23 -2
  74. package/dist/core/PlanEntitlementService.js +343 -57
  75. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  76. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  77. package/dist/core/PlanVersionPolicy.test.js +35 -2
  78. package/dist/core/PlanningEntities.test.js +52 -0
  79. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  80. package/dist/core/SyncReconciliationService.js +2 -65
  81. package/dist/core/SystemAdmin.test.js +42 -42
  82. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  83. package/dist/core/Taskforce.d.ts +225 -14
  84. package/dist/core/Taskforce.js +765 -192
  85. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  86. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  87. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  88. package/dist/core/WorkspaceLifecycleService.js +0 -5
  89. package/dist/core/WorkspacePermissions.test.js +25 -1
  90. package/dist/core/WorkspacePlanMode.test.js +117 -16
  91. package/dist/core/shared.d.ts +1 -1
  92. package/dist/core/shared.js +5 -1
  93. package/dist/core/types.d.ts +68 -2
  94. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  95. package/dist/hooks/sync/bootstrap.js +2 -0
  96. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  97. package/dist/hooks/sync/controlPlane.js +78 -0
  98. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  99. package/dist/hooks/sync/controlPlane.test.js +121 -0
  100. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  101. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  102. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  103. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  104. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  105. package/dist/hooks/sync/orchestratorShared.js +68 -47
  106. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  107. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  108. package/dist/hooks/sync/pullLifecycle.js +24 -0
  109. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  110. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  111. package/dist/hooks/sync/recovery.d.ts +16 -2
  112. package/dist/hooks/sync/recovery.js +171 -53
  113. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  114. package/dist/hooks/sync/recovery.test.js +292 -0
  115. package/dist/hooks/sync/transfers.d.ts +15 -3
  116. package/dist/hooks/sync/transfers.js +179 -62
  117. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  118. package/dist/hooks/sync/transfers.test.js +396 -0
  119. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  120. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  121. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  122. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  123. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  124. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  125. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  126. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  127. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  128. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  129. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  130. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  131. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  132. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  133. package/dist/hooks/useSyncOrchestrator.js +359 -178
  134. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  135. package/dist/hooks/useTaskData.js +8 -3
  136. package/dist/hooks/useTaskData.test.js +45 -0
  137. package/dist/hooks/useTaskforce.d.ts +60 -2
  138. package/dist/hooks/useTaskforce.js +549 -240
  139. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  140. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  141. package/dist/hooks/useTaskforce.sync-behavior.test.js +2380 -212
  142. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  143. package/dist/hooks/useWorkspaceSyncController.js +6 -1
  144. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  145. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  146. package/dist/localization/locales/en-US.d.ts +0 -1
  147. package/dist/localization/locales/en-US.js +0 -1
  148. package/dist/localization/locales/es-419.js +0 -1
  149. package/dist/localization/locales/pt-BR.js +0 -1
  150. package/dist/mcp/documentAssetRegistrar.js +3 -3
  151. package/dist/mcp/runtime.d.ts +2 -0
  152. package/dist/mcp/runtime.js +131 -64
  153. package/dist/mcp/runtime.test.js +291 -12
  154. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  155. package/dist/migrations/taskSchemaMigrations.js +171 -61
  156. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  157. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  158. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  159. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  160. package/dist/resources/templates/workflows/plan.yaml +1 -1
  161. package/dist/resources/templates/workflows/review.yaml +2 -2
  162. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  163. package/dist/server/auth/providers/apple.d.ts +33 -0
  164. package/dist/server/auth/providers/apple.js +82 -0
  165. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  166. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  167. package/dist/server/auth/providers/github.d.ts +26 -0
  168. package/dist/server/auth/providers/github.js +86 -0
  169. package/dist/server/auth/providers/google.d.ts +21 -0
  170. package/dist/server/auth/providers/google.js +56 -0
  171. package/dist/server/auth/providers/types.d.ts +21 -0
  172. package/dist/server/auth/providers/types.js +1 -0
  173. package/dist/server/auth.d.ts +2 -0
  174. package/dist/server/auth.js +6 -3
  175. package/dist/server/documentReviewRoutes.test.js +36 -3
  176. package/dist/server/index.cookieProxy.test.js +1 -0
  177. package/dist/server/index.d.ts +12 -0
  178. package/dist/server/index.js +120 -9
  179. package/dist/server/index.rateLimit.test.js +3 -0
  180. package/dist/server/index.test.js +106 -3
  181. package/dist/server/routes/admin.d.ts +3 -0
  182. package/dist/server/routes/admin.js +272 -19
  183. package/dist/server/routes/auth.d.ts +15 -2
  184. package/dist/server/routes/auth.js +994 -57
  185. package/dist/server/routes/authSupport.d.ts +1 -1
  186. package/dist/server/routes/authSupport.js +1 -1
  187. package/dist/server/routes/billing.js +361 -35
  188. package/dist/server/routes/billing.test.js +592 -12
  189. package/dist/server/routes/documents.d.ts +1 -0
  190. package/dist/server/routes/documents.js +172 -70
  191. package/dist/server/routes/shared.d.ts +28 -2
  192. package/dist/server/routes/shared.js +69 -4
  193. package/dist/server/routes/sync.integration.test.js +437 -39
  194. package/dist/server/routes/sync.js +61 -605
  195. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  196. package/dist/server/routes/syncAuxRoutes.js +91 -0
  197. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  198. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  199. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  200. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  201. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  202. package/dist/server/routes/syncPushRoutes.js +212 -0
  203. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  204. package/dist/server/routes/syncRouteGuards.js +67 -0
  205. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  206. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  207. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  208. package/dist/server/routes/syncRouteTypes.js +1 -0
  209. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  210. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  211. package/dist/server/routes/tasks.js +43 -1
  212. package/dist/server/routes/workspaces.js +36 -5
  213. package/dist/server/routes.js +60 -3
  214. package/dist/server/routes.test.js +1824 -171
  215. package/dist/services/workflowExportSnapshots.test.js +2 -0
  216. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  217. package/dist/shared/aiProfileSeatScope.js +21 -0
  218. package/dist/shared/passwordPolicy.d.ts +13 -0
  219. package/dist/shared/passwordPolicy.js +84 -0
  220. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  221. package/dist/shared/passwordPolicy.test.js +34 -0
  222. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  223. package/dist/storage/workspaceAssetStore.js +132 -44
  224. package/dist/storage/workspaceAssetStore.test.js +93 -6
  225. package/dist/styles/fonts.css +70 -0
  226. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  227. package/dist/sync/collaborationSyncPayload.js +17 -11
  228. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  229. package/dist/sync/contentSyncState.d.ts +4 -8
  230. package/dist/sync/contentSyncState.js +17 -70
  231. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  232. package/dist/sync/syncApplyHandlers.js +28 -13
  233. package/dist/sync/syncApplyHandlers.test.js +70 -3
  234. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  235. package/dist/sync/syncFieldSemantics.js +55 -0
  236. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  237. package/dist/sync/syncResourceAdapters.js +77 -0
  238. package/dist/sync/syncService.d.ts +5 -0
  239. package/dist/sync/syncService.js +52 -0
  240. package/dist/sync/syncService.test.js +57 -3
  241. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  242. package/dist/sync/taskSyncChangeKey.js +143 -0
  243. package/dist/sync/taskSyncPayload.js +3 -8
  244. package/dist/sync/workspacePullFeed.js +4 -3
  245. package/dist/sync/workspacePullFeed.test.js +43 -0
  246. package/dist/sync/workspaceRepair.js +5 -2
  247. package/dist/sync/workspaceSyncModel.d.ts +45 -0
  248. package/dist/sync/workspaceSyncModel.js +183 -28
  249. package/dist/sync/workspaceSyncModel.test.js +291 -12
  250. package/dist/sync/workspaceSyncState.d.ts +17 -0
  251. package/dist/sync/workspaceSyncState.js +2 -1
  252. package/dist/sync/workspaceSyncSurface.js +3 -8
  253. package/dist/types.d.ts +17 -0
  254. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  255. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BG6P_GVW.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  256. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  257. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  258. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  259. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  260. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  261. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  262. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-9E6ParrY.js} +1 -1
  263. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  264. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  265. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  266. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  267. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  268. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  269. package/dist/ui/assets/{TaskSettings-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
  270. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  271. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  272. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  273. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-DuEd_65c.js} +1 -1
  274. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  275. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  276. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  277. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  278. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  279. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  280. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  281. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  282. package/dist/ui/fonts/Noir_medium.otf +0 -0
  283. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  284. package/dist/ui/fonts/Noir_regular.otf +0 -0
  285. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  286. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  287. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  288. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  289. package/dist/ui/index.html +3 -3
  290. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  291. package/dist/utils/assignees.d.ts +2 -0
  292. package/dist/utils/assignees.js +2 -0
  293. package/dist/utils/avatarUpload.d.ts +27 -0
  294. package/dist/utils/avatarUpload.js +132 -0
  295. package/dist/utils/avatarUpload.test.d.ts +1 -0
  296. package/dist/utils/avatarUpload.test.js +40 -0
  297. package/dist/utils/bootstrapTrace.d.ts +7 -0
  298. package/dist/utils/bootstrapTrace.js +29 -0
  299. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  300. package/dist/utils/contextAssetEvents.d.ts +2 -0
  301. package/dist/utils/syncEventDetails.d.ts +3 -0
  302. package/dist/utils/syncEventDetails.js +64 -0
  303. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  304. package/dist/utils/syncEventDetails.test.js +23 -0
  305. package/dist/utils/syncEventPresentation.d.ts +15 -0
  306. package/dist/utils/syncEventPresentation.js +129 -0
  307. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  308. package/dist/utils/syncEventPresentation.test.js +64 -0
  309. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  310. package/dist/utils/syncStatusPresentation.js +217 -0
  311. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  312. package/dist/utils/syncStatusPresentation.test.js +164 -0
  313. package/dist/utils/taskActivity.d.ts +2 -0
  314. package/dist/utils/taskActivity.js +228 -22
  315. package/dist/utils/taskActivity.test.js +119 -8
  316. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  317. package/dist/utils/workspaceSyncPresentation.js +68 -2
  318. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  319. package/package.json +3 -2
  320. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  321. package/scripts/run-e2e-realtime.sh +17 -3
  322. package/scripts/run-smoke.sh +17 -5
  323. package/scripts/run-soak.sh +17 -5
  324. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  325. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  326. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  327. package/src/resources/templates/workflows/plan.yaml +1 -1
  328. package/src/resources/templates/workflows/review.yaml +2 -2
  329. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  330. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  331. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  332. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  333. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  334. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  335. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  336. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  337. package/scripts/migrate-planning-model.ts +0 -233
@@ -1,25 +1,65 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useState } from 'react';
3
- import { AlertTriangle, Bot, Check, Clock3, Loader2, Orbit, Sparkles } from 'lucide-react';
3
+ import { AlertTriangle, Bot, Check, Cloud, LaptopMinimal, Loader2 } from 'lucide-react';
4
4
  import styles from '../../Taskforce.module.css';
5
+ import { EditableAvatarButton } from '../ui/EditableAvatarButton';
6
+ import { AvatarImageManagerModal } from '../views/standalone/modals/AvatarImageManagerModal';
7
+ import { prepareAvatarUploadFile } from '../../utils/avatarUpload';
5
8
  import { AI_PROFILE_SURFACE_TYPE_SECTION_ORDER, formatAiProfileSurfaceTypeLabel, formatAiProfileSurfaceTypeSectionLabel, getAiProfileSurfaceTypeSection, parseAiProfileSurfaceType } from '../../shared/aiProfileSurfaceType.js';
6
- export function AgentsModule({ workspaceId }) {
9
+ import { formatAiProfileSeatScopeLabel, parseAiProfileSeatScope } from '../../shared/aiProfileSeatScope.js';
10
+ function parseAiProfileSeatUsage(value) {
11
+ if (!value || typeof value !== 'object')
12
+ return null;
13
+ return {
14
+ used: Math.max(0, Number(value.used || 0)),
15
+ limit: value.limit === null || value.limit === undefined
16
+ ? null
17
+ : Math.max(0, Number(value.limit || 0)),
18
+ remaining: value.remaining === null || value.remaining === undefined
19
+ ? null
20
+ : Math.max(0, Number(value.remaining || 0)),
21
+ };
22
+ }
23
+ function renderAiProfileSeatScopeIcon(seatScope, options) {
24
+ const className = options?.variant === 'inline'
25
+ ? `${styles.aiProfileSeatScopeIcon} ${styles.aiProfileSeatScopeIconInline}`
26
+ : options?.variant === 'detail'
27
+ ? `${styles.aiProfileSeatScopeIcon} ${styles.aiProfileSeatScopeIconDetail}`
28
+ : styles.aiProfileSeatScopeIcon;
29
+ if (seatScope === 'cloud_metered') {
30
+ return (_jsx("span", { className: `${className} ${styles.aiProfileSeatScopeIconCloud}`, title: "Cloud MCP", "aria-label": "Cloud MCP", children: _jsx(Cloud, { size: 20 }) }));
31
+ }
32
+ if (seatScope === 'local_unmetered') {
33
+ return (_jsx("span", { className: `${className} ${styles.aiProfileSeatScopeIconLocal}`, title: "Local MCP", "aria-label": "Local MCP", children: _jsx(LaptopMinimal, { size: 20 }) }));
34
+ }
35
+ return null;
36
+ }
37
+ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSessionResolved = false, isAuthenticated = false, cloudAiProfileSeatUsage = null, }) {
7
38
  const [aiProfiles, setAiProfiles] = useState([]);
39
+ const [localAiProfileSeatUsage, setLocalAiProfileSeatUsage] = useState(null);
8
40
  const [aiProfilesLoading, setAiProfilesLoading] = useState(false);
9
41
  const [aiProfileMergeState, setAiProfileMergeState] = useState(null);
10
42
  const [aiProfileMergeStatus, setAiProfileMergeStatus] = useState(null);
43
+ const [aiProfileArchiveError, setAiProfileArchiveError] = useState(null);
11
44
  const [selectedGroupId, setSelectedGroupId] = useState(null);
45
+ const [avatarEditorProfileId, setAvatarEditorProfileId] = useState(null);
46
+ const [aiAvatarBusy, setAiAvatarBusy] = useState(false);
47
+ const [aiAvatarError, setAiAvatarError] = useState(null);
12
48
  const loadAiProfiles = useCallback(async () => {
13
49
  if (!workspaceId)
14
50
  return;
15
51
  setAiProfilesLoading(true);
16
52
  try {
17
- const res = await fetch(`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(workspaceId)}`, {
53
+ const rosterUrl = `/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(workspaceId)}`;
54
+ const rosterResponse = await fetch(rosterUrl, {
18
55
  credentials: 'include',
19
56
  });
20
- const data = await res.json().catch(() => ({}));
21
- const agents = Array.isArray(data?.assignees)
22
- ? data.assignees
57
+ const rosterData = rosterResponse.ok
58
+ ? await rosterResponse.json().catch(() => ({}))
59
+ : {};
60
+ const fallbackSeatUsage = parseAiProfileSeatUsage(rosterData?.aiProfileSeatUsage);
61
+ const agents = Array.isArray(rosterData?.assignees)
62
+ ? rosterData.assignees
23
63
  .filter((assignee) => assignee.kind === 'agent')
24
64
  .map((assignee) => ({
25
65
  id: String(assignee.value || ''),
@@ -27,18 +67,26 @@ export function AgentsModule({ workspaceId }) {
27
67
  username: String(assignee.username || assignee.value || ''),
28
68
  icon: String(assignee.icon || 'Bot'),
29
69
  color: String(assignee.color || '#6B7280'),
70
+ avatarUrl: typeof assignee.avatarUrl === 'string' ? assignee.avatarUrl : null,
71
+ avatarSourceUrl: typeof assignee.avatarSourceUrl === 'string' ? assignee.avatarSourceUrl : null,
30
72
  kind: String(assignee.kind || 'agent'),
31
73
  description: typeof assignee.description === 'string' ? assignee.description : null,
32
74
  role: typeof assignee.role === 'string' ? assignee.role : null,
33
75
  provider: typeof assignee.provider === 'string' ? assignee.provider : null,
34
76
  model: typeof assignee.model === 'string' ? assignee.model : null,
35
77
  surfaceType: parseAiProfileSurfaceType(assignee.surfaceType),
78
+ seatScope: parseAiProfileSeatScope(assignee.seatScope),
79
+ archivedAt: typeof assignee.archivedAt === 'string' ? assignee.archivedAt : null,
36
80
  createdAt: String(assignee.createdAt || ''),
37
81
  updatedAt: String(assignee.updatedAt || assignee.createdAt || ''),
38
82
  lastActiveAt: typeof assignee.lastActiveAt === 'string' ? assignee.lastActiveAt : null,
39
83
  }))
40
84
  : [];
41
85
  setAiProfiles(agents);
86
+ setLocalAiProfileSeatUsage(fallbackSeatUsage);
87
+ setAiProfileArchiveError((current) => (current && !agents.some((agent) => agent.id === current.profileId)
88
+ ? null
89
+ : current));
42
90
  }
43
91
  finally {
44
92
  setAiProfilesLoading(false);
@@ -75,6 +123,7 @@ export function AgentsModule({ workspaceId }) {
75
123
  }, [aiProfiles]);
76
124
  const allGroups = useMemo(() => groupedProfilesBySurfaceType.flatMap((section) => section.groups), [groupedProfilesBySurfaceType]);
77
125
  const selectedGroup = useMemo(() => allGroups.find((group) => group.groupId === selectedGroupId) || allGroups[0] || null, [allGroups, selectedGroupId]);
126
+ const avatarEditorProfile = useMemo(() => aiProfiles.find((profile) => profile.id === avatarEditorProfileId) || null, [aiProfiles, avatarEditorProfileId]);
78
127
  useEffect(() => {
79
128
  if (!allGroups.length) {
80
129
  if (selectedGroupId !== null)
@@ -87,6 +136,7 @@ export function AgentsModule({ workspaceId }) {
87
136
  }, [allGroups, selectedGroupId]);
88
137
  const handleMerge = async (keepId, mergeId) => {
89
138
  setAiProfileMergeStatus(null);
139
+ setAiProfileArchiveError(null);
90
140
  try {
91
141
  const res = await fetch('/api/taskforce/workspace/ai-profiles/merge', {
92
142
  method: 'POST',
@@ -107,6 +157,152 @@ export function AgentsModule({ workspaceId }) {
107
157
  setAiProfileMergeStatus({ type: 'error', message: 'Failed to merge AI profiles.' });
108
158
  }
109
159
  };
160
+ const handleArchive = async (profile) => {
161
+ const confirmed = window.confirm(`Remove ${profile.name} from the active roster? This frees an AI profile seat and preserves task and comment history.`);
162
+ if (!confirmed)
163
+ return;
164
+ setAiProfileArchiveError(null);
165
+ setAiProfileMergeStatus(null);
166
+ try {
167
+ const res = await fetch('/api/taskforce/workspace/ai-profiles/archive', {
168
+ method: 'POST',
169
+ credentials: 'include',
170
+ headers: { 'Content-Type': 'application/json' },
171
+ body: JSON.stringify({ profileId: profile.id, reason: 'manual_archive' }),
172
+ });
173
+ const data = await res.json().catch(() => ({}));
174
+ if (!res.ok) {
175
+ setAiProfileArchiveError({
176
+ profileId: profile.id,
177
+ message: String(data?.error || 'Failed to remove AI profile from roster.')
178
+ });
179
+ return;
180
+ }
181
+ if (aiProfileMergeState?.keepId === profile.id || aiProfileMergeState?.mergeId === profile.id) {
182
+ setAiProfileMergeState(null);
183
+ }
184
+ setAiProfileMergeStatus({ type: 'success', message: 'AI profile removed from active roster.' });
185
+ await loadAiProfiles();
186
+ }
187
+ catch {
188
+ setAiProfileArchiveError({
189
+ profileId: profile.id,
190
+ message: 'Failed to remove AI profile from roster.'
191
+ });
192
+ }
193
+ };
194
+ const fileToDataUrl = (file) => new Promise((resolve, reject) => {
195
+ const reader = new FileReader();
196
+ reader.onload = () => resolve(String(reader.result || ''));
197
+ reader.onerror = () => reject(new Error('Failed to read image file.'));
198
+ reader.readAsDataURL(file);
199
+ });
200
+ const applyAiProfileAvatarUpdate = (updatedProfile) => {
201
+ const updatedId = String(updatedProfile?.id || '').trim();
202
+ if (!updatedId)
203
+ return;
204
+ setAiProfiles((current) => current.map((profile) => (profile.id === updatedId
205
+ ? {
206
+ ...profile,
207
+ avatarUrl: typeof updatedProfile.avatarUrl === 'string' ? updatedProfile.avatarUrl : null,
208
+ avatarSourceUrl: typeof updatedProfile.avatarSourceUrl === 'string' ? updatedProfile.avatarSourceUrl : null,
209
+ updatedAt: typeof updatedProfile.updatedAt === 'string' ? updatedProfile.updatedAt : profile.updatedAt
210
+ }
211
+ : profile)));
212
+ };
213
+ const updateAiProfileAvatar = async (profileId, avatarUrl, avatarSourceUrl) => {
214
+ const res = await fetch('/api/taskforce/workspace/ai-profiles/avatar', {
215
+ method: 'POST',
216
+ credentials: 'include',
217
+ headers: { 'Content-Type': 'application/json' },
218
+ body: JSON.stringify({
219
+ profileId,
220
+ avatarUrl,
221
+ ...(avatarSourceUrl !== undefined ? { avatarSourceUrl } : {})
222
+ }),
223
+ });
224
+ const data = await res.json().catch(() => ({}));
225
+ if (!res.ok || !data?.profile) {
226
+ throw new Error(String(data?.error || 'Failed to update AI profile avatar.'));
227
+ }
228
+ applyAiProfileAvatarUpdate(data.profile);
229
+ };
230
+ const uploadAiProfileAvatarFile = async (file, profile, kind) => {
231
+ const dataUrl = await fileToDataUrl(file);
232
+ const uploadRes = await fetch('/api/taskforce/context-upload', {
233
+ method: 'POST',
234
+ credentials: 'include',
235
+ headers: { 'Content-Type': 'application/json' },
236
+ body: JSON.stringify({
237
+ workspaceId,
238
+ file: dataUrl,
239
+ originalName: `${kind}-${file.name}`,
240
+ folderPath: `ai-profiles/${profile.id}/avatar`
241
+ })
242
+ });
243
+ const uploadData = await uploadRes.json().catch(() => ({}));
244
+ const avatarUrl = typeof uploadData?.path === 'string' ? uploadData.path : '';
245
+ if (!uploadRes.ok || !avatarUrl) {
246
+ throw new Error(String(uploadData?.error || 'Failed to upload AI profile photo.'));
247
+ }
248
+ return avatarUrl;
249
+ };
250
+ const handleAiProfileAvatarSelected = async (file, sourceFile) => {
251
+ if (!avatarEditorProfile)
252
+ return false;
253
+ if (!file.type.startsWith('image/')) {
254
+ setAiAvatarError('AI profile photo must be an image file.');
255
+ return false;
256
+ }
257
+ setAiAvatarBusy(true);
258
+ setAiAvatarError(null);
259
+ try {
260
+ const preparedAvatar = await prepareAvatarUploadFile(file, { maxBytes: 5 * 1024 * 1024 });
261
+ if (preparedAvatar.exceededLimit) {
262
+ throw new Error(file.type === 'image/gif'
263
+ ? 'Animated GIF AI profile photos must be 5 MB or smaller.'
264
+ : 'AI profile photo must be 5 MB or smaller.');
265
+ }
266
+ const uploadFile = preparedAvatar.file;
267
+ const avatarUrl = await uploadAiProfileAvatarFile(uploadFile, avatarEditorProfile, 'cropped');
268
+ let avatarSourceUrl;
269
+ if (sourceFile) {
270
+ const preparedSource = await prepareAvatarUploadFile(sourceFile, { maxBytes: 5 * 1024 * 1024 });
271
+ if (preparedSource.exceededLimit) {
272
+ throw new Error(sourceFile.type === 'image/gif'
273
+ ? 'Animated GIF AI profile photos must be 5 MB or smaller.'
274
+ : 'AI profile source photo must be 5 MB or smaller.');
275
+ }
276
+ avatarSourceUrl = sourceFile.type === 'image/gif'
277
+ ? avatarUrl
278
+ : await uploadAiProfileAvatarFile(preparedSource.file, avatarEditorProfile, 'source');
279
+ }
280
+ await updateAiProfileAvatar(avatarEditorProfile.id, avatarUrl, avatarSourceUrl);
281
+ return true;
282
+ }
283
+ catch (error) {
284
+ setAiAvatarError(String(error?.message || 'Failed to update AI profile photo.'));
285
+ return false;
286
+ }
287
+ finally {
288
+ setAiAvatarBusy(false);
289
+ }
290
+ };
291
+ const handleAiProfileAvatarRemove = async () => {
292
+ if (!avatarEditorProfile)
293
+ return;
294
+ setAiAvatarBusy(true);
295
+ setAiAvatarError(null);
296
+ try {
297
+ await updateAiProfileAvatar(avatarEditorProfile.id, null, null);
298
+ }
299
+ catch (error) {
300
+ setAiAvatarError(String(error?.message || 'Failed to remove AI profile photo.'));
301
+ }
302
+ finally {
303
+ setAiAvatarBusy(false);
304
+ }
305
+ };
110
306
  const formatTimestamp = (value) => {
111
307
  const normalized = String(value || '').trim();
112
308
  if (!normalized)
@@ -119,13 +315,62 @@ export function AgentsModule({ workspaceId }) {
119
315
  timeStyle: 'short'
120
316
  });
121
317
  };
122
- return (_jsx("div", { className: `${styles.standalonePage} ${styles.standaloneContent}`, style: { overflow: 'auto' }, children: _jsx("div", { className: styles.settingsContent, style: { maxWidth: 1080, margin: '0 auto', width: '100%' }, children: _jsx("div", { className: styles.settingGroup, children: _jsxs("div", { children: [_jsx("h4", { className: styles.settingSubTitle, children: "Registered AI Profiles" }), _jsx("p", { className: `${styles.settingsHint} ${styles.marginBottom12}`, children: "AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost." }), aiProfilesLoading && (_jsxs("div", { className: styles.settingsHint, children: [_jsx(Loader2, { size: 13, className: styles.spinner }), " Loading profiles\u2026"] })), !aiProfilesLoading && aiProfiles.length === 0 && (_jsx("div", { className: styles.settingsHint, children: "No AI profiles registered yet." })), !aiProfilesLoading && groupedProfilesBySurfaceType.length > 0 && (_jsxs("div", { className: styles.aiProfilesExplorer, children: [_jsx("div", { className: styles.aiProfilesListPane, children: _jsx("div", { className: styles.aiProfilesList, children: groupedProfilesBySurfaceType.map((section) => (_jsxs("div", { className: styles.aiProfilesSection, children: [_jsx("div", { className: styles.aiProfilesSectionHeader, children: section.label }), section.groups.map((group) => {
123
- const isSelected = selectedGroup?.groupId === group.groupId;
124
- const primary = group.primaryProfile;
125
- return (_jsxs("button", { type: "button", className: `${styles.aiProfileGroup} ${group.profiles.length > 1 ? styles.aiProfileGroupDuplicate : ''} ${isSelected ? styles.aiProfileGroupSelected : ''}`, onClick: () => setSelectedGroupId(group.groupId), "aria-pressed": isSelected, children: [_jsxs("div", { className: styles.aiProfileGroupHeader, children: [_jsx(Bot, { size: 13, style: { color: primary.color } }), _jsx("span", { className: styles.aiProfileName, children: primary.name }), group.profiles.length > 1 && (_jsxs("span", { className: styles.aiProfileDuplicateBadge, children: [_jsx(AlertTriangle, { size: 11 }), " ", group.profiles.length, " duplicates"] }))] }), _jsxs("div", { className: styles.settingsHint, style: { marginTop: 6 }, children: ["@", primary.username] }), (primary.surfaceType || primary.role || primary.provider || primary.model) && (_jsxs("div", { className: styles.aiProfileMetaRow, children: [primary.surfaceType && (_jsx("span", { className: styles.aiProfileSurfaceBadge, children: formatAiProfileSurfaceTypeLabel(primary.surfaceType) })), primary.role && _jsx("span", { children: primary.role }), primary.provider && _jsx("span", { children: primary.provider }), primary.model && _jsx("span", { children: primary.model })] })), primary.description && (_jsx("div", { className: styles.settingsHint, style: { marginTop: 4 }, children: primary.description }))] }, group.groupId));
126
- })] }, section.section))) }) }), selectedGroup && (_jsx("div", { className: styles.aiProfileDetailPane, children: _jsxs("div", { className: styles.aiProfileDetailCard, children: [_jsxs("div", { className: styles.aiProfileDetailHero, children: [_jsx("div", { className: styles.aiProfileDetailIcon, style: { color: selectedGroup.primaryProfile.color }, children: _jsx(Bot, { size: 18 }) }), _jsxs("div", { className: styles.aiProfileDetailHeading, children: [_jsx("div", { className: styles.aiProfileDetailTitleRow, children: _jsx("h5", { className: styles.aiProfileDetailTitle, children: selectedGroup.primaryProfile.name }) }), _jsxs("div", { className: styles.aiProfileDetailMetaRow, children: [_jsxs("span", { className: styles.aiProfileDetailHandle, children: ["@", selectedGroup.primaryProfile.username] }), _jsx("span", { className: styles.aiProfileDetailSectionBadge, children: selectedGroup.sectionLabel }), selectedGroup.profiles.length > 1 && (_jsxs("span", { className: styles.aiProfileDetailLinkedCount, children: [selectedGroup.profiles.length, " linked"] }))] })] })] }), selectedGroup.primaryProfile.description && (_jsx("div", { className: styles.aiProfileDetailDescription, children: selectedGroup.primaryProfile.description })), _jsxs("div", { className: styles.aiProfileDetailGrid, children: [_jsxs("div", { className: styles.aiProfileDetailStat, children: [_jsxs("span", { className: styles.aiProfileDetailStatLabel, children: [_jsx(Sparkles, { size: 12 }), " Role"] }), _jsx("span", { className: styles.aiProfileDetailStatValue, children: selectedGroup.primaryProfile.role || 'Not set' })] }), _jsxs("div", { className: styles.aiProfileDetailStat, children: [_jsxs("span", { className: styles.aiProfileDetailStatLabel, children: [_jsx(Orbit, { size: 12 }), " Provider"] }), _jsx("span", { className: styles.aiProfileDetailStatValue, children: [selectedGroup.primaryProfile.provider, selectedGroup.primaryProfile.model].filter(Boolean).join(' · ') || 'Not set' })] }), _jsxs("div", { className: styles.aiProfileDetailStat, children: [_jsxs("span", { className: styles.aiProfileDetailStatLabel, children: [_jsx(Clock3, { size: 12 }), " Last active"] }), _jsx("span", { className: styles.aiProfileDetailStatValue, children: formatTimestamp(selectedGroup.primaryProfile.lastActiveAt) })] }), _jsxs("div", { className: styles.aiProfileDetailStat, children: [_jsxs("span", { className: styles.aiProfileDetailStatLabel, children: [_jsx(Clock3, { size: 12 }), " Updated"] }), _jsx("span", { className: styles.aiProfileDetailStatValue, children: formatTimestamp(selectedGroup.primaryProfile.updatedAt) })] })] }), _jsxs("div", { className: styles.aiProfileInstanceSection, children: [_jsxs("div", { className: styles.aiProfileInstanceSectionHeader, children: [_jsx("span", { children: "Profile instances" }), _jsx("span", { className: styles.settingsHint, children: "Choose a keeper here if duplicates need to be merged." })] }), _jsx("div", { className: styles.aiProfileInstanceList, children: selectedGroup.profiles.map((profile) => (_jsxs("div", { className: styles.aiProfileInstanceCard, children: [_jsxs("div", { className: styles.aiProfileInstanceTopRow, children: [_jsxs("div", { children: [_jsxs("div", { className: styles.aiProfileInstanceName, children: ["@", profile.username] }), _jsx("div", { className: styles.aiProfileIdChip, children: profile.id })] }), selectedGroup.profiles.length > 1 && aiProfileMergeState?.keepId === profile.id && (_jsx("span", { className: styles.aiProfileKeepBadge, children: "Keeping" }))] }), _jsxs("div", { className: styles.aiProfileInstanceMeta, children: [_jsxs("span", { children: ["Created ", formatTimestamp(profile.createdAt)] }), _jsxs("span", { children: ["Updated ", formatTimestamp(profile.updatedAt)] })] }), selectedGroup.profiles.length > 1 && aiProfileMergeState?.keepId !== profile.id && (_jsx("button", { className: styles.secondaryHeaderBtn, title: "Keep this profile, merge others into it", onClick: () => {
127
- const otherId = selectedGroup.profiles.find((candidate) => candidate.id !== profile.id)?.id;
128
- if (otherId)
129
- setAiProfileMergeState({ keepId: profile.id, mergeId: otherId });
130
- }, children: "Keep this" }))] }, profile.id))) }), aiProfileMergeState && selectedGroup.profiles.some((profile) => profile.id === aiProfileMergeState.keepId) && (_jsxs("div", { className: styles.aiProfileMergeActions, children: [_jsx("button", { className: styles.dangerBtn, onClick: () => { void handleMerge(aiProfileMergeState.keepId, aiProfileMergeState.mergeId); }, children: "Merge duplicates" }), _jsx("button", { className: styles.secondaryHeaderBtn, onClick: () => setAiProfileMergeState(null), children: "Cancel" })] }))] })] }) }))] })), aiProfileMergeStatus && (_jsxs("div", { className: `${aiProfileMergeStatus.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [aiProfileMergeStatus.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), aiProfileMergeStatus.message] }))] }) }) }) }));
318
+ const selectedProfileDataGroups = useMemo(() => {
319
+ if (!selectedGroup)
320
+ return { attributes: [], dates: [] };
321
+ const profile = selectedGroup.primaryProfile;
322
+ const formatRowValue = (value) => value || 'Not set';
323
+ return {
324
+ attributes: [
325
+ { label: 'Category', value: profile.surfaceType ? formatAiProfileSurfaceTypeLabel(profile.surfaceType) : '' },
326
+ { label: 'Connection', value: profile.seatScope ? formatAiProfileSeatScopeLabel(profile.seatScope) : '' },
327
+ { label: 'Provider', value: profile.provider || '' },
328
+ { label: 'Model', value: profile.model || '' },
329
+ ].map((row) => ({ ...row, value: formatRowValue(row.value) })),
330
+ dates: [
331
+ { label: 'Recruited', value: formatTimestamp(profile.createdAt) },
332
+ { label: 'Last updated', value: formatTimestamp(profile.updatedAt) },
333
+ { label: 'Last active', value: formatTimestamp(profile.lastActiveAt) },
334
+ { label: 'Status', value: profile.archivedAt ? 'Retired' : 'Active' },
335
+ ].map((row) => ({ ...row, value: formatRowValue(row.value) })),
336
+ };
337
+ }, [selectedGroup]);
338
+ const selectedGroupHasDuplicates = !!selectedGroup && selectedGroup.profiles.length > 1;
339
+ const shouldPromptForCloudLogin = cloudAuthConfigured && authSessionResolved && !isAuthenticated;
340
+ const displayedSeatUsage = cloudAuthConfigured
341
+ ? parseAiProfileSeatUsage(cloudAiProfileSeatUsage)
342
+ : localAiProfileSeatUsage;
343
+ const cloudAgentSeatLabel = shouldPromptForCloudLogin
344
+ ? 'Log into account for Cloud agents'
345
+ : displayedSeatUsage
346
+ ? `${displayedSeatUsage.used}/${displayedSeatUsage.limit === null ? 'Unlimited' : displayedSeatUsage.limit}`
347
+ : null;
348
+ return (_jsxs("section", { className: styles.agentsModuleRoot, children: [_jsx("div", { className: styles.agentsModuleContent, children: _jsx("div", { className: `${styles.settingGroup} ${styles.agentsModuleGroup}`, children: _jsxs("div", { children: [_jsx("h4", { className: styles.settingSubTitle, children: "Registered AI Profiles" }), _jsx("p", { className: `${styles.settingsHint} ${styles.marginBottom12}`, children: "AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost, or remove inactive profiles from the active roster to free seats while preserving history." }), cloudAgentSeatLabel && (_jsx("div", { className: styles.aiProfileSeatSummary, children: shouldPromptForCloudLogin
349
+ ? cloudAgentSeatLabel
350
+ : `Registered Cloud Agents: ${cloudAgentSeatLabel}` })), aiProfilesLoading && (_jsxs("div", { className: styles.settingsHint, children: [_jsx(Loader2, { size: 13, className: styles.spinner }), " Loading profiles\u2026"] })), !aiProfilesLoading && aiProfiles.length === 0 && (_jsx("div", { className: styles.settingsHint, children: "No AI profiles registered yet." })), !aiProfilesLoading && groupedProfilesBySurfaceType.length > 0 && (_jsxs("div", { className: styles.aiProfilesExplorer, children: [_jsx("div", { className: `${styles.aiProfilesListPane} tf-scrollbar tf-scrollbar--track-transparent`, children: _jsx("div", { className: styles.aiProfilesList, children: groupedProfilesBySurfaceType.map((section) => (_jsxs("div", { className: styles.aiProfilesSection, children: [_jsx("div", { className: styles.aiProfilesSectionHeader, children: section.label }), section.groups.map((group) => {
351
+ const isSelected = selectedGroup?.groupId === group.groupId;
352
+ const primary = group.primaryProfile;
353
+ return (_jsxs("div", { role: "button", tabIndex: 0, className: `${styles.aiProfileGroup} ${group.profiles.length > 1 ? styles.aiProfileGroupDuplicate : ''} ${isSelected ? styles.aiProfileGroupSelected : ''}`, onClick: () => setSelectedGroupId(group.groupId), onKeyDown: (event) => {
354
+ if (event.key !== 'Enter' && event.key !== ' ')
355
+ return;
356
+ event.preventDefault();
357
+ setSelectedGroupId(group.groupId);
358
+ }, "aria-pressed": isSelected, children: [renderAiProfileSeatScopeIcon(primary.seatScope), _jsxs("div", { className: styles.aiProfileGroupHeader, children: [_jsx("span", { className: styles.aiProfileGroupAvatar, style: { color: primary.color }, children: primary.avatarUrl ? (_jsx("img", { src: primary.avatarUrl, alt: "" })) : (_jsx(Bot, { size: 22 })) }), _jsxs("span", { className: styles.aiProfileGroupIdentity, children: [_jsx("span", { className: styles.aiProfileName, children: primary.name }), _jsxs("span", { className: styles.aiProfileHandle, children: ["@", primary.username] }), _jsx("span", { className: styles.aiProfileRole, children: primary.role || 'Role not set' }), group.profiles.length > 1 && (_jsxs("span", { className: styles.aiProfileDuplicateBadge, children: [_jsx(AlertTriangle, { size: 11 }), " ", group.profiles.length, " duplicates"] }))] })] })] }, group.groupId));
359
+ })] }, section.section))) }) }), selectedGroup && (_jsx("div", { className: styles.aiProfileDetailPane, children: _jsxs("div", { className: styles.aiProfileDetailCard, children: [renderAiProfileSeatScopeIcon(selectedGroup.primaryProfile.seatScope, { variant: 'detail' }), _jsxs("div", { className: styles.aiProfileDetailHero, children: [_jsx(EditableAvatarButton, { label: "Edit AI profile photo", imageUrl: selectedGroup.primaryProfile.avatarUrl, fallback: _jsx(Bot, { size: 38 }), accentColor: selectedGroup.primaryProfile.color, size: 176, width: 153, height: 207, radius: 6, editBadgeSize: 28, editIconSize: 14, className: styles.aiProfileDetailAvatar, onClick: () => {
360
+ setAiAvatarError(null);
361
+ setAvatarEditorProfileId(selectedGroup.primaryProfile.id);
362
+ } }), _jsxs("div", { className: styles.aiProfileDetailHeading, children: [_jsx("div", { className: styles.aiProfileDetailTitleRow, children: _jsx("h5", { className: styles.aiProfileDetailTitle, children: selectedGroup.primaryProfile.name }) }), _jsxs("div", { className: styles.aiProfileDetailMetaRow, children: [_jsxs("span", { className: styles.aiProfileDetailHandle, children: ["@", selectedGroup.primaryProfile.username] }), selectedGroup.profiles.length > 1 && (_jsxs("span", { className: styles.aiProfileDetailLinkedCount, children: [selectedGroup.profiles.length, " linked"] }))] }), _jsxs("div", { className: styles.aiProfileDetailRole, children: ["Role: ", selectedGroup.primaryProfile.role || 'Not set'] }), selectedGroup.primaryProfile.description && (_jsx("div", { className: styles.aiProfileDetailDescription, children: selectedGroup.primaryProfile.description }))] })] }), _jsxs("div", { className: styles.aiProfileDetailDataList, children: [_jsx("div", { className: styles.aiProfileDetailDataGroup, children: selectedProfileDataGroups.attributes.map((row) => (_jsxs("div", { className: styles.aiProfileDetailDataRow, children: [_jsx("span", { className: styles.aiProfileDetailDataLabel, children: row.label }), _jsx("span", { className: styles.aiProfileDetailDataValue, children: row.value })] }, row.label))) }), _jsx("div", { className: `${styles.aiProfileDetailDataGroup} ${styles.aiProfileDetailDateGroup}`, children: selectedProfileDataGroups.dates.map((row) => (_jsxs("div", { className: styles.aiProfileDetailDataRow, children: [_jsx("span", { className: styles.aiProfileDetailDataLabel, children: row.label }), row.label === 'Status' ? (_jsx("span", { className: styles.aiProfileDetailDataValue, children: _jsxs("select", { className: styles.aiProfileStatusSelect, "aria-label": "Agent roster status", value: selectedGroup.primaryProfile.archivedAt ? 'retired' : 'active', onChange: (event) => {
363
+ if (event.target.value === 'retire') {
364
+ void handleArchive(selectedGroup.primaryProfile);
365
+ }
366
+ }, children: [_jsx("option", { value: "active", children: "Active" }), selectedGroup.primaryProfile.archivedAt ? (_jsx("option", { value: "retired", children: "Retired" })) : (_jsx("option", { value: "retire", children: "Retire from roster" }))] }) })) : (_jsx("span", { className: styles.aiProfileDetailDataValue, children: row.value }))] }, row.label))) })] }), selectedGroupHasDuplicates ? (_jsxs("div", { className: styles.aiProfileInstanceSection, children: [_jsxs("div", { className: styles.aiProfileInstanceSectionHeader, children: [_jsx("span", { children: "Profile instances" }), _jsx("span", { className: styles.settingsHint, children: "Choose a keeper here if duplicates need to be merged." })] }), _jsx("div", { className: styles.aiProfileInstanceList, children: selectedGroup.profiles.map((profile) => (_jsxs("div", { className: styles.aiProfileInstanceCard, children: [_jsxs("div", { className: styles.aiProfileInstanceTopRow, children: [_jsxs("div", { children: [_jsxs("div", { className: styles.aiProfileInstanceName, children: ["@", profile.username] }), _jsx("div", { className: styles.aiProfileIdChip, children: profile.id })] }), aiProfileMergeState?.keepId === profile.id && (_jsx("span", { className: styles.aiProfileKeepBadge, children: "Keeping" }))] }), _jsxs("div", { className: styles.aiProfileInstanceMeta, children: [_jsxs("span", { children: ["Created ", formatTimestamp(profile.createdAt)] }), _jsxs("span", { children: ["Updated ", formatTimestamp(profile.updatedAt)] })] }), aiProfileMergeState?.keepId !== profile.id && (_jsx("button", { className: styles.secondaryHeaderBtn, title: "Keep this profile, merge others into it", onClick: () => {
367
+ const otherId = selectedGroup.profiles.find((candidate) => candidate.id !== profile.id)?.id;
368
+ if (otherId)
369
+ setAiProfileMergeState({ keepId: profile.id, mergeId: otherId });
370
+ }, children: "Keep this" })), aiProfileArchiveError?.profileId === profile.id && (_jsxs("div", { className: styles.aiProfileInlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 12 }), _jsx("span", { children: aiProfileArchiveError.message })] })), _jsx("button", { className: styles.aiProfileDangerTextButton, title: "Remove this profile from the active roster", onClick: () => { void handleArchive(profile); }, children: "Remove from roster" })] }, profile.id))) }), aiProfileMergeState && selectedGroup.profiles.some((profile) => profile.id === aiProfileMergeState.keepId) && (_jsxs("div", { className: styles.aiProfileMergeActions, children: [_jsx("button", { className: styles.dangerBtn, onClick: () => { void handleMerge(aiProfileMergeState.keepId, aiProfileMergeState.mergeId); }, children: "Merge duplicates" }), _jsx("button", { className: styles.secondaryHeaderBtn, onClick: () => setAiProfileMergeState(null), children: "Cancel" })] }))] })) : (_jsxs("div", { className: styles.aiProfileIdFooter, children: [_jsx("div", { className: styles.aiProfileIdFooterValue, children: selectedGroup.primaryProfile.id }), aiProfileArchiveError?.profileId === selectedGroup.primaryProfile.id && (_jsxs("div", { className: styles.aiProfileInlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 12 }), _jsx("span", { children: aiProfileArchiveError.message })] }))] }))] }) }))] })), aiProfileMergeStatus && (_jsxs("div", { className: `${aiProfileMergeStatus.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [aiProfileMergeStatus.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), aiProfileMergeStatus.message] }))] }) }) }), _jsx(AvatarImageManagerModal, { isOpen: !!avatarEditorProfile, theme: "dark", title: "Edit AI Profile Photo", currentImageUrl: avatarEditorProfile?.avatarUrl || '', editorImageUrl: avatarEditorProfile?.avatarSourceUrl || '', fallbackInitial: (avatarEditorProfile?.name || 'AI').charAt(0).toUpperCase(), accept: "image/png,image/jpeg,image/webp,image/gif", busy: aiAvatarBusy, hasPendingImage: false, canRemove: !!avatarEditorProfile?.avatarUrl, error: aiAvatarError, notice: null, onClose: () => {
371
+ if (aiAvatarBusy)
372
+ return;
373
+ setAvatarEditorProfileId(null);
374
+ setAiAvatarError(null);
375
+ }, onApplyImage: handleAiProfileAvatarSelected, onRemoveImage: handleAiProfileAvatarRemove })] }));
131
376
  }