@taskforcehq/taskforce 0.3.327 → 0.3.329
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.
- package/dist/Taskforce.module.css +257 -87
- package/dist/TaskforceCore.js +237 -230
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
- package/dist/cli.js +34 -0
- package/dist/cliCloudTargetGuard.d.ts +5 -0
- package/dist/cliCloudTargetGuard.js +11 -0
- package/dist/components/activity/EntityActivityTimeline.js +15 -5
- package/dist/components/context/AttachmentPreviewHost.js +1 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
- package/dist/components/context/ContextAttachmentManager.js +116 -20
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
- package/dist/components/features/TaskSettings.d.ts +12 -1
- package/dist/components/features/TaskSettings.js +147 -40
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
- package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
- package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
- package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
- package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
- package/dist/components/features/planning/PlanningModule.js +28 -14
- package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
- package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
- package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
- package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
- package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
- package/dist/components/features/planning/planningTaskArrangement.js +5 -5
- package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
- package/dist/components/features/taskforceAgentEditorModel.js +3 -1
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +2 -2
- package/dist/components/task/TaskCard.d.ts +5 -0
- package/dist/components/task/TaskCard.js +101 -13
- package/dist/components/task/TaskContextUpload.d.ts +3 -0
- package/dist/components/task/TaskContextUpload.js +2 -2
- package/dist/components/task/TaskForm.d.ts +4 -0
- package/dist/components/task/TaskForm.js +32 -7
- package/dist/components/task/TaskImageReviews.js +28 -19
- package/dist/components/task/TaskKanban.d.ts +6 -1
- package/dist/components/task/TaskKanban.js +152 -6
- package/dist/components/task/TaskList.d.ts +5 -0
- package/dist/components/task/TaskList.js +3 -3
- package/dist/components/task/TaskStatusActions.js +11 -2
- package/dist/components/task/TaskWorkstreamPicker.js +2 -1
- package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
- package/dist/components/ui/DocumentTypeIcon.js +8 -0
- package/dist/components/ui/PasswordInput.d.ts +6 -0
- package/dist/components/ui/PasswordInput.js +11 -0
- package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
- package/dist/components/ui/TaxonomyDropdown.js +30 -3
- package/dist/components/ui/TopNoticeLayer.js +1 -1
- package/dist/components/views/AppShellHeader.js +32 -11
- package/dist/components/views/PlanComparisonPage.js +12 -0
- package/dist/components/views/StandaloneLayout.js +68 -39
- package/dist/components/views/WidgetView.js +10 -7
- package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
- package/dist/components/views/panels/PlanningDrawer.js +51 -54
- package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
- package/dist/config/envSchema.js +13 -0
- package/dist/config/localServiceCloudTarget.d.ts +10 -0
- package/dist/config/localServiceCloudTarget.js +76 -0
- package/dist/config/localServiceRuntimeContract.d.ts +13 -0
- package/dist/config/localServiceRuntimeContract.js +135 -0
- package/dist/core/AiProfileService.d.ts +1 -0
- package/dist/core/AiProfileService.js +48 -9
- package/dist/core/AttachmentLinkService.js +7 -4
- package/dist/core/DeletedTaskLifecycleService.d.ts +5 -0
- package/dist/core/DeletedTaskLifecycleService.js +55 -3
- package/dist/core/McpTokenService.d.ts +4 -0
- package/dist/core/McpTokenService.js +52 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +8 -2
- package/dist/core/TaskAttachmentLinksDurableMutationService.js +51 -18
- package/dist/core/Taskforce.d.ts +124 -2
- package/dist/core/Taskforce.js +1175 -87
- package/dist/core/types.d.ts +8 -1
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
- package/dist/documentReviews/service.d.ts +4 -0
- package/dist/documentReviews/service.js +141 -46
- package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
- package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
- package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
- package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
- package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
- package/dist/hooks/tasks/taskHttpMutation.js +1 -0
- package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
- package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
- package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
- package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
- package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
- package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
- package/dist/hooks/tasks/useTaskFormActions.d.ts +9 -0
- package/dist/hooks/tasks/useTaskFormActions.js +96 -17
- package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
- package/dist/hooks/ui/useSettingsModel.js +13 -2
- package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
- package/dist/hooks/ui/useSettingsPersistence.js +74 -47
- package/dist/hooks/useFilterSort.d.ts +7 -3
- package/dist/hooks/useFilterSort.js +15 -5
- package/dist/hooks/useTaskData.d.ts +10 -1
- package/dist/hooks/useTaskData.js +216 -7
- package/dist/hooks/useTaskMutations.d.ts +10 -2
- package/dist/hooks/useTaskMutations.js +223 -43
- package/dist/hooks/useTaskforce.d.ts +94 -57
- package/dist/hooks/useTaskforce.js +428 -16
- package/dist/hooks/useUiNotice.d.ts +4 -0
- package/dist/hooks/useUiNotice.js +4 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
- package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
- package/dist/mcp/canonicalAssetHelpers.js +39 -5
- package/dist/mcp/collaborationRegistrar.js +15 -13
- package/dist/mcp/documentAssetRegistrar.js +60 -44
- package/dist/mcp/documentHelpers.d.ts +1 -0
- package/dist/mcp/documentHelpers.js +7 -3
- package/dist/mcp/localServiceProxy.js +4 -1
- package/dist/mcp/runtime.d.ts +16 -0
- package/dist/mcp/runtime.js +735 -210
- package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
- package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
- package/dist/mcp/taskPlanningRegistrar.js +78 -67
- package/dist/mcp/toolCallCompatibility.js +38 -1
- package/dist/mcp/toolCatalog.d.ts +1 -0
- package/dist/mcp/toolRegistry.js +2 -2
- package/dist/migrations/taskSchemaMigrations.js +47 -0
- package/dist/runtime/appGateDefinitions.d.ts +30 -6
- package/dist/runtime/appGateDefinitions.js +6 -6
- package/dist/runtime/appGates.d.ts +12 -13
- package/dist/runtime/appGates.js +29 -13
- package/dist/runtime/nodeAppGates.d.ts +8 -0
- package/dist/runtime/nodeAppGates.js +27 -0
- package/dist/server/appAccess.d.ts +12 -0
- package/dist/server/appAccess.js +21 -0
- package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
- package/dist/server/authorizedWorkspaceAccess.js +40 -0
- package/dist/server/cloudCollectionRead.d.ts +25 -0
- package/dist/server/cloudCollectionRead.js +120 -0
- package/dist/server/cloudCollectionReadPool.d.ts +52 -0
- package/dist/server/cloudCollectionReadPool.js +140 -0
- package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
- package/dist/server/cloudCollectionReadWorker.js +36 -0
- package/dist/server/index.d.ts +6 -11
- package/dist/server/index.js +67 -42
- package/dist/server/localServiceLease.d.ts +6 -0
- package/dist/server/localServiceLease.js +17 -1
- package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
- package/dist/server/rateLimit.d.ts +1 -0
- package/dist/server/rateLimit.js +4 -0
- package/dist/server/realtimeSyncWs.js +36 -12
- package/dist/server/routes/admin.js +1 -0
- package/dist/server/routes/agentRoles.js +3 -2
- package/dist/server/routes/agentSkills.js +3 -2
- package/dist/server/routes/agents.js +118 -16
- package/dist/server/routes/annotatedAttachments.js +3 -2
- package/dist/server/routes/auth.js +3 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +160 -13
- package/dist/server/routes/durableTaskHttpRouters.d.ts +11 -2
- package/dist/server/routes/durableTaskHttpRouters.js +2 -2
- package/dist/server/routes/localService.js +4 -0
- package/dist/server/routes/primitives.d.ts +4 -0
- package/dist/server/routes/shared.d.ts +24 -0
- package/dist/server/routes/shared.js +114 -18
- package/dist/server/routes/sync.js +22 -0
- package/dist/server/routes/syncAuxRoutes.js +5 -2
- package/dist/server/routes/syncPullApplyRoutes.js +23 -4
- package/dist/server/routes/syncV3FeedRoutes.js +22 -18
- package/dist/server/routes/syncV3LocalCaptureRoutes.js +48 -4
- package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
- package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
- package/dist/server/routes/syncV3OperationRoutes.js +27 -2
- package/dist/server/routes/tasks.js +299 -97
- package/dist/server/routes/workspaces.js +43 -1
- package/dist/server/routes.js +35 -7
- package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
- package/dist/server/workspaceDeletionAdmission.js +50 -0
- package/dist/service/localServiceCli.js +52 -25
- package/dist/service/localServiceClientLifecycle.js +93 -6
- package/dist/services/codexAppServerModelGateway.d.ts +112 -0
- package/dist/services/codexAppServerModelGateway.js +438 -0
- package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
- package/dist/services/taskforceAgentMcpBridge.js +23 -3
- package/dist/shared/planningIdentity.d.ts +20 -0
- package/dist/shared/planningIdentity.js +79 -0
- package/dist/shared/taskforceAgentDefinition.js +7 -1
- package/dist/shared/taskforceAgentModels.d.ts +1 -0
- package/dist/shared/taskforceAgentModels.js +30 -1
- package/dist/storage/postgresAdapter.d.ts +20 -3
- package/dist/storage/postgresAdapter.js +110 -14
- package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
- package/dist/storage/postgresRequestDiagnostics.js +24 -0
- package/dist/storage/postgresWorker.js +13 -4
- package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
- package/dist/storage/postgresWorkerProtocol.js +30 -0
- package/dist/storage/workspaceAssetStore.d.ts +32 -0
- package/dist/storage/workspaceAssetStore.js +70 -0
- package/dist/sync/cloudSyncApi.d.ts +4 -1
- package/dist/sync/cloudSyncApi.js +7 -2
- package/dist/sync/contentSyncState.d.ts +1 -1
- package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +17 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +77 -23
- package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
- package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
- package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
- package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
- package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +16 -0
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +46 -4
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +51 -11
- package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
- package/dist/sync/syncApplyHandlers.d.ts +3 -1
- package/dist/sync/syncApplyHandlers.js +29 -0
- package/dist/sync/syncService.d.ts +2 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/taskSyncPayload.d.ts +1 -0
- package/dist/sync/taskSyncPayload.js +6 -0
- package/dist/sync/v3/combinedFeedCapability.js +4 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +3 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +31 -3
- package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
- package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +47 -12
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
- package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
- package/dist/sync/v3/localOutboxDispatcher.js +5 -1
- package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +3 -0
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +62 -10
- package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
- package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
- package/dist/sync/v3/localTaskMetadataClient.js +1 -0
- package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
- package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
- package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
- package/dist/sync/v3/localTaskStatusClient.js +6 -1
- package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
- package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
- package/dist/sync/v3/syncDurabilityStore.d.ts +15 -0
- package/dist/sync/v3/syncDurabilityStore.js +201 -43
- package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +7 -4
- package/dist/sync/v3/taskAttachmentLinksMutationService.js +37 -12
- package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
- package/dist/sync/v3/taskChecklistMutationService.js +4 -0
- package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
- package/dist/sync/v3/taskCreateMutationService.js +7 -4
- package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
- package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
- package/dist/sync/v3/taskMetadataMutationService.js +4 -0
- package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
- package/dist/sync/v3/taskRestoreMutationService.js +7 -4
- package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
- package/dist/sync/v3/taskStatusMutationService.js +4 -0
- package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
- package/dist/sync/v3/workflowAssignmentSync.js +3 -0
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +14 -12
- package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
- package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
- package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
- package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +10 -1
- package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +15 -4
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +66 -13
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +11 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +57 -22
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +10 -4
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +124 -51
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +120 -28
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
- package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
- package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
- package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
- package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
- package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
- package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +3 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
- package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
- package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
- package/dist/sync/workspacePullFeed.d.ts +6 -1
- package/dist/sync/workspacePullFeed.js +174 -162
- package/dist/sync/workspaceRepair.d.ts +3 -0
- package/dist/sync/workspaceRepair.js +67 -6
- package/dist/sync/workspaceSyncModel.d.ts +11 -0
- package/dist/sync/workspaceSyncModel.js +27 -2
- package/dist/types.d.ts +55 -1
- package/dist/types.js +6 -0
- package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
- package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
- package/dist/ui/assets/AssetTraySortControl-C8Rztyal.js +1 -0
- package/dist/ui/assets/ContextAttachmentManager-Cj3c7X9y.js +3 -0
- package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-B5uvzems.js} +4 -4
- package/dist/ui/assets/EntityActivityTimeline-BVFvCB5R.js +1 -0
- package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
- package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
- package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
- package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
- package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
- package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
- package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
- package/dist/ui/assets/WorkflowManagerModule-DEr5di_r.js +1 -0
- package/dist/ui/assets/index-I5zhdtgt.js +7 -0
- package/dist/ui/assets/index-VRFsx3wz.css +1 -0
- package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-D9Lpw-j4.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/constants.d.ts +2 -2
- package/dist/utils/constants.js +1 -1
- package/dist/utils/contextAttachmentUpload.d.ts +2 -0
- package/dist/utils/contextAttachmentUpload.js +7 -0
- package/dist/utils/contextFiles.d.ts +1 -1
- package/dist/utils/contextFiles.js +4 -4
- package/dist/utils/taskActivity.d.ts +3 -2
- package/dist/utils/taskActivity.js +19 -2
- package/dist/utils/taskEditorReconciliation.d.ts +40 -0
- package/dist/utils/taskEditorReconciliation.js +217 -0
- package/dist/utils/taskNormalization.d.ts +1 -0
- package/dist/utils/taskNormalization.js +39 -0
- package/dist/utils/taskSearch.js +2 -2
- package/dist/utils/uiNotice.d.ts +4 -0
- package/package.json +21 -4
- package/dist/ui/assets/AiProfilesModule-DPxQeF9N.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-DHNYaNg6.js +0 -3
- package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
- package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
- package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
- package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
- package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
- package/dist/ui/assets/PlansPage-BVH_NfgT.js +0 -1
- package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
- package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
- package/dist/ui/assets/TaskSettings-xVsxSa0g.js +0 -12
- package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
- package/dist/ui/assets/index-BzJlVnNS.css +0 -1
- package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
package/dist/mcp/runtime.js
CHANGED
|
@@ -16,7 +16,7 @@ import { AnnotatedAttachmentCommandError, createAnnotatedAttachmentCommandServic
|
|
|
16
16
|
import { createDocumentReviewCommandService, DocumentReviewCommandError, } from '../documentReviews/commandService.js';
|
|
17
17
|
import { COMPLEXITY_OPTIONS, resolveNumberOption, resolveStringOption, STATUS_OPTIONS } from './optionResolution.js';
|
|
18
18
|
import { buildResolveAiProfileInputFromBootstrap } from './aiProfileBootstrap.js';
|
|
19
|
-
import { createCanonicalAssetHelpers } from './canonicalAssetHelpers.js';
|
|
19
|
+
import { CanonicalDocumentHeadStaleError, createCanonicalAssetHelpers } from './canonicalAssetHelpers.js';
|
|
20
20
|
import { createDocumentHelpers } from './documentHelpers.js';
|
|
21
21
|
import { createTaskAttachmentHelpers } from './taskAttachmentHelpers.js';
|
|
22
22
|
import { listRegisteredGitWorktreeRoots } from './gitWorktreeRoots.js';
|
|
@@ -32,7 +32,8 @@ import { WorkspaceSyncContentVersionStore, } from '../sync/engine/workspaceSyncC
|
|
|
32
32
|
import { WorkspaceSyncPublishedContentResolver } from '../sync/engine/workspaceSyncPublishedContentResolver.js';
|
|
33
33
|
import { createWorkspaceSyncServerContentRuntime } from '../sync/engine/workspaceSyncServerContentRuntime.js';
|
|
34
34
|
import { resolveVirusScanConfigFromEnv, scanBufferForThreats } from '../security/virusScan.js';
|
|
35
|
-
import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY,
|
|
35
|
+
import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, } from '../runtime/appGates.js';
|
|
36
|
+
import { resolveNodeAppGateAccess } from '../runtime/nodeAppGates.js';
|
|
36
37
|
import { getDocumentReferenceLabel, parseDocumentReference } from '../utils/documentReferences.js';
|
|
37
38
|
import { getImageReferenceLabel, parseImageReference } from '../utils/imageReferences.js';
|
|
38
39
|
import { inferCanonicalAssetKind, isValidCanonicalDocumentAsset } from '../utils/canonicalAssetKind.js';
|
|
@@ -59,7 +60,7 @@ import { createTaskPlanCommand } from './taskPlanCommand.js';
|
|
|
59
60
|
import { WorkflowAssignmentService } from '../core/WorkflowAssignmentService.js';
|
|
60
61
|
import { WorkflowExecutionService, WorkflowExecutionServiceError, } from '../core/WorkflowExecutionService.js';
|
|
61
62
|
import { WorkflowStore } from '../storage/workflowStore.js';
|
|
62
|
-
import { getMcpClientRegistryEntry, normalizeMcpConnectionId, parseMcpClientId } from './clientRegistry.js';
|
|
63
|
+
import { getMcpClientRegistryEntry, inferLegacyMcpClientIdFromProfile, normalizeMcpConnectionId, parseMcpClientId } from './clientRegistry.js';
|
|
63
64
|
import { BedrockAvatarImageProvider } from '../services/avatarImageProvider.js';
|
|
64
65
|
import { AiProfileAvatarGenerationError, checkAiProfileAvatarGenerationRateLimit, generateAiProfileAvatar, } from '../services/aiProfileAvatarGenerationService.js';
|
|
65
66
|
import { isAiProfileAvatarGenerationEligible } from '../shared/aiProfileAvatarEligibility.js';
|
|
@@ -410,6 +411,22 @@ function resolveRecoverableToolError(error, context) {
|
|
|
410
411
|
};
|
|
411
412
|
}
|
|
412
413
|
if (error instanceof TaskforceRuleError) {
|
|
414
|
+
if (code === 'AI_PROFILE_SELECTION_REQUIRED') {
|
|
415
|
+
const selectionDetails = details && typeof details === 'object' && !Array.isArray(details)
|
|
416
|
+
? details
|
|
417
|
+
: {};
|
|
418
|
+
const suggestedArgs = selectionDetails.suggestedArgs;
|
|
419
|
+
return {
|
|
420
|
+
message,
|
|
421
|
+
code,
|
|
422
|
+
details,
|
|
423
|
+
nextTool: 'resolve_profile',
|
|
424
|
+
...(suggestedArgs && typeof suggestedArgs === 'object' && !Array.isArray(suggestedArgs)
|
|
425
|
+
? { suggestedArgs: suggestedArgs }
|
|
426
|
+
: {}),
|
|
427
|
+
retryHint: 'Choose the intended compatible profile candidate and retry resolve_profile with the same name plus its profileId. If more than one candidate is present and the intended identity is unclear, ask the user before selecting.',
|
|
428
|
+
};
|
|
429
|
+
}
|
|
413
430
|
if (code === 'AI_PROFILE_AVATAR_PROVIDER_FAILED' || code === 'AI_PROFILE_AVATAR_PERSISTENCE_FAILED') {
|
|
414
431
|
return {
|
|
415
432
|
message,
|
|
@@ -677,6 +694,14 @@ export function buildUnexpectedToolErrorResult(requestId) {
|
|
|
677
694
|
isError: true,
|
|
678
695
|
};
|
|
679
696
|
}
|
|
697
|
+
function measureToolResultBytes(result) {
|
|
698
|
+
try {
|
|
699
|
+
return Buffer.byteLength(JSON.stringify(result), 'utf8');
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
return 0;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
680
705
|
export function buildBatchToolError(error, fallbackStatusCode) {
|
|
681
706
|
const resolved = resolveRecoverableToolError(error);
|
|
682
707
|
if (!resolved) {
|
|
@@ -786,6 +811,29 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
786
811
|
}
|
|
787
812
|
const mcpRequestIdentity = new AsyncLocalStorage();
|
|
788
813
|
const cloudCreateRequestIdentity = new AsyncLocalStorage();
|
|
814
|
+
const mcpToolSubphaseTimings = new AsyncLocalStorage();
|
|
815
|
+
const measureToolSubphase = (phase, operation) => {
|
|
816
|
+
const startedAtMs = Date.now();
|
|
817
|
+
try {
|
|
818
|
+
return operation();
|
|
819
|
+
}
|
|
820
|
+
finally {
|
|
821
|
+
const timings = mcpToolSubphaseTimings.getStore();
|
|
822
|
+
if (timings)
|
|
823
|
+
timings[phase] = (timings[phase] || 0) + (Date.now() - startedAtMs);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
const measureToolSubphaseAsync = async (phase, operation) => {
|
|
827
|
+
const startedAtMs = Date.now();
|
|
828
|
+
try {
|
|
829
|
+
return await operation();
|
|
830
|
+
}
|
|
831
|
+
finally {
|
|
832
|
+
const timings = mcpToolSubphaseTimings.getStore();
|
|
833
|
+
if (timings)
|
|
834
|
+
timings[phase] = (timings[phase] || 0) + (Date.now() - startedAtMs);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
789
837
|
const operationIdentityScope = options.operationIdentityScope
|
|
790
838
|
? {
|
|
791
839
|
generation: String(options.operationIdentityScope.generation || '').trim(),
|
|
@@ -824,28 +872,34 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
824
872
|
return null;
|
|
825
873
|
return `${kind}-${createHash('sha256').update(`${kind}:${requestIdentity}:${discriminator || ''}`).digest('hex').slice(0, 32)}`;
|
|
826
874
|
};
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
875
|
+
const normalizeExplicitIdempotencyKey = (value) => {
|
|
876
|
+
if (value === undefined || value === null)
|
|
877
|
+
return null;
|
|
878
|
+
const key = String(value).trim();
|
|
879
|
+
if (!key)
|
|
880
|
+
throw new McpPublicError('idempotencyKey must contain non-whitespace text.');
|
|
881
|
+
if (key.length > 128)
|
|
882
|
+
throw new McpPublicError('idempotencyKey must contain at most 128 characters.');
|
|
883
|
+
return key;
|
|
884
|
+
};
|
|
885
|
+
const stableExplicitMutationId = (kind, tool, idempotencyKey, discriminator) => {
|
|
886
|
+
const fingerprint = createHash('sha256').update(JSON.stringify([
|
|
887
|
+
kind,
|
|
888
|
+
tenantId,
|
|
889
|
+
ACTIVE_WORKSPACE_ID,
|
|
890
|
+
getActiveMcpActorRef(),
|
|
891
|
+
tool,
|
|
892
|
+
idempotencyKey,
|
|
893
|
+
discriminator || '',
|
|
894
|
+
])).digest('hex');
|
|
847
895
|
return `${kind}-${fingerprint.slice(0, 32)}`;
|
|
848
896
|
};
|
|
897
|
+
const stableTaskCreateIdempotencyId = (kind, idempotencyKey, discriminator) => stableExplicitMutationId(kind, 'create_task', idempotencyKey, discriminator);
|
|
898
|
+
const requireDurableExplicitIdempotency = (enabled, tool) => {
|
|
899
|
+
if (enabled)
|
|
900
|
+
return;
|
|
901
|
+
throw new McpPublicError(`idempotencyKey requires the durable ${tool} mutation boundary, which is unavailable for this runtime.`, 'MCP_IDEMPOTENCY_UNAVAILABLE', 503);
|
|
902
|
+
};
|
|
849
903
|
const durableTaskMutationRouter = options.durableTaskMutationRouter || createDurableTaskMutationRouter({
|
|
850
904
|
core,
|
|
851
905
|
runtimeMode,
|
|
@@ -929,7 +983,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
929
983
|
cloudClientId: 'mcp-cloud',
|
|
930
984
|
createOperationId: () => mutationOptions?.operationId
|
|
931
985
|
|| requireStableMcpRequestId('operation', 'initiative'),
|
|
932
|
-
createCommentId: () =>
|
|
986
|
+
createCommentId: () => mutationOptions?.commentId
|
|
987
|
+
|| requireStableMcpRequestId('comment', 'initiative-comment'),
|
|
933
988
|
deferPostCommitNotification: mutationOptions?.deferPostCommitNotification,
|
|
934
989
|
});
|
|
935
990
|
const initiativeFromDurableResult = (result) => result.state === 'pending' ? result.provisionalInitiative : result.authoritativeInitiative;
|
|
@@ -946,7 +1001,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
946
1001
|
cloudClientId: 'mcp-cloud',
|
|
947
1002
|
createOperationId: () => mutationOptions?.operationId
|
|
948
1003
|
|| requireStableMcpRequestId('operation', 'workstream'),
|
|
949
|
-
createCommentId: () =>
|
|
1004
|
+
createCommentId: () => mutationOptions?.commentId
|
|
1005
|
+
|| requireStableMcpRequestId('comment', 'workstream-comment'),
|
|
950
1006
|
deferPostCommitNotification: mutationOptions?.deferPostCommitNotification,
|
|
951
1007
|
});
|
|
952
1008
|
const workstreamFromDurableResult = (result) => result.state === 'pending' ? result.provisionalWorkstream : result.authoritativeWorkstream;
|
|
@@ -1049,7 +1105,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1049
1105
|
legacyProvider: asset.storageProvider,
|
|
1050
1106
|
});
|
|
1051
1107
|
return resolved.source === 'versioned'
|
|
1052
|
-
&& resolved.
|
|
1108
|
+
&& resolved.storageProvider === asset.storageProvider
|
|
1109
|
+
&& resolved.contentSha256 === asset.contentSha256
|
|
1110
|
+
&& resolved.sizeBytes === asset.sizeBytes;
|
|
1053
1111
|
}
|
|
1054
1112
|
catch {
|
|
1055
1113
|
return false;
|
|
@@ -1151,7 +1209,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1151
1209
|
},
|
|
1152
1210
|
tools: {},
|
|
1153
1211
|
},
|
|
1154
|
-
instructions:
|
|
1212
|
+
instructions: runtimeMode === 'cloud' && options.auth?.actorType === 'token'
|
|
1213
|
+
? `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.`
|
|
1214
|
+
: `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.`,
|
|
1155
1215
|
});
|
|
1156
1216
|
const TOOL_PERMISSIONS = MCP_TOOL_PERMISSIONS;
|
|
1157
1217
|
const RESOURCE_PERMISSIONS = {
|
|
@@ -1243,14 +1303,21 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1243
1303
|
}
|
|
1244
1304
|
return { id: task.id, task };
|
|
1245
1305
|
}
|
|
1246
|
-
const buildDocumentDescriptor = (asset, options) =>
|
|
1306
|
+
const buildDocumentDescriptor = (asset, options) => {
|
|
1307
|
+
const descriptor = withDocumentUrl(documentHelpers.buildDocumentDescriptor(asset, options));
|
|
1308
|
+
if (runtimeMode === 'local')
|
|
1309
|
+
return descriptor;
|
|
1310
|
+
const { storageKey: _storageKey, ...cloudDescriptor } = descriptor;
|
|
1311
|
+
return cloudDescriptor;
|
|
1312
|
+
};
|
|
1247
1313
|
const buildCanonicalDocumentAttachmentHint = documentHelpers.buildCanonicalDocumentAttachmentHint;
|
|
1248
1314
|
function buildBoundedTextContent(content, maxChars) {
|
|
1249
1315
|
const requestedMaxChars = Number(maxChars);
|
|
1250
1316
|
const hasRequestedMaxChars = maxChars !== undefined && maxChars !== null && maxChars !== '';
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1317
|
+
if (hasRequestedMaxChars && (!Number.isInteger(requestedMaxChars) || requestedMaxChars < 1)) {
|
|
1318
|
+
throw new McpPublicError('maxChars must be a positive integer');
|
|
1319
|
+
}
|
|
1320
|
+
const resolvedMaxChars = hasRequestedMaxChars ? requestedMaxChars : 200000;
|
|
1254
1321
|
const characters = Array.from(content);
|
|
1255
1322
|
const totalChars = characters.length;
|
|
1256
1323
|
const boundedContent = totalChars > resolvedMaxChars
|
|
@@ -1271,7 +1338,19 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1271
1338
|
format: "metadata"
|
|
1272
1339
|
};
|
|
1273
1340
|
}
|
|
1341
|
+
const assertCurrentHead = () => {
|
|
1342
|
+
try {
|
|
1343
|
+
canonicalAssetHelpers.assertCanonicalDocumentHeadCurrent(asset);
|
|
1344
|
+
}
|
|
1345
|
+
catch (error) {
|
|
1346
|
+
if (error instanceof CanonicalDocumentHeadStaleError) {
|
|
1347
|
+
throw new McpPublicError(error.message, error.code, error.statusCode, error.details);
|
|
1348
|
+
}
|
|
1349
|
+
throw error;
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1274
1352
|
if (format === "download") {
|
|
1353
|
+
assertCurrentHead();
|
|
1275
1354
|
return {
|
|
1276
1355
|
format: "download",
|
|
1277
1356
|
downloadUrl: canonicalAssetHelpers.buildDocumentDownloadUrl(asset),
|
|
@@ -1279,6 +1358,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1279
1358
|
};
|
|
1280
1359
|
}
|
|
1281
1360
|
if (!isTextReadableMimeType(asset.mimeType || '')) {
|
|
1361
|
+
assertCurrentHead();
|
|
1282
1362
|
return {
|
|
1283
1363
|
format: "download",
|
|
1284
1364
|
downloadUrl: canonicalAssetHelpers.buildDocumentDownloadUrl(asset),
|
|
@@ -1290,6 +1370,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1290
1370
|
buffer = await canonicalAssetHelpers.readCanonicalDocumentBuffer(asset);
|
|
1291
1371
|
}
|
|
1292
1372
|
catch (error) {
|
|
1373
|
+
if (error instanceof CanonicalDocumentHeadStaleError) {
|
|
1374
|
+
throw new McpPublicError(error.message, error.code, error.statusCode, error.details);
|
|
1375
|
+
}
|
|
1293
1376
|
const message = error instanceof Error ? String(error.message || '').trim() : '';
|
|
1294
1377
|
if (/^Document .+ not found in workspace /i.test(message)) {
|
|
1295
1378
|
throw new McpPublicError(message, 'MCP_NOT_FOUND', 404);
|
|
@@ -1326,8 +1409,10 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1326
1409
|
}
|
|
1327
1410
|
const requestedLimit = Number(options?.limit);
|
|
1328
1411
|
const limit = Number.isFinite(requestedLimit)
|
|
1329
|
-
? Math.max(1, Math.min(
|
|
1412
|
+
? Math.max(1, Math.min(51, Math.trunc(requestedLimit)))
|
|
1330
1413
|
: 10;
|
|
1414
|
+
const requestedOffset = Number(options?.offset);
|
|
1415
|
+
const offset = Number.isFinite(requestedOffset) ? Math.max(0, Math.trunc(requestedOffset)) : 0;
|
|
1331
1416
|
const normalize = (value) => typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1332
1417
|
const tokenize = (value) => normalize(value).split(/[^a-z0-9]+/i).filter(Boolean);
|
|
1333
1418
|
const allImages = workspaceAssetStore.listAllByWorkspace(ACTIVE_WORKSPACE_ID, { kind: 'image' });
|
|
@@ -1371,8 +1456,10 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1371
1456
|
return [{ asset, matchedOn: Array.from(matchedOn), score }];
|
|
1372
1457
|
});
|
|
1373
1458
|
return scoredMatches
|
|
1374
|
-
.sort((a, b) => b.score - a.score
|
|
1375
|
-
.
|
|
1459
|
+
.sort((a, b) => b.score - a.score
|
|
1460
|
+
|| String(b.asset.updatedAt || '').localeCompare(String(a.asset.updatedAt || ''))
|
|
1461
|
+
|| a.asset.assetId.localeCompare(b.asset.assetId))
|
|
1462
|
+
.slice(offset, offset + limit);
|
|
1376
1463
|
}
|
|
1377
1464
|
const searchDocumentAssets = documentHelpers.searchDocumentAssets;
|
|
1378
1465
|
function buildImageDescriptor(asset) {
|
|
@@ -1384,9 +1471,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1384
1471
|
originalFilename: asset.originalFilename ?? null,
|
|
1385
1472
|
mimeType: asset.mimeType ?? null,
|
|
1386
1473
|
sizeBytes: typeof asset.sizeBytes === 'number' ? asset.sizeBytes : null,
|
|
1387
|
-
storageKey: asset.storageKey ?? null,
|
|
1474
|
+
...(runtimeMode === 'local' ? { storageKey: asset.storageKey ?? null } : {}),
|
|
1388
1475
|
updatedAt: asset.updatedAt ?? null,
|
|
1389
|
-
|
|
1476
|
+
...(runtimeMode === 'local'
|
|
1477
|
+
? { apiUrl: `/api/taskforce/context/${encodeURIComponent(String(asset.storageKey || '').trim())}` }
|
|
1478
|
+
: {}),
|
|
1390
1479
|
};
|
|
1391
1480
|
return withAttachmentUrl({ ...descriptor, kind: 'image' });
|
|
1392
1481
|
}
|
|
@@ -1396,6 +1485,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1396
1485
|
}
|
|
1397
1486
|
catch (error) {
|
|
1398
1487
|
const requestedIdentifier = typeof documentIdentifier === 'string' ? documentIdentifier.trim() : '';
|
|
1488
|
+
if (!requestedIdentifier) {
|
|
1489
|
+
throw new McpPublicError('documentId is required', 'MCP_INVALID_ARGUMENT', 400);
|
|
1490
|
+
}
|
|
1399
1491
|
if (requestedIdentifier && /not found in workspace/i.test(String(error?.message || error))) {
|
|
1400
1492
|
throw new TaskforceRuleError(String(error?.message || error), 404, 'DOCUMENT_NOT_FOUND', {
|
|
1401
1493
|
requestedIdentifier,
|
|
@@ -1436,9 +1528,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1436
1528
|
|| `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);
|
|
1437
1529
|
}
|
|
1438
1530
|
const auditActorType = options.auth?.actorType || (runtimeMode === 'cloud' ? 'user' : 'local');
|
|
1531
|
+
const requiresExplicitTokenAuthorship = runtimeMode === 'cloud' && auditActorType === 'token';
|
|
1439
1532
|
const auditActorUserId = String(options.auth?.userId || (runtimeMode === 'cloud' ? 'anonymous' : 'local-mcp')).trim() || 'anonymous';
|
|
1440
1533
|
const auditTokenId = options.auth?.tokenId ? String(options.auth.tokenId).trim() : null;
|
|
1441
1534
|
const configuredAiProfileId = options.auth?.aiProfileId ? String(options.auth.aiProfileId).trim() || null : null;
|
|
1535
|
+
const configuredAiProfileIdentityAsserted = options.auth?.aiProfileIdentityAsserted === true;
|
|
1442
1536
|
const configuredAiProfileToken = options.auth?.aiProfileToken ? String(options.auth.aiProfileToken).trim() || null : null;
|
|
1443
1537
|
const configuredAiProfileName = options.auth?.aiProfileName ? String(options.auth.aiProfileName).trim() || null : null;
|
|
1444
1538
|
const configuredAiProfileIcon = options.auth?.aiProfileIcon ? String(options.auth.aiProfileIcon).trim() || null : null;
|
|
@@ -1456,11 +1550,59 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1456
1550
|
const auditUserAgent = options.auth?.userAgent ? String(options.auth.userAgent).trim() || null : null;
|
|
1457
1551
|
let activeAiProfileId = null;
|
|
1458
1552
|
let activeAiBindingSource = null;
|
|
1553
|
+
let aiProfileIdentityConflictDetected = false;
|
|
1554
|
+
function failAiProfileBindingConflict(message, details) {
|
|
1555
|
+
aiProfileIdentityConflictDetected = true;
|
|
1556
|
+
let credentialRevoked = false;
|
|
1557
|
+
if (auditActorType === 'oauth' && auditTokenId) {
|
|
1558
|
+
core.revokeMcpOAuthConnectorGrant({
|
|
1559
|
+
actorUserId: auditActorUserId,
|
|
1560
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1561
|
+
grantId: auditTokenId,
|
|
1562
|
+
});
|
|
1563
|
+
credentialRevoked = true;
|
|
1564
|
+
}
|
|
1565
|
+
throw new TaskforceRuleError(credentialRevoked ? `${message} The conflicting credential was revoked; reconnect before retrying.` : message, 409, 'AI_PROFILE_BINDING_CONFLICT', { ...details, credentialRevoked, nextTool: 'resolve_profile' });
|
|
1566
|
+
}
|
|
1567
|
+
function assertRequestedIdentityMatchesBoundProfile(profile, input) {
|
|
1568
|
+
if (auditActorType !== 'oauth')
|
|
1569
|
+
return;
|
|
1570
|
+
const requestedName = String(input.name || '').trim();
|
|
1571
|
+
const requestedNameKey = requestedName.toLowerCase();
|
|
1572
|
+
const requestedUsernameKey = normalizeConfiguredAiProfileUsername(undefined, requestedName).toLowerCase();
|
|
1573
|
+
const boundNameKey = String(profile.name || '').trim().toLowerCase();
|
|
1574
|
+
const boundUsernameKey = String(profile.username || '').trim().toLowerCase();
|
|
1575
|
+
const nameMatches = Boolean(requestedNameKey) && (requestedNameKey === boundNameKey
|
|
1576
|
+
|| requestedNameKey === boundUsernameKey
|
|
1577
|
+
|| requestedUsernameKey === boundUsernameKey);
|
|
1578
|
+
const normalizeProvider = (value) => String(value || '')
|
|
1579
|
+
.trim()
|
|
1580
|
+
.toLowerCase()
|
|
1581
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
1582
|
+
const requestedProvider = normalizeProvider(input.provider);
|
|
1583
|
+
const boundProvider = normalizeProvider(profile.provider);
|
|
1584
|
+
const providerConflicts = Boolean(requestedProvider && boundProvider && requestedProvider !== boundProvider);
|
|
1585
|
+
if (nameMatches && !providerConflicts)
|
|
1586
|
+
return;
|
|
1587
|
+
failAiProfileBindingConflict('The requested AI identity conflicts with the profile already bound to this OAuth connection.', {
|
|
1588
|
+
reason: 'requested_identity_mismatch',
|
|
1589
|
+
boundProfile: {
|
|
1590
|
+
id: profile.id,
|
|
1591
|
+
name: profile.name,
|
|
1592
|
+
username: profile.username,
|
|
1593
|
+
provider: profile.provider || null,
|
|
1594
|
+
},
|
|
1595
|
+
requestedIdentity: {
|
|
1596
|
+
name: requestedName || null,
|
|
1597
|
+
provider: String(input.provider || '').trim() || null,
|
|
1598
|
+
},
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1459
1601
|
function applyConfiguredMcpClientContext(profileId) {
|
|
1460
1602
|
const shouldBindOAuthGrant = auditActorType === 'oauth'
|
|
1461
1603
|
&& Boolean(auditTokenId)
|
|
1462
1604
|
&& configuredAiProfileId !== profileId;
|
|
1463
|
-
const shouldApplyClientContext = Boolean((configuredMcpClientId && configuredMcpClientId !== 'general')
|
|
1605
|
+
const shouldApplyClientContext = auditActorType !== 'token' && Boolean((configuredMcpClientId && configuredMcpClientId !== 'general')
|
|
1464
1606
|
|| configuredMcpConnectionId);
|
|
1465
1607
|
if (!shouldBindOAuthGrant && !shouldApplyClientContext)
|
|
1466
1608
|
return;
|
|
@@ -1541,6 +1683,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1541
1683
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1542
1684
|
connectionId: configuredMcpConnectionId,
|
|
1543
1685
|
seatScope: configuredAiProfileSeatScope,
|
|
1686
|
+
repairLegacyBinding: options?.repairConnection !== false,
|
|
1544
1687
|
});
|
|
1545
1688
|
if (!match && auditActorType !== 'oauth' && configuredMcpClientId && configuredMcpClientId !== 'general') {
|
|
1546
1689
|
const candidates = core.listAiProfiles(ACTIVE_WORKSPACE_ID).filter((profile) => {
|
|
@@ -1551,12 +1694,14 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1551
1694
|
&& connection.clientId === configuredMcpClientId);
|
|
1552
1695
|
});
|
|
1553
1696
|
if (candidates.length === 1) {
|
|
1554
|
-
match =
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1697
|
+
match = options?.repairConnection === false
|
|
1698
|
+
? candidates[0]
|
|
1699
|
+
: core.bindAiProfileConnection({
|
|
1700
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1701
|
+
profileId: candidates[0].id,
|
|
1702
|
+
connectionId: configuredMcpConnectionId,
|
|
1703
|
+
clientId: configuredMcpClientId,
|
|
1704
|
+
});
|
|
1560
1705
|
}
|
|
1561
1706
|
}
|
|
1562
1707
|
if (match?.archivedAt) {
|
|
@@ -1575,6 +1720,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1575
1720
|
mergeTarget = next;
|
|
1576
1721
|
}
|
|
1577
1722
|
if (!mergeTarget.archivedAt) {
|
|
1723
|
+
if (options?.repairConnection === false)
|
|
1724
|
+
return mergeTarget;
|
|
1578
1725
|
return core.bindAiProfileConnection({
|
|
1579
1726
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1580
1727
|
profileId: mergeTarget.id,
|
|
@@ -1619,10 +1766,14 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1619
1766
|
return resolved;
|
|
1620
1767
|
}
|
|
1621
1768
|
function findTrustedBoundProfile(options) {
|
|
1769
|
+
// A generic access token proves workspace/tool authorization, not which AI
|
|
1770
|
+
// is acting. Never use its historical connection row as authorship proof.
|
|
1771
|
+
if (requiresExplicitTokenAuthorship)
|
|
1772
|
+
return null;
|
|
1622
1773
|
const grantProfile = findProfileForConfiguredGrant();
|
|
1623
1774
|
const connectionProfile = findProfileForConfiguredConnection(options);
|
|
1624
1775
|
if (grantProfile && connectionProfile && grantProfile.id !== connectionProfile.id) {
|
|
1625
|
-
|
|
1776
|
+
failAiProfileBindingConflict('The authenticated OAuth grant and MCP connection are bound to different AI profiles. Select the intended profile in Taskforce before retrying.', { reason: 'grant_connection_mismatch', grantProfileId: grantProfile.id, connectionProfileId: connectionProfile.id });
|
|
1626
1777
|
}
|
|
1627
1778
|
const profile = grantProfile || connectionProfile;
|
|
1628
1779
|
if (profile && configuredMcpConnectionId && !connectionProfile && options?.repairConnection !== false) {
|
|
@@ -1635,6 +1786,51 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1635
1786
|
}
|
|
1636
1787
|
return profile;
|
|
1637
1788
|
}
|
|
1789
|
+
function assertOAuthProfileClientCompatibility(profile) {
|
|
1790
|
+
if (auditActorType !== 'oauth' || !configuredMcpClientId)
|
|
1791
|
+
return;
|
|
1792
|
+
const connection = profile.providerMetadata?.connection;
|
|
1793
|
+
const storedClientId = connection && typeof connection === 'object' && !Array.isArray(connection)
|
|
1794
|
+
? parseMcpClientId(connection.clientId)
|
|
1795
|
+
: null;
|
|
1796
|
+
const inferredClientId = inferLegacyMcpClientIdFromProfile(profile);
|
|
1797
|
+
const profileClientId = storedClientId || inferredClientId;
|
|
1798
|
+
if (!profileClientId || profileClientId === configuredMcpClientId)
|
|
1799
|
+
return;
|
|
1800
|
+
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 });
|
|
1801
|
+
}
|
|
1802
|
+
function listSelectableOAuthProfiles(profileName) {
|
|
1803
|
+
if (auditActorType !== 'oauth')
|
|
1804
|
+
return [];
|
|
1805
|
+
const requestedName = String(profileName || '').trim().toLowerCase();
|
|
1806
|
+
if (!requestedName)
|
|
1807
|
+
return [];
|
|
1808
|
+
const requestedUsername = normalizeConfiguredAiProfileUsername(undefined, requestedName).toLowerCase();
|
|
1809
|
+
const restorableProfileIds = !configuredMcpClientId && auditTokenId
|
|
1810
|
+
? new Set(core.listMcpOAuthRestorableAiProfileIds({
|
|
1811
|
+
grantId: auditTokenId,
|
|
1812
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
1813
|
+
}))
|
|
1814
|
+
: null;
|
|
1815
|
+
return core.listAiProfiles(ACTIVE_WORKSPACE_ID).filter((profile) => {
|
|
1816
|
+
if ((profile.seatScope ?? null) !== configuredAiProfileSeatScope)
|
|
1817
|
+
return false;
|
|
1818
|
+
const profileNameMatches = profile.name.trim().toLowerCase() === requestedName
|
|
1819
|
+
|| profile.username.trim().toLowerCase() === requestedUsername;
|
|
1820
|
+
if (!profileNameMatches)
|
|
1821
|
+
return false;
|
|
1822
|
+
const connection = profile.providerMetadata?.connection;
|
|
1823
|
+
const storedClientId = connection && typeof connection === 'object' && !Array.isArray(connection)
|
|
1824
|
+
? parseMcpClientId(connection.clientId)
|
|
1825
|
+
: null;
|
|
1826
|
+
const inferredClientId = inferLegacyMcpClientIdFromProfile(profile);
|
|
1827
|
+
if (!configuredMcpClientId)
|
|
1828
|
+
return restorableProfileIds?.has(profile.id) === true;
|
|
1829
|
+
return storedClientId
|
|
1830
|
+
? storedClientId === configuredMcpClientId
|
|
1831
|
+
: inferredClientId === configuredMcpClientId;
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1638
1834
|
function getEligibleAssigneeActors() {
|
|
1639
1835
|
return core.listAssignableActors(ACTIVE_WORKSPACE_ID)
|
|
1640
1836
|
.filter((actor) => String(actor.id || '').trim().toLowerCase() !== 'agent')
|
|
@@ -1646,7 +1842,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
1646
1842
|
.filter((actor) => Boolean(actor.id));
|
|
1647
1843
|
}
|
|
1648
1844
|
function describeEligibleAssignees() {
|
|
1649
|
-
return "Use 'self'
|
|
1845
|
+
return "Use 'self', 'unassigned', or an ID from list_assignees.";
|
|
1650
1846
|
}
|
|
1651
1847
|
function buildEligibleAssigneePayload() {
|
|
1652
1848
|
return core.listAssignableActors(ACTIVE_WORKSPACE_ID)
|
|
@@ -2215,8 +2411,16 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2215
2411
|
function ensureResolvedProfileFromConnection(defaults) {
|
|
2216
2412
|
if (activeAiProfileId)
|
|
2217
2413
|
return;
|
|
2218
|
-
|
|
2414
|
+
if (requiresExplicitTokenAuthorship)
|
|
2415
|
+
return;
|
|
2416
|
+
const boundProfile = findTrustedBoundProfile({ repairConnection: false });
|
|
2219
2417
|
if (boundProfile) {
|
|
2418
|
+
if (configuredAiProfileIdentityAsserted) {
|
|
2419
|
+
assertRequestedIdentityMatchesBoundProfile(boundProfile, {
|
|
2420
|
+
name: configuredAiProfileName,
|
|
2421
|
+
provider: configuredAiProfileProvider,
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2220
2424
|
const configuredClientEntry = getMcpClientRegistryEntry(configuredMcpClientId);
|
|
2221
2425
|
const resolved = core.resolveAiProfile({
|
|
2222
2426
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -2279,7 +2483,29 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2279
2483
|
return;
|
|
2280
2484
|
if (!isMcpWriteToolName(name))
|
|
2281
2485
|
return;
|
|
2486
|
+
if (aiProfileIdentityConflictDetected) {
|
|
2487
|
+
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' });
|
|
2488
|
+
}
|
|
2282
2489
|
const profileToken = resolveProfileTokenFromArgs(args);
|
|
2490
|
+
if (requiresExplicitTokenAuthorship) {
|
|
2491
|
+
if (!profileToken) {
|
|
2492
|
+
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);
|
|
2493
|
+
}
|
|
2494
|
+
const tokenProfile = core.getAiProfileByToken({
|
|
2495
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
2496
|
+
profileToken,
|
|
2497
|
+
seatScope: configuredAiProfileSeatScope,
|
|
2498
|
+
});
|
|
2499
|
+
if (!tokenProfile) {
|
|
2500
|
+
throw new McpPublicError('Invalid AI profile token. Call resolve_profile again and retry.', 'INVALID_AI_PROFILE_TOKEN', 401);
|
|
2501
|
+
}
|
|
2502
|
+
if (activeAiProfileId && activeAiProfileId !== tokenProfile.id) {
|
|
2503
|
+
failAiProfileBindingConflict('The supplied profile token conflicts with the AI profile already active for this request.', { reason: 'request_token_mismatch', boundProfileId: activeAiProfileId, suppliedProfileId: tokenProfile.id });
|
|
2504
|
+
}
|
|
2505
|
+
if (!activeAiProfileId)
|
|
2506
|
+
activateAiProfile(tokenProfile.id, 'token_lookup');
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2283
2509
|
if (activeAiProfileId) {
|
|
2284
2510
|
if (!profileToken)
|
|
2285
2511
|
return;
|
|
@@ -2292,7 +2518,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2292
2518
|
throw new McpPublicError('Invalid AI profile token. Call resolve_profile again and retry.', 'INVALID_AI_PROFILE_TOKEN', 401);
|
|
2293
2519
|
}
|
|
2294
2520
|
if (tokenProfile.id !== activeAiProfileId) {
|
|
2295
|
-
|
|
2521
|
+
failAiProfileBindingConflict('The supplied profile token conflicts with the AI profile already bound to this MCP session.', { reason: 'session_token_mismatch', boundProfileId: activeAiProfileId, suppliedProfileId: tokenProfile.id });
|
|
2296
2522
|
}
|
|
2297
2523
|
return;
|
|
2298
2524
|
}
|
|
@@ -2306,6 +2532,22 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2306
2532
|
seatScope: configuredAiProfileSeatScope,
|
|
2307
2533
|
});
|
|
2308
2534
|
if (profile) {
|
|
2535
|
+
const trustedBoundProfile = findTrustedBoundProfile({ repairConnection: false });
|
|
2536
|
+
if (trustedBoundProfile && trustedBoundProfile.id !== profile.id) {
|
|
2537
|
+
failAiProfileBindingConflict('The supplied profile token conflicts with the profile already bound to this OAuth grant or MCP connection.', {
|
|
2538
|
+
reason: 'stateless_token_mismatch',
|
|
2539
|
+
boundProfileId: trustedBoundProfile.id,
|
|
2540
|
+
suppliedProfileId: profile.id,
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
if (!trustedBoundProfile)
|
|
2544
|
+
assertOAuthProfileClientCompatibility(profile);
|
|
2545
|
+
if (trustedBoundProfile && configuredAiProfileIdentityAsserted) {
|
|
2546
|
+
assertRequestedIdentityMatchesBoundProfile(trustedBoundProfile, {
|
|
2547
|
+
name: configuredAiProfileName,
|
|
2548
|
+
provider: configuredAiProfileProvider,
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2309
2551
|
activateAiProfile(profile.id, 'token_lookup');
|
|
2310
2552
|
return;
|
|
2311
2553
|
}
|
|
@@ -2334,7 +2576,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
2334
2576
|
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');
|
|
2335
2577
|
}
|
|
2336
2578
|
function assertAnnotatedAttachmentReadAccess() {
|
|
2337
|
-
const gate =
|
|
2579
|
+
const gate = resolveNodeAppGateAccess(ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY);
|
|
2338
2580
|
if (!gate.allowed) {
|
|
2339
2581
|
throw new TaskforceRuleError('Annotated attachments are unavailable for this workspace.', 403, gate.code || 'ANNOTATED_ATTACHMENTS_UNAVAILABLE');
|
|
2340
2582
|
}
|
|
@@ -3049,15 +3291,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3049
3291
|
|| planningSearchIncludes(initiative?.referenceLabel || formatInitiativeReference(initiative?.referenceNumber), query)
|
|
3050
3292
|
|| planningSearchIncludes(initiative?.title, query);
|
|
3051
3293
|
}
|
|
3052
|
-
function buildHistoricalWorkspaceTaskSnapshot() {
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
byId.set(task.id, task);
|
|
3058
|
-
for (const task of archivedTasks)
|
|
3059
|
-
byId.set(task.id, task);
|
|
3060
|
-
const tasks = Array.from(byId.values());
|
|
3294
|
+
function buildHistoricalWorkspaceTaskSnapshot(workstreamId) {
|
|
3295
|
+
const tasks = core.listTasksSlim(ACTIVE_WORKSPACE_ID, {
|
|
3296
|
+
...(workstreamId ? { workstreamId } : {}),
|
|
3297
|
+
includeArchived: true,
|
|
3298
|
+
});
|
|
3061
3299
|
const tasksByWorkstreamId = new Map();
|
|
3062
3300
|
for (const task of tasks) {
|
|
3063
3301
|
const normalizedWorkstreamId = String(task.workstreamId || '').trim();
|
|
@@ -3090,6 +3328,34 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3090
3328
|
semantics: 'historical: active + archived',
|
|
3091
3329
|
};
|
|
3092
3330
|
}
|
|
3331
|
+
function emptyPlanningTaskCounts() {
|
|
3332
|
+
return buildPlanningTaskCounts([]);
|
|
3333
|
+
}
|
|
3334
|
+
function buildHistoricalTaskCountSnapshot(workstreamId) {
|
|
3335
|
+
return new Map(core.listTaskCountsByWorkstream(ACTIVE_WORKSPACE_ID, workstreamId).map((entry) => [
|
|
3336
|
+
entry.workstreamId,
|
|
3337
|
+
{
|
|
3338
|
+
total: entry.total,
|
|
3339
|
+
active: entry.active,
|
|
3340
|
+
archived: entry.archived,
|
|
3341
|
+
semantics: 'historical: active + archived',
|
|
3342
|
+
},
|
|
3343
|
+
]));
|
|
3344
|
+
}
|
|
3345
|
+
function sumPlanningTaskCounts(workstreams, countsByWorkstreamId) {
|
|
3346
|
+
let total = 0;
|
|
3347
|
+
let active = 0;
|
|
3348
|
+
let archived = 0;
|
|
3349
|
+
for (const workstream of workstreams) {
|
|
3350
|
+
const counts = countsByWorkstreamId.get(workstream.id);
|
|
3351
|
+
if (!counts)
|
|
3352
|
+
continue;
|
|
3353
|
+
total += counts.total;
|
|
3354
|
+
active += counts.active;
|
|
3355
|
+
archived += counts.archived;
|
|
3356
|
+
}
|
|
3357
|
+
return { total, active, archived, semantics: 'historical: active + archived' };
|
|
3358
|
+
}
|
|
3093
3359
|
function buildPlanningWorkstreamCounts(workstreams) {
|
|
3094
3360
|
const active = workstreams.filter((workstream) => !workstream?.isArchived).length;
|
|
3095
3361
|
const archived = workstreams.length - active;
|
|
@@ -3125,6 +3391,17 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3125
3391
|
offset: Number(normalizedOffset),
|
|
3126
3392
|
};
|
|
3127
3393
|
}
|
|
3394
|
+
function normalizeSearchReadPage(limit, offset, defaultLimit) {
|
|
3395
|
+
const normalizedLimit = limit === undefined ? defaultLimit : limit;
|
|
3396
|
+
const normalizedOffset = offset === undefined ? 0 : offset;
|
|
3397
|
+
if (!Number.isInteger(normalizedLimit) || Number(normalizedLimit) < 1 || Number(normalizedLimit) > 50) {
|
|
3398
|
+
throw new McpPublicError('limit must be an integer from 1 to 50');
|
|
3399
|
+
}
|
|
3400
|
+
if (!Number.isInteger(normalizedOffset) || Number(normalizedOffset) < 0) {
|
|
3401
|
+
throw new McpPublicError('offset must be a non-negative integer');
|
|
3402
|
+
}
|
|
3403
|
+
return { limit: Number(normalizedLimit), offset: Number(normalizedOffset) };
|
|
3404
|
+
}
|
|
3128
3405
|
function buildCompactPlanningHistory(targetType, entity) {
|
|
3129
3406
|
const duplicatedCommentAction = `${targetType}-comment-added`;
|
|
3130
3407
|
const activity = Array.isArray(entity?.activity) ? entity.activity : [];
|
|
@@ -3192,16 +3469,19 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3192
3469
|
};
|
|
3193
3470
|
}
|
|
3194
3471
|
function buildInitiativeResourcePayload(initiative, options) {
|
|
3195
|
-
|
|
3472
|
+
if (options?.detail === 'history') {
|
|
3473
|
+
return buildPlanningHistoryPayload('initiative', initiative, options.offset ?? 0, options.limit ?? 25);
|
|
3474
|
+
}
|
|
3196
3475
|
const workstreams = options?.workstreamsByInitiativeId?.get(initiative.id)
|
|
3197
3476
|
|| core.listWorkstreams(ACTIVE_WORKSPACE_ID)
|
|
3198
3477
|
.filter((workstream) => workstream.initiativeId === initiative.id);
|
|
3199
|
-
const
|
|
3478
|
+
const taskCountsByWorkstreamId = options?.taskCountsByWorkstreamId || buildHistoricalTaskCountSnapshot();
|
|
3479
|
+
const taskCounts = sumPlanningTaskCounts(workstreams, taskCountsByWorkstreamId);
|
|
3200
3480
|
if (options?.detail === 'compact') {
|
|
3201
3481
|
return {
|
|
3202
3482
|
initiative: buildPlanningInitiativeSummary(initiative),
|
|
3203
3483
|
workstreamCount: workstreams.length,
|
|
3204
|
-
taskCount:
|
|
3484
|
+
taskCount: taskCounts.total,
|
|
3205
3485
|
nextDetailOption: 'full',
|
|
3206
3486
|
};
|
|
3207
3487
|
}
|
|
@@ -3215,13 +3495,12 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3215
3495
|
initiative: withPlanningEntityUrl('initiative', buildPlanningEntityMetadata('initiative', initiative)),
|
|
3216
3496
|
attachments: listPlanningAttachmentDescriptors({ id: initiative.id, entity: initiative }),
|
|
3217
3497
|
workstreamCounts: buildPlanningWorkstreamCounts(workstreams),
|
|
3218
|
-
taskCounts
|
|
3498
|
+
taskCounts,
|
|
3219
3499
|
workstreams: page.map((workstream) => {
|
|
3220
|
-
const workstreamTasks = tasksByWorkstreamId.get(workstream.id) || [];
|
|
3221
3500
|
return {
|
|
3222
3501
|
workstream: buildPlanningWorkstreamSummary(workstream),
|
|
3223
3502
|
attachments: listPlanningAttachmentDescriptors({ id: workstream.id, entity: workstream }),
|
|
3224
|
-
taskCounts:
|
|
3503
|
+
taskCounts: taskCountsByWorkstreamId.get(workstream.id) || emptyPlanningTaskCounts(),
|
|
3225
3504
|
};
|
|
3226
3505
|
}),
|
|
3227
3506
|
pagination,
|
|
@@ -3237,27 +3516,38 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3237
3516
|
} : { truncated: false }),
|
|
3238
3517
|
};
|
|
3239
3518
|
}
|
|
3240
|
-
if (options?.detail === 'history') {
|
|
3241
|
-
return buildPlanningHistoryPayload('initiative', initiative, options.offset ?? 0, options.limit ?? 25);
|
|
3242
|
-
}
|
|
3243
3519
|
return {
|
|
3244
3520
|
initiative: buildPlanningInitiativeDetail(initiative),
|
|
3245
3521
|
workstreamCount: workstreams.length,
|
|
3246
|
-
taskCount:
|
|
3522
|
+
taskCount: taskCounts.total,
|
|
3247
3523
|
workstreams: workstreams.map((workstream) => buildPlanningWorkstreamSummary(workstream)),
|
|
3248
3524
|
};
|
|
3249
3525
|
}
|
|
3250
3526
|
function buildWorkstreamResourcePayload(workstream, options) {
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3527
|
+
if (options?.detail === 'history') {
|
|
3528
|
+
return buildPlanningHistoryPayload('workstream', workstream, options.offset ?? 0, options.limit ?? 25);
|
|
3529
|
+
}
|
|
3254
3530
|
const workstreamReference = typeof workstream.referenceLabel === 'string'
|
|
3255
3531
|
? workstream.referenceLabel
|
|
3256
3532
|
: formatWorkstreamReference(workstream);
|
|
3533
|
+
if (options?.detail !== 'compact' && options?.detail !== 'planning') {
|
|
3534
|
+
const tasksByWorkstreamId = options?.tasksByWorkstreamId
|
|
3535
|
+
|| buildHistoricalWorkspaceTaskSnapshot(workstream.id).tasksByWorkstreamId;
|
|
3536
|
+
const tasks = tasksByWorkstreamId.get(workstream.id) || [];
|
|
3537
|
+
return {
|
|
3538
|
+
workstream: buildPlanningWorkstreamDetail(workstream),
|
|
3539
|
+
taskCount: tasks.length,
|
|
3540
|
+
tasks,
|
|
3541
|
+
nextTaskTool: 'list_tasks',
|
|
3542
|
+
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
const taskCounts = (options?.taskCountsByWorkstreamId || buildHistoricalTaskCountSnapshot(workstream.id))
|
|
3546
|
+
.get(workstream.id) || emptyPlanningTaskCounts();
|
|
3257
3547
|
if (options?.detail === 'compact') {
|
|
3258
3548
|
return {
|
|
3259
3549
|
workstream: buildPlanningWorkstreamSummary(workstream),
|
|
3260
|
-
taskCount:
|
|
3550
|
+
taskCount: taskCounts.total,
|
|
3261
3551
|
nextDetailOption: 'full',
|
|
3262
3552
|
nextTaskTool: 'list_tasks',
|
|
3263
3553
|
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
@@ -3267,22 +3557,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3267
3557
|
return {
|
|
3268
3558
|
workstream: withPlanningEntityUrl('workstream', buildPlanningEntityMetadata('workstream', workstream)),
|
|
3269
3559
|
attachments: listPlanningAttachmentDescriptors({ id: workstream.id, entity: workstream }),
|
|
3270
|
-
taskCounts
|
|
3560
|
+
taskCounts,
|
|
3271
3561
|
nextTaskTool: 'list_tasks',
|
|
3272
3562
|
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
3273
3563
|
guidance: 'taskCounts are historical (active + archived). list_tasks returns bounded active task rows; use search_tasks with scope=archive for archived task discovery.',
|
|
3274
3564
|
};
|
|
3275
3565
|
}
|
|
3276
|
-
|
|
3277
|
-
return buildPlanningHistoryPayload('workstream', workstream, options.offset ?? 0, options.limit ?? 25);
|
|
3278
|
-
}
|
|
3279
|
-
return {
|
|
3280
|
-
workstream: buildPlanningWorkstreamDetail(workstream),
|
|
3281
|
-
taskCount: tasks.length,
|
|
3282
|
-
tasks,
|
|
3283
|
-
nextTaskTool: 'list_tasks',
|
|
3284
|
-
suggestedTaskArgs: { workstreamId: workstreamReference, format: 'json', limit: 50, offset: 0 },
|
|
3285
|
-
};
|
|
3566
|
+
throw new Error(`Unhandled workstream detail: ${options?.detail}`);
|
|
3286
3567
|
}
|
|
3287
3568
|
function buildInitiativesResourcePayload(options) {
|
|
3288
3569
|
const query = normalizePlanningSearchQuery(options?.query);
|
|
@@ -3296,18 +3577,27 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3296
3577
|
current.push(workstream);
|
|
3297
3578
|
workstreamsByInitiativeId.set(normalizedInitiativeId, current);
|
|
3298
3579
|
}
|
|
3299
|
-
const
|
|
3300
|
-
const
|
|
3301
|
-
.filter((initiative) => matchesInitiativePlanningQuery(initiative, query, workstreamsByInitiativeId.get(initiative.id) || []))
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3580
|
+
const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
|
|
3581
|
+
const matches = core.listInitiatives(ACTIVE_WORKSPACE_ID)
|
|
3582
|
+
.filter((initiative) => matchesInitiativePlanningQuery(initiative, query, workstreamsByInitiativeId.get(initiative.id) || []));
|
|
3583
|
+
const offset = options?.offset ?? 0;
|
|
3584
|
+
const limit = options?.limit ?? 25;
|
|
3585
|
+
const initiatives = matches.slice(offset, offset + limit).map((initiative) => {
|
|
3586
|
+
const compact = buildInitiativeResourcePayload(initiative, {
|
|
3587
|
+
workstreamsByInitiativeId,
|
|
3588
|
+
taskCountsByWorkstreamId,
|
|
3589
|
+
detail: 'compact',
|
|
3590
|
+
});
|
|
3591
|
+
const { nextDetailOption: _nextDetailOption, ...summary } = compact;
|
|
3592
|
+
return summary;
|
|
3593
|
+
});
|
|
3594
|
+
const pagination = buildPlanningPagination(matches.length, offset, limit, initiatives.length);
|
|
3307
3595
|
return {
|
|
3308
|
-
total:
|
|
3596
|
+
total: matches.length,
|
|
3309
3597
|
...(query ? { query } : {}),
|
|
3310
3598
|
initiatives,
|
|
3599
|
+
pagination,
|
|
3600
|
+
guidance: 'Use get_initiative with the initiative ID or reference for details.',
|
|
3311
3601
|
};
|
|
3312
3602
|
}
|
|
3313
3603
|
function buildWorkstreamsResourcePayload(options) {
|
|
@@ -3316,14 +3606,23 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3316
3606
|
for (const initiative of core.listInitiatives(ACTIVE_WORKSPACE_ID)) {
|
|
3317
3607
|
initiativesById.set(initiative.id, initiative);
|
|
3318
3608
|
}
|
|
3319
|
-
const
|
|
3320
|
-
const
|
|
3321
|
-
.filter((workstream) => matchesWorkstreamPlanningQuery(workstream, query, initiativesById.get(workstream.initiativeId || '')))
|
|
3322
|
-
|
|
3609
|
+
const taskCountsByWorkstreamId = buildHistoricalTaskCountSnapshot();
|
|
3610
|
+
const matches = core.listWorkstreams(ACTIVE_WORKSPACE_ID)
|
|
3611
|
+
.filter((workstream) => matchesWorkstreamPlanningQuery(workstream, query, initiativesById.get(workstream.initiativeId || '')));
|
|
3612
|
+
const offset = options?.offset ?? 0;
|
|
3613
|
+
const limit = options?.limit ?? 25;
|
|
3614
|
+
const workstreams = matches.slice(offset, offset + limit).map((workstream) => {
|
|
3615
|
+
const compact = buildWorkstreamResourcePayload(workstream, { taskCountsByWorkstreamId, detail: 'compact' });
|
|
3616
|
+
const { nextDetailOption: _nextDetailOption, nextTaskTool: _nextTaskTool, suggestedTaskArgs: _suggestedTaskArgs, ...summary } = compact;
|
|
3617
|
+
return summary;
|
|
3618
|
+
});
|
|
3619
|
+
const pagination = buildPlanningPagination(matches.length, offset, limit, workstreams.length);
|
|
3323
3620
|
return {
|
|
3324
|
-
total:
|
|
3621
|
+
total: matches.length,
|
|
3325
3622
|
...(query ? { query } : {}),
|
|
3326
3623
|
workstreams,
|
|
3624
|
+
pagination,
|
|
3625
|
+
guidance: 'Use get_workstream for details or list_tasks with workstreamId for active tasks.',
|
|
3327
3626
|
};
|
|
3328
3627
|
}
|
|
3329
3628
|
function buildPlanningWriteEntitySummary(targetType, entity) {
|
|
@@ -3624,13 +3923,16 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3624
3923
|
registerAdminWorkspaceTools(registerTool, (name, args) => executeRegisteredTool(name, args));
|
|
3625
3924
|
const mutatingProfileTokenProperty = {
|
|
3626
3925
|
type: 'string',
|
|
3627
|
-
description:
|
|
3926
|
+
description: requiresExplicitTokenAuthorship
|
|
3927
|
+
? 'Required authorship proof for cloud access-token clients; obtain from resolve_profile.'
|
|
3928
|
+
: 'Optional profileToken from resolve_profile for stateless MCP clients.'
|
|
3628
3929
|
};
|
|
3629
3930
|
const nonDestructiveWriteTools = new Set([
|
|
3630
3931
|
'add_annotated_attachment_marker',
|
|
3631
3932
|
'add_comment',
|
|
3632
3933
|
'add_document_review_comment',
|
|
3633
3934
|
'add_initiative_comment',
|
|
3935
|
+
'add_task_checklist_item',
|
|
3634
3936
|
'add_workstream_comment',
|
|
3635
3937
|
'bulk_add_annotated_attachment_markers',
|
|
3636
3938
|
'create_annotated_attachment_session',
|
|
@@ -3639,6 +3941,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3639
3941
|
'create_task',
|
|
3640
3942
|
'create_workstream',
|
|
3641
3943
|
'link_document',
|
|
3944
|
+
'link_task_relationship',
|
|
3642
3945
|
'rehome_document',
|
|
3643
3946
|
'link_task_context',
|
|
3644
3947
|
'resolve_profile',
|
|
@@ -3655,6 +3958,9 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3655
3958
|
get priorityDesc() {
|
|
3656
3959
|
return `Priority level: ${getActivePriorities().map((priority) => `${priority.value} (${priority.label})`).join(', ')}`;
|
|
3657
3960
|
},
|
|
3961
|
+
get priorityEnum() {
|
|
3962
|
+
return getActivePriorities().map((priority) => priority.value);
|
|
3963
|
+
},
|
|
3658
3964
|
get typeEnum() {
|
|
3659
3965
|
return getActiveTypes().map((type) => type.value);
|
|
3660
3966
|
},
|
|
@@ -3662,7 +3968,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3662
3968
|
get assigneeHint() {
|
|
3663
3969
|
return describeEligibleAssignees();
|
|
3664
3970
|
},
|
|
3665
|
-
describeTool: (text) =>
|
|
3971
|
+
describeTool: (text) => text,
|
|
3666
3972
|
};
|
|
3667
3973
|
};
|
|
3668
3974
|
const titleForTool = (name) => String(name || '')
|
|
@@ -3695,6 +4001,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
3695
4001
|
inputSchema: {
|
|
3696
4002
|
...descriptor.inputSchema,
|
|
3697
4003
|
properties,
|
|
4004
|
+
...(requiresExplicitTokenAuthorship
|
|
4005
|
+
&& descriptor.name !== 'resolve_profile'
|
|
4006
|
+
&& isMcpWriteToolName(descriptor.name)
|
|
4007
|
+
? { required: Array.from(new Set([...(descriptor.inputSchema.required || []), 'profileToken'])) }
|
|
4008
|
+
: {}),
|
|
3698
4009
|
additionalProperties: false,
|
|
3699
4010
|
},
|
|
3700
4011
|
annotations: {
|
|
@@ -4529,6 +4840,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4529
4840
|
uploadDraftId: result.uploadDraftId,
|
|
4530
4841
|
mimeType: result.mimeType,
|
|
4531
4842
|
uploadedPath: result.uploadedPath,
|
|
4843
|
+
...(result.displayName ? { displayName: result.displayName } : {}),
|
|
4532
4844
|
...(result.caption ? { caption: result.caption } : {}),
|
|
4533
4845
|
...(result.overwrite ? { overwrite: true } : {}),
|
|
4534
4846
|
...(result.expectedSizeBytes !== undefined ? { expectedSizeBytes: result.expectedSizeBytes } : {}),
|
|
@@ -4770,15 +5082,15 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4770
5082
|
: resolveWorkstreamByIdentifierOrThrow(workstreamId).id;
|
|
4771
5083
|
const paginated = resolvedWorkstreamId !== undefined || offset !== undefined;
|
|
4772
5084
|
const parsedOffset = offset === undefined ? 0 : Number(offset);
|
|
4773
|
-
if (!Number.
|
|
4774
|
-
throw new McpPublicError('offset must be a non-negative
|
|
4775
|
-
const normalizedOffset =
|
|
5085
|
+
if (!Number.isInteger(parsedOffset) || parsedOffset < 0)
|
|
5086
|
+
throw new McpPublicError('offset must be a non-negative integer');
|
|
5087
|
+
const normalizedOffset = parsedOffset;
|
|
4776
5088
|
const parsedLimit = limit === undefined ? (paginated ? 50 : 0) : Number(limit);
|
|
4777
|
-
if (!Number.
|
|
4778
|
-
throw new McpPublicError('limit must be
|
|
5089
|
+
if (!Number.isInteger(parsedLimit) || parsedLimit < 0 || parsedLimit > 200)
|
|
5090
|
+
throw new McpPublicError('limit must be an integer from 1 to 200');
|
|
4779
5091
|
const normalizedLimit = paginated
|
|
4780
|
-
?
|
|
4781
|
-
:
|
|
5092
|
+
? (parsedLimit || 50)
|
|
5093
|
+
: parsedLimit;
|
|
4782
5094
|
const queriedTasks = core.listTasksSlim(ACTIVE_WORKSPACE_ID, {
|
|
4783
5095
|
category,
|
|
4784
5096
|
status,
|
|
@@ -4827,8 +5139,12 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4827
5139
|
};
|
|
4828
5140
|
}
|
|
4829
5141
|
case "resolve_profile": {
|
|
4830
|
-
const { name: profileName, icon, color, description, role, provider, model, surfaceType, profileToken } = args;
|
|
5142
|
+
const { name: profileName, icon, color, description, role, provider, model, surfaceType, profileId, profileToken } = args;
|
|
5143
|
+
if (aiProfileIdentityConflictDetected) {
|
|
5144
|
+
throw new TaskforceRuleError('This MCP session is quarantined after a profile identity conflict. Reconnect with the intended profile before retrying.', 409, 'AI_PROFILE_BINDING_CONFLICT', { reason: 'session_identity_quarantined', nextTool: 'resolve_profile' });
|
|
5145
|
+
}
|
|
4831
5146
|
const suppliedProfileToken = typeof profileToken === 'string' ? profileToken.trim() : '';
|
|
5147
|
+
const selectedProfileId = typeof profileId === 'string' ? profileId.trim() : '';
|
|
4832
5148
|
const sessionBoundProfile = activeAiProfileId
|
|
4833
5149
|
? core.listAiProfiles(ACTIVE_WORKSPACE_ID, { includeArchived: true })
|
|
4834
5150
|
.find((profile) => profile.id === activeAiProfileId) || null
|
|
@@ -4841,9 +5157,35 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4841
5157
|
repairConnection: false,
|
|
4842
5158
|
});
|
|
4843
5159
|
if (sessionBoundProfile && trustedBoundProfile && sessionBoundProfile.id !== trustedBoundProfile.id) {
|
|
4844
|
-
|
|
5160
|
+
failAiProfileBindingConflict('The MCP session and authenticated connection are bound to different AI profiles. Reconnect before retrying.', { reason: 'session_connection_mismatch', sessionProfileId: sessionBoundProfile.id, trustedProfileId: trustedBoundProfile.id });
|
|
5161
|
+
}
|
|
5162
|
+
const selectableProfiles = listSelectableOAuthProfiles(profileName);
|
|
5163
|
+
const explicitlySelectedProfile = selectedProfileId
|
|
5164
|
+
? selectableProfiles.find((profile) => profile.id === selectedProfileId) || null
|
|
5165
|
+
: null;
|
|
5166
|
+
if (selectedProfileId && !explicitlySelectedProfile) {
|
|
5167
|
+
throw new TaskforceRuleError('The selected AI profile is not an active compatible identity for this OAuth client and requested name.', 409, 'AI_PROFILE_SELECTION_INVALID', { selectedProfileId });
|
|
5168
|
+
}
|
|
5169
|
+
const alreadyBoundProfile = sessionBoundProfile || trustedBoundProfile;
|
|
5170
|
+
if (alreadyBoundProfile) {
|
|
5171
|
+
assertRequestedIdentityMatchesBoundProfile(alreadyBoundProfile, {
|
|
5172
|
+
name: profileName,
|
|
5173
|
+
provider,
|
|
5174
|
+
});
|
|
5175
|
+
}
|
|
5176
|
+
if (alreadyBoundProfile && explicitlySelectedProfile && alreadyBoundProfile.id !== explicitlySelectedProfile.id) {
|
|
5177
|
+
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 });
|
|
5178
|
+
}
|
|
5179
|
+
const boundProfile = alreadyBoundProfile || explicitlySelectedProfile;
|
|
5180
|
+
if (!boundProfile && suppliedProfileToken) {
|
|
5181
|
+
const suppliedProfile = core.getAiProfileByToken({
|
|
5182
|
+
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
5183
|
+
profileToken: suppliedProfileToken,
|
|
5184
|
+
seatScope: configuredAiProfileSeatScope,
|
|
5185
|
+
});
|
|
5186
|
+
if (suppliedProfile)
|
|
5187
|
+
assertOAuthProfileClientCompatibility(suppliedProfile);
|
|
4845
5188
|
}
|
|
4846
|
-
const boundProfile = sessionBoundProfile || trustedBoundProfile;
|
|
4847
5189
|
if (boundProfile && suppliedProfileToken) {
|
|
4848
5190
|
const tokenProfile = core.getAiProfileByToken({
|
|
4849
5191
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -4851,7 +5193,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4851
5193
|
seatScope: configuredAiProfileSeatScope,
|
|
4852
5194
|
});
|
|
4853
5195
|
if (!tokenProfile || tokenProfile.id !== boundProfile.id) {
|
|
4854
|
-
|
|
5196
|
+
failAiProfileBindingConflict('The supplied profile token conflicts with the AI profile already bound to this MCP session or authenticated connection.', { reason: 'bound_token_mismatch', boundProfileId: boundProfile.id, ...(tokenProfile ? { suppliedProfileId: tokenProfile.id } : {}) });
|
|
4855
5197
|
}
|
|
4856
5198
|
}
|
|
4857
5199
|
if (!boundProfile && !suppliedProfileToken && auditActorType === 'oauth') {
|
|
@@ -4860,7 +5202,16 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
4860
5202
|
const hasIdentityCandidate = core.listAiProfiles(ACTIVE_WORKSPACE_ID).some((profile) => ((profile.seatScope ?? null) === configuredAiProfileSeatScope
|
|
4861
5203
|
&& (profile.name.trim().toLowerCase() === requestedName || profile.username.trim().toLowerCase() === requestedUsername)));
|
|
4862
5204
|
if (hasIdentityCandidate) {
|
|
4863
|
-
|
|
5205
|
+
const candidates = selectableProfiles.map((profile) => buildAiProfileDescriptor(profile));
|
|
5206
|
+
const suggestedArgs = candidates.length === 1
|
|
5207
|
+
? { name: profileName, profileId: candidates[0].id }
|
|
5208
|
+
: undefined;
|
|
5209
|
+
throw new TaskforceRuleError(candidates.length > 0
|
|
5210
|
+
? 'An AI profile with this identity already exists but is not bound to this OAuth client. Select a compatible candidate explicitly and retry resolve_profile.'
|
|
5211
|
+
: 'An AI profile with this identity already exists but is not compatible with this OAuth client. Select the intended profile in Taskforce settings before retrying.', 409, 'AI_PROFILE_SELECTION_REQUIRED', {
|
|
5212
|
+
candidates,
|
|
5213
|
+
...(suggestedArgs ? { suggestedArgs } : {}),
|
|
5214
|
+
});
|
|
4864
5215
|
}
|
|
4865
5216
|
}
|
|
4866
5217
|
const configuredClientEntry = getMcpClientRegistryEntry(configuredMcpClientId);
|
|
@@ -5039,10 +5390,19 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5039
5390
|
};
|
|
5040
5391
|
}
|
|
5041
5392
|
case "search_tasks": {
|
|
5042
|
-
const { query, scope = "all" } = args;
|
|
5043
|
-
const
|
|
5393
|
+
const { query, scope = "all", limit, offset } = args;
|
|
5394
|
+
const page = normalizeSearchReadPage(limit, offset, 25);
|
|
5395
|
+
const queried = core.searchTaskSummaries(query, scope, ACTIVE_WORKSPACE_ID, {
|
|
5396
|
+
limit: page.limit + 1,
|
|
5397
|
+
offset: page.offset,
|
|
5398
|
+
});
|
|
5399
|
+
const hasMore = queried.length > page.limit;
|
|
5400
|
+
const results = hasMore ? queried.slice(0, page.limit) : queried;
|
|
5044
5401
|
return {
|
|
5045
|
-
content: [{
|
|
5402
|
+
content: [{
|
|
5403
|
+
type: "text",
|
|
5404
|
+
text: `${renderTaskTable(results)}\n\nPage: offset ${page.offset}, limit ${page.limit}, returned ${results.length}, hasMore ${hasMore}${hasMore ? `, nextOffset ${page.offset + results.length}` : ''}`,
|
|
5405
|
+
}],
|
|
5046
5406
|
};
|
|
5047
5407
|
}
|
|
5048
5408
|
case "get_task": {
|
|
@@ -5200,9 +5560,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5200
5560
|
case "list_task_relationships": {
|
|
5201
5561
|
const { taskId, type, direction = 'both' } = args;
|
|
5202
5562
|
const { id } = resolveTaskByIdentifierOrThrow(taskId);
|
|
5203
|
-
const
|
|
5204
|
-
|
|
5205
|
-
|
|
5563
|
+
const relationshipRows = core.listTaskRelationships(id, ACTIVE_WORKSPACE_ID, { type, direction });
|
|
5564
|
+
const taskSummariesById = new Map(core.listTaskReferenceSummaries(relationshipRows.flatMap((relationship) => [relationship.sourceTaskId, relationship.targetTaskId]), ACTIVE_WORKSPACE_ID).map((task) => [task.id, task]));
|
|
5565
|
+
const relationships = relationshipRows.map((relationship) => {
|
|
5566
|
+
const source = taskSummariesById.get(relationship.sourceTaskId) || null;
|
|
5567
|
+
const target = taskSummariesById.get(relationship.targetTaskId) || null;
|
|
5206
5568
|
const perspectiveType = relationship.kind === 'dependency'
|
|
5207
5569
|
? (relationship.sourceTaskId === id ? 'depends_on' : 'blocks')
|
|
5208
5570
|
: relationship.kind === 'related' ? 'related_to' : 'supersedes';
|
|
@@ -5244,9 +5606,10 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5244
5606
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
5245
5607
|
}
|
|
5246
5608
|
case "list_initiatives": {
|
|
5247
|
-
const { query } = args;
|
|
5609
|
+
const { query, limit, offset } = args;
|
|
5610
|
+
const page = normalizePlanningReadPage(limit, offset);
|
|
5248
5611
|
return {
|
|
5249
|
-
content: [{ type: "text", text: JSON.stringify(buildInitiativesResourcePayload({ query }), null, 2) }],
|
|
5612
|
+
content: [{ type: "text", text: JSON.stringify(buildInitiativesResourcePayload({ query, ...page }), null, 2) }],
|
|
5250
5613
|
};
|
|
5251
5614
|
}
|
|
5252
5615
|
case "get_initiative": {
|
|
@@ -5367,12 +5730,21 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5367
5730
|
return { content: [{ type: 'text', text: JSON.stringify(linkedResult, null, 2) }] };
|
|
5368
5731
|
}
|
|
5369
5732
|
case "create_initiative": {
|
|
5370
|
-
const
|
|
5733
|
+
const { idempotencyKey: rawIdempotencyKey, ...initiativeArgs } = args;
|
|
5734
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5735
|
+
const operationId = idempotencyKey
|
|
5736
|
+
? stableExplicitMutationId('operation', 'create_initiative', idempotencyKey)
|
|
5737
|
+
: undefined;
|
|
5738
|
+
const router = getDurableInitiativeMutationRouter(operationId ? { operationId } : undefined);
|
|
5739
|
+
if (idempotencyKey)
|
|
5740
|
+
requireDurableExplicitIdempotency(router.enabled, 'initiative');
|
|
5371
5741
|
let prepared;
|
|
5372
5742
|
try {
|
|
5373
|
-
prepared = planningCommandPreparationService.prepareInitiativeCreate(
|
|
5743
|
+
prepared = planningCommandPreparationService.prepareInitiativeCreate(initiativeArgs, {
|
|
5374
5744
|
durable: router.enabled,
|
|
5375
|
-
entityId:
|
|
5745
|
+
entityId: idempotencyKey
|
|
5746
|
+
? stableExplicitMutationId('initiative', 'create_initiative', idempotencyKey)
|
|
5747
|
+
: requireStableMcpRequestId('initiative', 'create'),
|
|
5376
5748
|
createdAt: new Date().toISOString(),
|
|
5377
5749
|
});
|
|
5378
5750
|
}
|
|
@@ -5421,14 +5793,23 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5421
5793
|
};
|
|
5422
5794
|
}
|
|
5423
5795
|
case "add_initiative_comment": {
|
|
5424
|
-
const { id, text } = args;
|
|
5796
|
+
const { id, text, idempotencyKey: rawIdempotencyKey } = args;
|
|
5425
5797
|
const { id: initiativeId } = resolveInitiativeByIdentifierOrThrow(id);
|
|
5426
5798
|
const validation = validateStructuredCommentForAdd(text);
|
|
5427
5799
|
if (!validation.ok) {
|
|
5428
5800
|
throw new McpPublicError(validation.message);
|
|
5429
5801
|
}
|
|
5430
5802
|
const effectiveAuthor = getActiveMcpActorRef();
|
|
5431
|
-
const
|
|
5803
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5804
|
+
const explicitCommentId = idempotencyKey
|
|
5805
|
+
? stableExplicitMutationId('comment', 'add_initiative_comment', idempotencyKey)
|
|
5806
|
+
: null;
|
|
5807
|
+
const router = getDurableInitiativeMutationRouter(idempotencyKey ? {
|
|
5808
|
+
operationId: stableExplicitMutationId('operation', 'add_initiative_comment', idempotencyKey),
|
|
5809
|
+
commentId: explicitCommentId,
|
|
5810
|
+
} : undefined);
|
|
5811
|
+
if (idempotencyKey)
|
|
5812
|
+
requireDurableExplicitIdempotency(router.enabled, 'initiative comment');
|
|
5432
5813
|
const updated = router.enabled
|
|
5433
5814
|
? initiativeFromDurableResult(router.appendComment(initiativeId, {
|
|
5434
5815
|
author: effectiveAuthor,
|
|
@@ -5440,7 +5821,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5440
5821
|
if (!updated) {
|
|
5441
5822
|
throw new McpPublicError(buildInitiativeLookupFailureMessage(String(id || '').trim()), 'MCP_NOT_FOUND', 404);
|
|
5442
5823
|
}
|
|
5443
|
-
const persistedComment = Array.isArray(updated.comments)
|
|
5824
|
+
const persistedComment = Array.isArray(updated.comments)
|
|
5825
|
+
? (explicitCommentId
|
|
5826
|
+
? updated.comments.find((candidate) => candidate.id === explicitCommentId) || null
|
|
5827
|
+
: updated.comments.at(-1) || null)
|
|
5828
|
+
: null;
|
|
5444
5829
|
recordMcpWriteAudit('add_initiative_comment', {
|
|
5445
5830
|
entityType: 'initiative',
|
|
5446
5831
|
initiativeId: updated.id,
|
|
@@ -5454,7 +5839,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5454
5839
|
return {
|
|
5455
5840
|
content: [{ type: "text", text: JSON.stringify(buildPlanningWriteAcknowledgement('initiative', updated, 'add_initiative_comment', {
|
|
5456
5841
|
changedFields: ['comments'],
|
|
5457
|
-
comment:
|
|
5842
|
+
comment: persistedComment,
|
|
5458
5843
|
}), null, 2) }],
|
|
5459
5844
|
};
|
|
5460
5845
|
}
|
|
@@ -5495,9 +5880,10 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5495
5880
|
};
|
|
5496
5881
|
}
|
|
5497
5882
|
case "list_workstreams": {
|
|
5498
|
-
const { query } = args;
|
|
5883
|
+
const { query, limit, offset } = args;
|
|
5884
|
+
const page = normalizePlanningReadPage(limit, offset);
|
|
5499
5885
|
return {
|
|
5500
|
-
content: [{ type: "text", text: JSON.stringify(buildWorkstreamsResourcePayload({ query }), null, 2) }],
|
|
5886
|
+
content: [{ type: "text", text: JSON.stringify(buildWorkstreamsResourcePayload({ query, ...page }), null, 2) }],
|
|
5501
5887
|
};
|
|
5502
5888
|
}
|
|
5503
5889
|
case "get_workstream": {
|
|
@@ -5515,12 +5901,21 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5515
5901
|
};
|
|
5516
5902
|
}
|
|
5517
5903
|
case "create_workstream": {
|
|
5518
|
-
const
|
|
5904
|
+
const { idempotencyKey: rawIdempotencyKey, ...workstreamArgs } = args;
|
|
5905
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5906
|
+
const operationId = idempotencyKey
|
|
5907
|
+
? stableExplicitMutationId('operation', 'create_workstream', idempotencyKey)
|
|
5908
|
+
: undefined;
|
|
5909
|
+
const router = getDurableWorkstreamMutationRouter(operationId ? { operationId } : undefined);
|
|
5910
|
+
if (idempotencyKey)
|
|
5911
|
+
requireDurableExplicitIdempotency(router.enabled, 'workstream');
|
|
5519
5912
|
let prepared;
|
|
5520
5913
|
try {
|
|
5521
|
-
prepared = planningCommandPreparationService.prepareWorkstreamCreate(
|
|
5914
|
+
prepared = planningCommandPreparationService.prepareWorkstreamCreate(workstreamArgs, {
|
|
5522
5915
|
durable: router.enabled,
|
|
5523
|
-
entityId:
|
|
5916
|
+
entityId: idempotencyKey
|
|
5917
|
+
? stableExplicitMutationId('workstream', 'create_workstream', idempotencyKey)
|
|
5918
|
+
: requireStableMcpRequestId('workstream', 'create'),
|
|
5524
5919
|
createdAt: new Date().toISOString(),
|
|
5525
5920
|
});
|
|
5526
5921
|
}
|
|
@@ -5576,43 +5971,64 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5576
5971
|
};
|
|
5577
5972
|
}
|
|
5578
5973
|
case "add_workstream_comment": {
|
|
5579
|
-
const { id, text } = args;
|
|
5580
|
-
const { id: workstreamId } = resolveWorkstreamByIdentifierOrThrow(id);
|
|
5581
|
-
const
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5974
|
+
const { id, text, idempotencyKey: rawIdempotencyKey } = args;
|
|
5975
|
+
const { id: workstreamId } = measureToolSubphase('lookup', () => resolveWorkstreamByIdentifierOrThrow(id));
|
|
5976
|
+
const { effectiveAuthor, router, comment, explicitCommentId } = measureToolSubphase('prepare', () => {
|
|
5977
|
+
const validation = validateStructuredCommentForAdd(text);
|
|
5978
|
+
if (!validation.ok) {
|
|
5979
|
+
throw new McpPublicError(validation.message);
|
|
5980
|
+
}
|
|
5981
|
+
const effectiveAuthor = getActiveMcpActorRef();
|
|
5982
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
5983
|
+
const explicitCommentId = idempotencyKey
|
|
5984
|
+
? stableExplicitMutationId('comment', 'add_workstream_comment', idempotencyKey)
|
|
5985
|
+
: null;
|
|
5986
|
+
const router = getDurableWorkstreamMutationRouter(idempotencyKey ? {
|
|
5987
|
+
operationId: stableExplicitMutationId('operation', 'add_workstream_comment', idempotencyKey),
|
|
5988
|
+
commentId: explicitCommentId,
|
|
5989
|
+
} : undefined);
|
|
5990
|
+
if (idempotencyKey)
|
|
5991
|
+
requireDurableExplicitIdempotency(router.enabled, 'workstream comment');
|
|
5992
|
+
return {
|
|
5993
|
+
effectiveAuthor,
|
|
5994
|
+
router,
|
|
5995
|
+
explicitCommentId,
|
|
5996
|
+
comment: {
|
|
5997
|
+
author: effectiveAuthor,
|
|
5998
|
+
submittedAuthor: effectiveAuthor,
|
|
5999
|
+
text,
|
|
6000
|
+
timestamp: new Date().toISOString(),
|
|
6001
|
+
},
|
|
6002
|
+
};
|
|
6003
|
+
});
|
|
6004
|
+
const updated = measureToolSubphase('durable-write', () => router.enabled
|
|
6005
|
+
? workstreamFromDurableResult(router.appendComment(workstreamId, comment))
|
|
6006
|
+
: core.addWorkstreamComment(workstreamId, text, effectiveAuthor, ACTIVE_WORKSPACE_ID, { actorRef: effectiveAuthor }));
|
|
5595
6007
|
if (!updated) {
|
|
5596
6008
|
throw new McpPublicError(buildWorkstreamLookupFailureMessage(String(id || '').trim()), 'MCP_NOT_FOUND', 404);
|
|
5597
6009
|
}
|
|
5598
|
-
const persistedComment = Array.isArray(updated.comments)
|
|
5599
|
-
|
|
6010
|
+
const persistedComment = Array.isArray(updated.comments)
|
|
6011
|
+
? (explicitCommentId
|
|
6012
|
+
? updated.comments.find((candidate) => candidate.id === explicitCommentId) || null
|
|
6013
|
+
: updated.comments.at(-1) || null)
|
|
6014
|
+
: null;
|
|
6015
|
+
measureToolSubphase('audit', () => recordMcpWriteAudit('add_workstream_comment', {
|
|
5600
6016
|
entityType: 'workstream',
|
|
5601
6017
|
workstreamId: updated.id,
|
|
5602
6018
|
commentId: persistedComment?.id || null,
|
|
5603
6019
|
contentActorId: effectiveAuthor,
|
|
5604
|
-
});
|
|
5605
|
-
await notifyResourceMutation({
|
|
6020
|
+
}));
|
|
6021
|
+
await measureToolSubphaseAsync('notify', () => notifyResourceMutation({
|
|
5606
6022
|
includeWorkstreams: true,
|
|
5607
6023
|
workstreamIds: [updated.id],
|
|
5608
6024
|
...(updated.initiativeId ? { includeInitiatives: true, initiativeIds: [updated.initiativeId] } : {}),
|
|
5609
|
-
});
|
|
5610
|
-
return {
|
|
6025
|
+
}));
|
|
6026
|
+
return measureToolSubphase('serialize', () => ({
|
|
5611
6027
|
content: [{ type: "text", text: JSON.stringify(buildPlanningWriteAcknowledgement('workstream', updated, 'add_workstream_comment', {
|
|
5612
6028
|
changedFields: ['comments'],
|
|
5613
|
-
comment:
|
|
6029
|
+
comment: persistedComment,
|
|
5614
6030
|
}), null, 2) }],
|
|
5615
|
-
};
|
|
6031
|
+
}));
|
|
5616
6032
|
}
|
|
5617
6033
|
case "archive_workstream": {
|
|
5618
6034
|
const { id } = args;
|
|
@@ -5655,34 +6071,54 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5655
6071
|
case "update_task_workstream": {
|
|
5656
6072
|
const { id: legacyInputId, taskId, workstreamId } = args;
|
|
5657
6073
|
const inputId = taskId ?? legacyInputId;
|
|
5658
|
-
const { task } = resolveTaskByIdentifierOrThrow(inputId);
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
6074
|
+
const { task } = measureToolSubphase('lookup', () => resolveTaskByIdentifierOrThrow(inputId));
|
|
6075
|
+
const prepared = measureToolSubphase('prepare', () => {
|
|
6076
|
+
try {
|
|
6077
|
+
return planningCommandPreparationService.prepareTaskWorkstreamPlacement(task, workstreamId);
|
|
6078
|
+
}
|
|
6079
|
+
catch (error) {
|
|
6080
|
+
return rethrowPlanningPreparationError(error);
|
|
6081
|
+
}
|
|
6082
|
+
});
|
|
6083
|
+
if (prepared.nextWorkstreamId === prepared.previousWorkstreamId) {
|
|
6084
|
+
measureToolSubphase('audit', () => recordMcpWriteAudit('update_task_workstream', {
|
|
6085
|
+
taskId: task.id,
|
|
6086
|
+
workstreamId: prepared.nextWorkstreamId,
|
|
6087
|
+
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
6088
|
+
noOp: true,
|
|
6089
|
+
}));
|
|
6090
|
+
return measureToolSubphase('serialize', () => ({
|
|
6091
|
+
content: [{
|
|
6092
|
+
type: "text",
|
|
6093
|
+
text: JSON.stringify({
|
|
6094
|
+
task: buildTaskWriteSummary(task),
|
|
6095
|
+
workstreamId: prepared.nextWorkstreamId,
|
|
6096
|
+
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
6097
|
+
noOp: true,
|
|
6098
|
+
}, null, 2)
|
|
6099
|
+
}],
|
|
6100
|
+
}));
|
|
5665
6101
|
}
|
|
5666
|
-
const updated = mutateTaskThroughStatusBoundary({
|
|
6102
|
+
const updated = measureToolSubphase('durable-write', () => mutateTaskThroughStatusBoundary({
|
|
5667
6103
|
task,
|
|
5668
6104
|
updates: { workstreamId: prepared.nextWorkstreamId },
|
|
5669
|
-
});
|
|
6105
|
+
}));
|
|
5670
6106
|
if (!updated) {
|
|
5671
6107
|
throw new McpPublicError(buildTaskLookupFailureMessage(String(inputId || '').trim()), 'MCP_NOT_FOUND', 404);
|
|
5672
6108
|
}
|
|
5673
|
-
recordMcpWriteAudit('update_task_workstream', {
|
|
6109
|
+
measureToolSubphase('audit', () => recordMcpWriteAudit('update_task_workstream', {
|
|
5674
6110
|
taskId: updated.id,
|
|
5675
6111
|
workstreamId: prepared.nextWorkstreamId,
|
|
5676
6112
|
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
5677
|
-
});
|
|
5678
|
-
await notifyResourceMutation({
|
|
6113
|
+
}));
|
|
6114
|
+
await measureToolSubphaseAsync('notify', () => notifyResourceMutation({
|
|
5679
6115
|
includeTasks: true,
|
|
5680
6116
|
taskIds: [updated.id],
|
|
5681
6117
|
...(prepared.affectedWorkstreamIds.length > 0
|
|
5682
6118
|
? { includeWorkstreams: true, workstreamIds: prepared.affectedWorkstreamIds }
|
|
5683
6119
|
: {}),
|
|
5684
|
-
});
|
|
5685
|
-
return {
|
|
6120
|
+
}));
|
|
6121
|
+
return measureToolSubphase('serialize', () => ({
|
|
5686
6122
|
content: [{
|
|
5687
6123
|
type: "text",
|
|
5688
6124
|
text: JSON.stringify({
|
|
@@ -5691,7 +6127,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5691
6127
|
previousWorkstreamId: prepared.previousWorkstreamId,
|
|
5692
6128
|
}, null, 2)
|
|
5693
6129
|
}],
|
|
5694
|
-
};
|
|
6130
|
+
}));
|
|
5695
6131
|
}
|
|
5696
6132
|
case "update_workstream_initiative": {
|
|
5697
6133
|
const { id: legacyInputId, workstreamId, initiativeId } = args;
|
|
@@ -5864,8 +6300,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5864
6300
|
}
|
|
5865
6301
|
case "search_images": {
|
|
5866
6302
|
assertAnnotatedAttachmentReadAccess();
|
|
5867
|
-
const { query, limit } = args;
|
|
5868
|
-
const
|
|
6303
|
+
const { query, limit, offset } = args;
|
|
6304
|
+
const page = normalizeSearchReadPage(limit, offset, 10);
|
|
6305
|
+
const queried = searchImageAssets(query, { limit: page.limit + 1, offset: page.offset });
|
|
6306
|
+
const hasMore = queried.length > page.limit;
|
|
6307
|
+
const results = (hasMore ? queried.slice(0, page.limit) : queried).map(({ asset, matchedOn, score }) => ({
|
|
5869
6308
|
image: buildImageDescriptor(asset),
|
|
5870
6309
|
matchedOn,
|
|
5871
6310
|
score,
|
|
@@ -5881,6 +6320,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5881
6320
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
5882
6321
|
query: typeof query === 'string' ? query.trim() : '',
|
|
5883
6322
|
results,
|
|
6323
|
+
pagination: {
|
|
6324
|
+
offset: page.offset,
|
|
6325
|
+
limit: page.limit,
|
|
6326
|
+
returned: results.length,
|
|
6327
|
+
hasMore,
|
|
6328
|
+
...(hasMore ? { nextOffset: page.offset + results.length } : {}),
|
|
6329
|
+
},
|
|
5884
6330
|
guidance: results.length > 0
|
|
5885
6331
|
? 'After picking a match, call get_image with the suggestedArgs.imageId to inspect linked tasks and image notes.'
|
|
5886
6332
|
: 'No matching canonical images were found. Try an I-123 reference, title keyword, or filename fragment.',
|
|
@@ -5951,8 +6397,11 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5951
6397
|
};
|
|
5952
6398
|
}
|
|
5953
6399
|
case "search_documents": {
|
|
5954
|
-
const { query, limit } = args;
|
|
5955
|
-
const
|
|
6400
|
+
const { query, limit, offset } = args;
|
|
6401
|
+
const page = normalizeSearchReadPage(limit, offset, 10);
|
|
6402
|
+
const queried = searchDocumentAssets(query, { limit: page.limit + 1, offset: page.offset });
|
|
6403
|
+
const hasMore = queried.length > page.limit;
|
|
6404
|
+
const results = (hasMore ? queried.slice(0, page.limit) : queried).map(({ asset, matchedOn, score }) => ({
|
|
5956
6405
|
document: buildDocumentDescriptor(asset),
|
|
5957
6406
|
matchedOn,
|
|
5958
6407
|
score,
|
|
@@ -5969,6 +6418,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
5969
6418
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
5970
6419
|
query: typeof query === 'string' ? query.trim() : '',
|
|
5971
6420
|
results,
|
|
6421
|
+
pagination: {
|
|
6422
|
+
offset: page.offset,
|
|
6423
|
+
limit: page.limit,
|
|
6424
|
+
returned: results.length,
|
|
6425
|
+
hasMore,
|
|
6426
|
+
...(hasMore ? { nextOffset: page.offset + results.length } : {}),
|
|
6427
|
+
},
|
|
5972
6428
|
guidance: results.length > 0
|
|
5973
6429
|
? 'After picking a match, call get_document with the suggestedArgs.documentId. Pass includeContent=true when you need the body.'
|
|
5974
6430
|
: 'No matching canonical documents were found. Try a D-123 reference, document id, title keyword, or filename fragment.',
|
|
@@ -6585,7 +7041,7 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6585
7041
|
};
|
|
6586
7042
|
}
|
|
6587
7043
|
case "add_document_review_comment": {
|
|
6588
|
-
const { sessionId, body, anchor, parentCommentId } = args;
|
|
7044
|
+
const { sessionId, body, anchor, parentCommentId, idempotencyKey: rawIdempotencyKey } = args;
|
|
6589
7045
|
if (!sessionId || typeof sessionId !== 'string') {
|
|
6590
7046
|
throw new McpPublicError('sessionId is required');
|
|
6591
7047
|
}
|
|
@@ -6596,6 +7052,10 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6596
7052
|
&& Object.prototype.hasOwnProperty.call(args, 'anchor') && anchor !== null && anchor !== undefined) {
|
|
6597
7053
|
throw new McpPublicError('Replies cannot have anchors');
|
|
6598
7054
|
}
|
|
7055
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
7056
|
+
const idempotentCommentId = idempotencyKey
|
|
7057
|
+
? stableExplicitMutationId('comment', 'add_document_review_comment', idempotencyKey)
|
|
7058
|
+
: null;
|
|
6599
7059
|
const session = typeof parentCommentId === 'string' && parentCommentId.trim()
|
|
6600
7060
|
? core.addDocumentReviewReply({
|
|
6601
7061
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -6603,6 +7063,8 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6603
7063
|
parentCommentId: parentCommentId.trim(),
|
|
6604
7064
|
body,
|
|
6605
7065
|
actorId: getActiveMcpActorRef(),
|
|
7066
|
+
idempotencyKey,
|
|
7067
|
+
commentId: idempotentCommentId,
|
|
6606
7068
|
})
|
|
6607
7069
|
: core.addDocumentReviewComment({
|
|
6608
7070
|
workspaceId: ACTIVE_WORKSPACE_ID,
|
|
@@ -6610,9 +7072,13 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
6610
7072
|
body,
|
|
6611
7073
|
anchor: anchor ?? null,
|
|
6612
7074
|
actorId: getActiveMcpActorRef(),
|
|
7075
|
+
idempotencyKey,
|
|
7076
|
+
commentId: idempotentCommentId,
|
|
6613
7077
|
});
|
|
6614
7078
|
const asset = workspaceAssetStore?.getDocument(session.assetId, ACTIVE_WORKSPACE_ID) || null;
|
|
6615
|
-
const comment =
|
|
7079
|
+
const comment = idempotentCommentId
|
|
7080
|
+
? session.comments.find((candidate) => candidate.id === idempotentCommentId) || null
|
|
7081
|
+
: session.comments.at(-1) || null;
|
|
6616
7082
|
recordMcpWriteAudit('add_document_review_comment', {
|
|
6617
7083
|
sessionId: session.id,
|
|
6618
7084
|
documentAssetId: session.assetId,
|
|
@@ -7150,17 +7616,24 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
7150
7616
|
};
|
|
7151
7617
|
}
|
|
7152
7618
|
case "add_comment": {
|
|
7153
|
-
const { id: inputId, text } = args;
|
|
7619
|
+
const { id: inputId, text, idempotencyKey: rawIdempotencyKey } = args;
|
|
7154
7620
|
const { id, task } = resolveTaskByIdentifierOrThrow(inputId);
|
|
7155
7621
|
const validation = validateStructuredCommentForAdd(text);
|
|
7156
7622
|
if (!validation.ok) {
|
|
7157
7623
|
throw new McpPublicError(validation.message);
|
|
7158
7624
|
}
|
|
7159
7625
|
const effectiveAuthor = getActiveMcpActorRef();
|
|
7160
|
-
const
|
|
7161
|
-
const
|
|
7626
|
+
const idempotencyKey = normalizeExplicitIdempotencyKey(rawIdempotencyKey);
|
|
7627
|
+
const operationId = idempotencyKey
|
|
7628
|
+
? stableExplicitMutationId('operation', 'add_comment', idempotencyKey)
|
|
7629
|
+
: requireStableMcpRequestId('operation', `task-comment:${id}`);
|
|
7630
|
+
const commentId = idempotencyKey
|
|
7631
|
+
? stableExplicitMutationId('comment', 'add_comment', idempotencyKey)
|
|
7632
|
+
: requireStableMcpRequestId('comment', id);
|
|
7162
7633
|
const existingCommentIds = new Set((task.comments || []).map((comment) => comment.id));
|
|
7163
7634
|
const durableRouter = getDurableTaskCommentMutationRouter(effectiveAuthor, operationId);
|
|
7635
|
+
if (idempotencyKey)
|
|
7636
|
+
requireDurableExplicitIdempotency(durableRouter.enabled, 'task comment');
|
|
7164
7637
|
const updated = durableRouter.enabled
|
|
7165
7638
|
? durableRouter.append({
|
|
7166
7639
|
taskId: id,
|
|
@@ -7558,61 +8031,113 @@ export function createTaskforceMcpServer(options = {}) {
|
|
|
7558
8031
|
const mcpInputValidator = createMcpInputValidator();
|
|
7559
8032
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
7560
8033
|
const { name, arguments: args } = resolveMcpToolCallCompatibility(request.params.name, request.params.arguments);
|
|
8034
|
+
const toolStartedAtMs = Date.now();
|
|
8035
|
+
let preparationMs = 0;
|
|
8036
|
+
let validationMs = 0;
|
|
8037
|
+
let handlerMs = 0;
|
|
8038
|
+
let responseBytes = 0;
|
|
8039
|
+
let timingOutcome = 'success';
|
|
8040
|
+
const subphases = {};
|
|
7561
8041
|
try {
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
8042
|
+
const preparationStartedAtMs = Date.now();
|
|
8043
|
+
let handler;
|
|
8044
|
+
try {
|
|
8045
|
+
assertToolProfile(name);
|
|
8046
|
+
assertToolPermission(name);
|
|
8047
|
+
handler = toolHandlers.get(name);
|
|
8048
|
+
if (!handler) {
|
|
8049
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
8050
|
+
}
|
|
8051
|
+
ensureResolvedProfileForTool(name, args);
|
|
8052
|
+
assertResolvedProfileForMutatingTool(name);
|
|
8053
|
+
}
|
|
8054
|
+
finally {
|
|
8055
|
+
preparationMs = Date.now() - preparationStartedAtMs;
|
|
7567
8056
|
}
|
|
7568
|
-
ensureResolvedProfileForTool(name, args);
|
|
7569
|
-
assertResolvedProfileForMutatingTool(name);
|
|
7570
8057
|
const execute = () => executeMcpToolWithReadRetry(name, (args || {}), async (validatedArgs) => {
|
|
8058
|
+
const validationStartedAtMs = Date.now();
|
|
7571
8059
|
const descriptor = getCurrentToolDescriptor(name);
|
|
7572
8060
|
if (!descriptor)
|
|
7573
8061
|
throw new Error(`Unknown tool: ${name}`);
|
|
7574
8062
|
const validation = mcpInputValidator.validate(name, descriptor.inputSchema, validatedArgs);
|
|
8063
|
+
validationMs += Date.now() - validationStartedAtMs;
|
|
7575
8064
|
if (!validation.valid) {
|
|
7576
8065
|
throw new McpPublicError(`Arguments for ${name} do not match the declared input schema.`, 'MCP_INVALID_ARGUMENT', 400, { issues: validation.issues });
|
|
7577
8066
|
}
|
|
7578
|
-
|
|
8067
|
+
const handlerStartedAtMs = Date.now();
|
|
8068
|
+
try {
|
|
8069
|
+
return await mcpToolSubphaseTimings.run(subphases, () => handler(stripMcpEnvelopeArgsForHandler(name, validatedArgs)));
|
|
8070
|
+
}
|
|
8071
|
+
finally {
|
|
8072
|
+
handlerMs += Date.now() - handlerStartedAtMs;
|
|
8073
|
+
}
|
|
7579
8074
|
});
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
8075
|
+
let result;
|
|
8076
|
+
if (extra.requestId === undefined || extra.requestId === null) {
|
|
8077
|
+
result = await execute();
|
|
8078
|
+
}
|
|
8079
|
+
else {
|
|
8080
|
+
const transportSessionId = String(extra.sessionId || '').trim();
|
|
8081
|
+
const requestIdentity = operationIdentityScope
|
|
8082
|
+
? fingerprintWorkspaceSyncPayload({
|
|
8083
|
+
kind: 'shared-mcp-request',
|
|
8084
|
+
operationIdentityScope,
|
|
8085
|
+
transportSessionId: transportSessionId
|
|
8086
|
+
|| options.auth?.mcpConnectionId
|
|
8087
|
+
|| options.auth?.mcpClientId
|
|
8088
|
+
|| 'mcp',
|
|
8089
|
+
requestId: { type: typeof extra.requestId, value: extra.requestId },
|
|
8090
|
+
tool: name,
|
|
8091
|
+
})
|
|
8092
|
+
: [
|
|
8093
|
+
...(runtimeMode === 'local' || !transportSessionId ? [runtimeRequestNamespace] : []),
|
|
8094
|
+
String(transportSessionId || options.auth?.mcpConnectionId || options.auth?.mcpClientId || 'mcp'),
|
|
8095
|
+
String(extra.requestId),
|
|
8096
|
+
name,
|
|
8097
|
+
].join(':');
|
|
8098
|
+
const logicalCloudCreateIdentity = [
|
|
7596
8099
|
String(transportSessionId || options.auth?.mcpConnectionId || options.auth?.mcpClientId || 'mcp'),
|
|
7597
8100
|
String(extra.requestId),
|
|
7598
8101
|
name,
|
|
7599
8102
|
].join(':');
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
].join(':');
|
|
7605
|
-
return await cloudCreateRequestIdentity.run(logicalCloudCreateIdentity, () => mcpRequestIdentity.run(requestIdentity, execute));
|
|
8103
|
+
result = await cloudCreateRequestIdentity.run(logicalCloudCreateIdentity, () => mcpRequestIdentity.run(requestIdentity, execute));
|
|
8104
|
+
}
|
|
8105
|
+
responseBytes = measureToolResultBytes(result);
|
|
8106
|
+
return result;
|
|
7606
8107
|
}
|
|
7607
8108
|
catch (error) {
|
|
8109
|
+
timingOutcome = 'failure';
|
|
7608
8110
|
const recoverable = buildRecoverableToolErrorResult(error, auditRequestId);
|
|
7609
8111
|
if (recoverable) {
|
|
7610
|
-
|
|
8112
|
+
const result = {
|
|
7611
8113
|
...recoverable,
|
|
7612
8114
|
isError: true,
|
|
7613
8115
|
};
|
|
8116
|
+
responseBytes = measureToolResultBytes(result);
|
|
8117
|
+
return result;
|
|
8118
|
+
}
|
|
8119
|
+
const result = buildUnexpectedToolErrorResult(auditRequestId || extra.requestId);
|
|
8120
|
+
responseBytes = measureToolResultBytes(result);
|
|
8121
|
+
return result;
|
|
8122
|
+
}
|
|
8123
|
+
finally {
|
|
8124
|
+
try {
|
|
8125
|
+
options.onToolCallTiming?.({
|
|
8126
|
+
requestId: String(auditRequestId || extra.requestId || '').trim() || null,
|
|
8127
|
+
toolName: name,
|
|
8128
|
+
isWrite: isMcpWriteToolName(name),
|
|
8129
|
+
outcome: timingOutcome,
|
|
8130
|
+
totalMs: Date.now() - toolStartedAtMs,
|
|
8131
|
+
preparationMs,
|
|
8132
|
+
validationMs,
|
|
8133
|
+
handlerMs,
|
|
8134
|
+
responseBytes,
|
|
8135
|
+
subphases: { ...subphases },
|
|
8136
|
+
});
|
|
8137
|
+
}
|
|
8138
|
+
catch {
|
|
8139
|
+
// Observability must never change tool behavior.
|
|
7614
8140
|
}
|
|
7615
|
-
return buildUnexpectedToolErrorResult(auditRequestId || extra.requestId);
|
|
7616
8141
|
}
|
|
7617
8142
|
});
|
|
7618
8143
|
return {
|