@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
@@ -1,6 +1,5 @@
1
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { CallToolRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
1
+ import { Server } from '@modelcontextprotocol/server';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
4
3
  import * as fs from 'fs';
5
4
  import * as path from 'path';
6
5
  import { createHash, randomUUID } from 'node:crypto';
@@ -32,7 +31,8 @@ import { WorkspaceSyncContentVersionStore, } from '../sync/engine/workspaceSyncC
32
31
  import { WorkspaceSyncPublishedContentResolver } from '../sync/engine/workspaceSyncPublishedContentResolver.js';
33
32
  import { createWorkspaceSyncServerContentRuntime } from '../sync/engine/workspaceSyncServerContentRuntime.js';
34
33
  import { resolveVirusScanConfigFromEnv, scanBufferForThreats } from '../security/virusScan.js';
35
- import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } from '../runtime/appGates.js';
34
+ import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, } from '../runtime/appGates.js';
35
+ import { resolveNodeAppGateAccess } from '../runtime/nodeAppGates.js';
36
36
  import { getDocumentReferenceLabel, parseDocumentReference } from '../utils/documentReferences.js';
37
37
  import { getImageReferenceLabel, parseImageReference } from '../utils/imageReferences.js';
38
38
  import { inferCanonicalAssetKind, isValidCanonicalDocumentAsset } from '../utils/canonicalAssetKind.js';
@@ -79,7 +79,7 @@ import { createDurableTaskChecklistMutationRouter, DurableTaskChecklistRouterInP
79
79
  import { DurableTaskChecklistMutationError } from '../sync/v3/taskChecklistMutationService.js';
80
80
  import { createDurableTaskAttachmentLinksMutationRouter, DurableTaskAttachmentLinksRouterInProgressError, } from '../sync/v3/durableTaskAttachmentLinksMutationRouter.js';
81
81
  import { DurableTaskAttachmentLinksMutationError } from '../sync/v3/taskAttachmentLinksMutationService.js';
82
- import { createDurableWorkflowRuntimeMutationRouter, resolveWorkflowRuntimeMutationCapability, } from '../sync/v3/durableWorkflowRuntimeMutationRouter.js';
82
+ import { areWorkflowRuntimeMutationsFenced, createDurableWorkflowRuntimeMutationRouter, resolveWorkflowRuntimeMutationCapability, } from '../sync/v3/durableWorkflowRuntimeMutationRouter.js';
83
83
  import { serializeCanonicalPlanningAttachmentLinks } from '../sync/planningCanonicalFields.js';
84
84
  import { buildCanonicalTaxonomyStateMutation, } from '../sync/v3/taxonomyStateMutation.js';
85
85
  import { buildTaskDeepLink, buildTaskforceDeepLink, normalizeTaskforceApplicationBaseUrl, } from '../shared/taskDeepLinks.js';
@@ -746,6 +746,9 @@ function createOwnedTaskforceMcpCore(projectRoot, tenantId, localDataOwnerPermit
746
746
  },
747
747
  });
748
748
  }
749
+ export function createStatelessTaskforceMcpServer(options = {}, factory = createTaskforceMcpServer) {
750
+ return factory({ ...options, resourceSubscriptionMode: 'disabled' });
751
+ }
749
752
  export function createTaskforceMcpServer(options = {}) {
750
753
  // In dev (tsx), this module lives in src/mcp/.
751
754
  // In prod, it lives in dist/mcp/.
@@ -753,6 +756,9 @@ export function createTaskforceMcpServer(options = {}) {
753
756
  const PROJECT_NAME = path.basename(PROJECT_ROOT);
754
757
  const logStartup = options.logStartup !== false;
755
758
  const runtimeMode = options.runtimeMode === 'cloud' ? 'cloud' : 'local';
759
+ const modernProtocol = options.protocolEra === 'modern';
760
+ const supportsLegacyResourceSubscriptions = !modernProtocol
761
+ && options.resourceSubscriptionMode === 'legacy-long-lived';
756
762
  const applicationBaseUrl = normalizeTaskforceApplicationBaseUrl(options.applicationBaseUrl)
757
763
  || (runtimeMode === 'cloud'
758
764
  ? resolveCloudApplicationBaseUrl()
@@ -835,8 +841,6 @@ export function createTaskforceMcpServer(options = {}) {
835
841
  };
836
842
  const operationIdentityScope = options.operationIdentityScope
837
843
  ? {
838
- generation: String(options.operationIdentityScope.generation || '').trim(),
839
- sessionId: String(options.operationIdentityScope.sessionId || '').trim(),
840
844
  connectionInstance: String(options.operationIdentityScope.connectionInstance || '').trim(),
841
845
  }
842
846
  : null;
@@ -844,7 +848,7 @@ export function createTaskforceMcpServer(options = {}) {
844
848
  throw new Error('Shared MCP operation identity scope is incomplete.');
845
849
  }
846
850
  const runtimeRequestNamespace = operationIdentityScope
847
- ? fingerprintWorkspaceSyncPayload({ kind: 'shared-mcp-session', ...operationIdentityScope })
851
+ ? fingerprintWorkspaceSyncPayload({ kind: 'shared-mcp-process', ...operationIdentityScope })
848
852
  : runtimeMode === 'cloud'
849
853
  ? String(options.auth?.requestId || '').trim() || randomUUID()
850
854
  : randomUUID();
@@ -1203,12 +1207,14 @@ export function createTaskforceMcpServer(options = {}) {
1203
1207
  }, {
1204
1208
  capabilities: {
1205
1209
  resources: {
1206
- subscribe: true,
1207
- listChanged: true,
1210
+ ...(supportsLegacyResourceSubscriptions ? { subscribe: true } : {}),
1211
+ ...(!modernProtocol ? { listChanged: true } : {}),
1208
1212
  },
1209
1213
  tools: {},
1210
1214
  },
1211
- instructions: `Before writes, check get_current_profile; if unbound, use resolve_profile. Keep profileToken private. Use T-, IN-, WS-, I-, and D- references. Link url as [reference](url). For tracked work, prefer start_task and finish_task.`,
1215
+ instructions: runtimeMode === 'cloud' && options.auth?.actorType === 'token'
1216
+ ? `Cloud access-token clients must pass profileToken on every write; obtain it from resolve_profile and keep it private. Use T-, IN-, WS-, I-, and D- references. Link url as [reference](url). For tracked work, prefer start_task and finish_task.`
1217
+ : `Before writes, check get_current_profile; if unbound, use resolve_profile. Keep profileToken private. Use T-, IN-, WS-, I-, and D- references. Link url as [reference](url). For tracked work, prefer start_task and finish_task.`,
1212
1218
  });
1213
1219
  const TOOL_PERMISSIONS = MCP_TOOL_PERMISSIONS;
1214
1220
  const RESOURCE_PERMISSIONS = {
@@ -1311,9 +1317,10 @@ export function createTaskforceMcpServer(options = {}) {
1311
1317
  function buildBoundedTextContent(content, maxChars) {
1312
1318
  const requestedMaxChars = Number(maxChars);
1313
1319
  const hasRequestedMaxChars = maxChars !== undefined && maxChars !== null && maxChars !== '';
1314
- const resolvedMaxChars = hasRequestedMaxChars && Number.isFinite(requestedMaxChars)
1315
- ? Math.max(1, Math.floor(requestedMaxChars))
1316
- : 200000;
1320
+ if (hasRequestedMaxChars && (!Number.isInteger(requestedMaxChars) || requestedMaxChars < 1)) {
1321
+ throw new McpPublicError('maxChars must be a positive integer');
1322
+ }
1323
+ const resolvedMaxChars = hasRequestedMaxChars ? requestedMaxChars : 200000;
1317
1324
  const characters = Array.from(content);
1318
1325
  const totalChars = characters.length;
1319
1326
  const boundedContent = totalChars > resolvedMaxChars
@@ -1405,8 +1412,10 @@ export function createTaskforceMcpServer(options = {}) {
1405
1412
  }
1406
1413
  const requestedLimit = Number(options?.limit);
1407
1414
  const limit = Number.isFinite(requestedLimit)
1408
- ? Math.max(1, Math.min(50, Math.trunc(requestedLimit)))
1415
+ ? Math.max(1, Math.min(51, Math.trunc(requestedLimit)))
1409
1416
  : 10;
1417
+ const requestedOffset = Number(options?.offset);
1418
+ const offset = Number.isFinite(requestedOffset) ? Math.max(0, Math.trunc(requestedOffset)) : 0;
1410
1419
  const normalize = (value) => typeof value === 'string' ? value.trim().toLowerCase() : '';
1411
1420
  const tokenize = (value) => normalize(value).split(/[^a-z0-9]+/i).filter(Boolean);
1412
1421
  const allImages = workspaceAssetStore.listAllByWorkspace(ACTIVE_WORKSPACE_ID, { kind: 'image' });
@@ -1450,8 +1459,10 @@ export function createTaskforceMcpServer(options = {}) {
1450
1459
  return [{ asset, matchedOn: Array.from(matchedOn), score }];
1451
1460
  });
1452
1461
  return scoredMatches
1453
- .sort((a, b) => b.score - a.score || String(b.asset.updatedAt || '').localeCompare(String(a.asset.updatedAt || '')))
1454
- .slice(0, limit);
1462
+ .sort((a, b) => b.score - a.score
1463
+ || String(b.asset.updatedAt || '').localeCompare(String(a.asset.updatedAt || ''))
1464
+ || a.asset.assetId.localeCompare(b.asset.assetId))
1465
+ .slice(offset, offset + limit);
1455
1466
  }
1456
1467
  const searchDocumentAssets = documentHelpers.searchDocumentAssets;
1457
1468
  function buildImageDescriptor(asset) {
@@ -1465,7 +1476,9 @@ export function createTaskforceMcpServer(options = {}) {
1465
1476
  sizeBytes: typeof asset.sizeBytes === 'number' ? asset.sizeBytes : null,
1466
1477
  ...(runtimeMode === 'local' ? { storageKey: asset.storageKey ?? null } : {}),
1467
1478
  updatedAt: asset.updatedAt ?? null,
1468
- apiUrl: `/api/taskforce/context/${encodeURIComponent(String(asset.storageKey || '').trim())}`,
1479
+ ...(runtimeMode === 'local'
1480
+ ? { apiUrl: `/api/taskforce/context/${encodeURIComponent(String(asset.storageKey || '').trim())}` }
1481
+ : {}),
1469
1482
  };
1470
1483
  return withAttachmentUrl({ ...descriptor, kind: 'image' });
1471
1484
  }
@@ -1518,6 +1531,7 @@ export function createTaskforceMcpServer(options = {}) {
1518
1531
  || `Image ${normalizedValue} not found in workspace ${ACTIVE_WORKSPACE_ID}. If you only know a keyword, filename, or rough title, call search_images first.`, 'MCP_NOT_FOUND', 404);
1519
1532
  }
1520
1533
  const auditActorType = options.auth?.actorType || (runtimeMode === 'cloud' ? 'user' : 'local');
1534
+ const requiresExplicitTokenAuthorship = runtimeMode === 'cloud' && auditActorType === 'token';
1521
1535
  const auditActorUserId = String(options.auth?.userId || (runtimeMode === 'cloud' ? 'anonymous' : 'local-mcp')).trim() || 'anonymous';
1522
1536
  const auditTokenId = options.auth?.tokenId ? String(options.auth.tokenId).trim() : null;
1523
1537
  const configuredAiProfileId = options.auth?.aiProfileId ? String(options.auth.aiProfileId).trim() || null : null;
@@ -1591,7 +1605,7 @@ export function createTaskforceMcpServer(options = {}) {
1591
1605
  const shouldBindOAuthGrant = auditActorType === 'oauth'
1592
1606
  && Boolean(auditTokenId)
1593
1607
  && configuredAiProfileId !== profileId;
1594
- const shouldApplyClientContext = Boolean((configuredMcpClientId && configuredMcpClientId !== 'general')
1608
+ const shouldApplyClientContext = auditActorType !== 'token' && Boolean((configuredMcpClientId && configuredMcpClientId !== 'general')
1595
1609
  || configuredMcpConnectionId);
1596
1610
  if (!shouldBindOAuthGrant && !shouldApplyClientContext)
1597
1611
  return;
@@ -1755,6 +1769,10 @@ export function createTaskforceMcpServer(options = {}) {
1755
1769
  return resolved;
1756
1770
  }
1757
1771
  function findTrustedBoundProfile(options) {
1772
+ // A generic access token proves workspace/tool authorization, not which AI
1773
+ // is acting. Never use its historical connection row as authorship proof.
1774
+ if (requiresExplicitTokenAuthorship)
1775
+ return null;
1758
1776
  const grantProfile = findProfileForConfiguredGrant();
1759
1777
  const connectionProfile = findProfileForConfiguredConnection(options);
1760
1778
  if (grantProfile && connectionProfile && grantProfile.id !== connectionProfile.id) {
@@ -1771,6 +1789,19 @@ export function createTaskforceMcpServer(options = {}) {
1771
1789
  }
1772
1790
  return profile;
1773
1791
  }
1792
+ function assertOAuthProfileClientCompatibility(profile) {
1793
+ if (auditActorType !== 'oauth' || !configuredMcpClientId)
1794
+ return;
1795
+ const connection = profile.providerMetadata?.connection;
1796
+ const storedClientId = connection && typeof connection === 'object' && !Array.isArray(connection)
1797
+ ? parseMcpClientId(connection.clientId)
1798
+ : null;
1799
+ const inferredClientId = inferLegacyMcpClientIdFromProfile(profile);
1800
+ const profileClientId = storedClientId || inferredClientId;
1801
+ if (!profileClientId || profileClientId === configuredMcpClientId)
1802
+ return;
1803
+ throw new TaskforceRuleError('The supplied AI profile is not compatible with this OAuth client product.', 409, 'AI_PROFILE_SELECTION_INVALID', { profileId: profile.id, profileClientId, configuredClientId: configuredMcpClientId });
1804
+ }
1774
1805
  function listSelectableOAuthProfiles(profileName) {
1775
1806
  if (auditActorType !== 'oauth')
1776
1807
  return [];
@@ -2197,42 +2228,38 @@ export function createTaskforceMcpServer(options = {}) {
2197
2228
  return null;
2198
2229
  return resolved;
2199
2230
  }
2200
- function resolveFinishTaskReviewer(task) {
2231
+ function resolveFinishTaskReviewer(task, reviewer) {
2232
+ if (reviewer !== undefined) {
2233
+ const resolvedReviewer = resolveRuntimeTaskAssigneeInput(reviewer);
2234
+ const actor = resolvedReviewer
2235
+ ? core.resolveWorkspaceActor(ACTIVE_WORKSPACE_ID, resolvedReviewer)
2236
+ : null;
2237
+ const eligible = actor?.kind === 'ai'
2238
+ ? core.listAiProfiles(ACTIVE_WORKSPACE_ID).some((profile) => profile.id === actor.id)
2239
+ : core.listWorkspaceUsers(ACTIVE_WORKSPACE_ID).some((user) => (user.userId === actor?.id
2240
+ && String(user.status || '').trim().toLowerCase() === 'active'
2241
+ && user.disabled !== true));
2242
+ if (!actor || actor.meta?.unresolved === true || !eligible) {
2243
+ throw new McpPublicError('reviewer must identify an active human or AI assignee in the current workspace. Call list_assignees and retry with an assignee ID.', 'MCP_REVIEWER_INVALID');
2244
+ }
2245
+ return { assignee: actor.id, source: 'explicit' };
2246
+ }
2201
2247
  const existingAssignee = String(task?.assignee || '').trim();
2202
2248
  if (existingAssignee) {
2203
2249
  const existingActor = core.resolveWorkspaceActor(ACTIVE_WORKSPACE_ID, existingAssignee);
2204
- if (existingActor?.kind === 'human' && existingActor.meta?.unresolved !== true) {
2250
+ const eligibleExistingHuman = existingActor?.kind === 'human'
2251
+ && existingActor.meta?.unresolved !== true
2252
+ && core.listWorkspaceUsers(ACTIVE_WORKSPACE_ID).some((user) => (user.userId === existingActor.id
2253
+ && String(user.status || '').trim().toLowerCase() === 'active'
2254
+ && user.disabled !== true));
2255
+ if (eligibleExistingHuman) {
2205
2256
  return {
2206
2257
  assignee: existingActor.id,
2207
2258
  source: 'existing_human_owner',
2208
2259
  };
2209
2260
  }
2210
2261
  }
2211
- if (runtimeMode === 'cloud' && auditActorUserId !== 'anonymous') {
2212
- const currentUser = core.listWorkspaceUsers(ACTIVE_WORKSPACE_ID)
2213
- .find((user) => user.userId === auditActorUserId
2214
- && String(user.status || '').trim().toLowerCase() === 'active'
2215
- && user.disabled !== true);
2216
- if (currentUser) {
2217
- return { assignee: currentUser.userId, source: 'current_user' };
2218
- }
2219
- }
2220
- const workspaceOwner = core.listWorkspaceUsers(ACTIVE_WORKSPACE_ID)
2221
- .filter((user) => String(user.status || '').trim().toLowerCase() === 'active' && user.disabled !== true)
2222
- .sort((left, right) => {
2223
- const leftRank = left.role === 'owner' ? 0 : left.role === 'admin' ? 1 : 2;
2224
- const rightRank = right.role === 'owner' ? 0 : right.role === 'admin' ? 1 : 2;
2225
- if (leftRank !== rightRank)
2226
- return leftRank - rightRank;
2227
- return String(left.displayName || left.email || left.userId).localeCompare(String(right.displayName || right.email || right.userId), undefined, { sensitivity: 'base' });
2228
- })[0];
2229
- if (workspaceOwner && (workspaceOwner.role === 'owner' || workspaceOwner.role === 'admin')) {
2230
- return {
2231
- assignee: workspaceOwner.userId,
2232
- source: 'workspace_owner',
2233
- };
2234
- }
2235
- throw new McpPublicError('Could not resolve a human reviewer for review handoff. Resolve a current human user, set a human owner on the task, or ensure the workspace has an active owner/admin.', 'MCP_REVIEWER_REQUIRED');
2262
+ throw new McpPublicError('Review handoff requires an explicit reviewer when the task has no eligible existing human reviewer. Call list_assignees and retry finish_task with reviewer set to a human or AI assignee ID.', 'MCP_REVIEWER_REQUIRED');
2236
2263
  }
2237
2264
  function getActiveMcpActorRef() {
2238
2265
  if (activeAiProfileId)
@@ -2273,13 +2300,19 @@ export function createTaskforceMcpServer(options = {}) {
2273
2300
  }).task;
2274
2301
  }
2275
2302
  if (!useStatusBoundary) {
2276
- return core.updateTask(input.task.id, input.updates, ACTIVE_WORKSPACE_ID, { actorRef: getActiveMcpActorRef() });
2303
+ return core.updateTask(input.task.id, input.updates, ACTIVE_WORKSPACE_ID, {
2304
+ actorRef: getActiveMcpActorRef(),
2305
+ ...(input.deferNotifications ? { persistBackup: false, emitRealtime: false } : {}),
2306
+ });
2277
2307
  }
2278
2308
  const unsupported = Object.keys(input.updates).filter((field) => (!durableStatusControlFields.has(field) && !durableTaskMetadataFieldNames.has(field)));
2279
2309
  if (unsupported.length > 0) {
2280
2310
  throw new TaskforceRuleError(`Combined durable task status update contains unsupported fields: ${unsupported.sort().join(', ')}.`, 400, 'TASK_STATUS_INVALID_PAYLOAD');
2281
2311
  }
2282
2312
  const status = String(hasStatus ? input.updates.status : input.task.status || 'task').trim();
2313
+ if (status === 'review' && String(input.task.status || '').trim() !== 'review' && !input.reviewHandoff) {
2314
+ throw new McpPublicError('Moving a task to review requires finish_task with a nonblank evidence comment.', 'TASK_REVIEW_HANDOFF_REQUIRED', 400);
2315
+ }
2283
2316
  const metadataPatch = selectDurableTaskMetadataPatch(input.updates);
2284
2317
  delete metadataPatch.assignee;
2285
2318
  const assigneeIntent = input.assigneeIntent || (Object.prototype.hasOwnProperty.call(input.updates, 'assignee')
@@ -2294,10 +2327,73 @@ export function createTaskforceMcpServer(options = {}) {
2294
2327
  : null;
2295
2328
  return durableTaskMutationRouter.setStatus({
2296
2329
  taskId: input.task.id,
2297
- payload: { status, assigneeIntent, canceledReason, metadataPatch },
2330
+ payload: {
2331
+ status,
2332
+ assigneeIntent,
2333
+ canceledReason,
2334
+ metadataPatch,
2335
+ ...(input.reviewHandoff ? { reviewHandoff: input.reviewHandoff } : {}),
2336
+ },
2298
2337
  actorRef: getActiveMcpActorRef(),
2299
2338
  }).task;
2300
2339
  }
2340
+ function mutateTaskReviewHandoffThroughBoundary(input) {
2341
+ const validation = validateStructuredCommentForAdd(input.handoffComment);
2342
+ if (!validation.ok)
2343
+ throw new McpPublicError(validation.message);
2344
+ const actorRef = getActiveMcpActorRef();
2345
+ const operationId = requireStableMcpRequestId('operation', `review-handoff-comment:${input.task.id}`);
2346
+ const commentId = requireStableMcpRequestId('comment', `review-handoff:${input.task.id}`);
2347
+ const comment = {
2348
+ id: commentId,
2349
+ author: actorRef,
2350
+ submittedAuthor: actorRef,
2351
+ text: input.handoffComment,
2352
+ };
2353
+ if (durableTaskMutationRouter.enabled) {
2354
+ return mutateTaskThroughStatusBoundary({
2355
+ task: input.task,
2356
+ updates: input.updates,
2357
+ assigneeIntent: input.assigneeIntent,
2358
+ reviewHandoff: { comment },
2359
+ });
2360
+ }
2361
+ let updated;
2362
+ try {
2363
+ updated = core.getDatabaseAdapter().transaction(() => {
2364
+ mutateTaskThroughStatusBoundary({
2365
+ task: input.task,
2366
+ updates: input.updates,
2367
+ assigneeIntent: input.assigneeIntent,
2368
+ deferNotifications: true,
2369
+ });
2370
+ const durableRouter = getDurableTaskCommentMutationRouter(actorRef, operationId);
2371
+ const task = durableRouter.enabled
2372
+ ? durableRouter.append({
2373
+ taskId: input.task.id,
2374
+ comment: { ...comment, timestamp: new Date().toISOString() },
2375
+ }).task
2376
+ : core.addComment(input.task.id, input.handoffComment, actorRef, ACTIVE_WORKSPACE_ID, {
2377
+ actorRef,
2378
+ persistBackup: false,
2379
+ emitRealtime: false,
2380
+ commentId,
2381
+ });
2382
+ if (!task)
2383
+ throw new Error(`Task ${input.task.id} not found`);
2384
+ return task;
2385
+ })();
2386
+ }
2387
+ catch (error) {
2388
+ try {
2389
+ core.notifyDurableTaskMetadataCommitted(ACTIVE_WORKSPACE_ID, input.task.id);
2390
+ }
2391
+ catch { /* Preserve the original handoff failure. */ }
2392
+ throw error;
2393
+ }
2394
+ core.notifyDurableTaskMetadataCommitted(ACTIVE_WORKSPACE_ID, input.task.id);
2395
+ return updated;
2396
+ }
2301
2397
  function mutateTaskLifecycleThroughBoundary(input) {
2302
2398
  if (!durableTaskMutationRouter.enabled)
2303
2399
  return input.legacy();
@@ -2383,6 +2479,8 @@ export function createTaskforceMcpServer(options = {}) {
2383
2479
  function ensureResolvedProfileFromConnection(defaults) {
2384
2480
  if (activeAiProfileId)
2385
2481
  return;
2482
+ if (requiresExplicitTokenAuthorship)
2483
+ return;
2386
2484
  const boundProfile = findTrustedBoundProfile({ repairConnection: false });
2387
2485
  if (boundProfile) {
2388
2486
  if (configuredAiProfileIdentityAsserted) {
@@ -2457,6 +2555,25 @@ export function createTaskforceMcpServer(options = {}) {
2457
2555
  throw new TaskforceRuleError('Writes are blocked because this MCP session requested an identity that conflicts with its OAuth-bound profile. Reconnect with the intended profile and retry resolve_profile.', 409, 'AI_PROFILE_BINDING_CONFLICT', { reason: 'session_identity_quarantined', nextTool: 'resolve_profile' });
2458
2556
  }
2459
2557
  const profileToken = resolveProfileTokenFromArgs(args);
2558
+ if (requiresExplicitTokenAuthorship) {
2559
+ if (!profileToken) {
2560
+ throw new McpPublicError('Cloud access-token clients must pass profileToken on every mutating call. Call resolve_profile to obtain it, keep it private, and retry this write.', 'AI_PROFILE_PROOF_REQUIRED', 401);
2561
+ }
2562
+ const tokenProfile = core.getAiProfileByToken({
2563
+ workspaceId: ACTIVE_WORKSPACE_ID,
2564
+ profileToken,
2565
+ seatScope: configuredAiProfileSeatScope,
2566
+ });
2567
+ if (!tokenProfile) {
2568
+ throw new McpPublicError('Invalid AI profile token. Call resolve_profile again and retry.', 'INVALID_AI_PROFILE_TOKEN', 401);
2569
+ }
2570
+ if (activeAiProfileId && activeAiProfileId !== tokenProfile.id) {
2571
+ failAiProfileBindingConflict('The supplied profile token conflicts with the AI profile already active for this request.', { reason: 'request_token_mismatch', boundProfileId: activeAiProfileId, suppliedProfileId: tokenProfile.id });
2572
+ }
2573
+ if (!activeAiProfileId)
2574
+ activateAiProfile(tokenProfile.id, 'token_lookup');
2575
+ return;
2576
+ }
2460
2577
  if (activeAiProfileId) {
2461
2578
  if (!profileToken)
2462
2579
  return;
@@ -2491,6 +2608,8 @@ export function createTaskforceMcpServer(options = {}) {
2491
2608
  suppliedProfileId: profile.id,
2492
2609
  });
2493
2610
  }
2611
+ if (!trustedBoundProfile)
2612
+ assertOAuthProfileClientCompatibility(profile);
2494
2613
  if (trustedBoundProfile && configuredAiProfileIdentityAsserted) {
2495
2614
  assertRequestedIdentityMatchesBoundProfile(trustedBoundProfile, {
2496
2615
  name: configuredAiProfileName,
@@ -2525,7 +2644,7 @@ export function createTaskforceMcpServer(options = {}) {
2525
2644
  throw new McpPublicError('This tool requires an AI profile. Call resolve_profile first, then retry. If your MCP client is stateless, pass profileToken on mutating calls.', 'AI_PROFILE_REQUIRED');
2526
2645
  }
2527
2646
  function assertAnnotatedAttachmentReadAccess() {
2528
- const gate = resolveAppGateAccess(ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY);
2647
+ const gate = resolveNodeAppGateAccess(ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY);
2529
2648
  if (!gate.allowed) {
2530
2649
  throw new TaskforceRuleError('Annotated attachments are unavailable for this workspace.', 403, gate.code || 'ANNOTATED_ATTACHMENTS_UNAVAILABLE');
2531
2650
  }
@@ -3340,6 +3459,17 @@ export function createTaskforceMcpServer(options = {}) {
3340
3459
  offset: Number(normalizedOffset),
3341
3460
  };
3342
3461
  }
3462
+ function normalizeSearchReadPage(limit, offset, defaultLimit) {
3463
+ const normalizedLimit = limit === undefined ? defaultLimit : limit;
3464
+ const normalizedOffset = offset === undefined ? 0 : offset;
3465
+ if (!Number.isInteger(normalizedLimit) || Number(normalizedLimit) < 1 || Number(normalizedLimit) > 50) {
3466
+ throw new McpPublicError('limit must be an integer from 1 to 50');
3467
+ }
3468
+ if (!Number.isInteger(normalizedOffset) || Number(normalizedOffset) < 0) {
3469
+ throw new McpPublicError('offset must be a non-negative integer');
3470
+ }
3471
+ return { limit: Number(normalizedLimit), offset: Number(normalizedOffset) };
3472
+ }
3343
3473
  function buildCompactPlanningHistory(targetType, entity) {
3344
3474
  const duplicatedCommentAction = `${targetType}-comment-added`;
3345
3475
  const activity = Array.isArray(entity?.activity) ? entity.activity : [];
@@ -3516,17 +3646,26 @@ export function createTaskforceMcpServer(options = {}) {
3516
3646
  workstreamsByInitiativeId.set(normalizedInitiativeId, current);
3517
3647
  }
3518
3648
  const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
3519
- const initiatives = core.listInitiatives(ACTIVE_WORKSPACE_ID)
3520
- .filter((initiative) => matchesInitiativePlanningQuery(initiative, query, workstreamsByInitiativeId.get(initiative.id) || []))
3521
- .map((initiative) => buildInitiativeResourcePayload(initiative, {
3522
- workstreamsByInitiativeId,
3523
- taskCountsByWorkstreamId,
3524
- detail: 'compact',
3525
- }));
3649
+ const matches = core.listInitiatives(ACTIVE_WORKSPACE_ID)
3650
+ .filter((initiative) => matchesInitiativePlanningQuery(initiative, query, workstreamsByInitiativeId.get(initiative.id) || []));
3651
+ const offset = options?.offset ?? 0;
3652
+ const limit = options?.limit ?? 25;
3653
+ const initiatives = matches.slice(offset, offset + limit).map((initiative) => {
3654
+ const compact = buildInitiativeResourcePayload(initiative, {
3655
+ workstreamsByInitiativeId,
3656
+ taskCountsByWorkstreamId,
3657
+ detail: 'compact',
3658
+ });
3659
+ const { nextDetailOption: _nextDetailOption, ...summary } = compact;
3660
+ return summary;
3661
+ });
3662
+ const pagination = buildPlanningPagination(matches.length, offset, limit, initiatives.length);
3526
3663
  return {
3527
- total: initiatives.length,
3664
+ total: matches.length,
3528
3665
  ...(query ? { query } : {}),
3529
3666
  initiatives,
3667
+ pagination,
3668
+ guidance: 'Use get_initiative with the initiative ID or reference for details.',
3530
3669
  };
3531
3670
  }
3532
3671
  function buildWorkstreamsResourcePayload(options) {
@@ -3536,13 +3675,22 @@ export function createTaskforceMcpServer(options = {}) {
3536
3675
  initiativesById.set(initiative.id, initiative);
3537
3676
  }
3538
3677
  const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
3539
- const workstreams = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
3540
- .filter((workstream) => matchesWorkstreamPlanningQuery(workstream, query, initiativesById.get(workstream.initiativeId || '')))
3541
- .map((workstream) => buildWorkstreamResourcePayload(workstream, { taskCountsByWorkstreamId, detail: 'compact' }));
3678
+ const matches = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
3679
+ .filter((workstream) => matchesWorkstreamPlanningQuery(workstream, query, initiativesById.get(workstream.initiativeId || '')));
3680
+ const offset = options?.offset ?? 0;
3681
+ const limit = options?.limit ?? 25;
3682
+ const workstreams = matches.slice(offset, offset + limit).map((workstream) => {
3683
+ const compact = buildWorkstreamResourcePayload(workstream, { taskCountsByWorkstreamId, detail: 'compact' });
3684
+ const { nextDetailOption: _nextDetailOption, nextTaskTool: _nextTaskTool, suggestedTaskArgs: _suggestedTaskArgs, ...summary } = compact;
3685
+ return summary;
3686
+ });
3687
+ const pagination = buildPlanningPagination(matches.length, offset, limit, workstreams.length);
3542
3688
  return {
3543
- total: workstreams.length,
3689
+ total: matches.length,
3544
3690
  ...(query ? { query } : {}),
3545
3691
  workstreams,
3692
+ pagination,
3693
+ guidance: 'Use get_workstream for details or list_tasks with workstreamId for active tasks.',
3546
3694
  };
3547
3695
  }
3548
3696
  function buildPlanningWriteEntitySummary(targetType, entity) {
@@ -3626,7 +3774,7 @@ export function createTaskforceMcpServer(options = {}) {
3626
3774
  const hydratedWorkstream = core.getWorkstream(workstream.id, ACTIVE_WORKSPACE_ID) || workstream;
3627
3775
  return { id: hydratedWorkstream.id, workstream: hydratedWorkstream };
3628
3776
  }
3629
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
3777
+ server.setRequestHandler('resources/list', async () => {
3630
3778
  const resources = [
3631
3779
  {
3632
3780
  uri: RESOURCE_CONFIG_URI,
@@ -3673,7 +3821,7 @@ export function createTaskforceMcpServer(options = {}) {
3673
3821
  resources,
3674
3822
  };
3675
3823
  });
3676
- server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
3824
+ server.setRequestHandler('resources/templates/list', async () => {
3677
3825
  if (!hasPermission(RESOURCE_PERMISSIONS.task)) {
3678
3826
  return { resourceTemplates: [] };
3679
3827
  }
@@ -3705,7 +3853,7 @@ export function createTaskforceMcpServer(options = {}) {
3705
3853
  ],
3706
3854
  };
3707
3855
  });
3708
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
3856
+ server.setRequestHandler('resources/read', async (request) => {
3709
3857
  const uri = request.params.uri;
3710
3858
  assertResourcePermission(uri);
3711
3859
  if (uri === RESOURCE_CONFIG_URI) {
@@ -3811,24 +3959,26 @@ export function createTaskforceMcpServer(options = {}) {
3811
3959
  }
3812
3960
  throw new Error(`Unknown resource URI: ${uri}`);
3813
3961
  });
3814
- server.setRequestHandler(SubscribeRequestSchema, async (request) => {
3815
- const uri = request.params.uri;
3816
- if (!isSupportedResourceUri(uri)) {
3817
- throw new Error(`Unsupported resource URI for subscribe: ${uri}`);
3818
- }
3819
- assertResourcePermission(uri);
3820
- resourceSubscriptions.add(uri);
3821
- return {};
3822
- });
3823
- server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
3824
- const uri = request.params.uri;
3825
- if (!isSupportedResourceUri(uri)) {
3826
- throw new Error(`Unsupported resource URI for unsubscribe: ${uri}`);
3827
- }
3828
- assertResourcePermission(uri);
3829
- resourceSubscriptions.delete(uri);
3830
- return {};
3831
- });
3962
+ if (supportsLegacyResourceSubscriptions) {
3963
+ server.setRequestHandler('resources/subscribe', async (request) => {
3964
+ const uri = request.params.uri;
3965
+ if (!isSupportedResourceUri(uri)) {
3966
+ throw new Error(`Unsupported resource URI for subscribe: ${uri}`);
3967
+ }
3968
+ assertResourcePermission(uri);
3969
+ resourceSubscriptions.add(uri);
3970
+ return {};
3971
+ });
3972
+ server.setRequestHandler('resources/unsubscribe', async (request) => {
3973
+ const uri = request.params.uri;
3974
+ if (!isSupportedResourceUri(uri)) {
3975
+ throw new Error(`Unsupported resource URI for unsubscribe: ${uri}`);
3976
+ }
3977
+ assertResourcePermission(uri);
3978
+ resourceSubscriptions.delete(uri);
3979
+ return {};
3980
+ });
3981
+ }
3832
3982
  /**
3833
3983
  * Tool Definitions
3834
3984
  */
@@ -3843,7 +3993,9 @@ export function createTaskforceMcpServer(options = {}) {
3843
3993
  registerAdminWorkspaceTools(registerTool, (name, args) => executeRegisteredTool(name, args));
3844
3994
  const mutatingProfileTokenProperty = {
3845
3995
  type: 'string',
3846
- description: 'Optional profileToken from resolve_profile for stateless MCP clients.'
3996
+ description: requiresExplicitTokenAuthorship
3997
+ ? 'Required authorship proof for cloud access-token clients; obtain from resolve_profile.'
3998
+ : 'Optional profileToken from resolve_profile for stateless MCP clients.'
3847
3999
  };
3848
4000
  const nonDestructiveWriteTools = new Set([
3849
4001
  'add_annotated_attachment_marker',
@@ -3876,6 +4028,9 @@ export function createTaskforceMcpServer(options = {}) {
3876
4028
  get priorityDesc() {
3877
4029
  return `Priority level: ${getActivePriorities().map((priority) => `${priority.value} (${priority.label})`).join(', ')}`;
3878
4030
  },
4031
+ get priorityEnum() {
4032
+ return getActivePriorities().map((priority) => priority.value);
4033
+ },
3879
4034
  get typeEnum() {
3880
4035
  return getActiveTypes().map((type) => type.value);
3881
4036
  },
@@ -3916,6 +4071,11 @@ export function createTaskforceMcpServer(options = {}) {
3916
4071
  inputSchema: {
3917
4072
  ...descriptor.inputSchema,
3918
4073
  properties,
4074
+ ...(requiresExplicitTokenAuthorship
4075
+ && descriptor.name !== 'resolve_profile'
4076
+ && isMcpWriteToolName(descriptor.name)
4077
+ ? { required: Array.from(new Set([...(descriptor.inputSchema.required || []), 'profileToken'])) }
4078
+ : {}),
3919
4079
  additionalProperties: false,
3920
4080
  },
3921
4081
  annotations: {
@@ -3934,16 +4094,19 @@ export function createTaskforceMcpServer(options = {}) {
3934
4094
  const tool = registeredTools.find((candidate) => candidate.name === name);
3935
4095
  return tool ? materializeToolDescriptor(tool, buildToolDescriptorContext()) : null;
3936
4096
  };
3937
- server.setRequestHandler(ListToolsRequestSchema, async () => {
4097
+ const listTools = async () => {
3938
4098
  const context = buildToolDescriptorContext();
3939
4099
  const tools = registeredTools
3940
4100
  .map((tool) => materializeToolDescriptor(tool, context))
3941
4101
  .filter((tool) => (isMcpToolVisibleInProfile(tool.name, activeToolProfile)
3942
4102
  && hasPermission(TOOL_PERMISSIONS[tool.name])));
3943
4103
  return {
3944
- tools,
4104
+ // Descriptors are validated by Taskforce's JSON Schema contract before
4105
+ // registration; v2 narrows the equivalent recursive JSON value type.
4106
+ tools: tools,
3945
4107
  };
3946
- });
4108
+ };
4109
+ server.setRequestHandler('tools/list', listTools);
3947
4110
  const resolvePlanningAttachmentTarget = (targetType, inputId) => {
3948
4111
  if (targetType === 'initiative') {
3949
4112
  const { id, initiative } = resolveInitiativeByIdentifierOrThrow(inputId);
@@ -4863,7 +5026,9 @@ export function createTaskforceMcpServer(options = {}) {
4863
5026
  const taskLifecycleCommandService = createTaskLifecycleCommandService({
4864
5027
  formatTaskIdentifier,
4865
5028
  resolveAssigneeLabel: (assignee) => core.resolveWorkspaceActor(ACTIVE_WORKSPACE_ID, assignee)?.label || assignee,
4866
- setStatus: (input) => mutateTaskThroughStatusBoundary(input),
5029
+ setStatus: (input) => input.handoffComment
5030
+ ? mutateTaskReviewHandoffThroughBoundary(input)
5031
+ : mutateTaskThroughStatusBoundary(input),
4867
5032
  mutateLifecycle: (input) => mutateTaskLifecycleThroughBoundary({
4868
5033
  taskId: input.taskId,
4869
5034
  mutationKind: input.mutationKind,
@@ -4883,7 +5048,7 @@ export function createTaskforceMcpServer(options = {}) {
4883
5048
  });
4884
5049
  },
4885
5050
  }),
4886
- resolveReviewer: (task) => resolveFinishTaskReviewer(task),
5051
+ resolveReviewer: (task, reviewer) => resolveFinishTaskReviewer(task, reviewer),
4887
5052
  });
4888
5053
  const planningCommandPreparationService = new PlanningCommandPreparationService({
4889
5054
  resolveOwner: resolvePlanningOwnerInput,
@@ -4896,12 +5061,17 @@ export function createTaskforceMcpServer(options = {}) {
4896
5061
  listItems: (taskId) => core.listChecklistItems(taskId, ACTIVE_WORKSPACE_ID),
4897
5062
  createId: (scope, key) => requireStableMcpRequestId(scope, key),
4898
5063
  now: () => new Date().toISOString(),
4899
- replace: ({ taskId, items, operationId }) => {
5064
+ replace: ({ taskId, items, operationId, expectedDetailRevision, requireDurableIdempotency, itemUpdateIntent, }) => {
4900
5065
  const actorRef = getActiveMcpActorRef();
4901
5066
  const durableRouter = getDurableTaskChecklistMutationRouter(actorRef, operationId);
5067
+ if (requireDurableIdempotency) {
5068
+ requireDurableExplicitIdempotency(durableRouter.enabled, 'task checklist');
5069
+ }
4902
5070
  return durableRouter.enabled
4903
- ? durableRouter.replace({ taskId, items }).task
4904
- : core.updateTask(taskId, { checklistItems: items }, ACTIVE_WORKSPACE_ID, { actorRef });
5071
+ ? durableRouter.replace({ taskId, items, expectedDetailRevision, itemUpdateIntent }).task
5072
+ : expectedDetailRevision
5073
+ ? core.updateTaskIfDetailRevisionMatches(taskId, expectedDetailRevision, { checklistItems: items }, ACTIVE_WORKSPACE_ID, { actorRef })
5074
+ : core.updateTask(taskId, { checklistItems: items }, ACTIVE_WORKSPACE_ID, { actorRef });
4905
5075
  },
4906
5076
  });
4907
5077
  const annotatedAttachmentCommandService = createAnnotatedAttachmentCommandService({
@@ -4924,7 +5094,7 @@ export function createTaskforceMcpServer(options = {}) {
4924
5094
  }
4925
5095
  function rethrowChecklistCommandError(error) {
4926
5096
  if (error instanceof TaskChecklistCommandError) {
4927
- if (error.code === 'TASK_NOT_FOUND') {
5097
+ if (error.code === 'TASK_NOT_FOUND' || error.code === 'TASK_CHECKLIST_ITEM_NOT_FOUND') {
4928
5098
  throw new McpPublicError(error.message, 'MCP_NOT_FOUND', error.statusCode);
4929
5099
  }
4930
5100
  throw new McpPublicError(error.message);
@@ -4992,15 +5162,15 @@ export function createTaskforceMcpServer(options = {}) {
4992
5162
  : resolveWorkstreamByIdentifierOrThrow(workstreamId).id;
4993
5163
  const paginated = resolvedWorkstreamId !== undefined || offset !== undefined;
4994
5164
  const parsedOffset = offset === undefined ? 0 : Number(offset);
4995
- if (!Number.isFinite(parsedOffset) || parsedOffset < 0)
4996
- throw new McpPublicError('offset must be a non-negative number');
4997
- const normalizedOffset = Math.floor(parsedOffset);
5165
+ if (!Number.isInteger(parsedOffset) || parsedOffset < 0)
5166
+ throw new McpPublicError('offset must be a non-negative integer');
5167
+ const normalizedOffset = parsedOffset;
4998
5168
  const parsedLimit = limit === undefined ? (paginated ? 50 : 0) : Number(limit);
4999
- if (!Number.isFinite(parsedLimit) || parsedLimit < 0)
5000
- throw new McpPublicError('limit must be a non-negative number');
5169
+ if (!Number.isInteger(parsedLimit) || parsedLimit < 0 || parsedLimit > 200)
5170
+ throw new McpPublicError('limit must be an integer from 1 to 200');
5001
5171
  const normalizedLimit = paginated
5002
- ? Math.min(200, Math.max(1, Math.floor(parsedLimit || 50)))
5003
- : Math.floor(parsedLimit);
5172
+ ? (parsedLimit || 50)
5173
+ : parsedLimit;
5004
5174
  const queriedTasks = core.listTasksSlim(ACTIVE_WORKSPACE_ID, {
5005
5175
  category,
5006
5176
  status,
@@ -5087,6 +5257,15 @@ export function createTaskforceMcpServer(options = {}) {
5087
5257
  failAiProfileBindingConflict('The selected AI profile conflicts with the profile already bound to this MCP session or authenticated connection.', { reason: 'selected_profile_mismatch', boundProfileId: alreadyBoundProfile.id, selectedProfileId: explicitlySelectedProfile.id });
5088
5258
  }
5089
5259
  const boundProfile = alreadyBoundProfile || explicitlySelectedProfile;
5260
+ if (!boundProfile && suppliedProfileToken) {
5261
+ const suppliedProfile = core.getAiProfileByToken({
5262
+ workspaceId: ACTIVE_WORKSPACE_ID,
5263
+ profileToken: suppliedProfileToken,
5264
+ seatScope: configuredAiProfileSeatScope,
5265
+ });
5266
+ if (suppliedProfile)
5267
+ assertOAuthProfileClientCompatibility(suppliedProfile);
5268
+ }
5090
5269
  if (boundProfile && suppliedProfileToken) {
5091
5270
  const tokenProfile = core.getAiProfileByToken({
5092
5271
  workspaceId: ACTIVE_WORKSPACE_ID,
@@ -5291,10 +5470,19 @@ export function createTaskforceMcpServer(options = {}) {
5291
5470
  };
5292
5471
  }
5293
5472
  case "search_tasks": {
5294
- const { query, scope = "all" } = args;
5295
- const results = core.searchTaskSummaries(query, scope, ACTIVE_WORKSPACE_ID);
5473
+ const { query, scope = "all", limit, offset } = args;
5474
+ const page = normalizeSearchReadPage(limit, offset, 25);
5475
+ const queried = core.searchTaskSummaries(query, scope, ACTIVE_WORKSPACE_ID, {
5476
+ limit: page.limit + 1,
5477
+ offset: page.offset,
5478
+ });
5479
+ const hasMore = queried.length > page.limit;
5480
+ const results = hasMore ? queried.slice(0, page.limit) : queried;
5296
5481
  return {
5297
- content: [{ type: "text", text: renderTaskTable(results) }],
5482
+ content: [{
5483
+ type: "text",
5484
+ text: `${renderTaskTable(results)}\n\nPage: offset ${page.offset}, limit ${page.limit}, returned ${results.length}, hasMore ${hasMore}${hasMore ? `, nextOffset ${page.offset + results.length}` : ''}`,
5485
+ }],
5298
5486
  };
5299
5487
  }
5300
5488
  case "get_task": {
@@ -5347,6 +5535,9 @@ export function createTaskforceMcpServer(options = {}) {
5347
5535
  requiresCommentEvidence: step.requiresCommentEvidence,
5348
5536
  } : {}),
5349
5537
  }));
5538
+ const currentStep = assignment.execution.status === 'running'
5539
+ ? assignment.steps.find((step) => step.id === assignment.execution.currentStepId)
5540
+ : undefined;
5350
5541
  return {
5351
5542
  content: [{ type: 'text', text: JSON.stringify({
5352
5543
  task: withTaskUrl({ id: task.id, referenceLabel: formatTaskIdentifier(task), title: task.title }),
@@ -5358,11 +5549,23 @@ export function createTaskforceMcpServer(options = {}) {
5358
5549
  currentStepId: assignment.execution.currentStepId || null,
5359
5550
  steps,
5360
5551
  },
5552
+ completionCall: currentStep ? {
5553
+ tool: 'complete_workflow_step',
5554
+ arguments: {
5555
+ taskId,
5556
+ executionId: assignment.execution.id,
5557
+ stepId: currentStep.id,
5558
+ },
5559
+ } : null,
5361
5560
  }, null, 2) }],
5362
5561
  };
5363
5562
  }
5364
5563
  case "complete_workflow_step": {
5365
5564
  const { taskId, executionId, stepId, evidenceCommentId } = args;
5565
+ const normalizedEvidenceCommentId = evidenceCommentId == null ? undefined : evidenceCommentId;
5566
+ if (areWorkflowRuntimeMutationsFenced()) {
5567
+ throw new McpPublicError('Workflow execution changes are temporarily paused.', 'WORKFLOW_RUNTIME_MUTATIONS_FENCED', 503);
5568
+ }
5366
5569
  const { task } = resolveTaskByIdentifierOrThrow(taskId);
5367
5570
  ensureResolvedProfileForSessionBootstrap();
5368
5571
  const agentProfileId = String(activeAiProfileId || '').trim();
@@ -5375,7 +5578,7 @@ export function createTaskforceMcpServer(options = {}) {
5375
5578
  throw new McpPublicError('The workflow execution does not belong to this task.', 'WORKFLOW_TASK_MISMATCH', 409);
5376
5579
  }
5377
5580
  const idempotencyKey = `agent-step-${createHash('sha256')
5378
- .update(`${executionId}:${stepId}:${agentProfileId}:${String(evidenceCommentId || '')}`)
5581
+ .update(`${executionId}:${stepId}:${agentProfileId}:${String(normalizedEvidenceCommentId || '')}`)
5379
5582
  .digest('hex')
5380
5583
  .slice(0, 32)}`;
5381
5584
  let result;
@@ -5397,7 +5600,7 @@ export function createTaskforceMcpServer(options = {}) {
5397
5600
  }).completeStep({
5398
5601
  executionId,
5399
5602
  expectedStepId: stepId,
5400
- evidenceCommentId,
5603
+ evidenceCommentId: normalizedEvidenceCommentId,
5401
5604
  })
5402
5605
  : executionService.completeCurrentStep({
5403
5606
  workspaceId: ACTIVE_WORKSPACE_ID,
@@ -5405,7 +5608,7 @@ export function createTaskforceMcpServer(options = {}) {
5405
5608
  actorId: agentProfileId,
5406
5609
  actorType: 'agent',
5407
5610
  expectedStepId: stepId,
5408
- evidenceCommentId,
5611
+ evidenceCommentId: normalizedEvidenceCommentId,
5409
5612
  idempotencyKey,
5410
5613
  });
5411
5614
  }
@@ -5498,9 +5701,10 @@ export function createTaskforceMcpServer(options = {}) {
5498
5701
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5499
5702
  }
5500
5703
  case "list_initiatives": {
5501
- const { query } = args;
5704
+ const { query, limit, offset } = args;
5705
+ const page = normalizePlanningReadPage(limit, offset);
5502
5706
  return {
5503
- content: [{ type: "text", text: JSON.stringify(buildInitiativesResourcePayload({ query }), null, 2) }],
5707
+ content: [{ type: "text", text: JSON.stringify(buildInitiativesResourcePayload({ query, ...page }), null, 2) }],
5504
5708
  };
5505
5709
  }
5506
5710
  case "get_initiative": {
@@ -5771,9 +5975,10 @@ export function createTaskforceMcpServer(options = {}) {
5771
5975
  };
5772
5976
  }
5773
5977
  case "list_workstreams": {
5774
- const { query } = args;
5978
+ const { query, limit, offset } = args;
5979
+ const page = normalizePlanningReadPage(limit, offset);
5775
5980
  return {
5776
- content: [{ type: "text", text: JSON.stringify(buildWorkstreamsResourcePayload({ query }), null, 2) }],
5981
+ content: [{ type: "text", text: JSON.stringify(buildWorkstreamsResourcePayload({ query, ...page }), null, 2) }],
5777
5982
  };
5778
5983
  }
5779
5984
  case "get_workstream": {
@@ -6060,12 +6265,14 @@ export function createTaskforceMcpServer(options = {}) {
6060
6265
  const { taskId } = args;
6061
6266
  const { id: resolvedTaskId } = resolveTaskByIdentifierOrThrow(taskId);
6062
6267
  const checklist = core.listChecklistItems(resolvedTaskId, ACTIVE_WORKSPACE_ID);
6268
+ const detailRevision = core.getTaskDetailRevision(resolvedTaskId, ACTIVE_WORKSPACE_ID);
6063
6269
  return {
6064
6270
  content: [{
6065
6271
  type: "text",
6066
6272
  text: JSON.stringify({
6067
6273
  taskId: resolvedTaskId,
6068
6274
  ...(buildTaskUrl({ id: resolvedTaskId }) ? { url: buildTaskUrl({ id: resolvedTaskId }) } : {}),
6275
+ detailRevision,
6069
6276
  items: checklist,
6070
6277
  }, null, 2),
6071
6278
  }],
@@ -6135,6 +6342,49 @@ export function createTaskforceMcpServer(options = {}) {
6135
6342
  }],
6136
6343
  };
6137
6344
  }
6345
+ case "update_task_checklist_item": {
6346
+ const { taskId, itemId, text, done, order, expectedDetailRevision, idempotencyKey: rawIdempotencyKey, } = args;
6347
+ const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
6348
+ const operationId = idempotencyKey
6349
+ ? stableExplicitMutationId('operation', 'update_task_checklist_item', idempotencyKey)
6350
+ : requireStableMcpRequestId('operation', 'task-checklist-update');
6351
+ let result;
6352
+ try {
6353
+ result = taskChecklistCommandService.update({
6354
+ taskId,
6355
+ itemId,
6356
+ text,
6357
+ done,
6358
+ order,
6359
+ expectedDetailRevision,
6360
+ operationId,
6361
+ requireDurableIdempotency: Boolean(idempotencyKey),
6362
+ });
6363
+ }
6364
+ catch (error) {
6365
+ rethrowChecklistCommandError(error);
6366
+ }
6367
+ const detailRevision = core.getTaskDetailRevision(result.taskId, ACTIVE_WORKSPACE_ID);
6368
+ recordMcpWriteAudit('update_task_checklist_item', {
6369
+ taskId: result.taskId,
6370
+ itemId: result.item.id,
6371
+ });
6372
+ await notifyResourceMutation({
6373
+ includeTasks: true,
6374
+ taskIds: [result.taskId],
6375
+ });
6376
+ return {
6377
+ content: [{
6378
+ type: "text",
6379
+ text: JSON.stringify({
6380
+ taskId: result.taskId,
6381
+ ...(buildTaskUrl({ id: result.taskId }) ? { url: buildTaskUrl({ id: result.taskId }) } : {}),
6382
+ detailRevision,
6383
+ item: result.item,
6384
+ }, null, 2),
6385
+ }],
6386
+ };
6387
+ }
6138
6388
  case "get_task_attachments": {
6139
6389
  const { id, taskId, attachmentId } = args;
6140
6390
  const taskIdentifier = taskId ?? id;
@@ -6190,8 +6440,11 @@ export function createTaskforceMcpServer(options = {}) {
6190
6440
  }
6191
6441
  case "search_images": {
6192
6442
  assertAnnotatedAttachmentReadAccess();
6193
- const { query, limit } = args;
6194
- const results = searchImageAssets(query, { limit }).map(({ asset, matchedOn, score }) => ({
6443
+ const { query, limit, offset } = args;
6444
+ const page = normalizeSearchReadPage(limit, offset, 10);
6445
+ const queried = searchImageAssets(query, { limit: page.limit + 1, offset: page.offset });
6446
+ const hasMore = queried.length > page.limit;
6447
+ const results = (hasMore ? queried.slice(0, page.limit) : queried).map(({ asset, matchedOn, score }) => ({
6195
6448
  image: buildImageDescriptor(asset),
6196
6449
  matchedOn,
6197
6450
  score,
@@ -6207,6 +6460,13 @@ export function createTaskforceMcpServer(options = {}) {
6207
6460
  workspaceId: ACTIVE_WORKSPACE_ID,
6208
6461
  query: typeof query === 'string' ? query.trim() : '',
6209
6462
  results,
6463
+ pagination: {
6464
+ offset: page.offset,
6465
+ limit: page.limit,
6466
+ returned: results.length,
6467
+ hasMore,
6468
+ ...(hasMore ? { nextOffset: page.offset + results.length } : {}),
6469
+ },
6210
6470
  guidance: results.length > 0
6211
6471
  ? 'After picking a match, call get_image with the suggestedArgs.imageId to inspect linked tasks and image notes.'
6212
6472
  : 'No matching canonical images were found. Try an I-123 reference, title keyword, or filename fragment.',
@@ -6277,8 +6537,11 @@ export function createTaskforceMcpServer(options = {}) {
6277
6537
  };
6278
6538
  }
6279
6539
  case "search_documents": {
6280
- const { query, limit } = args;
6281
- const results = searchDocumentAssets(query, { limit }).map(({ asset, matchedOn, score }) => ({
6540
+ const { query, limit, offset } = args;
6541
+ const page = normalizeSearchReadPage(limit, offset, 10);
6542
+ const queried = searchDocumentAssets(query, { limit: page.limit + 1, offset: page.offset });
6543
+ const hasMore = queried.length > page.limit;
6544
+ const results = (hasMore ? queried.slice(0, page.limit) : queried).map(({ asset, matchedOn, score }) => ({
6282
6545
  document: buildDocumentDescriptor(asset),
6283
6546
  matchedOn,
6284
6547
  score,
@@ -6295,6 +6558,13 @@ export function createTaskforceMcpServer(options = {}) {
6295
6558
  workspaceId: ACTIVE_WORKSPACE_ID,
6296
6559
  query: typeof query === 'string' ? query.trim() : '',
6297
6560
  results,
6561
+ pagination: {
6562
+ offset: page.offset,
6563
+ limit: page.limit,
6564
+ returned: results.length,
6565
+ hasMore,
6566
+ ...(hasMore ? { nextOffset: page.offset + results.length } : {}),
6567
+ },
6298
6568
  guidance: results.length > 0
6299
6569
  ? 'After picking a match, call get_document with the suggestedArgs.documentId. Pass includeContent=true when you need the body.'
6300
6570
  : 'No matching canonical documents were found. Try a D-123 reference, document id, title keyword, or filename fragment.',
@@ -6848,14 +7118,40 @@ export function createTaskforceMcpServer(options = {}) {
6848
7118
  };
6849
7119
  }
6850
7120
  case "review_document": {
6851
- const { id, documentId, sessionId, title, comments, prompt } = args;
7121
+ const { id, documentId, sessionId, title, comments, prompt, parentCommentId, threadCommentId, threadStatus, } = args;
6852
7122
  const asset = resolveDocumentAssetByIdentifierOrThrow(documentId ?? id);
6853
7123
  const normalizedComments = Array.isArray(comments)
6854
7124
  ? comments
6855
7125
  : (typeof prompt === 'string' && prompt.trim()
6856
7126
  ? [{ body: prompt.trim() }]
6857
7127
  : []);
6858
- if (normalizedComments.length === 0) {
7128
+ const normalizedSessionId = typeof sessionId === 'string' ? sessionId.trim() : '';
7129
+ const normalizedParentCommentId = typeof parentCommentId === 'string' ? parentCommentId.trim() : '';
7130
+ const normalizedThreadCommentId = typeof threadCommentId === 'string' ? threadCommentId.trim() : '';
7131
+ const hasThreadStatus = threadStatus !== undefined;
7132
+ const hasThreadUpdate = Boolean(normalizedThreadCommentId) || hasThreadStatus;
7133
+ const hasCommentsArgument = Object.prototype.hasOwnProperty.call(args, 'comments');
7134
+ const hasPromptArgument = Object.prototype.hasOwnProperty.call(args, 'prompt');
7135
+ const hasTitleArgument = Object.prototype.hasOwnProperty.call(args, 'title');
7136
+ if (hasThreadUpdate && (!normalizedThreadCommentId || (threadStatus !== 'open' && threadStatus !== 'resolved'))) {
7137
+ throw new McpPublicError('threadCommentId and threadStatus are required together');
7138
+ }
7139
+ if ((normalizedParentCommentId || hasThreadUpdate) && !normalizedSessionId) {
7140
+ throw new McpPublicError('sessionId is required for reply and thread-status updates');
7141
+ }
7142
+ if (hasThreadUpdate && (normalizedParentCommentId || hasCommentsArgument || hasPromptArgument || hasTitleArgument)) {
7143
+ throw new McpPublicError('Thread-status updates cannot be combined with comments, replies, or a session title');
7144
+ }
7145
+ if (normalizedParentCommentId && hasTitleArgument) {
7146
+ throw new McpPublicError('Replies cannot set a session title');
7147
+ }
7148
+ if (hasCommentsArgument && hasPromptArgument) {
7149
+ throw new McpPublicError('comments and prompt cannot be combined');
7150
+ }
7151
+ if (normalizedParentCommentId && normalizedComments.length !== 1) {
7152
+ throw new McpPublicError('Replies require exactly one comment or prompt');
7153
+ }
7154
+ if (!hasThreadUpdate && normalizedComments.length === 0) {
6859
7155
  throw new McpPublicError('comments array is required');
6860
7156
  }
6861
7157
  for (const comment of normalizedComments) {
@@ -6867,13 +7163,115 @@ export function createTaskforceMcpServer(options = {}) {
6867
7163
  throw new McpPublicError(anchorError);
6868
7164
  }
6869
7165
  }
7166
+ if (normalizedParentCommentId && normalizedComments[0]?.anchor !== undefined && normalizedComments[0]?.anchor !== null) {
7167
+ throw new McpPublicError('Replies cannot have anchors');
7168
+ }
7169
+ if (hasThreadUpdate) {
7170
+ const existingSession = core.getDocumentReviewSession({
7171
+ workspaceId: ACTIVE_WORKSPACE_ID,
7172
+ sessionId: normalizedSessionId,
7173
+ });
7174
+ if (!existingSession) {
7175
+ throw new McpPublicError(`Document review session ${normalizedSessionId} not found.`, 'MCP_NOT_FOUND', 404);
7176
+ }
7177
+ if (existingSession.assetId !== asset.assetId) {
7178
+ throw new McpPublicError(`Document review session ${normalizedSessionId} is not linked to document ${getDocumentReferenceLabel(asset) || asset.assetId}.`, 'MCP_ATTACHMENT_CONFLICT', 409);
7179
+ }
7180
+ const updatedSession = core.updateDocumentReviewThreadStatus({
7181
+ workspaceId: ACTIVE_WORKSPACE_ID,
7182
+ sessionId: normalizedSessionId,
7183
+ commentId: normalizedThreadCommentId,
7184
+ status: threadStatus,
7185
+ actorId: getActiveMcpActorRef(),
7186
+ });
7187
+ const updatedThread = updatedSession.comments.find((entry) => entry.id === normalizedThreadCommentId) || null;
7188
+ const sessionSummary = buildDocumentReviewSessionSummary(updatedSession);
7189
+ recordMcpWriteAudit('review_document', {
7190
+ documentAssetId: asset.assetId,
7191
+ sessionId: updatedSession.id,
7192
+ sessionAction: 'reused',
7193
+ threadCommentId: normalizedThreadCommentId,
7194
+ threadStatus,
7195
+ });
7196
+ return {
7197
+ content: [{
7198
+ type: 'text',
7199
+ text: JSON.stringify({
7200
+ document: {
7201
+ assetId: asset.assetId,
7202
+ referenceLabel: getDocumentReferenceLabel(asset),
7203
+ },
7204
+ sessionId: updatedSession.id,
7205
+ sessionAction: 'reused',
7206
+ postedCommentIds: [],
7207
+ commentCount: 0,
7208
+ thread: buildCompactDocumentReviewComment(updatedThread),
7209
+ unresolvedThreadCount: sessionSummary.unresolvedThreadCount,
7210
+ nextTool: 'get_document_reviews',
7211
+ suggestedArgs: {
7212
+ documentId: getDocumentReferenceLabel(asset) || asset.assetId,
7213
+ sessionId: updatedSession.id,
7214
+ },
7215
+ }, null, 2),
7216
+ }],
7217
+ };
7218
+ }
7219
+ if (normalizedParentCommentId) {
7220
+ const existingSession = core.getDocumentReviewSession({
7221
+ workspaceId: ACTIVE_WORKSPACE_ID,
7222
+ sessionId: normalizedSessionId,
7223
+ });
7224
+ if (!existingSession) {
7225
+ throw new McpPublicError(`Document review session ${normalizedSessionId} not found.`, 'MCP_NOT_FOUND', 404);
7226
+ }
7227
+ if (existingSession.assetId !== asset.assetId) {
7228
+ throw new McpPublicError(`Document review session ${normalizedSessionId} is not linked to document ${getDocumentReferenceLabel(asset) || asset.assetId}.`, 'MCP_ATTACHMENT_CONFLICT', 409);
7229
+ }
7230
+ const repliedSession = core.addDocumentReviewReply({
7231
+ workspaceId: ACTIVE_WORKSPACE_ID,
7232
+ sessionId: normalizedSessionId,
7233
+ parentCommentId: normalizedParentCommentId,
7234
+ body: normalizedComments[0].body,
7235
+ actorId: getActiveMcpActorRef(),
7236
+ });
7237
+ const postedReply = repliedSession.comments.at(-1) || null;
7238
+ recordMcpWriteAudit('review_document', {
7239
+ documentAssetId: asset.assetId,
7240
+ sessionId: repliedSession.id,
7241
+ sessionAction: 'reused',
7242
+ parentCommentId: normalizedParentCommentId,
7243
+ commentCount: 1,
7244
+ });
7245
+ return {
7246
+ content: [{
7247
+ type: 'text',
7248
+ text: JSON.stringify({
7249
+ document: {
7250
+ assetId: asset.assetId,
7251
+ referenceLabel: getDocumentReferenceLabel(asset),
7252
+ },
7253
+ sessionId: repliedSession.id,
7254
+ sessionAction: 'reused',
7255
+ postedCommentIds: postedReply ? [postedReply.id] : [],
7256
+ commentCount: postedReply ? 1 : 0,
7257
+ reply: buildCompactDocumentReviewComment(postedReply),
7258
+ nextTool: 'get_document_reviews',
7259
+ suggestedArgs: {
7260
+ documentId: getDocumentReferenceLabel(asset) || asset.assetId,
7261
+ sessionId: repliedSession.id,
7262
+ },
7263
+ }, null, 2),
7264
+ }],
7265
+ };
7266
+ }
6870
7267
  let commandResult;
6871
7268
  try {
6872
7269
  commandResult = documentReviewCommandService.review({
6873
7270
  workspaceId: ACTIVE_WORKSPACE_ID,
6874
7271
  assetId: asset.assetId,
7272
+ documentVersion: asset.version,
6875
7273
  documentLabel: getDocumentReferenceLabel(asset) || asset.assetId,
6876
- sessionId: sessionId && typeof sessionId === 'string' ? sessionId : null,
7274
+ sessionId: normalizedSessionId || null,
6877
7275
  title: typeof title === 'string' ? title : null,
6878
7276
  comments: normalizedComments,
6879
7277
  actorId: getActiveMcpActorRef(),
@@ -6899,6 +7297,7 @@ export function createTaskforceMcpServer(options = {}) {
6899
7297
  },
6900
7298
  sessionId: currentSession.id,
6901
7299
  sessionAction,
7300
+ documentVersion: currentSession.documentVersion,
6902
7301
  postedCommentIds,
6903
7302
  commentCount: postedCommentIds.length,
6904
7303
  nextTool: 'get_document_reviews',
@@ -7223,13 +7622,6 @@ export function createTaskforceMcpServer(options = {}) {
7223
7622
  actorRef: getActiveMcpActorRef(),
7224
7623
  provisionalLocalReference: shouldUseProvisionalTaskReferences(runtimeMode)
7225
7624
  }), replayed: false };
7226
- if ('operationId' in task) {
7227
- options.onOperationAccepted?.({
7228
- operationId: task.operationId,
7229
- mutationKind: 'create',
7230
- payloadFingerprint: fingerprintWorkspaceSyncPayload(prepared.durableTask || {}),
7231
- });
7232
- }
7233
7625
  recordMcpWriteAudit('create_task', {
7234
7626
  taskId: task.task?.id || null,
7235
7627
  title: task.task?.title || null,
@@ -7348,6 +7740,9 @@ export function createTaskforceMcpServer(options = {}) {
7348
7740
  const inputId = args.id;
7349
7741
  const { id, task: existingTask } = resolveTaskByIdentifierOrThrow(inputId);
7350
7742
  const prepared = taskRootCommandPreparationService.prepareUpdate(args);
7743
+ if (prepared.updates.status === 'review' && existingTask.status !== 'review') {
7744
+ throw new McpPublicError('update_task cannot move a task to review. Use finish_task with outcome=\'review\' and a nonblank evidence comment so the handoff commits atomically.', 'TASK_REVIEW_HANDOFF_REQUIRED', 400);
7745
+ }
7351
7746
  const updated = mutateTaskThroughStatusBoundary({ task: existingTask, updates: prepared.updates });
7352
7747
  if (!updated) {
7353
7748
  throw new Error(`Task ${id} not found`);
@@ -7386,13 +7781,13 @@ export function createTaskforceMcpServer(options = {}) {
7386
7781
  }
7387
7782
  case "finish_task": {
7388
7783
  try {
7389
- const { id: inputId, outcome, comment } = args;
7784
+ const { id: inputId, outcome, comment, reviewer } = args;
7390
7785
  const normalizedOutcome = String(outcome || '').trim().toLowerCase();
7391
7786
  if (normalizedOutcome !== 'done' && normalizedOutcome !== 'review') {
7392
7787
  throw new McpPublicError("outcome must be 'done' or 'review'");
7393
7788
  }
7394
7789
  const { id, task: existingTask } = resolveTaskByIdentifierOrThrow(inputId);
7395
- const result = taskLifecycleCommandService.finish(existingTask, { outcome: normalizedOutcome, comment });
7790
+ const result = taskLifecycleCommandService.finish(existingTask, { outcome: normalizedOutcome, comment, reviewer });
7396
7791
  recordMcpWriteAudit('finish_task', {
7397
7792
  taskId: id,
7398
7793
  outcome: normalizedOutcome,
@@ -7776,6 +8171,14 @@ export function createTaskforceMcpServer(options = {}) {
7776
8171
  update.complexity = resolveNumberOption('complexity', update.complexity, COMPLEXITY_OPTIONS);
7777
8172
  }
7778
8173
  const preview = core.bulkUpdateFieldsPreview(resolvedList, ACTIVE_WORKSPACE_ID);
8174
+ for (const item of preview.toApply) {
8175
+ if (item.fields.status !== 'review')
8176
+ continue;
8177
+ const existingTask = core.getTask(item.id, ACTIVE_WORKSPACE_ID);
8178
+ if (existingTask?.status !== 'review') {
8179
+ throw new McpPublicError('bulk_update_fields cannot move a task to review. Use finish_task with outcome=\'review\' and a nonblank evidence comment.', 'TASK_REVIEW_HANDOFF_REQUIRED', 400);
8180
+ }
8181
+ }
7779
8182
  const forceStatusBoundary = (durableTaskMutationRouter.enabled
7780
8183
  && preview.toApply.some((item) => Object.prototype.hasOwnProperty.call(item.fields, 'status')));
7781
8184
  const durableBatch = forceStatusBoundary || durableTaskRootMutationRouter.metadataEnabled;
@@ -7899,8 +8302,8 @@ export function createTaskforceMcpServer(options = {}) {
7899
8302
  };
7900
8303
  const toolHandlers = new Map(registeredTools.map((tool) => [tool.name, tool.handler]));
7901
8304
  const mcpInputValidator = createMcpInputValidator();
7902
- server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
7903
- const { name, arguments: args } = resolveMcpToolCallCompatibility(request.params.name, request.params.arguments);
8305
+ const callTool = async (params, context = {}) => {
8306
+ const { name, arguments: args } = resolveMcpToolCallCompatibility(params.name, params.arguments);
7904
8307
  const toolStartedAtMs = Date.now();
7905
8308
  let preparationMs = 0;
7906
8309
  let validationMs = 0;
@@ -7943,11 +8346,11 @@ export function createTaskforceMcpServer(options = {}) {
7943
8346
  }
7944
8347
  });
7945
8348
  let result;
7946
- if (extra.requestId === undefined || extra.requestId === null) {
8349
+ if (context.requestId === undefined || context.requestId === null) {
7947
8350
  result = await execute();
7948
8351
  }
7949
8352
  else {
7950
- const transportSessionId = String(extra.sessionId || '').trim();
8353
+ const transportSessionId = String(context.sessionId || '').trim();
7951
8354
  const requestIdentity = operationIdentityScope
7952
8355
  ? fingerprintWorkspaceSyncPayload({
7953
8356
  kind: 'shared-mcp-request',
@@ -7956,18 +8359,18 @@ export function createTaskforceMcpServer(options = {}) {
7956
8359
  || options.auth?.mcpConnectionId
7957
8360
  || options.auth?.mcpClientId
7958
8361
  || 'mcp',
7959
- requestId: { type: typeof extra.requestId, value: extra.requestId },
8362
+ requestId: { type: typeof context.requestId, value: context.requestId },
7960
8363
  tool: name,
7961
8364
  })
7962
8365
  : [
7963
8366
  ...(runtimeMode === 'local' || !transportSessionId ? [runtimeRequestNamespace] : []),
7964
8367
  String(transportSessionId || options.auth?.mcpConnectionId || options.auth?.mcpClientId || 'mcp'),
7965
- String(extra.requestId),
8368
+ String(context.requestId),
7966
8369
  name,
7967
8370
  ].join(':');
7968
8371
  const logicalCloudCreateIdentity = [
7969
8372
  String(transportSessionId || options.auth?.mcpConnectionId || options.auth?.mcpClientId || 'mcp'),
7970
- String(extra.requestId),
8373
+ String(context.requestId),
7971
8374
  name,
7972
8375
  ].join(':');
7973
8376
  result = await cloudCreateRequestIdentity.run(logicalCloudCreateIdentity, () => mcpRequestIdentity.run(requestIdentity, execute));
@@ -7986,14 +8389,14 @@ export function createTaskforceMcpServer(options = {}) {
7986
8389
  responseBytes = measureToolResultBytes(result);
7987
8390
  return result;
7988
8391
  }
7989
- const result = buildUnexpectedToolErrorResult(auditRequestId || extra.requestId);
8392
+ const result = buildUnexpectedToolErrorResult(auditRequestId || context.requestId);
7990
8393
  responseBytes = measureToolResultBytes(result);
7991
8394
  return result;
7992
8395
  }
7993
8396
  finally {
7994
8397
  try {
7995
8398
  options.onToolCallTiming?.({
7996
- requestId: String(auditRequestId || extra.requestId || '').trim() || null,
8399
+ requestId: String(auditRequestId || context.requestId || '').trim() || null,
7997
8400
  toolName: name,
7998
8401
  isWrite: isMcpWriteToolName(name),
7999
8402
  outcome: timingOutcome,
@@ -8009,7 +8412,11 @@ export function createTaskforceMcpServer(options = {}) {
8009
8412
  // Observability must never change tool behavior.
8010
8413
  }
8011
8414
  }
8012
- });
8415
+ };
8416
+ server.setRequestHandler('tools/call', (request, ctx) => callTool(request.params, {
8417
+ requestId: ctx.mcpReq.id,
8418
+ sessionId: ctx.sessionId,
8419
+ }));
8013
8420
  return {
8014
8421
  server,
8015
8422
  core,
@@ -8019,6 +8426,16 @@ export function createTaskforceMcpServer(options = {}) {
8019
8426
  tenantId,
8020
8427
  version: MCP_SERVER_VERSION,
8021
8428
  toolProfile: activeToolProfile,
8429
+ listTools,
8430
+ callTool,
8431
+ };
8432
+ }
8433
+ export async function connectMcpRuntimeToStdio(runtime, transport = new StdioServerTransport()) {
8434
+ await runtime.server.connect(transport);
8435
+ return {
8436
+ ...runtime,
8437
+ transport,
8438
+ close: () => runtime.server.close(),
8022
8439
  };
8023
8440
  }
8024
8441
  export async function startTaskforceMcpStdioServer(options = {}) {
@@ -8034,18 +8451,18 @@ export async function startTaskforceMcpStdioServer(options = {}) {
8034
8451
  projectRoot,
8035
8452
  tenantId,
8036
8453
  core,
8454
+ resourceSubscriptionMode: options.resourceSubscriptionMode ?? 'legacy-long-lived',
8037
8455
  auth: {
8038
8456
  ...envAuth,
8039
8457
  ...(options.auth || {}),
8040
8458
  },
8041
8459
  });
8042
- const transport = new StdioServerTransport();
8043
- await runtime.server.connect(transport);
8460
+ const connected = await connectMcpRuntimeToStdio(runtime);
8044
8461
  const close = createTaskforceMcpStdioClose({
8045
- closeServer: () => runtime.server.close(),
8462
+ closeServer: connected.close,
8046
8463
  ...(ownsCore ? { closeOwnedCore: () => core.close() } : {}),
8047
8464
  });
8048
- return { ...runtime, transport, close };
8465
+ return { ...runtime, transport: connected.transport, close };
8049
8466
  }
8050
8467
  catch (error) {
8051
8468
  try {