@taskforcehq/taskforce 0.3.328 → 0.3.330

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 (485) hide show
  1. package/dist/Taskforce.module.css +523 -79
  2. package/dist/TaskforceCore.js +237 -230
  3. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
  4. package/dist/cli.js +34 -0
  5. package/dist/cliCloudTargetGuard.d.ts +5 -0
  6. package/dist/cliCloudTargetGuard.js +11 -0
  7. package/dist/components/features/AiProfilesModule.js +3 -5
  8. package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
  9. package/dist/components/features/TaskSettings.d.ts +12 -1
  10. package/dist/components/features/TaskSettings.js +217 -54
  11. package/dist/components/features/TaskforceAgentsModule.d.ts +1 -2
  12. package/dist/components/features/TaskforceAgentsModule.js +586 -116
  13. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
  14. package/dist/components/features/agentConnections/AgentConnectionControl.d.ts +15 -0
  15. package/dist/components/features/agentConnections/AgentConnectionControl.js +125 -0
  16. package/dist/components/features/agentConnections/ModelConnectionsPanel.d.ts +18 -0
  17. package/dist/components/features/agentConnections/ModelConnectionsPanel.js +393 -0
  18. package/dist/components/features/agentConnections/modelConnectionsApi.d.ts +65 -0
  19. package/dist/components/features/agentConnections/modelConnectionsApi.js +129 -0
  20. package/dist/components/features/planning/PlanningModule.js +2 -5
  21. package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
  22. package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
  23. package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
  24. package/dist/components/features/planning/planningTaskArrangement.js +5 -5
  25. package/dist/components/features/taskforceAgentAssignments/AgentBehaviorAssignments.js +15 -24
  26. package/dist/components/features/taskforceAgentEditorModel.d.ts +2 -1
  27. package/dist/components/features/taskforceAgentEditorModel.js +5 -1
  28. package/dist/components/features/workflowManager/workflowManagerApi.d.ts +1 -1
  29. package/dist/components/features/workflowManager/workflowManagerApi.js +3 -0
  30. package/dist/components/task/TaskCard.d.ts +5 -0
  31. package/dist/components/task/TaskCard.js +65 -10
  32. package/dist/components/task/TaskImageReviews.js +14 -5
  33. package/dist/components/task/TaskKanban.d.ts +6 -1
  34. package/dist/components/task/TaskKanban.js +152 -6
  35. package/dist/components/task/TaskList.d.ts +5 -0
  36. package/dist/components/task/TaskList.js +3 -3
  37. package/dist/components/task/TaskWorkflowAssignmentField.js +68 -10
  38. package/dist/components/ui/PasswordInput.d.ts +6 -0
  39. package/dist/components/ui/PasswordInput.js +11 -0
  40. package/dist/components/ui/TopNoticeLayer.js +1 -1
  41. package/dist/components/views/AppShellHeader.js +32 -11
  42. package/dist/components/views/PlanComparisonPage.js +12 -0
  43. package/dist/components/views/StandaloneLayout.js +32 -55
  44. package/dist/components/views/WidgetView.js +3 -3
  45. package/dist/components/views/panels/PlanningDrawer.js +5 -12
  46. package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
  47. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +4 -3
  48. package/dist/components/views/standalone/modals/SyncStatusModal.js +33 -38
  49. package/dist/config/envSchema.js +60 -1
  50. package/dist/config/localServiceRuntimeContract.d.ts +13 -0
  51. package/dist/config/localServiceRuntimeContract.js +135 -0
  52. package/dist/core/AiProfileService.js +46 -8
  53. package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
  54. package/dist/core/DeletedTaskLifecycleService.js +37 -6
  55. package/dist/core/McpTokenService.d.ts +23 -1
  56. package/dist/core/McpTokenService.js +93 -10
  57. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
  58. package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
  59. package/dist/core/TaskChecklistCommandService.d.ts +27 -0
  60. package/dist/core/TaskChecklistCommandService.js +83 -5
  61. package/dist/core/TaskLifecycleCommandService.d.ts +3 -1
  62. package/dist/core/TaskLifecycleCommandService.js +6 -2
  63. package/dist/core/Taskforce.d.ts +94 -2
  64. package/dist/core/Taskforce.js +760 -36
  65. package/dist/core/types.d.ts +14 -0
  66. package/dist/documentReviews/commandService.d.ts +2 -1
  67. package/dist/documentReviews/commandService.js +6 -3
  68. package/dist/hooks/sync/orchestratorShared.d.ts +1 -0
  69. package/dist/hooks/sync/orchestratorShared.js +2 -0
  70. package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
  71. package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
  72. package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
  73. package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
  74. package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
  75. package/dist/hooks/sync/useLocalSyncCoordinatorSnapshot.d.ts +3 -0
  76. package/dist/hooks/sync/useLocalSyncCoordinatorSnapshot.js +85 -9
  77. package/dist/hooks/sync/useSyncStatusControls.d.ts +4 -1
  78. package/dist/hooks/sync/useSyncStatusControls.js +47 -11
  79. package/dist/hooks/tasks/taskHttpMutation.js +1 -0
  80. package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
  81. package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
  82. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
  83. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
  84. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
  85. package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
  86. package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
  87. package/dist/hooks/tasks/useTaskFormActions.js +65 -12
  88. package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
  89. package/dist/hooks/ui/useSettingsModel.js +13 -2
  90. package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
  91. package/dist/hooks/ui/useSettingsPersistence.js +74 -47
  92. package/dist/hooks/useFilterSort.d.ts +7 -3
  93. package/dist/hooks/useFilterSort.js +15 -5
  94. package/dist/hooks/useSyncOrchestrator.d.ts +3 -0
  95. package/dist/hooks/useSyncOrchestrator.js +111 -2
  96. package/dist/hooks/useTaskData.d.ts +40 -1
  97. package/dist/hooks/useTaskData.js +240 -9
  98. package/dist/hooks/useTaskMutations.d.ts +12 -2
  99. package/dist/hooks/useTaskMutations.js +266 -52
  100. package/dist/hooks/useTaskforce.d.ts +39 -9
  101. package/dist/hooks/useTaskforce.js +359 -40
  102. package/dist/hooks/useUiNotice.d.ts +4 -0
  103. package/dist/hooks/useUiNotice.js +4 -0
  104. package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
  105. package/dist/mcp/clientRegistry.d.ts +2 -2
  106. package/dist/mcp/clientRegistry.js +1 -1
  107. package/dist/mcp/documentAssetRegistrar.js +53 -41
  108. package/dist/mcp/documentHelpers.d.ts +1 -0
  109. package/dist/mcp/documentHelpers.js +7 -3
  110. package/dist/mcp/durableTaskMutationRouter.d.ts +4 -1
  111. package/dist/mcp/durableTaskMutationRouter.js +10 -0
  112. package/dist/mcp/durableTaskRelationshipMutationRouter.js +4 -0
  113. package/dist/mcp/httpProtocolRouting.d.ts +10 -0
  114. package/dist/mcp/httpProtocolRouting.js +19 -0
  115. package/dist/mcp/localCliHealth.d.ts +2 -2
  116. package/dist/mcp/localCliHealth.js +2 -2
  117. package/dist/mcp/localServiceProxy.d.ts +1 -0
  118. package/dist/mcp/localServiceProxy.js +530 -68
  119. package/dist/mcp/planningAttachmentCommandAdapter.d.ts +3 -1
  120. package/dist/mcp/planningAttachmentCommandAdapter.js +20 -2
  121. package/dist/mcp/runtime.d.ts +54 -79
  122. package/dist/mcp/runtime.js +556 -139
  123. package/dist/mcp/taskPlanningRegistrar.js +64 -42
  124. package/dist/mcp/toolCallCompatibility.js +38 -1
  125. package/dist/mcp/toolCatalog.d.ts +1 -0
  126. package/dist/mcp/toolProfiles.d.ts +2 -0
  127. package/dist/mcp/toolProfiles.js +10 -0
  128. package/dist/mcp/toolRegistry.d.ts +1 -0
  129. package/dist/mcp/toolRegistry.js +1 -0
  130. package/dist/mcp/workflowExecutionRegistrar.js +4 -4
  131. package/dist/migrations/taskSchemaMigrations.d.ts +5 -0
  132. package/dist/migrations/taskSchemaMigrations.js +427 -0
  133. package/dist/runtime/appGateDefinitions.d.ts +30 -6
  134. package/dist/runtime/appGateDefinitions.js +6 -6
  135. package/dist/runtime/appGates.d.ts +13 -13
  136. package/dist/runtime/appGates.js +36 -13
  137. package/dist/runtime/nodeAppGates.d.ts +8 -0
  138. package/dist/runtime/nodeAppGates.js +27 -0
  139. package/dist/server/cloudCollectionRead.d.ts +1 -1
  140. package/dist/server/cloudCollectionRead.js +2 -2
  141. package/dist/server/index.d.ts +6 -0
  142. package/dist/server/index.js +123 -14
  143. package/dist/server/localDatabaseIdentity.js +4 -1
  144. package/dist/server/localServiceLease.d.ts +3 -0
  145. package/dist/server/localServiceLease.js +11 -2
  146. package/dist/server/localServiceSessions.d.ts +14 -17
  147. package/dist/server/localServiceSessions.js +308 -94
  148. package/dist/server/localWorkspaceSyncServerRuntime.js +12 -7
  149. package/dist/server/rateLimit.d.ts +1 -0
  150. package/dist/server/rateLimit.js +4 -0
  151. package/dist/server/realtimeSyncWs.js +36 -12
  152. package/dist/server/routes/admin.d.ts +4 -0
  153. package/dist/server/routes/admin.js +113 -3
  154. package/dist/server/routes/agentRoles.js +3 -2
  155. package/dist/server/routes/agentSkills.js +3 -2
  156. package/dist/server/routes/agents.d.ts +7 -0
  157. package/dist/server/routes/agents.js +394 -39
  158. package/dist/server/routes/annotatedAttachments.js +3 -2
  159. package/dist/server/routes/auth.js +3 -1
  160. package/dist/server/routes/billing.js +5 -1
  161. package/dist/server/routes/durableTaskHttpRouters.d.ts +11 -2
  162. package/dist/server/routes/executorPhaseAApprovals.d.ts +19 -0
  163. package/dist/server/routes/executorPhaseAApprovals.js +140 -0
  164. package/dist/server/routes/executorPhaseAIssuerRedemptions.d.ts +11 -0
  165. package/dist/server/routes/executorPhaseAIssuerRedemptions.js +24 -0
  166. package/dist/server/routes/githubReviewEvidence.d.ts +19 -0
  167. package/dist/server/routes/githubReviewEvidence.js +116 -0
  168. package/dist/server/routes/localService.d.ts +15 -4
  169. package/dist/server/routes/localService.js +138 -40
  170. package/dist/server/routes/modelConnections.d.ts +24 -0
  171. package/dist/server/routes/modelConnections.js +205 -0
  172. package/dist/server/routes/primitives.d.ts +2 -0
  173. package/dist/server/routes/shared.d.ts +19 -0
  174. package/dist/server/routes/shared.js +95 -19
  175. package/dist/server/routes/sync.js +1 -0
  176. package/dist/server/routes/syncAuxRoutes.js +9 -2
  177. package/dist/server/routes/syncPullApplyRoutes.js +23 -4
  178. package/dist/server/routes/syncPushRoutes.d.ts +1 -0
  179. package/dist/server/routes/syncPushRoutes.js +1 -0
  180. package/dist/server/routes/syncV3FeedRoutes.js +27 -16
  181. package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
  182. package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
  183. package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
  184. package/dist/server/routes/syncV3OperationRoutes.js +45 -9
  185. package/dist/server/routes/tasks.js +124 -13
  186. package/dist/server/routes/workflows.js +9 -1
  187. package/dist/server/routes/workspaces.js +43 -1
  188. package/dist/server/routes.js +59 -0
  189. package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
  190. package/dist/server/workspaceDeletionAdmission.js +50 -0
  191. package/dist/service/localServiceCli.js +16 -0
  192. package/dist/service/localServiceClientLifecycle.js +36 -10
  193. package/dist/services/codexAppServerModelGateway.d.ts +161 -0
  194. package/dist/services/codexAppServerModelGateway.js +663 -0
  195. package/dist/services/codexDeviceCodeLoginManager.d.ts +39 -0
  196. package/dist/services/codexDeviceCodeLoginManager.js +265 -0
  197. package/dist/services/codexModelProviderConnectionLifecycle.d.ts +8 -0
  198. package/dist/services/codexModelProviderConnectionLifecycle.js +73 -0
  199. package/dist/services/executorPhaseAApprovalRuntime.d.ts +14 -0
  200. package/dist/services/executorPhaseAApprovalRuntime.js +24 -0
  201. package/dist/services/executorPhaseAApprovalService.d.ts +40 -0
  202. package/dist/services/executorPhaseAApprovalService.js +609 -0
  203. package/dist/services/executorPhaseADriftEvaluator.d.ts +78 -0
  204. package/dist/services/executorPhaseADriftEvaluator.js +93 -0
  205. package/dist/services/executorPhaseAIssuerCore.d.ts +126 -0
  206. package/dist/services/executorPhaseAIssuerCore.js +295 -0
  207. package/dist/services/executorPhaseAIssuerRedemptionService.d.ts +34 -0
  208. package/dist/services/executorPhaseAIssuerRedemptionService.js +158 -0
  209. package/dist/services/githubReviewEvidenceRuntime.d.ts +14 -0
  210. package/dist/services/githubReviewEvidenceRuntime.js +30 -0
  211. package/dist/services/githubReviewEvidenceService.d.ts +93 -0
  212. package/dist/services/githubReviewEvidenceService.js +336 -0
  213. package/dist/services/modelGateway.d.ts +7 -0
  214. package/dist/services/modelProviderConnectionService.d.ts +71 -0
  215. package/dist/services/modelProviderConnectionService.js +279 -0
  216. package/dist/services/modelProviderCredentialHome.d.ts +32 -0
  217. package/dist/services/modelProviderCredentialHome.js +271 -0
  218. package/dist/services/ownerScopedModelConnectionGateway.d.ts +12 -0
  219. package/dist/services/ownerScopedModelConnectionGateway.js +30 -0
  220. package/dist/services/taskforceAgentConnectionBindingService.d.ts +66 -0
  221. package/dist/services/taskforceAgentConnectionBindingService.js +185 -0
  222. package/dist/services/taskforceAgentMcpBridge.d.ts +8 -2
  223. package/dist/services/taskforceAgentMcpBridge.js +30 -27
  224. package/dist/shared/adminEdgeProxy.d.ts +5 -0
  225. package/dist/shared/adminEdgeProxy.js +94 -0
  226. package/dist/shared/adminProxySessionCookies.d.ts +16 -0
  227. package/dist/shared/adminProxySessionCookies.js +86 -0
  228. package/dist/shared/aiProfileColors.d.ts +31 -1
  229. package/dist/shared/aiProfileColors.js +14 -6
  230. package/dist/shared/executorPhaseAApprovalGoldenVector.json +269 -0
  231. package/dist/shared/executorPhaseAApprovalReceipt.d.ts +112 -0
  232. package/dist/shared/executorPhaseAApprovalReceipt.js +58 -0
  233. package/dist/shared/executorPhaseAApprovalTrustPolicy.json +10 -0
  234. package/dist/shared/executorPhaseADriftContracts.d.ts +37 -0
  235. package/dist/shared/executorPhaseADriftContracts.js +63 -0
  236. package/dist/shared/executorPhaseAIssuerContracts.d.ts +75 -0
  237. package/dist/shared/executorPhaseAIssuerContracts.js +90 -0
  238. package/dist/shared/executorPhaseARunAuthorization.d.ts +120 -0
  239. package/dist/shared/executorPhaseARunAuthorization.js +232 -0
  240. package/dist/shared/executorPhaseASchemaValidation.d.ts +7 -0
  241. package/dist/shared/executorPhaseASchemaValidation.js +53 -0
  242. package/dist/shared/executorPhaseASchemas/approval-receipt.schema.json +106 -0
  243. package/dist/shared/executorPhaseASchemas/drift-attestation.schema.json +54 -0
  244. package/dist/shared/executorPhaseASchemas/drift-request.schema.json +47 -0
  245. package/dist/shared/executorPhaseASchemas/foundation-manifest.schema.json +29 -0
  246. package/dist/shared/executorPhaseASchemas/issuer-audit-record.schema.json +24 -0
  247. package/dist/shared/executorPhaseASchemas/issuer-proof.schema.json +43 -0
  248. package/dist/shared/executorPhaseASchemas/issuer-redemption-request.schema.json +18 -0
  249. package/dist/shared/executorPhaseASchemas/issuer-redemption-response.schema.json +39 -0
  250. package/dist/shared/executorPhaseASchemas/logical-envelope.schema.json +149 -0
  251. package/dist/shared/executorPhaseASchemas/preflight-report.schema.json +216 -0
  252. package/dist/shared/executorPhaseASchemas/run-capability.schema.json +111 -0
  253. package/dist/shared/executorPhaseASchemas/sender-proof.schema.json +37 -0
  254. package/dist/shared/executorPhaseASchemas/workload-grant.schema.json +68 -0
  255. package/dist/shared/executorPhaseASchemas/workload-jwt-claims.schema.json +67 -0
  256. package/dist/shared/executorPhaseATiming.d.ts +1 -0
  257. package/dist/shared/executorPhaseATiming.js +1 -0
  258. package/dist/shared/planningIdentity.js +1 -1
  259. package/dist/shared/productLogoKeys.d.ts +2 -0
  260. package/dist/shared/productLogoKeys.js +18 -0
  261. package/dist/shared/taskforceAgentDefinition.d.ts +0 -1
  262. package/dist/shared/taskforceAgentDefinition.js +6 -3
  263. package/dist/shared/taskforceAgentModels.d.ts +1 -0
  264. package/dist/shared/taskforceAgentModels.js +36 -3
  265. package/dist/shared/userFeatureGrants.d.ts +4 -0
  266. package/dist/shared/userFeatureGrants.js +7 -0
  267. package/dist/storage/executorPhaseAApprovalReceiptStore.d.ts +37 -0
  268. package/dist/storage/executorPhaseAApprovalReceiptStore.js +81 -0
  269. package/dist/storage/executorPhaseARunAuthorizationStore.d.ts +103 -0
  270. package/dist/storage/executorPhaseARunAuthorizationStore.js +492 -0
  271. package/dist/storage/githubReviewEvidenceInstallationStore.d.ts +26 -0
  272. package/dist/storage/githubReviewEvidenceInstallationStore.js +89 -0
  273. package/dist/storage/modelProviderConnectionStore.d.ts +99 -0
  274. package/dist/storage/modelProviderConnectionStore.js +446 -0
  275. package/dist/storage/postgresAdapter.d.ts +24 -3
  276. package/dist/storage/postgresAdapter.js +117 -14
  277. package/dist/storage/postgresWorker.js +22 -5
  278. package/dist/storage/postgresWorkerProtocol.d.ts +30 -0
  279. package/dist/storage/postgresWorkerProtocol.js +30 -0
  280. package/dist/storage/taskforceAgentConnectionBindingStore.d.ts +31 -0
  281. package/dist/storage/taskforceAgentConnectionBindingStore.js +84 -0
  282. package/dist/sync/cloudSyncApi.d.ts +4 -1
  283. package/dist/sync/cloudSyncApi.js +7 -2
  284. package/dist/sync/contentSyncState.d.ts +1 -1
  285. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
  286. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +67 -30
  287. package/dist/sync/coordinator/localWorkspaceSyncPendingV2PushProgress.d.ts +10 -0
  288. package/dist/sync/coordinator/localWorkspaceSyncPendingV2PushProgress.js +29 -0
  289. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
  290. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.d.ts +15 -1
  291. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.js +99 -2
  292. package/dist/sync/coordinator/workspaceSyncCredentialBroker.d.ts +1 -0
  293. package/dist/sync/coordinator/workspaceSyncCredentialBroker.js +33 -4
  294. package/dist/sync/engine/workspaceSyncContentLifecycleAdapter.d.ts +2 -1
  295. package/dist/sync/engine/workspaceSyncContentVersionStore.d.ts +38 -0
  296. package/dist/sync/engine/workspaceSyncContentVersionStore.js +50 -1
  297. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
  298. package/dist/sync/engine/workspaceSyncEngineLifecyclePolicy.js +6 -6
  299. package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
  300. package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
  301. package/dist/sync/engine/workspaceSyncEnginePushCompletion.d.ts +14 -0
  302. package/dist/sync/engine/workspaceSyncEnginePushCompletion.js +84 -0
  303. package/dist/sync/engine/workspaceSyncEngineRecovery.js +1 -0
  304. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
  305. package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
  306. package/dist/sync/engine/workspaceSyncEngineRuntime.d.ts +1 -0
  307. package/dist/sync/engine/workspaceSyncEngineRuntime.js +1 -0
  308. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
  309. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
  310. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +134 -43
  311. package/dist/sync/engine/workspaceSyncEngineServerSnapshotReader.js +6 -0
  312. package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
  313. package/dist/sync/engine/workspaceSyncEngineTransfers.js +11 -4
  314. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.d.ts +1 -0
  315. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +29 -0
  316. package/dist/sync/syncApplyHandlers.d.ts +2 -0
  317. package/dist/sync/syncApplyHandlers.js +10 -1
  318. package/dist/sync/syncService.d.ts +17 -0
  319. package/dist/sync/syncService.js +87 -19
  320. package/dist/sync/taskforceAgentSyncAuthorization.d.ts +16 -0
  321. package/dist/sync/taskforceAgentSyncAuthorization.js +20 -0
  322. package/dist/sync/v3/combinedFeedCapability.js +8 -0
  323. package/dist/sync/v3/durableInitiativeMutationRouter.js +18 -10
  324. package/dist/sync/v3/durableOperationReplay.d.ts +14 -0
  325. package/dist/sync/v3/durableOperationReplay.js +18 -0
  326. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
  327. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +14 -1
  328. package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +4 -1
  329. package/dist/sync/v3/durableTaskChecklistMutationRouter.js +62 -15
  330. package/dist/sync/v3/durableTaskCommentMutationRouter.js +3 -0
  331. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
  332. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
  333. package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
  334. package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +37 -2
  335. package/dist/sync/v3/durableTaxonomyMutationRouter.js +5 -0
  336. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +11 -0
  337. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +39 -10
  338. package/dist/sync/v3/durableWorkstreamMutationRouter.js +18 -10
  339. package/dist/sync/v3/durableWorkstreamTaskOrderMutationRouter.js +16 -5
  340. package/dist/sync/v3/localOutboxDispatchRuntime.js +2 -2
  341. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
  342. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
  343. package/dist/sync/v3/localTaskChecklistOutboxHandler.js +16 -2
  344. package/dist/sync/v3/localTaskChecklistOutboxService.d.ts +4 -0
  345. package/dist/sync/v3/localTaskChecklistOutboxService.js +14 -1
  346. package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
  347. package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
  348. package/dist/sync/v3/localTaskMetadataClient.js +1 -0
  349. package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
  350. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
  351. package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
  352. package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
  353. package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
  354. package/dist/sync/v3/localTaskStatusClient.js +6 -1
  355. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +10 -0
  356. package/dist/sync/v3/localTaskStatusOutboxService.js +272 -24
  357. package/dist/sync/v3/syncDurabilityStore.d.ts +41 -0
  358. package/dist/sync/v3/syncDurabilityStore.js +199 -13
  359. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
  360. package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
  361. package/dist/sync/v3/taskChecklistMutationService.d.ts +15 -2
  362. package/dist/sync/v3/taskChecklistMutationService.js +48 -1
  363. package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
  364. package/dist/sync/v3/taskCreateMutationService.js +7 -4
  365. package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
  366. package/dist/sync/v3/taskMetadataMutationService.js +4 -0
  367. package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
  368. package/dist/sync/v3/taskRestoreMutationService.js +7 -4
  369. package/dist/sync/v3/taskStatusMutationService.d.ts +31 -1
  370. package/dist/sync/v3/taskStatusMutationService.js +123 -3
  371. package/dist/sync/v3/workflowAssignmentSync.d.ts +6 -0
  372. package/dist/sync/v3/workflowAssignmentSync.js +30 -0
  373. package/dist/sync/v3/workspaceSyncJournalReader.js +1 -1
  374. package/dist/sync/v3/workspaceSyncOperationRetentionService.d.ts +16 -0
  375. package/dist/sync/v3/workspaceSyncOperationRetentionService.js +83 -0
  376. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
  377. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
  378. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
  379. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
  380. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
  381. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
  382. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.d.ts +1 -0
  383. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +18 -3
  384. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
  385. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
  386. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +14 -0
  387. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +49 -25
  388. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +13 -4
  389. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +124 -57
  390. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +20 -0
  391. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +106 -30
  392. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +4 -1
  393. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +34 -0
  394. package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +12 -0
  395. package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
  396. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
  397. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
  398. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
  399. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
  400. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
  401. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
  402. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
  403. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
  404. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
  405. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
  406. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +5 -0
  407. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +7 -0
  408. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +55 -9
  409. package/dist/sync/v3/workspaceSyncV3RepairSnapshotRetentionService.d.ts +17 -0
  410. package/dist/sync/v3/workspaceSyncV3RepairSnapshotRetentionService.js +64 -0
  411. package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
  412. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
  413. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
  414. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
  415. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
  416. package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverCoverage.d.ts +7 -0
  417. package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverCoverage.js +33 -0
  418. package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverWireContract.d.ts +7 -0
  419. package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverWireContract.js +8 -0
  420. package/dist/sync/workspacePullFeed.js +2 -1
  421. package/dist/sync/workspaceRepair.d.ts +8 -2
  422. package/dist/sync/workspaceRepair.js +222 -6
  423. package/dist/sync/workspaceSyncModel.d.ts +21 -0
  424. package/dist/sync/workspaceSyncModel.js +35 -7
  425. package/dist/sync/workspaceSyncState.d.ts +1 -0
  426. package/dist/sync/workspaceSyncV2SnapshotBuilder.d.ts +6 -0
  427. package/dist/sync/workspaceSyncV2SnapshotBuilder.js +5 -0
  428. package/dist/types.d.ts +47 -1
  429. package/dist/types.js +6 -0
  430. package/dist/ui/assets/AiIdentityRosterCard-OcSz1PkK.js +1 -0
  431. package/dist/ui/assets/AiProfilesModule-ByhYW5Xi.js +1 -0
  432. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTM6nIg_.js +3 -0
  433. package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
  434. package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-DabEG8L4.js} +1 -1
  435. package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Coa8yVGM.js} +2 -2
  436. package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-BB0qhdix.js} +1 -1
  437. package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-CZ8x4UJE.js} +1 -1
  438. package/dist/ui/assets/PlanningModule-pRPjR7v5.js +1 -0
  439. package/dist/ui/assets/PlansPage-D86pZSSl.js +1 -0
  440. package/dist/ui/assets/TaskContextUpload-BVIkU9yT.js +1 -0
  441. package/dist/ui/assets/TaskSettings-Cv1mNhv_.js +12 -0
  442. package/dist/ui/assets/TaskforceAgentsModule-b-8R25cJ.css +1 -0
  443. package/dist/ui/assets/TaskforceAgentsModule-gmH-PD2q.js +24 -0
  444. package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DbfnHJ-f.js} +1 -1
  445. package/dist/ui/assets/index-BZ-hdZSk.css +1 -0
  446. package/dist/ui/assets/index-CTsDBaef.js +7 -0
  447. package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-Bsq-mcEn.js} +1 -1
  448. package/dist/ui/branding/logos/letterhead_dark.png +0 -0
  449. package/dist/ui/branding/logos/letterhead_light.png +0 -0
  450. package/dist/ui/branding/logos/signature_dark.png +0 -0
  451. package/dist/ui/branding/logos/signature_light.png +0 -0
  452. package/dist/ui/index.html +3 -3
  453. package/dist/utils/aiProfileDefaultLogo.js +16 -48
  454. package/dist/utils/httpSetCookie.d.ts +1 -0
  455. package/dist/utils/httpSetCookie.js +4 -1
  456. package/dist/utils/localRuntimeUrl.d.ts +1 -0
  457. package/dist/utils/localRuntimeUrl.js +14 -0
  458. package/dist/utils/productLogoRegistry.d.ts +2 -0
  459. package/dist/utils/productLogoRegistry.js +36 -0
  460. package/dist/utils/syncStatusPresentation.d.ts +4 -0
  461. package/dist/utils/syncStatusPresentation.js +17 -0
  462. package/dist/utils/taskActivity.d.ts +3 -2
  463. package/dist/utils/taskActivity.js +19 -2
  464. package/dist/utils/taskEditorReconciliation.d.ts +40 -0
  465. package/dist/utils/taskEditorReconciliation.js +217 -0
  466. package/dist/utils/taskEvents.d.ts +2 -0
  467. package/dist/utils/taskNormalization.d.ts +1 -0
  468. package/dist/utils/taskNormalization.js +39 -0
  469. package/dist/utils/taskSearch.js +2 -2
  470. package/dist/utils/uiNotice.d.ts +4 -0
  471. package/dist/utils/workspaceSyncPresentation.d.ts +1 -1
  472. package/dist/utils/workspaceSyncPresentation.js +23 -17
  473. package/package.json +36 -6
  474. package/dist/ui/assets/AiIdentityRosterCard-MZs7lVED.js +0 -1
  475. package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
  476. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
  477. package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
  478. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
  479. package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
  480. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
  481. package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
  482. package/dist/ui/assets/TaskforceAgentsModule-BazdUwxZ.js +0 -21
  483. package/dist/ui/assets/TaskforceAgentsModule-D6IC0S7Q.css +0 -1
  484. package/dist/ui/assets/index-BUplSsv_.js +0 -7
  485. package/dist/ui/assets/index-gz2EXuoK.css +0 -1
@@ -27,6 +27,7 @@ import { listTaskAttachmentLinksForDurableSync as listTaskAttachmentLinksForDura
27
27
  import { EntityEventIdentityCollisionError, addEntityEventService, addTaskEventService, buildEntityActivityService, buildTaskActivityService, getEntityEventForSyncService, getTaskEventForSyncService, listEntityEventsInternalService, listWorkspaceEntityEventsForSyncService, listTaskEventsInternalService, listWorkspaceTaskEventsForSyncService, replaceEntityEventsService, replaceTaskCommentsService, replaceTaskEventsService, upsertEntityEventForSyncService, deleteEntityEventForSyncService, upsertTaskEventForSyncService, } from './TaskActivityService.js';
28
28
  import { addDeletedTaskRecordService, getDeletedTaskService, listDeletedTasksService, listDeletedTasksReadOnlyService, mapDeletedTaskRowService, normalizeTaskSnapshotForRestoreService, normalizeDeletedTaskRestoreLifecycleService, purgeExpiredDeletedTasksService, } from './DeletedTaskLifecycleService.js';
29
29
  import { DELETED_TASK_RETENTION_DAYS } from '../shared/deletedTaskRetention.js';
30
+ import { INTERNAL_SUBSCRIPTION_MODEL_FEATURE_KEY, isSupportedInternalUserFeatureKey, } from '../shared/userFeatureGrants.js';
30
31
  import { applyWorkspaceSyncSnapshotService, claimPushIdempotencyService, completePushIdempotencyClaimService, deleteDocumentWatermarkService, deleteDocumentWatermarkStrictService, getDocumentWatermarkService, listTasksForSyncService, listWorkspaceSyncDeletesSinceService, readPushIdempotencyService, releasePushIdempotencyClaimService, renewPushIdempotencyClaimService, recordSyncEventDurablyService, recordSyncEventService, upsertDocumentWatermarkService, upsertDocumentWatermarkStrictService, writePushIdempotencyService, } from './SyncReconciliationService.js';
31
32
  import { getGlobalSettingsService, hasPersistedGlobalSettingsService, readGlobalConfigService, updateGlobalSettingsService, writeGlobalConfigService, } from './GlobalSettingsService.js';
32
33
  import { getWorkspaceTaxonomyStateService, upsertWorkspaceTaxonomyStateService, } from './TaxonomySettingsService.js';
@@ -61,7 +62,8 @@ import { getImageReferenceLabel } from '../utils/imageReferences.js';
61
62
  import { getCanonicalDocumentName } from '../utils/documentNames.js';
62
63
  import { isPathInsideRoot, resolveStorageKeyPathInsideRoot } from '../utils/pathSafety.js';
63
64
  import { DEFAULT_TASKFORCE_THEME } from '../utils/theme.js';
64
- import { ensureAssigneeTaskSchema, ensureAnnotatedAttachmentSessionsSchema, ensureAuthIdentitySchema, backfillPlanningAttachmentAssetIdentities, ensureBillingSchema, ensureDocumentReviewSessionsSchema, ensureDocumentReferenceSchema, ensureEntitlementsSchema, ensurePlanningReferenceSchema, ensureScheduleTaskSchema, ensureWorkstreamTaskOrderSchema, ensureTaskAssetsSchema, ensureTaskApproachRemoved, ensureAgentRolesSchema, ensureTaskforceAgentsSchema, ensureTaskforceAgentSkillsSchema, ensureTaskReferenceSchema, ensureTenantSchema, ensureWorkflowSchema, ensureWorkspaceAssetsSchema, ensureWorkspaceSchema, ensureLocalSyncCoordinatorSchema, ensureWorkspaceSyncDurabilitySchema } from '../migrations/taskSchemaMigrations.js';
65
+ import { normalizeTaskSearchQuery } from '../utils/taskSearch.js';
66
+ import { ensureAssigneeTaskSchema, ensureAnnotatedAttachmentSessionsSchema, ensureAuthIdentitySchema, backfillPlanningAttachmentAssetIdentities, ensureBillingSchema, ensureDocumentReviewSessionsSchema, ensureDocumentReferenceSchema, ensureEntitlementsSchema, ensurePlanningReferenceSchema, ensureScheduleTaskSchema, ensureWorkstreamTaskOrderSchema, ensureTaskAssetsSchema, ensureTaskApproachRemoved, ensureAgentRolesSchema, ensureGitHubReviewEvidenceInstallationBindingsSchema, ensureExecutorPhaseAApprovalReceiptsSchema, ensureExecutorPhaseARunAuthorizationsSchema, ensureModelProviderConnectionsSchema, ensureTaskforceAgentConnectionBindingsSchema, ensureTaskforceAgentsSchema, ensureTaskforceAgentSkillsSchema, ensureTaskReferenceSchema, ensureTenantSchema, ensureWorkflowSchema, ensureWorkspaceAssetsSchema, ensureWorkspaceSchema, ensureLocalSyncCoordinatorSchema, ensureWorkspaceSyncDurabilitySchema } from '../migrations/taskSchemaMigrations.js';
65
67
  import { repairTaskUpdatedAtOnlySyncDrift } from '../migrations/taskUpdatedAtSyncDriftMigration.js';
66
68
  export class TaskforceRuleError extends Error {
67
69
  statusCode;
@@ -1456,7 +1458,14 @@ export class TaskforceCore {
1456
1458
  ensureDocumentReferenceSchema(this.db);
1457
1459
  ensureDocumentReviewSessionsSchema(this.db);
1458
1460
  ensureAnnotatedAttachmentSessionsSchema(this.db);
1461
+ ensureModelProviderConnectionsSchema(this.db);
1459
1462
  ensureTaskforceAgentsSchema(this.db);
1463
+ ensureTaskforceAgentConnectionBindingsSchema(this.db);
1464
+ ensureGitHubReviewEvidenceInstallationBindingsSchema(this.db);
1465
+ if (this.runtimeMode === 'cloud') {
1466
+ ensureExecutorPhaseAApprovalReceiptsSchema(this.db);
1467
+ ensureExecutorPhaseARunAuthorizationsSchema(this.db);
1468
+ }
1460
1469
  ensureAgentRolesSchema(this.db);
1461
1470
  ensureTaskforceAgentSkillsSchema(this.db);
1462
1471
  ensureWorkflowSchema(this.db);
@@ -1968,6 +1977,18 @@ export class TaskforceCore {
1968
1977
  PRIMARY KEY (tenant_id, user_id, prefs_key)
1969
1978
  );
1970
1979
 
1980
+ CREATE TABLE IF NOT EXISTS user_feature_grants (
1981
+ tenant_id TEXT NOT NULL DEFAULT 'default',
1982
+ user_id TEXT NOT NULL,
1983
+ feature_key TEXT NOT NULL,
1984
+ enabled INTEGER NOT NULL DEFAULT 0,
1985
+ created_at TEXT NOT NULL,
1986
+ updated_at TEXT NOT NULL,
1987
+ updated_by TEXT,
1988
+ PRIMARY KEY (tenant_id, user_id, feature_key),
1989
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
1990
+ );
1991
+
1971
1992
  CREATE TABLE IF NOT EXISTS admin_audit_logs (
1972
1993
  id TEXT PRIMARY KEY,
1973
1994
  tenant_id TEXT NOT NULL DEFAULT 'default',
@@ -2046,6 +2067,7 @@ export class TaskforceCore {
2046
2067
  user_id TEXT NOT NULL,
2047
2068
  workspace_id TEXT NOT NULL DEFAULT 'default',
2048
2069
  scopes_json TEXT NOT NULL,
2070
+ tool_profile TEXT NOT NULL DEFAULT 'default',
2049
2071
  token_prefix TEXT NOT NULL,
2050
2072
  token_hash TEXT NOT NULL,
2051
2073
  status TEXT NOT NULL DEFAULT 'active',
@@ -2187,6 +2209,7 @@ export class TaskforceCore {
2187
2209
  CREATE INDEX IF NOT EXISTS idx_ui_state_workspace_key ON ui_state(workspace_id, state_key);
2188
2210
  CREATE INDEX IF NOT EXISTS idx_workspace_settings_workspace_key ON workspace_settings(workspace_id, settings_key);
2189
2211
  CREATE INDEX IF NOT EXISTS idx_user_preferences_user_key ON user_preferences(user_id, prefs_key);
2212
+ CREATE INDEX IF NOT EXISTS idx_user_feature_grants_feature_enabled ON user_feature_grants(tenant_id, feature_key, enabled);
2190
2213
  CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_tenant_created ON admin_audit_logs(tenant_id, created_at);
2191
2214
  CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_workspace_created ON admin_audit_logs(workspace_id, created_at);
2192
2215
  CREATE INDEX IF NOT EXISTS idx_task_events_task_created ON task_events(tenant_id, workspace_id, task_id, created_at);
@@ -2375,6 +2398,9 @@ export class TaskforceCore {
2375
2398
  if (!mcpOauthGrantColumns.some((c) => c.name === 'ai_profile_name')) {
2376
2399
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN ai_profile_name TEXT`);
2377
2400
  }
2401
+ if (!mcpOauthGrantColumns.some((c) => c.name === 'tool_profile')) {
2402
+ this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN tool_profile TEXT NOT NULL DEFAULT 'default'`);
2403
+ }
2378
2404
  }
2379
2405
  else {
2380
2406
  try {
@@ -2383,6 +2409,7 @@ export class TaskforceCore {
2383
2409
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN IF NOT EXISTS ai_profile_id TEXT`);
2384
2410
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN IF NOT EXISTS ai_profile_token TEXT`);
2385
2411
  this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN IF NOT EXISTS ai_profile_name TEXT`);
2412
+ this.db.exec(`ALTER TABLE mcp_oauth_connector_grants ADD COLUMN IF NOT EXISTS tool_profile TEXT NOT NULL DEFAULT 'default'`);
2386
2413
  }
2387
2414
  catch {
2388
2415
  // Best-effort schema repair for existing deployments.
@@ -3158,7 +3185,17 @@ export class TaskforceCore {
3158
3185
  return ['manualComplexityEnabled'];
3159
3186
  }
3160
3187
  getUserBackedProjectConfigKeys() {
3161
- return ['theme', 'jsonBackupEnabled', 'checklistDropdownEnabled', 'showTaskCardStatusLabel'];
3188
+ return [
3189
+ 'theme',
3190
+ 'jsonBackupEnabled',
3191
+ 'checklistDropdownEnabled',
3192
+ 'showTaskCardStatusLabel',
3193
+ 'showTaskCardDescription',
3194
+ 'showTaskCardAttachments',
3195
+ 'showTaskCardChecklistProgress',
3196
+ 'showTaskCardLatestActivity',
3197
+ 'tintTaskCardActivityBackground',
3198
+ ];
3162
3199
  }
3163
3200
  isWorkspaceBackedProjectConfigKey(key) {
3164
3201
  return this.getWorkspaceBackedProjectConfigKeys().includes(key);
@@ -3201,6 +3238,21 @@ export class TaskforceCore {
3201
3238
  if (typeof source.showTaskCardStatusLabel === 'boolean') {
3202
3239
  next.showTaskCardStatusLabel = source.showTaskCardStatusLabel;
3203
3240
  }
3241
+ if (typeof source.showTaskCardDescription === 'boolean') {
3242
+ next.showTaskCardDescription = source.showTaskCardDescription;
3243
+ }
3244
+ if (typeof source.showTaskCardAttachments === 'boolean') {
3245
+ next.showTaskCardAttachments = source.showTaskCardAttachments;
3246
+ }
3247
+ if (typeof source.showTaskCardChecklistProgress === 'boolean') {
3248
+ next.showTaskCardChecklistProgress = source.showTaskCardChecklistProgress;
3249
+ }
3250
+ if (typeof source.showTaskCardLatestActivity === 'boolean') {
3251
+ next.showTaskCardLatestActivity = source.showTaskCardLatestActivity;
3252
+ }
3253
+ if (typeof source.tintTaskCardActivityBackground === 'boolean') {
3254
+ next.tintTaskCardActivityBackground = source.tintTaskCardActivityBackground;
3255
+ }
3204
3256
  return next;
3205
3257
  }
3206
3258
  stripPersistedProjectConfigKeys(config) {
@@ -4019,7 +4071,7 @@ export class TaskforceCore {
4019
4071
  AND identity.entity_type = 'initiative'
4020
4072
  AND identity.entity_id = i.id
4021
4073
  WHERE i.tenant_id = ? AND i.workspace_id = ?
4022
- ORDER BY COALESCE(i.sort_order, 2147483647), i.created_at ASC
4074
+ ORDER BY COALESCE(i.sort_order, 2147483647), i.created_at ASC, i.id ASC
4023
4075
  `).all(this.tenantId, normalizedWorkspaceId);
4024
4076
  return rows.map((row) => this.rowToInitiative(row));
4025
4077
  }
@@ -4326,7 +4378,7 @@ export class TaskforceCore {
4326
4378
  AND identity.entity_type = 'workstream'
4327
4379
  AND identity.entity_id = w.id
4328
4380
  WHERE w.tenant_id = ? AND w.workspace_id = ?
4329
- ORDER BY COALESCE(w.sort_order, 2147483647), w.created_at ASC
4381
+ ORDER BY COALESCE(w.sort_order, 2147483647), w.created_at ASC, w.id ASC
4330
4382
  `).all(this.tenantId, normalizedWorkspaceId);
4331
4383
  return rows.map((row) => this.rowToWorkstream(row));
4332
4384
  }
@@ -5493,6 +5545,7 @@ export class TaskforceCore {
5493
5545
  generateEntityId: (prefix) => this.generateEntityId(prefix),
5494
5546
  normalizeWorkspaceId: (workspaceId) => this.normalizeWorkspaceId(workspaceId),
5495
5547
  resolveWorkspaceActor: (workspaceId, actorRef) => this.resolveWorkspaceActor(workspaceId, actorRef),
5548
+ resolveWorkspaceActors: (workspaceId, actorRefs) => this.resolveWorkspaceActors(workspaceId, actorRefs),
5496
5549
  sanitizeOrderInDay: (value) => this.sanitizeOrderInDay(value),
5497
5550
  sanitizeDateOnly: (value) => this.sanitizeDateOnly(value),
5498
5551
  normalizeTaskEventActor: (actorRef, workspaceId) => this.normalizeTaskEventActor(actorRef, workspaceId),
@@ -5572,6 +5625,489 @@ export class TaskforceCore {
5572
5625
  config: this.readConfig()
5573
5626
  };
5574
5627
  }
5628
+ listTaskSummaries(workspaceId, archived) {
5629
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
5630
+ const rows = this.db.prepare(`
5631
+ WITH ranked_events AS (
5632
+ SELECT e.entity_id,
5633
+ e.id,
5634
+ e.action,
5635
+ e.actor,
5636
+ e.actor_type,
5637
+ e.details_json,
5638
+ e.created_at,
5639
+ ROW_NUMBER() OVER (
5640
+ PARTITION BY e.entity_id
5641
+ ORDER BY e.created_at DESC, e.id DESC
5642
+ ) AS event_rank
5643
+ FROM entity_events e
5644
+ JOIN tasks scoped_task
5645
+ ON scoped_task.tenant_id = e.tenant_id
5646
+ AND scoped_task.workspace_id = e.workspace_id
5647
+ AND scoped_task.id = e.entity_id
5648
+ WHERE e.tenant_id = ? AND e.workspace_id = ?
5649
+ AND e.entity_type = 'task'
5650
+ AND scoped_task.is_archived = ?
5651
+ -- Comment activity is represented by the comment itself; the
5652
+ -- paired audit event is intentionally hidden by taskActivity.
5653
+ AND e.action <> 'task-comment-added'
5654
+ -- Durable text-only autosaves retain an empty task-updated event
5655
+ -- for stable replay identity, but full task activity hides it.
5656
+ -- Match that visibility rule before ranking so an internal event
5657
+ -- cannot displace the latest meaningful card activity. REPLACE is
5658
+ -- intentionally used instead of dialect-specific JSON operators.
5659
+ AND NOT (
5660
+ e.action = 'task-updated'
5661
+ AND REPLACE(COALESCE(e.details_json, ''), ' ', '') LIKE '%"saveSource":"autosave"%'
5662
+ AND REPLACE(COALESCE(e.details_json, ''), ' ', '') LIKE '%"changes":{}%'
5663
+ )
5664
+ )
5665
+ SELECT t.id, t.reference_number, t.local_reference_number, t.title, t.status, t.priority,
5666
+ t.complexity, t.type, t.category, t.created_at, t.updated_at, t.completed_at,
5667
+ t.canceled_reason, t.is_archived, t.scheduled_date, t.due_date,
5668
+ t.scheduled_week_key, t.order_in_day, t.workstream_order, t.workstream_id,
5669
+ t.card_cover_asset_id, t.taxonomies, t.created_by, t.assignee,
5670
+ event.id AS latest_event_id,
5671
+ event.action AS latest_event_action,
5672
+ event.actor AS latest_event_actor,
5673
+ event.actor_type AS latest_event_actor_type,
5674
+ event.details_json AS latest_event_details_json,
5675
+ event.created_at AS latest_event_at
5676
+ FROM tasks t
5677
+ LEFT JOIN ranked_events event
5678
+ ON event.entity_id = t.id AND event.event_rank = 1
5679
+ WHERE t.tenant_id = ? AND t.workspace_id = ? AND t.is_archived = ?
5680
+ ORDER BY ${archived ? 't.completed_at DESC, t.created_at DESC' : 't.created_at DESC'}
5681
+ `).all(this.tenantId, normalizedWorkspaceId, archived ? 1 : 0, this.tenantId, normalizedWorkspaceId, archived ? 1 : 0);
5682
+ const comments = this.db.prepare(`
5683
+ WITH ranked_comments AS (
5684
+ SELECT c.task_id,
5685
+ c.id,
5686
+ c.text,
5687
+ c.author,
5688
+ c.timestamp,
5689
+ COUNT(*) OVER (PARTITION BY c.task_id) AS count,
5690
+ MAX(c.timestamp) OVER (PARTITION BY c.task_id) AS revision,
5691
+ ROW_NUMBER() OVER (
5692
+ PARTITION BY c.task_id
5693
+ ORDER BY c.timestamp DESC, c.id DESC
5694
+ ) AS comment_rank
5695
+ FROM comments c
5696
+ JOIN tasks t
5697
+ ON t.tenant_id = c.tenant_id
5698
+ AND t.workspace_id = c.workspace_id
5699
+ AND t.id = c.task_id
5700
+ WHERE c.tenant_id = ? AND c.workspace_id = ? AND t.is_archived = ?
5701
+ )
5702
+ SELECT task_id, id, text, author, timestamp, count, revision
5703
+ FROM ranked_comments
5704
+ WHERE comment_rank = 1
5705
+ `).all(this.tenantId, normalizedWorkspaceId, archived ? 1 : 0);
5706
+ const checklist = this.db.prepare(`
5707
+ SELECT c.task_id, COUNT(*) AS count,
5708
+ SUM(CASE WHEN c.is_completed = 1 THEN 1 ELSE 0 END) AS completed_count,
5709
+ MAX(COALESCE(c.updated_at, c.created_at)) AS revision
5710
+ FROM checklist_items c
5711
+ JOIN tasks t ON t.tenant_id = c.tenant_id AND t.workspace_id = c.workspace_id AND t.id = c.task_id
5712
+ WHERE c.tenant_id = ? AND c.workspace_id = ? AND t.is_archived = ?
5713
+ GROUP BY c.task_id
5714
+ `).all(this.tenantId, normalizedWorkspaceId, archived ? 1 : 0);
5715
+ const attachments = this.db.prepare(`
5716
+ SELECT l.task_id, COUNT(a.asset_id) AS count,
5717
+ MAX(CASE WHEN l.asset_id = t.card_cover_asset_id THEN l.asset_id ELSE NULL END) AS cover_asset_id,
5718
+ MAX(CASE WHEN l.asset_id = t.card_cover_asset_id THEN a.storage_provider ELSE NULL END) AS cover_storage_provider,
5719
+ MAX(CASE WHEN l.asset_id = t.card_cover_asset_id THEN a.storage_key ELSE NULL END) AS cover_storage_key,
5720
+ MAX(CASE WHEN l.asset_id = t.card_cover_asset_id THEN a.original_filename ELSE NULL END) AS cover_original_filename,
5721
+ MAX(CASE WHEN l.asset_id = t.card_cover_asset_id THEN COALESCE(l.display_name, a.logical_name) ELSE NULL END) AS cover_display_name,
5722
+ MAX(CASE
5723
+ WHEN COALESCE(a.updated_at, '') > COALESCE(l.updated_at, '') THEN a.updated_at
5724
+ ELSE l.updated_at
5725
+ END) AS revision
5726
+ FROM workspace_asset_links l
5727
+ JOIN tasks t ON t.tenant_id = l.tenant_id AND t.workspace_id = l.workspace_id AND t.id = l.task_id
5728
+ JOIN workspace_assets a ON a.tenant_id = l.tenant_id AND a.workspace_id = l.workspace_id AND a.asset_id = l.asset_id AND a.deleted_at IS NULL
5729
+ WHERE l.tenant_id = ? AND l.workspace_id = ? AND t.is_archived = ? AND l.target_type = 'task' AND l.deleted_at IS NULL
5730
+ GROUP BY l.task_id
5731
+ `).all(this.tenantId, normalizedWorkspaceId, archived ? 1 : 0);
5732
+ const relationships = this.db.prepare(`
5733
+ SELECT task_id, COUNT(*) AS count, MAX(updated_at) AS revision
5734
+ FROM (
5735
+ SELECT r.source_task_id AS task_id, r.updated_at
5736
+ FROM task_relationships r
5737
+ JOIN tasks t ON t.tenant_id = r.tenant_id AND t.workspace_id = r.workspace_id AND t.id = r.source_task_id
5738
+ WHERE r.tenant_id = ? AND r.workspace_id = ? AND r.deleted_at IS NULL AND t.is_archived = ?
5739
+ UNION ALL
5740
+ SELECT r.target_task_id AS task_id, r.updated_at
5741
+ FROM task_relationships r
5742
+ JOIN tasks t ON t.tenant_id = r.tenant_id AND t.workspace_id = r.workspace_id AND t.id = r.target_task_id
5743
+ WHERE r.tenant_id = ? AND r.workspace_id = ? AND r.deleted_at IS NULL
5744
+ AND r.target_task_id <> r.source_task_id AND t.is_archived = ?
5745
+ ) scoped_relationships
5746
+ GROUP BY task_id
5747
+ `).all(this.tenantId, normalizedWorkspaceId, archived ? 1 : 0, this.tenantId, normalizedWorkspaceId, archived ? 1 : 0);
5748
+ const countByTask = (entries) => new Map(entries.map((entry) => [String(entry.task_id || ''), Number(entry.count || 0)]));
5749
+ const commentCountByTask = countByTask(comments);
5750
+ const attachmentCountByTask = countByTask(attachments);
5751
+ const commentRevisionByTask = new Map(comments.map((entry) => [String(entry.task_id || ''), String(entry.revision || '')]));
5752
+ const attachmentRevisionByTask = new Map(attachments.map((entry) => [String(entry.task_id || ''), String(entry.revision || '')]));
5753
+ const coverAttachmentByTask = new Map(attachments.flatMap((entry) => {
5754
+ const taskId = String(entry.task_id || '');
5755
+ const assetId = String(entry.cover_asset_id || '').trim();
5756
+ const storageKey = String(entry.cover_storage_key || '').trim();
5757
+ const storageProvider = String(entry.cover_storage_provider || 'local').trim();
5758
+ if (!taskId || !assetId || !storageKey)
5759
+ return [];
5760
+ const isExternalHttpReference = storageProvider === 'external' && /^https?:\/\//i.test(storageKey);
5761
+ return [[taskId, {
5762
+ assetId,
5763
+ path: isExternalHttpReference
5764
+ ? storageKey
5765
+ : storageProvider === 'external'
5766
+ ? `/api/taskforce/context-link?path=${encodeURIComponent(storageKey)}`
5767
+ : `/api/taskforce/context/${encodeURIComponent(storageKey)}`,
5768
+ ...(isExternalHttpReference ? {} : { fsPath: storageKey }),
5769
+ displayName: entry.cover_display_name ? String(entry.cover_display_name) : undefined,
5770
+ originalFilename: entry.cover_original_filename ? String(entry.cover_original_filename) : undefined,
5771
+ linkRole: 'image',
5772
+ taskId,
5773
+ }]];
5774
+ }));
5775
+ const checklistByTask = new Map(checklist.map((entry) => [String(entry.task_id || ''), {
5776
+ total: Number(entry.count || 0),
5777
+ completed: Number(entry.completed_count || 0),
5778
+ revision: String(entry.revision || ''),
5779
+ }]));
5780
+ const relationshipCountByTask = countByTask(relationships);
5781
+ const relationshipRevisionByTask = new Map(relationships.map((entry) => [
5782
+ String(entry.task_id || ''),
5783
+ String(entry.revision || ''),
5784
+ ]));
5785
+ const latestActivityByTask = new Map();
5786
+ const latestActivityRevisionByTask = new Map();
5787
+ const considerLatestActivity = (taskId, item) => {
5788
+ const existing = latestActivityByTask.get(taskId);
5789
+ const nextTimestamp = Date.parse(String(item.timestamp || ''));
5790
+ const existingTimestamp = existing ? Date.parse(String(existing.timestamp || '')) : Number.NaN;
5791
+ const isNewer = !existing
5792
+ || (Number.isFinite(nextTimestamp) && !Number.isFinite(existingTimestamp))
5793
+ || (Number.isFinite(nextTimestamp) && Number.isFinite(existingTimestamp) && nextTimestamp > existingTimestamp)
5794
+ || (nextTimestamp === existingTimestamp && item.id.localeCompare(existing.id) > 0);
5795
+ if (!isNewer)
5796
+ return;
5797
+ latestActivityByTask.set(taskId, item);
5798
+ latestActivityRevisionByTask.set(taskId, `${item.timestamp}:${item.id}`);
5799
+ };
5800
+ for (const entry of comments) {
5801
+ const taskId = String(entry.task_id || '').trim();
5802
+ const commentId = String(entry.id || '').trim();
5803
+ const timestamp = String(entry.timestamp || '');
5804
+ if (!taskId || !commentId)
5805
+ continue;
5806
+ considerLatestActivity(taskId, {
5807
+ id: `comment:${commentId}`,
5808
+ type: 'comment',
5809
+ timestamp,
5810
+ comment: {
5811
+ text: String(entry.text || ''),
5812
+ author: String(entry.author || 'user').trim() || 'user',
5813
+ },
5814
+ });
5815
+ }
5816
+ for (const row of rows) {
5817
+ const taskId = String(row.id || '').trim();
5818
+ const eventId = String(row.latest_event_id || '').trim();
5819
+ const timestamp = String(row.latest_event_at || '');
5820
+ if (!taskId || !eventId)
5821
+ continue;
5822
+ let details = {};
5823
+ try {
5824
+ const parsed = row.latest_event_details_json ? JSON.parse(String(row.latest_event_details_json)) : {};
5825
+ details = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
5826
+ }
5827
+ catch {
5828
+ details = {};
5829
+ }
5830
+ considerLatestActivity(taskId, {
5831
+ id: `event:${eventId}`,
5832
+ type: 'event',
5833
+ timestamp,
5834
+ event: {
5835
+ action: String(row.latest_event_action || ''),
5836
+ actor: String(row.latest_event_actor || 'system').trim() || 'system',
5837
+ actorType: row.latest_event_actor_type === 'human' || row.latest_event_actor_type === 'ai'
5838
+ ? row.latest_event_actor_type
5839
+ : 'system',
5840
+ details,
5841
+ },
5842
+ });
5843
+ }
5844
+ return rows.map((row) => {
5845
+ const id = String(row.id || '').trim();
5846
+ const scheduledDate = this.sanitizeDateOnly(row.scheduled_date);
5847
+ const dueDate = this.sanitizeDateOnly(row.due_date);
5848
+ const referenceNumber = this.sanitizeTaskReferenceNumber(row.reference_number);
5849
+ const localReferenceNumber = this.sanitizeTaskReferenceNumber(row.local_reference_number);
5850
+ let taxonomies = {};
5851
+ try {
5852
+ taxonomies = JSON.parse(row.taxonomies || '{}');
5853
+ }
5854
+ catch {
5855
+ taxonomies = {};
5856
+ }
5857
+ const checklistCounts = checklistByTask.get(id) || { total: 0, completed: 0, revision: '' };
5858
+ const commentCount = commentCountByTask.get(id) || 0;
5859
+ const attachmentCount = attachmentCountByTask.get(id) || 0;
5860
+ const relationshipCount = relationshipCountByTask.get(id) || 0;
5861
+ const detailRevision = JSON.stringify([
5862
+ String(row.updated_at || row.created_at || ''),
5863
+ commentCount,
5864
+ commentRevisionByTask.get(id) || '',
5865
+ checklistCounts.total,
5866
+ checklistCounts.completed,
5867
+ checklistCounts.revision,
5868
+ attachmentCount,
5869
+ attachmentRevisionByTask.get(id) || '',
5870
+ relationshipCount,
5871
+ relationshipRevisionByTask.get(id) || '',
5872
+ latestActivityRevisionByTask.get(id) || '',
5873
+ ]);
5874
+ return {
5875
+ id,
5876
+ referenceNumber,
5877
+ localReferenceNumber,
5878
+ referenceLabel: deriveTaskReferenceLabel({ referenceNumber, localReferenceNumber }),
5879
+ title: String(row.title || ''),
5880
+ status: this.normalizeStatusValue(row.status),
5881
+ priority: this.normalizePriorityValue(row.priority),
5882
+ complexity: Number(row.complexity || 3),
5883
+ type: String(row.type || 'task'),
5884
+ category: String(row.category || 'default'),
5885
+ createdAt: String(row.created_at || ''),
5886
+ updatedAt: row.updated_at ? String(row.updated_at) : undefined,
5887
+ completedAt: row.completed_at ? String(row.completed_at) : null,
5888
+ canceledReason: row.canceled_reason ? String(row.canceled_reason) : null,
5889
+ isArchived: row.is_archived === 1 || row.is_archived === true,
5890
+ scheduledDate,
5891
+ dueDate,
5892
+ scheduledWeekKey: row.scheduled_week_key || (scheduledDate ? this.dateOnlyToIsoWeekKey(scheduledDate) : null),
5893
+ orderInDay: this.sanitizeOrderInDay(row.order_in_day),
5894
+ workstreamOrder: this.sanitizeOrderInDay(row.workstream_order),
5895
+ workstreamId: this.sanitizeWorkstreamId(row.workstream_id),
5896
+ cardCoverAssetId: typeof row.card_cover_asset_id === 'string' && row.card_cover_asset_id.trim()
5897
+ ? row.card_cover_asset_id.trim()
5898
+ : null,
5899
+ cardCoverAttachment: coverAttachmentByTask.get(id) || null,
5900
+ taxonomies,
5901
+ createdBy: this.normalizeCreatedBy(row.created_by, normalizedWorkspaceId, { preserveGenericAi: true }),
5902
+ assignee: this.sanitizeAssignee(row.assignee, normalizedWorkspaceId),
5903
+ isSummary: true,
5904
+ commentCount,
5905
+ checklistCount: checklistCounts.total,
5906
+ completedChecklistCount: checklistCounts.completed,
5907
+ attachmentCount,
5908
+ relationshipCount,
5909
+ latestActivity: latestActivityByTask.get(id) || null,
5910
+ detailRevision,
5911
+ };
5912
+ });
5913
+ }
5914
+ listActiveTaskSummaries(workspaceId) {
5915
+ return { tasks: this.listTaskSummaries(workspaceId, false) };
5916
+ }
5917
+ listArchiveSummaries(workspaceId) {
5918
+ return { archived: this.listTaskSummaries(workspaceId, true) };
5919
+ }
5920
+ getTaskDetailRevision(id, workspaceId) {
5921
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
5922
+ const taskId = String(id || '').trim();
5923
+ if (!taskId)
5924
+ return null;
5925
+ const row = this.db.prepare(`
5926
+ WITH target_task AS (
5927
+ SELECT id, created_at, updated_at
5928
+ FROM tasks
5929
+ WHERE tenant_id = ? AND workspace_id = ? AND id = ?
5930
+ ),
5931
+ comment_stats AS (
5932
+ SELECT COUNT(*) AS count, MAX(c.timestamp) AS revision
5933
+ FROM comments c
5934
+ JOIN target_task t ON t.id = c.task_id
5935
+ WHERE c.tenant_id = ? AND c.workspace_id = ?
5936
+ ),
5937
+ latest_comment AS (
5938
+ SELECT c.id, c.timestamp
5939
+ FROM comments c
5940
+ JOIN target_task t ON t.id = c.task_id
5941
+ WHERE c.tenant_id = ? AND c.workspace_id = ?
5942
+ ORDER BY c.timestamp DESC, c.id DESC
5943
+ LIMIT 1
5944
+ ),
5945
+ checklist_stats AS (
5946
+ SELECT COUNT(*) AS count,
5947
+ SUM(CASE WHEN c.is_completed = 1 THEN 1 ELSE 0 END) AS completed_count,
5948
+ MAX(COALESCE(c.updated_at, c.created_at)) AS revision
5949
+ FROM checklist_items c
5950
+ JOIN target_task t ON t.id = c.task_id
5951
+ WHERE c.tenant_id = ? AND c.workspace_id = ?
5952
+ ),
5953
+ attachment_stats AS (
5954
+ SELECT COUNT(a.asset_id) AS count,
5955
+ MAX(CASE
5956
+ WHEN COALESCE(a.updated_at, '') > COALESCE(l.updated_at, '') THEN a.updated_at
5957
+ ELSE l.updated_at
5958
+ END) AS revision
5959
+ FROM workspace_asset_links l
5960
+ JOIN target_task t ON t.id = l.task_id
5961
+ JOIN workspace_assets a
5962
+ ON a.tenant_id = l.tenant_id
5963
+ AND a.workspace_id = l.workspace_id
5964
+ AND a.asset_id = l.asset_id
5965
+ AND a.deleted_at IS NULL
5966
+ WHERE l.tenant_id = ? AND l.workspace_id = ?
5967
+ AND l.target_type = 'task' AND l.deleted_at IS NULL
5968
+ ),
5969
+ relationship_rows AS (
5970
+ SELECT r.updated_at
5971
+ FROM task_relationships r
5972
+ JOIN target_task t ON t.id = r.source_task_id
5973
+ WHERE r.tenant_id = ? AND r.workspace_id = ? AND r.deleted_at IS NULL
5974
+ UNION ALL
5975
+ SELECT r.updated_at
5976
+ FROM task_relationships r
5977
+ JOIN target_task t ON t.id = r.target_task_id
5978
+ WHERE r.tenant_id = ? AND r.workspace_id = ? AND r.deleted_at IS NULL
5979
+ AND r.target_task_id <> r.source_task_id
5980
+ ),
5981
+ relationship_stats AS (
5982
+ SELECT COUNT(*) AS count, MAX(updated_at) AS revision
5983
+ FROM relationship_rows
5984
+ ),
5985
+ latest_event AS (
5986
+ SELECT e.id, e.created_at
5987
+ FROM entity_events e
5988
+ JOIN target_task t ON t.id = e.entity_id
5989
+ WHERE e.tenant_id = ? AND e.workspace_id = ?
5990
+ AND e.entity_type = 'task'
5991
+ AND e.action <> 'task-comment-added'
5992
+ AND NOT (
5993
+ e.action = 'task-updated'
5994
+ AND REPLACE(COALESCE(e.details_json, ''), ' ', '') LIKE '%"saveSource":"autosave"%'
5995
+ AND REPLACE(COALESCE(e.details_json, ''), ' ', '') LIKE '%"changes":{}%'
5996
+ )
5997
+ ORDER BY e.created_at DESC, e.id DESC
5998
+ LIMIT 1
5999
+ )
6000
+ SELECT t.created_at, t.updated_at,
6001
+ comments.count AS comment_count, comments.revision AS comment_revision,
6002
+ latest_comment.id AS latest_comment_id,
6003
+ latest_comment.timestamp AS latest_comment_at,
6004
+ checklist.count AS checklist_count,
6005
+ checklist.completed_count AS completed_checklist_count,
6006
+ checklist.revision AS checklist_revision,
6007
+ attachments.count AS attachment_count,
6008
+ attachments.revision AS attachment_revision,
6009
+ relationships.count AS relationship_count,
6010
+ relationships.revision AS relationship_revision,
6011
+ latest_event.id AS latest_event_id,
6012
+ latest_event.created_at AS latest_event_at
6013
+ FROM target_task t
6014
+ CROSS JOIN comment_stats comments
6015
+ CROSS JOIN checklist_stats checklist
6016
+ CROSS JOIN attachment_stats attachments
6017
+ CROSS JOIN relationship_stats relationships
6018
+ LEFT JOIN latest_comment ON 1 = 1
6019
+ LEFT JOIN latest_event ON 1 = 1
6020
+ `).get(this.tenantId, normalizedWorkspaceId, taskId, this.tenantId, normalizedWorkspaceId, this.tenantId, normalizedWorkspaceId, this.tenantId, normalizedWorkspaceId, this.tenantId, normalizedWorkspaceId, this.tenantId, normalizedWorkspaceId, this.tenantId, normalizedWorkspaceId, this.tenantId, normalizedWorkspaceId);
6021
+ if (!row)
6022
+ return null;
6023
+ const latestCommentId = String(row.latest_comment_id || '').trim();
6024
+ const latestCommentAt = String(row.latest_comment_at || '');
6025
+ const latestEventId = String(row.latest_event_id || '').trim();
6026
+ const latestEventAt = String(row.latest_event_at || '');
6027
+ const commentTimestamp = Date.parse(latestCommentAt);
6028
+ const eventTimestamp = Date.parse(latestEventAt);
6029
+ const commentActivityId = latestCommentId ? `comment:${latestCommentId}` : '';
6030
+ const eventActivityId = latestEventId ? `event:${latestEventId}` : '';
6031
+ const latestActivityRevision = !latestEventId
6032
+ || (latestCommentId && ((Number.isFinite(commentTimestamp) && !Number.isFinite(eventTimestamp))
6033
+ || commentTimestamp > eventTimestamp
6034
+ || (commentTimestamp === eventTimestamp && commentActivityId.localeCompare(eventActivityId) > 0)))
6035
+ ? (latestCommentId ? `${latestCommentAt}:${commentActivityId}` : '')
6036
+ : `${latestEventAt}:${eventActivityId}`;
6037
+ return JSON.stringify([
6038
+ String(row.updated_at || row.created_at || ''),
6039
+ Number(row.comment_count || 0),
6040
+ String(row.comment_revision || ''),
6041
+ Number(row.checklist_count || 0),
6042
+ Number(row.completed_checklist_count || 0),
6043
+ String(row.checklist_revision || ''),
6044
+ Number(row.attachment_count || 0),
6045
+ String(row.attachment_revision || ''),
6046
+ Number(row.relationship_count || 0),
6047
+ String(row.relationship_revision || ''),
6048
+ latestActivityRevision,
6049
+ ]);
6050
+ }
6051
+ getTaskDetailSnapshot(id, workspaceId) {
6052
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
6053
+ return this.db.transaction(() => {
6054
+ if (this.db.provider === 'postgres') {
6055
+ this.db.exec('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
6056
+ }
6057
+ const task = this.getTask(id, normalizedWorkspaceId);
6058
+ if (!task)
6059
+ return null;
6060
+ const detailRevision = this.getTaskDetailRevision(id, normalizedWorkspaceId);
6061
+ return detailRevision ? { ...task, detailRevision } : null;
6062
+ })();
6063
+ }
6064
+ updateTaskIfDetailRevisionMatches(id, expectedDetailRevision, updates, workspaceId, options) {
6065
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
6066
+ const updated = this.db.transaction(() => {
6067
+ if (this.db.provider === 'postgres') {
6068
+ this.db.exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
6069
+ }
6070
+ const currentDetailRevision = this.getTaskDetailRevision(id, normalizedWorkspaceId);
6071
+ if (currentDetailRevision === null)
6072
+ return null;
6073
+ if (currentDetailRevision !== expectedDetailRevision) {
6074
+ throw new TaskforceRuleError('Task changed after this editor was loaded. Reload the latest task before saving.', 409, 'TASK_DETAIL_REVISION_CONFLICT', { currentDetailRevision });
6075
+ }
6076
+ const task = this.updateTask(id, updates, normalizedWorkspaceId, {
6077
+ actorRef: options?.actorRef,
6078
+ saveSource: options?.saveSource,
6079
+ persistBackup: false,
6080
+ emitRealtime: false,
6081
+ });
6082
+ if (!task)
6083
+ return null;
6084
+ const detailRevision = this.getTaskDetailRevision(id, normalizedWorkspaceId);
6085
+ return detailRevision ? { ...task, detailRevision } : null;
6086
+ })();
6087
+ if (updated) {
6088
+ this.persistBackup();
6089
+ this.emitRealtimeMutation({
6090
+ workspaceId: normalizedWorkspaceId,
6091
+ entityType: 'task',
6092
+ changeType: 'upsert',
6093
+ entityIds: [updated.id],
6094
+ });
6095
+ }
6096
+ return updated;
6097
+ }
6098
+ assertTaskDetailRevisionForUpdate(id, expectedDetailRevision, workspaceId) {
6099
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
6100
+ const lockSql = this.db.provider === 'postgres'
6101
+ ? `SELECT id FROM tasks WHERE tenant_id = ? AND workspace_id = ? AND id = ? FOR UPDATE`
6102
+ : `SELECT id FROM tasks WHERE tenant_id = ? AND workspace_id = ? AND id = ?`;
6103
+ const task = this.db.prepare(lockSql).get(this.tenantId, normalizedWorkspaceId, id);
6104
+ if (!task)
6105
+ throw new TaskforceRuleError('Task not found.', 404, 'TASK_NOT_FOUND');
6106
+ const currentDetailRevision = this.getTaskDetailRevision(id, normalizedWorkspaceId);
6107
+ if (currentDetailRevision !== expectedDetailRevision) {
6108
+ throw new TaskforceRuleError('Task changed after this editor was loaded. Reload the latest task before saving.', 409, 'TASK_DETAIL_REVISION_CONFLICT', { currentDetailRevision });
6109
+ }
6110
+ }
5575
6111
  /**
5576
6112
  * Filter-pushdown listing used by read-only surfaces that don't need the
5577
6113
  * full task graph (MCP list_tasks, CLI summaries). Avoids the per-task
@@ -5750,41 +6286,53 @@ export class TaskforceCore {
5750
6286
  };
5751
6287
  });
5752
6288
  }
5753
- searchTaskSummaries(query, scope = 'all', workspaceId) {
6289
+ searchTaskSummaries(query, scope = 'all', workspaceId, options) {
5754
6290
  const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
5755
- const normalizedQuery = String(query || '').trim();
5756
- if (!normalizedQuery)
6291
+ const normalizedQuery = normalizeTaskSearchQuery(String(query || ''));
6292
+ if (!normalizedQuery.raw)
5757
6293
  return [];
5758
- const exactReferenceNumber = parseTaskReference(normalizedQuery);
5759
- const exactLocalReferenceNumber = parseLocalTaskReference(normalizedQuery);
5760
- const term = `%${normalizedQuery}%`;
6294
+ const exactReferenceNumber = parseTaskReference(normalizedQuery.raw);
6295
+ const exactLocalReferenceNumber = parseLocalTaskReference(normalizedQuery.raw);
6296
+ const searchableText = `
6297
+ COALESCE(title, '') || ' ' ||
6298
+ COALESCE(description, '') || ' ' ||
6299
+ COALESCE(id, '') || ' ' ||
6300
+ CASE WHEN reference_number IS NULL THEN '' ELSE 'T-' || CAST(reference_number AS TEXT) END || ' ' ||
6301
+ CASE WHEN local_reference_number IS NULL THEN '' ELSE 'LT-' || CAST(local_reference_number AS TEXT) END || ' ' ||
6302
+ COALESCE(category, '') || ' ' ||
6303
+ COALESCE(type, '') || ' ' ||
6304
+ COALESCE(CAST(priority AS TEXT), '') || ' ' ||
6305
+ COALESCE(status, '') || ' ' ||
6306
+ COALESCE(assignee, '') || ' ' ||
6307
+ COALESCE(canceled_reason, '')
6308
+ `;
6309
+ const escapedTerms = normalizedQuery.tokens.map((token) => (`%${token.replace(/[\\%_]/g, '\\$&')}%`));
5761
6310
  const clauses = [
5762
6311
  'tenant_id = ?',
5763
6312
  'workspace_id = ?',
5764
- '(title LIKE ? OR description LIKE ? OR id LIKE ? OR reference_number = ? OR local_reference_number = ?)',
6313
+ ...escapedTerms.map(() => `LOWER(${searchableText}) LIKE ? ESCAPE '\\'`),
5765
6314
  ];
5766
6315
  const params = [
5767
6316
  this.tenantId,
5768
6317
  normalizedWorkspaceId,
5769
- term,
5770
- term,
5771
- term,
5772
- exactReferenceNumber || -1,
5773
- exactLocalReferenceNumber || -1,
6318
+ ...escapedTerms,
5774
6319
  ];
5775
6320
  if (scope === 'active')
5776
6321
  clauses.push('is_archived = 0');
5777
6322
  if (scope === 'archive')
5778
6323
  clauses.push('is_archived = 1');
6324
+ const limit = Math.max(1, Math.min(51, Math.trunc(options?.limit ?? 50)));
6325
+ const offset = Math.max(0, Math.trunc(options?.offset ?? 0));
5779
6326
  const rows = this.db.prepare(`
5780
- SELECT id, reference_number, local_reference_number, title, status, assignee, category, priority
6327
+ SELECT id, reference_number, local_reference_number, title, status, assignee, category, priority, is_archived
5781
6328
  FROM tasks
5782
6329
  WHERE ${clauses.join(' AND ')}
5783
6330
  ORDER BY
5784
6331
  CASE WHEN id = ? OR reference_number = ? OR local_reference_number = ? THEN 0 ELSE 1 END,
5785
- created_at DESC
5786
- LIMIT 50
5787
- `).all(...params, normalizedQuery, exactReferenceNumber || -1, exactLocalReferenceNumber || -1);
6332
+ created_at DESC,
6333
+ id DESC
6334
+ LIMIT ? OFFSET ?
6335
+ `).all(...params, normalizedQuery.raw, exactReferenceNumber || -1, exactLocalReferenceNumber || -1, limit, offset);
5788
6336
  return rows.map((row) => {
5789
6337
  const referenceNumber = this.sanitizeTaskReferenceNumber(row.reference_number);
5790
6338
  const localReferenceNumber = this.sanitizeTaskReferenceNumber(row.local_reference_number);
@@ -5798,6 +6346,7 @@ export class TaskforceCore {
5798
6346
  assignee: String(row.assignee || '').trim() || null,
5799
6347
  category: String(row.category || ''),
5800
6348
  priority: this.normalizePriorityValue(row.priority),
6349
+ isArchived: row.is_archived === 1 || row.is_archived === true,
5801
6350
  };
5802
6351
  });
5803
6352
  }
@@ -6433,7 +6982,7 @@ export class TaskforceCore {
6433
6982
  OR LOWER(COALESCE(u.system_role, 'user')) LIKE ?
6434
6983
  )`
6435
6984
  : '';
6436
- const params = [this.tenantId];
6985
+ const params = [INTERNAL_SUBSCRIPTION_MODEL_FEATURE_KEY, this.tenantId];
6437
6986
  if (query) {
6438
6987
  params.push(queryPattern, queryPattern, queryPattern, queryPattern, queryPattern, queryPattern, queryPattern);
6439
6988
  }
@@ -6446,6 +6995,14 @@ export class TaskforceCore {
6446
6995
  COALESCE(u.system_role, 'user') AS system_role,
6447
6996
  COALESCE(u.beta_access, 1) AS beta_access,
6448
6997
  COALESCE(u.is_disabled, 0) AS is_disabled,
6998
+ CASE WHEN EXISTS (
6999
+ SELECT 1
7000
+ FROM user_feature_grants g
7001
+ WHERE g.tenant_id = u.tenant_id
7002
+ AND g.user_id = u.id
7003
+ AND g.feature_key = ?
7004
+ AND g.enabled = 1
7005
+ ) THEN 1 ELSE 0 END AS subscription_model_access,
6449
7006
  u.created_at,
6450
7007
  u.updated_at,
6451
7008
  u.email_verified_at,
@@ -6477,6 +7034,9 @@ export class TaskforceCore {
6477
7034
  systemRole: this.normalizeSystemRole(row.system_role, 'user'),
6478
7035
  betaAccess: row.beta_access === undefined || row.beta_access === null ? true : Boolean(row.beta_access),
6479
7036
  disabled: Boolean(row.is_disabled),
7037
+ internalFeatureGrants: Boolean(row.subscription_model_access)
7038
+ ? [INTERNAL_SUBSCRIPTION_MODEL_FEATURE_KEY]
7039
+ : [],
6480
7040
  planId: row.plan_id ? String(row.plan_id) : null,
6481
7041
  planName: row.plan_name ? String(row.plan_name) : null,
6482
7042
  planVersionId: row.plan_version_id ? String(row.plan_version_id) : null,
@@ -6550,6 +7110,53 @@ export class TaskforceCore {
6550
7110
  `).run(enabled ? 1 : 0, new Date().toISOString(), this.tenantId, normalizedUserId);
6551
7111
  return Number(result?.changes || 0) > 0;
6552
7112
  }
7113
+ hasUserFeatureGrant(userId, featureKey) {
7114
+ const normalizedUserId = String(userId || '').trim();
7115
+ if (!normalizedUserId || !isSupportedInternalUserFeatureKey(featureKey))
7116
+ return false;
7117
+ const row = this.db.prepare(`
7118
+ SELECT enabled
7119
+ FROM user_feature_grants
7120
+ WHERE tenant_id = ? AND user_id = ? AND feature_key = ?
7121
+ LIMIT 1
7122
+ `).get(this.tenantId, normalizedUserId, featureKey);
7123
+ return Boolean(row?.enabled);
7124
+ }
7125
+ listUserFeatureGrants(userId) {
7126
+ const normalizedUserId = String(userId || '').trim();
7127
+ if (!normalizedUserId)
7128
+ return [];
7129
+ const rows = this.db.prepare(`
7130
+ SELECT feature_key
7131
+ FROM user_feature_grants
7132
+ WHERE tenant_id = ? AND user_id = ? AND enabled = 1
7133
+ ORDER BY feature_key
7134
+ `).all(this.tenantId, normalizedUserId);
7135
+ return rows
7136
+ .map((row) => row.feature_key)
7137
+ .filter(isSupportedInternalUserFeatureKey);
7138
+ }
7139
+ setUserFeatureGrant(userId, featureKey, enabled, updatedBy) {
7140
+ const normalizedUserId = String(userId || '').trim();
7141
+ if (!normalizedUserId || !isSupportedInternalUserFeatureKey(featureKey))
7142
+ return false;
7143
+ const user = this.db.prepare(`
7144
+ SELECT id FROM users WHERE tenant_id = ? AND id = ? LIMIT 1
7145
+ `).get(this.tenantId, normalizedUserId);
7146
+ if (!user?.id)
7147
+ return false;
7148
+ const now = new Date().toISOString();
7149
+ this.db.prepare(`
7150
+ INSERT INTO user_feature_grants (
7151
+ tenant_id, user_id, feature_key, enabled, created_at, updated_at, updated_by
7152
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
7153
+ ON CONFLICT (tenant_id, user_id, feature_key) DO UPDATE SET
7154
+ enabled = excluded.enabled,
7155
+ updated_at = excluded.updated_at,
7156
+ updated_by = excluded.updated_by
7157
+ `).run(this.tenantId, normalizedUserId, featureKey, enabled ? 1 : 0, now, now, String(updatedBy || '').trim() || null);
7158
+ return true;
7159
+ }
6553
7160
  markUserEmailVerified(userId) {
6554
7161
  const normalizedUserId = String(userId || '').trim();
6555
7162
  if (!normalizedUserId)
@@ -6656,6 +7263,14 @@ export class TaskforceCore {
6656
7263
  if (hasCommercialBillingState) {
6657
7264
  throw new TaskforceRuleError('Cannot purge a user with active commercial billing state', 409, 'SYSTEM_USER_PURGE_BLOCKED_BILLING_STATE');
6658
7265
  }
7266
+ const modelConnectionCount = Number(this.db.prepare(`
7267
+ SELECT COUNT(*) AS count
7268
+ FROM model_provider_connections
7269
+ WHERE tenant_id = ? AND owner_user_id = ?
7270
+ `).get(this.tenantId, targetUserId)?.count || 0);
7271
+ if (modelConnectionCount > 0) {
7272
+ throw new TaskforceRuleError('Disconnect and clean up the user model connections before purging this account', 409, 'SYSTEM_USER_PURGE_BLOCKED_MODEL_CONNECTIONS');
7273
+ }
6659
7274
  const now = new Date().toISOString();
6660
7275
  const runCount = (sql, ...params) => Number(this.db.prepare(sql).run(...params)?.changes || 0);
6661
7276
  const workspaceIds = this.db.prepare(`
@@ -6755,6 +7370,11 @@ export class TaskforceCore {
6755
7370
  WHERE tenant_id = ?
6756
7371
  AND user_id = ?
6757
7372
  `, this.tenantId, targetUserId);
7373
+ const deletedUserFeatureGrantCount = runCount(`
7374
+ DELETE FROM user_feature_grants
7375
+ WHERE tenant_id = ?
7376
+ AND user_id = ?
7377
+ `, this.tenantId, targetUserId);
6758
7378
  const deletedUserBillingCount = runCount(`
6759
7379
  DELETE FROM user_billing
6760
7380
  WHERE tenant_id = ?
@@ -6782,6 +7402,7 @@ export class TaskforceCore {
6782
7402
  deletedMcpOauthConnectorGrantCount,
6783
7403
  deletedUiStateCount,
6784
7404
  deletedUserPreferenceCount,
7405
+ deletedUserFeatureGrantCount,
6785
7406
  deletedEntitlementCount,
6786
7407
  deletedUserBillingCount,
6787
7408
  deletedUserCount
@@ -7229,6 +7850,20 @@ export class TaskforceCore {
7229
7850
  runTaskMetadataOwnershipTransaction: (input) => this.runTaskMetadataOwnershipTransaction(input),
7230
7851
  };
7231
7852
  }
7853
+ buildUnresolvedWorkspaceActor(raw) {
7854
+ const normalized = raw.toLowerCase();
7855
+ const unresolvedKind = (normalized.startsWith('ai-profile-') || this.isKnownAiModelName(normalized)) ? 'ai' : 'human';
7856
+ return {
7857
+ id: raw,
7858
+ kind: unresolvedKind,
7859
+ label: this.formatActorLabel(raw),
7860
+ username: this.normalizeAiProfileUsername(raw, raw),
7861
+ icon: unresolvedKind === 'ai' ? GENERIC_AI_ACTOR_ICON : 'User',
7862
+ color: unresolvedKind === 'ai' ? GENERIC_AI_ACTOR_COLOR : '#22c55e',
7863
+ isNamed: true,
7864
+ meta: { unresolved: true }
7865
+ };
7866
+ }
7232
7867
  resolveWorkspaceActor(workspaceId, actorRef) {
7233
7868
  const raw = String(actorRef || '').trim();
7234
7869
  const normalized = raw.toLowerCase();
@@ -7258,17 +7893,81 @@ export class TaskforceCore {
7258
7893
  if (workspaceUser) {
7259
7894
  return this.workspaceUserToActor(workspaceUser);
7260
7895
  }
7261
- const unresolvedKind = (normalized.startsWith('ai-profile-') || this.isKnownAiModelName(normalized)) ? 'ai' : 'human';
7262
- return {
7263
- id: raw,
7264
- kind: unresolvedKind,
7265
- label: this.formatActorLabel(raw),
7266
- username: this.normalizeAiProfileUsername(raw, raw),
7267
- icon: unresolvedKind === 'ai' ? GENERIC_AI_ACTOR_ICON : 'User',
7268
- color: unresolvedKind === 'ai' ? GENERIC_AI_ACTOR_COLOR : '#22c55e',
7269
- isNamed: true,
7270
- meta: { unresolved: true }
7271
- };
7896
+ return this.buildUnresolvedWorkspaceActor(raw);
7897
+ }
7898
+ resolveWorkspaceActors(workspaceId, actorRefs) {
7899
+ const rawActorRefs = [...new Set(actorRefs
7900
+ .map((actorRef) => String(actorRef || '').trim())
7901
+ .filter(Boolean))];
7902
+ const resolvedActors = new Map();
7903
+ const lookupActorRefs = [];
7904
+ for (const raw of rawActorRefs) {
7905
+ const normalized = raw.toLowerCase();
7906
+ if (normalized === 'unassigned' || normalized === 'none' || normalized === 'null') {
7907
+ resolvedActors.set(raw, null);
7908
+ }
7909
+ else if (this.isHumanActorReference(raw)) {
7910
+ resolvedActors.set(raw, this.buildHumanActorFallback());
7911
+ }
7912
+ else if (this.isGenericAiActorReference(raw)) {
7913
+ resolvedActors.set(raw, this.buildGenericAiActor());
7914
+ }
7915
+ else {
7916
+ lookupActorRefs.push(raw);
7917
+ }
7918
+ }
7919
+ if (lookupActorRefs.length === 0)
7920
+ return resolvedActors;
7921
+ const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
7922
+ const aiProfileRows = this.db.prepare(`
7923
+ SELECT *
7924
+ FROM ai_profiles
7925
+ WHERE tenant_id = ?
7926
+ AND workspace_id = ?
7927
+ ORDER BY COALESCE(last_active_at, updated_at, created_at) DESC, id ASC
7928
+ `).all(this.tenantId, normalizedWorkspaceId);
7929
+ const aiProfileRowsById = new Map(aiProfileRows
7930
+ .map((row) => [String(row.id || '').trim(), row])
7931
+ .filter(([id]) => Boolean(id)));
7932
+ const aiProfileRowsByAlias = new Map();
7933
+ for (const row of aiProfileRows) {
7934
+ const aliases = new Set([String(row.name || '').trim().toLowerCase(), String(row.username || '').trim().toLowerCase()]);
7935
+ for (const alias of aliases) {
7936
+ if (!alias)
7937
+ continue;
7938
+ const matches = aiProfileRowsByAlias.get(alias) ?? [];
7939
+ matches.push(row);
7940
+ aiProfileRowsByAlias.set(alias, matches);
7941
+ }
7942
+ }
7943
+ const workspaceUsersById = new Map(this.listWorkspaceUsers(normalizedWorkspaceId)
7944
+ .map((user) => [user.userId, user]));
7945
+ for (const raw of lookupActorRefs) {
7946
+ const directAiProfileRow = aiProfileRowsById.get(raw);
7947
+ if (directAiProfileRow) {
7948
+ resolvedActors.set(raw, this.aiProfileToActor(this.aiProfileRowToRecord(directAiProfileRow)));
7949
+ continue;
7950
+ }
7951
+ const aliasMatches = aiProfileRowsByAlias.get(raw.toLowerCase()) ?? [];
7952
+ const aliasRowsById = new Map(aliasMatches
7953
+ .map((row) => [String(row.id || '').trim(), row])
7954
+ .filter(([id]) => Boolean(id)));
7955
+ const canonicalAliasIds = [...new Set(aliasMatches
7956
+ .map((row) => this.resolveCanonicalAiProfileId(aliasRowsById, String(row.id || '').trim()))
7957
+ .filter(Boolean))];
7958
+ if (canonicalAliasIds.length === 1) {
7959
+ const canonicalAiProfileRow = aiProfileRowsById.get(canonicalAliasIds[0]);
7960
+ if (canonicalAiProfileRow) {
7961
+ resolvedActors.set(raw, this.aiProfileToActor(this.aiProfileRowToRecord(canonicalAiProfileRow)));
7962
+ continue;
7963
+ }
7964
+ }
7965
+ const workspaceUser = workspaceUsersById.get(raw);
7966
+ resolvedActors.set(raw, workspaceUser
7967
+ ? this.workspaceUserToActor(workspaceUser)
7968
+ : this.buildUnresolvedWorkspaceActor(raw));
7969
+ }
7970
+ return resolvedActors;
7272
7971
  }
7273
7972
  listAssignableActors(workspaceId) {
7274
7973
  const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
@@ -8142,6 +8841,9 @@ export class TaskforceCore {
8142
8841
  regenerateMcpAccessToken(input) {
8143
8842
  return this.mcpTokenService.regenerateMcpAccessToken(input);
8144
8843
  }
8844
+ updateMcpAccessTokenToolProfile(input) {
8845
+ return this.mcpTokenService.updateMcpAccessTokenToolProfile(input);
8846
+ }
8145
8847
  authenticateMcpAccessToken(token, workspaceId) {
8146
8848
  return this.mcpTokenService.authenticateMcpAccessToken(token, workspaceId);
8147
8849
  }
@@ -8169,6 +8871,9 @@ export class TaskforceCore {
8169
8871
  revokeMcpOAuthConnectorGrant(input) {
8170
8872
  return this.mcpTokenService.revokeMcpOAuthConnectorGrant(input);
8171
8873
  }
8874
+ updateMcpOAuthConnectorGrantToolProfile(input) {
8875
+ return this.mcpTokenService.updateMcpOAuthConnectorGrantToolProfile(input);
8876
+ }
8172
8877
  authenticateMcpOAuthAccessToken(token, workspaceId) {
8173
8878
  return this.mcpTokenService.authenticateMcpOAuthAccessToken(token, workspaceId);
8174
8879
  }
@@ -9716,9 +10421,11 @@ export class TaskforceCore {
9716
10421
  return null;
9717
10422
  const patchFields = Object.keys(patch);
9718
10423
  const archivedNestedContentWrite = options?.allowArchivedNestedContentSyncWrite === true
9719
- && patchFields.some((field) => ['comments', 'attachments', 'checklistItems'].includes(field))
10424
+ && patchFields.some((field) => [
10425
+ 'comments', 'attachments', 'checklistItems', 'cardCoverAssetId',
10426
+ ].includes(field))
9720
10427
  && patchFields.every((field) => [
9721
- 'id', 'comments', 'attachments', 'checklistItems',
10428
+ 'id', 'comments', 'attachments', 'checklistItems', 'cardCoverAssetId',
9722
10429
  ].includes(field));
9723
10430
  if (existing.isArchived && !archivedNestedContentWrite) {
9724
10431
  throw new TaskforceRuleError('Archived tasks are read-only. Unarchive first to make changes.', 409, 'TASK_ARCHIVED_READ_ONLY');
@@ -10050,8 +10757,25 @@ export class TaskforceCore {
10050
10757
  return null;
10051
10758
  const submittedAuthor = String(author || '').trim() || 'user';
10052
10759
  const normalizedAuthor = this.normalizeCommentAuthor(author, normalizedWorkspaceId);
10760
+ const explicitCommentId = String(options?.commentId || '').trim();
10761
+ if (explicitCommentId) {
10762
+ const persisted = this.db.prepare(`
10763
+ SELECT task_id, text, author, submitted_author
10764
+ FROM comments
10765
+ WHERE tenant_id = ? AND workspace_id = ? AND id = ?
10766
+ `).get(this.tenantId, normalizedWorkspaceId, explicitCommentId);
10767
+ if (persisted) {
10768
+ if (persisted.task_id !== id
10769
+ || persisted.text !== text
10770
+ || persisted.author !== normalizedAuthor
10771
+ || String(persisted.submitted_author || persisted.author) !== submittedAuthor) {
10772
+ throw new TaskforceRuleError('Task comment identity is assigned to different content.', 409, 'TASK_COMMENT_IDENTITY_CONFLICT');
10773
+ }
10774
+ return this.getTask(id, normalizedWorkspaceId);
10775
+ }
10776
+ }
10053
10777
  const comment = {
10054
- id: this.generateEntityId('comment'),
10778
+ id: explicitCommentId || this.generateEntityId('comment'),
10055
10779
  author: normalizedAuthor,
10056
10780
  submittedAuthor,
10057
10781
  text,
@@ -10094,7 +10818,7 @@ export class TaskforceCore {
10094
10818
  if (options?.persistBackup !== false)
10095
10819
  this.persistBackup();
10096
10820
  const updated = this.getTask(id, normalizedWorkspaceId);
10097
- if (updated) {
10821
+ if (updated && options?.emitRealtime !== false) {
10098
10822
  this.emitRealtimeMutation({
10099
10823
  workspaceId: normalizedWorkspaceId,
10100
10824
  entityType: 'task',