@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
@@ -0,0 +1,70 @@
1
+ @font-face {
2
+ font-family: "Russo One";
3
+ src: url("../assets/fonts/RussoOne-Regular.woff2") format("woff2");
4
+ font-weight: 400;
5
+ font-style: normal;
6
+ font-display: swap;
7
+ }
8
+
9
+ @font-face {
10
+ font-family: "Courier Prime";
11
+ src: url("../assets/fonts/CourierPrime-Regular.woff2") format("woff2");
12
+ font-weight: 400;
13
+ font-style: normal;
14
+ font-display: swap;
15
+ }
16
+
17
+ @font-face {
18
+ font-family: "Courier Prime";
19
+ src: url("../assets/fonts/CourierPrime-Italic.woff2") format("woff2");
20
+ font-weight: 400;
21
+ font-style: italic;
22
+ font-display: swap;
23
+ }
24
+
25
+ @font-face {
26
+ font-family: "Courier Prime";
27
+ src: url("../assets/fonts/CourierPrime-Bold.woff2") format("woff2");
28
+ font-weight: 700;
29
+ font-style: normal;
30
+ font-display: swap;
31
+ }
32
+
33
+ @font-face {
34
+ font-family: "Courier Prime";
35
+ src: url("../assets/fonts/CourierPrime-BoldItalic.woff2") format("woff2");
36
+ font-weight: 700;
37
+ font-style: italic;
38
+ font-display: swap;
39
+ }
40
+
41
+ @font-face {
42
+ font-family: "Special Elite";
43
+ src: url("../assets/fonts/SpecialElite-Regular.woff2") format("woff2");
44
+ font-weight: 400;
45
+ font-style: normal;
46
+ font-display: swap;
47
+ }
48
+
49
+ @font-face {
50
+ font-family: "Noir";
51
+ src: url("../assets/fonts/Noir_regular.woff2") format("woff2");
52
+ font-weight: 400;
53
+ font-style: normal;
54
+ font-display: swap;
55
+ }
56
+
57
+ @font-face {
58
+ font-family: "Noir";
59
+ src: url("../assets/fonts/Noir_medium.woff2") format("woff2");
60
+ font-weight: 500;
61
+ font-style: normal;
62
+ font-display: swap;
63
+ }
64
+
65
+ :root {
66
+ --font-brand: "Russo One", sans-serif;
67
+ --font-typewriter: "Courier Prime", "Courier New", Courier, monospace;
68
+ --font-stamped: "Special Elite", "Courier Prime", "Courier New", Courier, monospace;
69
+ --font-noir: "Noir", var(--font-sans, ui-sans-serif, system-ui, sans-serif);
70
+ }
@@ -1,10 +1,15 @@
1
1
  import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot } from './workspaceSyncModel.js';
2
2
  import type { AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
3
+ import type { AiProfileSeatScope } from '../shared/aiProfileSeatScope.js';
3
4
  export declare function normalizeAiProfileSyncPayload(raw: unknown, workspaceId: string, normalizeSurfaceType: (value: unknown, context: {
4
5
  source: string;
5
6
  profileId?: string;
6
7
  workspaceId?: string;
7
- }) => AiProfileSurfaceType | null, source: string): WorkspaceSyncAiProfileSnapshot | null;
8
+ }) => AiProfileSurfaceType | null, normalizeSeatScope: (value: unknown, context: {
9
+ source: string;
10
+ profileId?: string;
11
+ workspaceId?: string;
12
+ }) => AiProfileSeatScope | null, source: string): WorkspaceSyncAiProfileSnapshot | null;
8
13
  export declare function normalizeDocumentReviewSessionSyncPayload(raw: unknown, sourceWatermark?: string): WorkspaceSyncDocumentReviewSessionSnapshot | null;
9
14
  export declare function normalizeDocumentReviewCommentSyncPayload(raw: unknown, options?: {
10
15
  sessionId?: string;
@@ -1,11 +1,9 @@
1
+ import { hasOwnSyncField, normalizeSyncNullableString, normalizeSyncOptionalString, readSyncNullableIdField, readSyncNullableIsoStringField, readSyncNullableStringField, readSyncSameOriginAssetUrlField, } from './syncFieldSemantics.js';
1
2
  function toOptionalTrimmedString(value) {
2
- if (typeof value !== 'string')
3
- return undefined;
4
- const normalized = value.trim();
5
- return normalized.length > 0 ? normalized : undefined;
3
+ return normalizeSyncOptionalString(value);
6
4
  }
7
5
  function toNullableTrimmedString(value) {
8
- return toOptionalTrimmedString(value) ?? null;
6
+ return normalizeSyncNullableString(value);
9
7
  }
10
8
  function toOptionalPositiveInteger(value) {
11
9
  if (value === undefined || value === null || value === '')
@@ -23,7 +21,7 @@ function toOptionalNonNegativeInteger(value) {
23
21
  return 0;
24
22
  return Math.max(0, Math.floor(normalized));
25
23
  }
26
- export function normalizeAiProfileSyncPayload(raw, workspaceId, normalizeSurfaceType, source) {
24
+ export function normalizeAiProfileSyncPayload(raw, workspaceId, normalizeSurfaceType, normalizeSeatScope, source) {
27
25
  if (!raw || typeof raw !== 'object' || Array.isArray(raw))
28
26
  return null;
29
27
  const profile = raw;
@@ -39,17 +37,25 @@ export function normalizeAiProfileSyncPayload(raw, workspaceId, normalizeSurface
39
37
  username: String(profile.username || '').trim(),
40
38
  icon: String(profile.icon || 'Bot').trim() || 'Bot',
41
39
  color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
42
- description: toNullableTrimmedString(profile.description),
43
- role: toNullableTrimmedString(profile.role),
44
- provider: toNullableTrimmedString(profile.provider),
45
- model: toNullableTrimmedString(profile.model),
40
+ ...(hasOwnSyncField(profile, 'avatarUrl') ? { avatarUrl: readSyncSameOriginAssetUrlField(profile, 'avatarUrl') ?? null } : {}),
41
+ ...(hasOwnSyncField(profile, 'avatarSourceUrl') ? { avatarSourceUrl: readSyncSameOriginAssetUrlField(profile, 'avatarSourceUrl') ?? null } : {}),
42
+ description: readSyncNullableStringField(profile, 'description') ?? null,
43
+ role: readSyncNullableStringField(profile, 'role') ?? null,
44
+ provider: readSyncNullableStringField(profile, 'provider') ?? null,
45
+ model: readSyncNullableStringField(profile, 'model') ?? null,
46
46
  surfaceType: normalizeSurfaceType(profile.surfaceType, { source, profileId, workspaceId: profileWorkspaceId }),
47
+ seatScope: normalizeSeatScope(profile.seatScope, { source, profileId, workspaceId: profileWorkspaceId }),
47
48
  providerMetadata: profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
48
49
  ? profile.providerMetadata
49
50
  : null,
51
+ archivedAt: readSyncNullableIsoStringField(profile, 'archivedAt') ?? null,
52
+ archivedBy: readSyncNullableIdField(profile, 'archivedBy') ?? null,
53
+ archivedReason: readSyncNullableStringField(profile, 'archivedReason') ?? null,
54
+ mergedIntoProfileId: readSyncNullableIdField(profile, 'mergedIntoProfileId') ?? null,
55
+ rosterStateUpdatedAt: readSyncNullableIsoStringField(profile, 'rosterStateUpdatedAt') ?? null,
50
56
  createdAt: String(profile.createdAt || '').trim(),
51
57
  updatedAt: String(profile.updatedAt || profile.createdAt || '').trim(),
52
- lastActiveAt: toNullableTrimmedString(profile.lastActiveAt),
58
+ lastActiveAt: readSyncNullableIsoStringField(profile, 'lastActiveAt') ?? toNullableTrimmedString(profile.lastActiveAt),
53
59
  };
54
60
  }
55
61
  export function normalizeDocumentReviewSessionSyncPayload(raw, sourceWatermark) {
@@ -6,6 +6,11 @@ describe('collaborationSyncPayload', () => {
6
6
  return null;
7
7
  return String(value).trim();
8
8
  };
9
+ const normalizeSeatScope = (value) => {
10
+ if (value === undefined || value === null || value === '')
11
+ return null;
12
+ return String(value).trim();
13
+ };
9
14
  it('normalizes ai profile sync payloads with workspace and surface type', () => {
10
15
  const profile = normalizeAiProfileSyncPayload({
11
16
  id: 'profile-1',
@@ -20,11 +25,12 @@ describe('collaborationSyncPayload', () => {
20
25
  provider: ' OpenAI ',
21
26
  model: ' gpt-5.4 ',
22
27
  surfaceType: 'agent',
28
+ seatScope: 'cloud_metered',
23
29
  providerMetadata: { foo: 'bar' },
24
30
  createdAt: '2026-04-10T10:00:00.000Z',
25
31
  updatedAt: '2026-04-10T11:00:00.000Z',
26
32
  lastActiveAt: '2026-04-10T12:00:00.000Z',
27
- }, 'workspace-a', normalizeSurfaceType, 'test.ai');
33
+ }, 'workspace-a', normalizeSurfaceType, normalizeSeatScope, 'test.ai');
28
34
  expect(profile).toEqual({
29
35
  id: 'profile-1',
30
36
  workspaceId: 'workspace-a',
@@ -38,7 +44,13 @@ describe('collaborationSyncPayload', () => {
38
44
  provider: 'OpenAI',
39
45
  model: 'gpt-5.4',
40
46
  surfaceType: 'agent',
47
+ seatScope: 'cloud_metered',
41
48
  providerMetadata: { foo: 'bar' },
49
+ archivedAt: null,
50
+ archivedBy: null,
51
+ archivedReason: null,
52
+ mergedIntoProfileId: null,
53
+ rosterStateUpdatedAt: null,
42
54
  createdAt: '2026-04-10T10:00:00.000Z',
43
55
  updatedAt: '2026-04-10T11:00:00.000Z',
44
56
  lastActiveAt: '2026-04-10T12:00:00.000Z',
@@ -28,12 +28,8 @@ export declare function resolveCanonicalAssetSyncLinkState(deps: ContentSyncStat
28
28
  effectiveUpdatedAt: string;
29
29
  };
30
30
  export declare function getCanonicalSyncDocumentMetadata(deps: ContentSyncStateDeps, relativePath: string, workspaceId: string): Omit<DocumentSyncPayload, 'path' | 'updatedAt' | 'content'>;
31
- export declare function hasDocumentSyncMetadataDrift(deps: ContentSyncStateDeps, workspaceId: string, doc: DocumentSyncPayload, options?: {
32
- allowReferenceNumberReassignment?: boolean;
33
- }): boolean;
34
- export declare function hasBinaryAssetSyncMetadataDrift(deps: ContentSyncStateDeps, workspaceId: string, asset: BinaryAssetSyncPayload, options?: {
35
- allowReferenceNumberReassignment?: boolean;
36
- }): boolean;
31
+ export declare function hasDocumentSyncMetadataDrift(deps: ContentSyncStateDeps, workspaceId: string, doc: DocumentSyncPayload): boolean;
32
+ export declare function hasBinaryAssetSyncMetadataDrift(deps: ContentSyncStateDeps, workspaceId: string, asset: BinaryAssetSyncPayload): boolean;
37
33
  export declare function reconcileWorkspaceSyncLinks(deps: ContentSyncStateDeps, input: {
38
34
  workspaceId: string;
39
35
  assetId: string;
@@ -64,10 +60,10 @@ export declare function createContentSyncState(deps: ContentSyncStateDeps): {
64
60
  effectiveUpdatedAt: string;
65
61
  };
66
62
  getCanonicalSyncDocumentMetadata: (relativePath: string, workspaceId: string) => Omit<DocumentSyncPayload, "path" | "updatedAt" | "content">;
67
- hasDocumentSyncMetadataDrift: (workspaceId: string, doc: DocumentSyncPayload, options?: {
63
+ hasDocumentSyncMetadataDrift: (workspaceId: string, doc: DocumentSyncPayload, _options?: {
68
64
  allowReferenceNumberReassignment?: boolean;
69
65
  }) => boolean;
70
- hasBinaryAssetSyncMetadataDrift: (workspaceId: string, asset: BinaryAssetSyncPayload, options?: {
66
+ hasBinaryAssetSyncMetadataDrift: (workspaceId: string, asset: BinaryAssetSyncPayload, _options?: {
71
67
  allowReferenceNumberReassignment?: boolean;
72
68
  }) => boolean;
73
69
  reconcileWorkspaceSyncLinks: (input: {
@@ -38,7 +38,7 @@ export function getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId
38
38
  linkRole: primaryLink?.role === 'reference' ? 'reference' : 'attachment'
39
39
  };
40
40
  }
41
- export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options) {
41
+ export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc) {
42
42
  if (!deps.workspaceAssetStore)
43
43
  return false;
44
44
  const existingAsset = deps.workspaceAssetStore.getByStorageKey(doc.path, workspaceId);
@@ -67,20 +67,10 @@ export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options) {
67
67
  ? doc.taskId.trim()
68
68
  : null;
69
69
  const normalizedIncomingLinkRole = doc.linkRole === 'reference' ? 'reference' : 'attachment';
70
- const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
71
70
  const referenceNumberMismatch = Boolean(existingAsset.referenceNumber
72
71
  && normalizedIncomingReference
73
72
  && existingAsset.referenceNumber !== normalizedIncomingReference);
74
- if (referenceNumberMismatch) {
75
- deps.recordSyncDiagnostic(workspaceId, 'apply', 'Detected document reference number mismatch during normal sync.', {
76
- source: 'documents.referenceNumber.mismatch',
77
- path: doc.path,
78
- assetId: existingAsset.assetId,
79
- existingReferenceNumber: existingAsset.referenceNumber,
80
- incomingReferenceNumber: normalizedIncomingReference
81
- });
82
- }
83
- return (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
73
+ return referenceNumberMismatch
84
74
  || existingAsset.version !== normalizedIncomingVersion
85
75
  || (existingAsset.documentId || null) !== normalizedIncomingDocumentId
86
76
  || (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
@@ -89,7 +79,7 @@ export function hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options) {
89
79
  || (primaryLink?.displayName || null) !== normalizedIncomingCaption
90
80
  || ((primaryLink?.role === 'reference' ? 'reference' : 'attachment')) !== normalizedIncomingLinkRole;
91
81
  }
92
- export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, options) {
82
+ export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset) {
93
83
  if (!deps.workspaceAssetStore)
94
84
  return false;
95
85
  const existingAsset = deps.workspaceAssetStore.getByStorageKey(asset.path, workspaceId);
@@ -116,20 +106,10 @@ export function hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, option
116
106
  : asset.kind === 'image'
117
107
  ? 'image'
118
108
  : 'attachment';
119
- const allowReferenceNumberReassignment = options?.allowReferenceNumberReassignment === true;
120
109
  const referenceNumberMismatch = Boolean(existingAsset.referenceNumber
121
110
  && normalizedIncomingReference
122
111
  && existingAsset.referenceNumber !== normalizedIncomingReference);
123
- if (referenceNumberMismatch) {
124
- deps.recordSyncDiagnostic(workspaceId, 'apply', 'Detected asset reference number mismatch during normal sync.', {
125
- source: 'assets.referenceNumber.mismatch',
126
- path: asset.path,
127
- assetId: existingAsset.assetId,
128
- existingReferenceNumber: existingAsset.referenceNumber,
129
- incomingReferenceNumber: normalizedIncomingReference
130
- });
131
- }
132
- return (allowReferenceNumberReassignment && existingAsset.referenceNumber !== normalizedIncomingReference)
112
+ return referenceNumberMismatch
133
113
  || (existingAsset.logicalName || null) !== normalizedIncomingLogicalName
134
114
  || (existingAsset.originalFilename || null) !== normalizedIncomingFilename
135
115
  || (primaryLink?.taskId || null) !== normalizedIncomingTaskId
@@ -175,7 +155,8 @@ export function reconcileWorkspaceSyncLinks(deps, input) {
175
155
  deps.workspaceAssetStore.deleteLink({
176
156
  workspaceId,
177
157
  assetId,
178
- taskId: link.taskId,
158
+ targetType: link.targetType,
159
+ targetId: link.targetId,
179
160
  role: link.role
180
161
  });
181
162
  }
@@ -205,26 +186,9 @@ export async function listWorkspaceSyncDocumentsSnapshot(deps, basePath, workspa
205
186
  const root = path.resolve(basePath);
206
187
  const objectStorageClient = deps.getObjectStorageClient();
207
188
  const candidatePaths = new Set();
208
- const docsRoot = path.join(root, 'docs');
209
- const collectMarkdownPaths = (directory) => {
210
- if (!fs.existsSync(directory))
211
- return;
212
- const entries = fs.readdirSync(directory, { withFileTypes: true });
213
- for (const entry of entries) {
214
- const absolutePath = path.join(directory, entry.name);
215
- if (entry.isDirectory()) {
216
- collectMarkdownPaths(absolutePath);
217
- continue;
218
- }
219
- if (!entry.isFile())
220
- continue;
221
- const relativeFromRoot = path.relative(root, absolutePath).replace(/\\/g, '/');
222
- const normalized = deps.normalizeWorkspaceSyncDocumentPath(relativeFromRoot);
223
- if (normalized)
224
- candidatePaths.add(normalized);
225
- }
226
- };
227
- collectMarkdownPaths(docsRoot);
189
+ // Shared document sync is canonical-document-only. Raw .taskforce/docs files
190
+ // are intentionally excluded because they are not workspace-scoped and must
191
+ // not re-enter cross-runtime shared document sync.
228
192
  if (deps.workspaceAssetStore) {
229
193
  const canonicalAssets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
230
194
  includeDeleted: false,
@@ -345,28 +309,9 @@ export async function listWorkspaceSyncDocumentsSnapshot(deps, basePath, workspa
345
309
  return docs;
346
310
  }
347
311
  export async function listWorkspaceSyncDocumentIndex(deps, basePath, workspaceId = 'default') {
348
- const root = path.resolve(basePath);
349
312
  const candidatePaths = new Set();
350
- const docsRoot = path.join(root, 'docs');
351
- const collectMarkdownPaths = (directory) => {
352
- if (!fs.existsSync(directory))
353
- return;
354
- const entries = fs.readdirSync(directory, { withFileTypes: true });
355
- for (const entry of entries) {
356
- const absolutePath = path.join(directory, entry.name);
357
- if (entry.isDirectory()) {
358
- collectMarkdownPaths(absolutePath);
359
- continue;
360
- }
361
- if (!entry.isFile())
362
- continue;
363
- const relativeFromRoot = path.relative(root, absolutePath).replace(/\\/g, '/');
364
- const normalized = deps.normalizeWorkspaceSyncDocumentPath(relativeFromRoot);
365
- if (normalized)
366
- candidatePaths.add(normalized);
367
- }
368
- };
369
- collectMarkdownPaths(docsRoot);
313
+ // Keep the document index aligned with the snapshot invariant above: only
314
+ // canonical workspace document assets may participate in shared document sync.
370
315
  if (deps.workspaceAssetStore) {
371
316
  const canonicalAssets = deps.workspaceAssetStore.listAllByWorkspace(workspaceId, {
372
317
  includeDeleted: false,
@@ -926,7 +871,8 @@ export function deleteWorkspaceSyncBinaryAsset(deps, basePath, assetPath, worksp
926
871
  deps.workspaceAssetStore.deleteLink({
927
872
  workspaceId,
928
873
  assetId: existing.assetId,
929
- taskId: link.taskId,
874
+ targetType: link.targetType,
875
+ targetId: link.targetId,
930
876
  role: link.role
931
877
  });
932
878
  }
@@ -976,7 +922,8 @@ export function deleteWorkspaceSyncDocument(deps, basePath, docPath, workspaceId
976
922
  deps.workspaceAssetStore.deleteLink({
977
923
  workspaceId,
978
924
  assetId: existing.assetId,
979
- taskId: link.taskId,
925
+ targetType: link.targetType,
926
+ targetId: link.targetId,
980
927
  role: link.role
981
928
  });
982
929
  }
@@ -996,8 +943,8 @@ export function createContentSyncState(deps) {
996
943
  return {
997
944
  resolveCanonicalAssetSyncLinkState: (asset, workspaceId) => resolveCanonicalAssetSyncLinkState(deps, asset, workspaceId),
998
945
  getCanonicalSyncDocumentMetadata: (relativePath, workspaceId) => getCanonicalSyncDocumentMetadata(deps, relativePath, workspaceId),
999
- hasDocumentSyncMetadataDrift: (workspaceId, doc, options) => hasDocumentSyncMetadataDrift(deps, workspaceId, doc, options),
1000
- hasBinaryAssetSyncMetadataDrift: (workspaceId, asset, options) => hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset, options),
946
+ hasDocumentSyncMetadataDrift: (workspaceId, doc, _options) => hasDocumentSyncMetadataDrift(deps, workspaceId, doc),
947
+ hasBinaryAssetSyncMetadataDrift: (workspaceId, asset, _options) => hasBinaryAssetSyncMetadataDrift(deps, workspaceId, asset),
1001
948
  reconcileWorkspaceSyncLinks: (input) => reconcileWorkspaceSyncLinks(deps, input),
1002
949
  listWorkspaceSyncDocumentDeletesSnapshot: (workspaceId = 'default') => listWorkspaceSyncDocumentDeletesSnapshot(deps, workspaceId),
1003
950
  listWorkspaceSyncDocumentIndex: (basePath, workspaceId = 'default') => listWorkspaceSyncDocumentIndex(deps, basePath, workspaceId),
@@ -2,6 +2,7 @@ import type { TaskforceCore, TaskforceSyncCapable } from '../core/Taskforce.js';
2
2
  import type { WorkspaceSyncAiProfileSnapshot, WorkspaceSyncAnnotatedAttachmentSessionSnapshot, WorkspaceSyncChange, WorkspaceSyncDocumentReviewCommentSnapshot, WorkspaceSyncDocumentReviewSessionSnapshot, WorkspaceSyncInitiativeSnapshot, WorkspaceSyncTaskEventSnapshot, WorkspaceSyncTaskSnapshot, WorkspaceSyncWorkspaceMemberSnapshot, WorkspaceSyncWorkstreamSnapshot } from './workspaceSyncModel.js';
3
3
  import type { BinaryAssetSyncPayload, DocumentSyncPayload } from './contentSyncPayload.js';
4
4
  import type { AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
5
+ import type { AiProfileSeatScope } from '../shared/aiProfileSeatScope.js';
5
6
  type SyncCore = TaskforceCore & TaskforceSyncCapable;
6
7
  type SyncRouteError = Error & {
7
8
  code?: string;
@@ -18,6 +19,11 @@ type NormalizeIncomingAiProfileSurfaceType = (value: unknown, context: {
18
19
  profileId?: string;
19
20
  workspaceId?: string;
20
21
  }) => AiProfileSurfaceType | null;
22
+ type NormalizeIncomingAiProfileSeatScope = (value: unknown, context: {
23
+ source: string;
24
+ profileId?: string;
25
+ workspaceId?: string;
26
+ }) => AiProfileSeatScope | null;
21
27
  type NormalizeTaskEventSyncPayload = (raw: unknown, workspaceId: string, sourceWatermark?: string) => WorkspaceSyncTaskEventSnapshot | null;
22
28
  export interface WorkspaceSyncIncomingTaskMutation {
23
29
  archived: boolean;
@@ -68,6 +74,7 @@ export declare function classifyWorkspaceSyncChanges(input: {
68
74
  normalizeWorkspaceSyncDocumentPath: (rawPath: unknown) => string | null;
69
75
  normalizeTaskAssetPath: (rawPath: unknown) => string | null;
70
76
  normalizeIncomingAiProfileSurfaceType: NormalizeIncomingAiProfileSurfaceType;
77
+ normalizeIncomingAiProfileSeatScope: NormalizeIncomingAiProfileSeatScope;
71
78
  normalizeTaskEventSyncPayload: NormalizeTaskEventSyncPayload;
72
79
  recordSyncDiagnostic: RecordSyncDiagnostic;
73
80
  }): WorkspaceSyncIncomingApplyModel;
@@ -83,7 +83,7 @@ function recordTaskReferenceNumberMismatch(input) {
83
83
  return true;
84
84
  }
85
85
  export function classifyWorkspaceSyncChanges(input) {
86
- const { workspaceId, incomingChanges, normalizeWorkspaceSyncDocumentPath, normalizeTaskAssetPath, normalizeIncomingAiProfileSurfaceType, normalizeTaskEventSyncPayload, recordSyncDiagnostic, } = input;
86
+ const { workspaceId, incomingChanges, normalizeWorkspaceSyncDocumentPath, normalizeTaskAssetPath, normalizeIncomingAiProfileSurfaceType, normalizeIncomingAiProfileSeatScope, normalizeTaskEventSyncPayload, recordSyncDiagnostic, } = input;
87
87
  const model = createIncomingApplyModel();
88
88
  for (const change of incomingChanges) {
89
89
  const raw = change;
@@ -111,7 +111,7 @@ export function classifyWorkspaceSyncChanges(input) {
111
111
  continue;
112
112
  }
113
113
  if (op === 'ai-profile-upsert') {
114
- const profile = normalizeAiProfileSyncPayload(raw.profile, workspaceId, normalizeIncomingAiProfileSurfaceType, 'apply.aiProfileUpsert');
114
+ const profile = normalizeAiProfileSyncPayload(raw.profile, workspaceId, normalizeIncomingAiProfileSurfaceType, normalizeIncomingAiProfileSeatScope, 'apply.aiProfileUpsert');
115
115
  const profileId = String(profile?.id || '').trim();
116
116
  if (profileId && profile) {
117
117
  const profileWorkspaceId = String(profile.workspaceId || '').trim();
@@ -382,12 +382,20 @@ export function applyAiProfileAndMemberSyncChanges(input) {
382
382
  username: profile.username,
383
383
  icon: profile.icon,
384
384
  color: profile.color,
385
+ ...(Object.prototype.hasOwnProperty.call(profile, 'avatarUrl') ? { avatarUrl: profile.avatarUrl ?? null } : {}),
386
+ ...(Object.prototype.hasOwnProperty.call(profile, 'avatarSourceUrl') ? { avatarSourceUrl: profile.avatarSourceUrl ?? null } : {}),
385
387
  description: profile.description ?? null,
386
388
  role: profile.role ?? null,
387
389
  provider: profile.provider ?? null,
388
390
  model: profile.model ?? null,
389
391
  surfaceType: profile.surfaceType,
392
+ seatScope: profile.seatScope ?? null,
390
393
  providerMetadata: profile.providerMetadata,
394
+ archivedAt: profile.archivedAt ?? null,
395
+ archivedBy: profile.archivedBy ?? null,
396
+ archivedReason: profile.archivedReason ?? null,
397
+ mergedIntoProfileId: profile.mergedIntoProfileId ?? null,
398
+ rosterStateUpdatedAt: profile.rosterStateUpdatedAt ?? null,
391
399
  createdAt: profile.createdAt,
392
400
  updatedAt: profile.updatedAt,
393
401
  lastActiveAt: profile.lastActiveAt
@@ -434,14 +442,18 @@ export async function applyContentSyncChanges(input) {
434
442
  for (const doc of incomingDocuments) {
435
443
  try {
436
444
  const existingDoc = core.getDocumentWatermark(workspaceId, doc.path);
437
- const existingDocFallback = getExistingDocument ? getExistingDocument(doc.path, workspaceId) : null;
438
- const localDocumentPath = path.resolve(basePath, doc.path);
439
- const localDocumentHash = !hasObjectStorageClient() && fs.existsSync(localDocumentPath)
440
- ? createHash('sha256').update(fs.readFileSync(localDocumentPath, 'utf8')).digest('hex')
441
- : '';
445
+ const existingDocFallback = (!existingDoc && getExistingDocument)
446
+ ? getExistingDocument(doc.path, workspaceId)
447
+ : null;
442
448
  const incomingHash = createHash('sha256').update(String(doc.content || '')).digest('hex');
443
449
  const existingUpdatedAt = String(existingDoc?.updatedAt || existingDocFallback?.updatedAt || '').trim();
444
- const existingHash = String(existingDoc?.contentHash || localDocumentHash || existingDocFallback?.contentHash || '').trim();
450
+ let existingHash = String(existingDoc?.contentHash || existingDocFallback?.contentHash || '').trim();
451
+ if (!existingHash && !hasObjectStorageClient()) {
452
+ const localDocumentPath = path.resolve(basePath, doc.path);
453
+ if (fs.existsSync(localDocumentPath)) {
454
+ existingHash = createHash('sha256').update(fs.readFileSync(localDocumentPath, 'utf8')).digest('hex');
455
+ }
456
+ }
445
457
  const compare = resolveSyncConflictOrdering(doc.updatedAt, existingUpdatedAt, incomingHash, existingHash);
446
458
  const metadataDrift = hasDocumentSyncMetadataDrift(workspaceId, doc, options);
447
459
  if (compare < 0)
@@ -450,7 +462,9 @@ export async function applyContentSyncChanges(input) {
450
462
  continue;
451
463
  await writeWorkspaceSyncDocument(basePath, doc, workspaceId, {
452
464
  ...options,
453
- allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true || existingHash !== incomingHash
465
+ allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true
466
+ || existingHash !== incomingHash
467
+ || metadataDrift
454
468
  });
455
469
  }
456
470
  catch (error) {
@@ -481,11 +495,11 @@ export async function applyContentSyncChanges(input) {
481
495
  const existing = getExistingBinaryAsset(asset.path, workspaceId);
482
496
  const incomingHash = createHash('sha256').update(Buffer.from(String(asset.contentBase64 || ''), 'base64')).digest('hex');
483
497
  const localFileMissing = !hasObjectStorageClient() && !fs.existsSync(localAssetPath);
484
- const localAssetHash = !hasObjectStorageClient() && !localFileMissing
485
- ? createHash('sha256').update(fs.readFileSync(localAssetPath)).digest('hex')
486
- : '';
487
498
  const existingUpdatedAt = String(existing?.updatedAt || '').trim();
488
- const existingHash = String(localAssetHash || existing?.contentSha256 || '').trim();
499
+ let existingHash = String(existing?.contentSha256 || '').trim();
500
+ if (!existingHash && !hasObjectStorageClient() && !localFileMissing) {
501
+ existingHash = createHash('sha256').update(fs.readFileSync(localAssetPath)).digest('hex');
502
+ }
489
503
  const compare = resolveSyncConflictOrdering(asset.updatedAt, existingUpdatedAt, incomingHash, existingHash);
490
504
  const metadataDrift = hasBinaryAssetSyncMetadataDrift(workspaceId, asset, options);
491
505
  if (compare < 0)
@@ -497,6 +511,7 @@ export async function applyContentSyncChanges(input) {
497
511
  allowReferenceNumberReassignment: options?.allowReferenceNumberReassignment === true
498
512
  || existingHash !== incomingHash
499
513
  || localFileMissing
514
+ || metadataDrift
500
515
  });
501
516
  }
502
517
  catch (error) {
@@ -1,4 +1,6 @@
1
- import { describe, expect, it, vi } from 'vitest';
1
+ import fs from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { describe, expect, it, vi, afterEach } from 'vitest';
2
4
  import { applyCollaborationSyncChanges, applyContentSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from './syncApplyHandlers.js';
3
5
  function createCoreFixture(overrides = {}) {
4
6
  return {
@@ -29,6 +31,9 @@ function createCoreFixture(overrides = {}) {
29
31
  };
30
32
  }
31
33
  describe('syncApplyHandlers', () => {
34
+ afterEach(() => {
35
+ vi.restoreAllMocks();
36
+ });
32
37
  it('classifies task and workstream changes, then prepares and applies the task snapshot pipeline', () => {
33
38
  const diagnostics = [];
34
39
  const workspaceId = 'workspace-1';
@@ -87,6 +92,7 @@ describe('syncApplyHandlers', () => {
87
92
  normalizeWorkspaceSyncDocumentPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
88
93
  normalizeTaskAssetPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
89
94
  normalizeIncomingAiProfileSurfaceType: () => null,
95
+ normalizeIncomingAiProfileSeatScope: () => null,
90
96
  normalizeTaskEventSyncPayload: () => null,
91
97
  recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
92
98
  diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
@@ -231,7 +237,7 @@ describe('syncApplyHandlers', () => {
231
237
  expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(1, 'workspace-1', ['docs/spec.md', 'assets/proof.png'], 'upsert');
232
238
  expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(2, 'workspace-1', ['docs/stale.md', 'assets/stale.png'], 'delete');
233
239
  });
234
- it('only allows reference reassignment for content writes when the content changed or repair is explicit', async () => {
240
+ it('allows reference reassignment when metadata drift requires normal sync convergence', async () => {
235
241
  const core = createCoreFixture({
236
242
  getDocumentWatermark: vi.fn().mockReturnValue(null),
237
243
  });
@@ -280,9 +286,70 @@ describe('syncApplyHandlers', () => {
280
286
  }),
281
287
  recordSyncDiagnostic: vi.fn(),
282
288
  });
283
- expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: false });
289
+ expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: true });
284
290
  expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png' }), 'workspace-1', { allowReferenceNumberReassignment: true });
285
291
  });
292
+ it('reuses persisted content hashes instead of re-reading unchanged local files', async () => {
293
+ const existingDocContent = '# Spec';
294
+ const existingAssetBytes = Buffer.from('png-bytes');
295
+ const core = createCoreFixture({
296
+ getDocumentWatermark: vi.fn().mockReturnValue({
297
+ updatedAt: '2026-04-10T10:00:00.000Z',
298
+ contentHash: createHash('sha256').update(existingDocContent).digest('hex'),
299
+ }),
300
+ });
301
+ const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
302
+ const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
303
+ const readSpy = vi.spyOn(fs, 'readFileSync').mockImplementation(((targetPath) => {
304
+ const value = String(targetPath);
305
+ if (value.endsWith('spec.md'))
306
+ return existingDocContent;
307
+ return existingAssetBytes;
308
+ }));
309
+ await applyContentSyncChanges({
310
+ core,
311
+ workspaceId: 'workspace-1',
312
+ incomingDocuments: [
313
+ {
314
+ path: 'docs/spec.md',
315
+ updatedAt: '2026-04-10T10:00:00.000Z',
316
+ content: existingDocContent,
317
+ assetId: 'asset-doc-1',
318
+ },
319
+ ],
320
+ incomingDocumentDeletes: new Map(),
321
+ incomingAssets: [
322
+ {
323
+ path: 'assets/proof.png',
324
+ updatedAt: '2026-04-10T10:05:00.000Z',
325
+ contentBase64: existingAssetBytes.toString('base64'),
326
+ assetId: 'asset-image-1',
327
+ kind: 'image',
328
+ mimeType: 'image/png',
329
+ },
330
+ ],
331
+ incomingAssetDeletes: new Map(),
332
+ basePath: '/tmp/taskforce-sync-content',
333
+ hasObjectStorageClient: () => true,
334
+ hasDocumentSyncMetadataDrift: () => false,
335
+ hasBinaryAssetSyncMetadataDrift: () => false,
336
+ getExistingDocument: () => {
337
+ throw new Error('document fallback should not be used when persisted metadata exists');
338
+ },
339
+ writeWorkspaceSyncDocument,
340
+ deleteWorkspaceSyncDocument: vi.fn(),
341
+ writeWorkspaceSyncBinaryAsset,
342
+ deleteWorkspaceSyncBinaryAsset: vi.fn(),
343
+ getExistingBinaryAsset: () => ({
344
+ updatedAt: '2026-04-10T10:05:00.000Z',
345
+ contentSha256: createHash('sha256').update(existingAssetBytes).digest('hex'),
346
+ }),
347
+ recordSyncDiagnostic: vi.fn(),
348
+ });
349
+ expect(readSpy).not.toHaveBeenCalled();
350
+ expect(writeWorkspaceSyncDocument).not.toHaveBeenCalled();
351
+ expect(writeWorkspaceSyncBinaryAsset).not.toHaveBeenCalled();
352
+ });
286
353
  it('applies collaboration upserts and deletes through the collaboration handlers', () => {
287
354
  const core = createCoreFixture();
288
355
  applyCollaborationSyncChanges({
@@ -0,0 +1,12 @@
1
+ export declare function hasOwnSyncField(source: unknown, field: string): boolean;
2
+ export declare function normalizeSyncOptionalString(value: unknown): string | undefined;
3
+ export declare function normalizeSyncNullableString(value: unknown): string | null;
4
+ export declare function normalizeSyncOptionalId(value: unknown): string | undefined;
5
+ export declare function normalizeSyncNullableId(value: unknown): string | null;
6
+ export declare function normalizeSyncNullableIsoString(value: unknown): string | null;
7
+ export declare function normalizeSyncSameOriginAssetUrl(value: unknown): string | null;
8
+ export declare function readSyncOptionalStringField(source: Record<string, unknown>, field: string): string | undefined;
9
+ export declare function readSyncNullableStringField(source: Record<string, unknown>, field: string): string | null | undefined;
10
+ export declare function readSyncNullableIdField(source: Record<string, unknown>, field: string): string | null | undefined;
11
+ export declare function readSyncNullableIsoStringField(source: Record<string, unknown>, field: string): string | null | undefined;
12
+ export declare function readSyncSameOriginAssetUrlField(source: Record<string, unknown>, field: string): string | null | undefined;