Changeset 3775519
- Timestamp:
- 10/30/2026 02:56:36 PM (4 days ago)
- Location:
- agent-mod
- Files:
-
- 364 added
- 15 edited
-
tags/1.3.4 (added)
-
tags/1.3.4/LICENSE (added)
-
tags/1.3.4/agent-mod.php (added)
-
tags/1.3.4/build (added)
-
tags/1.3.4/build/425.js (added)
-
tags/1.3.4/build/ability-list (added)
-
tags/1.3.4/build/ability-list/index.asset.php (added)
-
tags/1.3.4/build/ability-list/index.js (added)
-
tags/1.3.4/build/ability-list/style-index-rtl.css (added)
-
tags/1.3.4/build/ability-list/style-index.css (added)
-
tags/1.3.4/build/admin-chat (added)
-
tags/1.3.4/build/admin-chat/index.asset.php (added)
-
tags/1.3.4/build/admin-chat/index.js (added)
-
tags/1.3.4/build/admin-chat/style-index-rtl.css (added)
-
tags/1.3.4/build/admin-chat/style-index.css (added)
-
tags/1.3.4/build/dashboard (added)
-
tags/1.3.4/build/dashboard/index.asset.php (added)
-
tags/1.3.4/build/dashboard/index.js (added)
-
tags/1.3.4/build/dashboard/style-index-rtl.css (added)
-
tags/1.3.4/build/dashboard/style-index.css (added)
-
tags/1.3.4/build/settings-editor (added)
-
tags/1.3.4/build/settings-editor/index.asset.php (added)
-
tags/1.3.4/build/settings-editor/index.js (added)
-
tags/1.3.4/build/settings-editor/style-index-rtl.css (added)
-
tags/1.3.4/build/settings-editor/style-index.css (added)
-
tags/1.3.4/build/setup (added)
-
tags/1.3.4/build/setup/index.asset.php (added)
-
tags/1.3.4/build/setup/index.js (added)
-
tags/1.3.4/build/setup/style-index-rtl.css (added)
-
tags/1.3.4/build/setup/style-index.css (added)
-
tags/1.3.4/composer.json (added)
-
tags/1.3.4/includes (added)
-
tags/1.3.4/includes/App.php (added)
-
tags/1.3.4/includes/Common (added)
-
tags/1.3.4/includes/Common/Constants.php (added)
-
tags/1.3.4/includes/Common/DI.php (added)
-
tags/1.3.4/includes/Common/Helper.php (added)
-
tags/1.3.4/includes/Presentation (added)
-
tags/1.3.4/includes/Presentation/Admin (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/Css (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/Css/agent-mod-admin.css (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/Js (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/Js/agent-mod-admin.js (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/img (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/img/agent_mod_colored_icon.png (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/img/agent_mod_icon.png (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/img/agent_mod_logo.jpg (added)
-
tags/1.3.4/includes/Presentation/Admin/Assets/img/ai_workspace.JPG (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers/AIChatRestController.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers/AIChatWidgetController.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers/AbilitiesController.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers/AdminController.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers/SettingsController.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers/SetupController.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Controllers/SetupRestController.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Views (added)
-
tags/1.3.4/includes/Presentation/Admin/Views/abilities-content.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Views/admin-menu-content.php (added)
-
tags/1.3.4/includes/Presentation/Admin/Views/setup-content.php (added)
-
tags/1.3.4/includes/Presentation/ControllerInit.php (added)
-
tags/1.3.4/includes/Services (added)
-
tags/1.3.4/includes/Services/AI (added)
-
tags/1.3.4/includes/Services/AI/AIClientAdapter.php (added)
-
tags/1.3.4/includes/Services/AI/AIOrchestratorService.php (added)
-
tags/1.3.4/includes/Services/AI/AbilityCatalog.php (added)
-
tags/1.3.4/includes/Services/AI/AbilityResolver.php (added)
-
tags/1.3.4/includes/Services/AI/ConfirmationStore.php (added)
-
tags/1.3.4/includes/Services/AI/DTO (added)
-
tags/1.3.4/includes/Services/AI/DTO/AgentConfig.php (added)
-
tags/1.3.4/includes/Services/AI/DTO/AgentResponse.php (added)
-
tags/1.3.4/includes/Services/AI/HistoryCompactor.php (added)
-
tags/1.3.4/includes/Services/AI/Http (added)
-
tags/1.3.4/includes/Services/AI/Http/AnthropicConnectorRepairer.php (added)
-
tags/1.3.4/includes/Services/AI/Http/GoogleConnectorRepairer.php (added)
-
tags/1.3.4/includes/Services/AI/Http/HttpTimeoutManager.php (added)
-
tags/1.3.4/includes/Services/AI/Http/ProviderToolCallRepairerInterface.php (added)
-
tags/1.3.4/includes/Services/AI/Http/ToolCallRepairManager.php (added)
-
tags/1.3.4/includes/Services/AI/KnowledgeResolver.php (added)
-
tags/1.3.4/includes/Services/AI/ProgressStore.php (added)
-
tags/1.3.4/includes/Services/AI/PromptBuilder.php (added)
-
tags/1.3.4/includes/Services/AI/ProviderInfoService.php (added)
-
tags/1.3.4/includes/Services/AbilityRegistrarService.php (added)
-
tags/1.3.4/includes/Services/ActivationService.php (added)
-
tags/1.3.4/includes/Services/AskUserAbilityService.php (added)
-
tags/1.3.4/includes/Services/BlockMarkupValidator.php (added)
-
tags/1.3.4/includes/Services/DeactivationService.php (added)
-
tags/1.3.4/includes/Services/FindAbilitiesAbilityService.php (added)
-
tags/1.3.4/includes/Services/LibraryService.php (added)
-
tags/1.3.4/includes/Services/NavigationAbilityService.php (added)
-
tags/1.3.4/includes/Services/SettingsService.php (added)
-
tags/1.3.4/includes/Services/SiteManagement (added)
-
tags/1.3.4/includes/Services/SiteManagement/Guard.php (added)
-
tags/1.3.4/includes/Services/SiteManagement/OptionManager.php (added)
-
tags/1.3.4/includes/Services/SiteManagement/PluginManager.php (added)
-
tags/1.3.4/includes/Services/SiteManagement/SystemManager.php (added)
-
tags/1.3.4/includes/Services/SiteManagement/ThemeManager.php (added)
-
tags/1.3.4/includes/Services/SiteManagement/UpgraderContext.php (added)
-
tags/1.3.4/includes/Services/SiteManagement/UserManager.php (added)
-
tags/1.3.4/includes/Services/SiteManagementAbilityService.php (added)
-
tags/1.3.4/includes/Services/TaxonomyAbilityService.php (added)
-
tags/1.3.4/languages (added)
-
tags/1.3.4/languages/index.php (added)
-
tags/1.3.4/readme.txt (added)
-
tags/1.3.4/uninstall.php (added)
-
tags/1.3.4/vendor (added)
-
tags/1.3.4/vendor/autoload.php (added)
-
tags/1.3.4/vendor/composer (added)
-
tags/1.3.4/vendor/composer/ClassLoader.php (added)
-
tags/1.3.4/vendor/composer/InstalledVersions.php (added)
-
tags/1.3.4/vendor/composer/LICENSE (added)
-
tags/1.3.4/vendor/composer/autoload_classmap.php (added)
-
tags/1.3.4/vendor/composer/autoload_files.php (added)
-
tags/1.3.4/vendor/composer/autoload_namespaces.php (added)
-
tags/1.3.4/vendor/composer/autoload_psr4.php (added)
-
tags/1.3.4/vendor/composer/autoload_real.php (added)
-
tags/1.3.4/vendor/composer/autoload_static.php (added)
-
tags/1.3.4/vendor/composer/installed.json (added)
-
tags/1.3.4/vendor/composer/installed.php (added)
-
tags/1.3.4/vendor/composer/platform_check.php (added)
-
tags/1.3.4/vendor/laravel (added)
-
tags/1.3.4/vendor/laravel/serializable-closure (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/LICENSE.md (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/README.md (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/composer.json (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Contracts (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Contracts/Serializable.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Contracts/Signer.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Exceptions (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Exceptions/InvalidSignatureException.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Exceptions/MissingSecretKeyException.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/SerializableClosure.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Serializers (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Serializers/Native.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Serializers/Signed.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Signers (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Signers/Hmac.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Support (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Support/ClosureScope.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Support/ClosureStream.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/Support/SelfReference.php (added)
-
tags/1.3.4/vendor/laravel/serializable-closure/src/UnsignedSerializableClosure.php (added)
-
tags/1.3.4/vendor/native-custom-fields (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/admin (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/admin/index-rtl.css (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/admin/index.asset.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/admin/index.css (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/admin/index.js (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/editor (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/editor/block-bindings.asset.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/build/editor/block-bindings.js (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/composer.json (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/App.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Common (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Common/Constants.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Common/DI.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Common/Helper.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Common (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Common/FieldsConfigModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Common/ResponseModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Options (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Options/OptionMenuConfigModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Options/OptionMenuConfigResponseModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Options/OptionMenuModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Options/OptionsMenuListItemModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/Options/OptionsMenuListResponseModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/PostMeta (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/PostMeta/PostMetaFieldsConfigModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/PostMeta/PostMetaFieldsConfigResponseModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/PostMeta/PostTypeListItemModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/PostMeta/PostTypeListResponseModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/TermMeta (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/TermMeta/TaxonomyListItemModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/TermMeta/TaxonomyListResponseModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/TermMeta/TermMetaFieldsConfigModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/UserMeta (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Models/UserMeta/UserMetaFieldsConfigModel.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Assets (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Assets/css (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Assets/css/native-custom-fields-import-export.css (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Assets/img (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Assets/img/file_thumb.png (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Assets/img/ncf_icon.png (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Assets/img/ncf_logo.png (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/AbilityController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/AdminController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/BlockBindingsController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/ImportExportController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/OptionsController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/PostMetaController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/TermMetaController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Controllers/UserMetaController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Views (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Views/admin-dashboard-page.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Admin/Views/admin-import-export-page.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Client (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Client/Controllers (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/Client/Controllers/ClientController.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Presentation/ControllerInit.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Repositories (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Repositories/BaseRepository.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Repositories/OptionRepository.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Repositories/PostMetaRepository.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Repositories/TermMetaRepository.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Repositories/UserMetaRepository.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/AbilityFieldAdapterTrait.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/AbilityReadTrait.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/OptionsPageAbilitiesService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/PostMetaFieldsAbilitiesService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/PostTypeAbilitiesService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/TaxonomyAbilitiesService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/TermMetaFieldsAbilitiesService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Abilities/UserMetaFieldsAbilitiesService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/ActivationService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/AjaxService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/BlockBindingsService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/DeactivationService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/FieldService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/ImportExportService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Interfaces (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Interfaces/BaseMetaServiceInterface.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Interfaces/OptionServiceInterface.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Interfaces/PostMetaServiceInterface.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Interfaces/TermMetaServiceInterface.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/Interfaces/UserMetaServiceInterface.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/OptionService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/PostMetaService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/TermMetaService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/includes/Services/UserMetaService.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/languages (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/languages/index.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/native-custom-fields.php (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/readme.txt (added)
-
tags/1.3.4/vendor/native-custom-fields/native-custom-fields/uninstall.php (added)
-
tags/1.3.4/vendor/php-di (added)
-
tags/1.3.4/vendor/php-di/invoker (added)
-
tags/1.3.4/vendor/php-di/invoker/LICENSE (added)
-
tags/1.3.4/vendor/php-di/invoker/README.md (added)
-
tags/1.3.4/vendor/php-di/invoker/composer.json (added)
-
tags/1.3.4/vendor/php-di/invoker/src (added)
-
tags/1.3.4/vendor/php-di/invoker/src/CallableResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/Exception (added)
-
tags/1.3.4/vendor/php-di/invoker/src/Exception/InvocationException.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/Exception/NotCallableException.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/Exception/NotEnoughParametersException.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/Invoker.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/InvokerInterface.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/Container (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/ParameterResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/ResolverChain.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/ParameterResolver/TypeHintResolver.php (added)
-
tags/1.3.4/vendor/php-di/invoker/src/Reflection (added)
-
tags/1.3.4/vendor/php-di/invoker/src/Reflection/CallableReflection.php (added)
-
tags/1.3.4/vendor/php-di/php-di (added)
-
tags/1.3.4/vendor/php-di/php-di/LICENSE (added)
-
tags/1.3.4/vendor/php-di/php-di/README.md (added)
-
tags/1.3.4/vendor/php-di/php-di/change-log.md (added)
-
tags/1.3.4/vendor/php-di/php-di/composer.json (added)
-
tags/1.3.4/vendor/php-di/php-di/src (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Attribute (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Attribute/Inject.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Attribute/Injectable.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/CompiledContainer.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Compiler (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Compiler/Compiler.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Compiler/ObjectCreationCompiler.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Compiler/RequestedEntryHolder.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Compiler/Template.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Container.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/ContainerBuilder.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ArrayDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ArrayDefinitionExtension.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/AutowireDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/DecoratorDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Definition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Dumper (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Dumper/ObjectDefinitionDumper.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/EnvironmentVariableDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Exception (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Exception/InvalidAttribute.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Exception/InvalidDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ExtendsPreviousDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/FactoryDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Helper (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Helper/AutowireDefinitionHelper.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Helper/CreateDefinitionHelper.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Helper/DefinitionHelper.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Helper/FactoryDefinitionHelper.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/InstanceDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ObjectDefinition (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ObjectDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ObjectDefinition/MethodInjection.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ObjectDefinition/PropertyInjection.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Reference.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/ArrayResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/DecoratorResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/DefinitionResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/EnvironmentVariableResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/FactoryResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/InstanceInjector.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/ObjectCreator.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/ParameterResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Resolver/ResolverDispatcher.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/SelfResolvingDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/AttributeBasedAutowiring.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/Autowiring.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/DefinitionArray.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/DefinitionFile.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/DefinitionNormalizer.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/DefinitionSource.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/MutableDefinitionSource.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/NoAutowiring.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/ReflectionBasedAutowiring.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/SourceCache.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/Source/SourceChain.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/StringDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Definition/ValueDefinition.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/DependencyException.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Factory (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Factory/RequestedEntry.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/FactoryInterface.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Invoker (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Invoker/DefinitionParameterResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Invoker/FactoryParameterResolver.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/NotFoundException.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Proxy (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Proxy/NativeProxyFactory.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Proxy/ProxyFactory.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/Proxy/ProxyFactoryInterface.php (added)
-
tags/1.3.4/vendor/php-di/php-di/src/functions.php (added)
-
tags/1.3.4/vendor/php-di/php-di/support.md (added)
-
tags/1.3.4/vendor/psr (added)
-
tags/1.3.4/vendor/psr/container (added)
-
tags/1.3.4/vendor/psr/container/.gitignore (added)
-
tags/1.3.4/vendor/psr/container/LICENSE (added)
-
tags/1.3.4/vendor/psr/container/README.md (added)
-
tags/1.3.4/vendor/psr/container/composer.json (added)
-
tags/1.3.4/vendor/psr/container/src (added)
-
tags/1.3.4/vendor/psr/container/src/ContainerExceptionInterface.php (added)
-
tags/1.3.4/vendor/psr/container/src/ContainerInterface.php (added)
-
tags/1.3.4/vendor/psr/container/src/NotFoundExceptionInterface.php (added)
-
trunk/agent-mod.php (modified) (1 diff)
-
trunk/includes/App.php (modified) (2 diffs)
-
trunk/includes/Common/Constants.php (modified) (3 diffs)
-
trunk/includes/Presentation/Admin/Controllers/SettingsController.php (modified) (2 diffs)
-
trunk/includes/Services/AI/AIClientAdapter.php (modified) (23 diffs)
-
trunk/includes/Services/AI/AIOrchestratorService.php (modified) (10 diffs)
-
trunk/includes/Services/AI/AbilityCatalog.php (added)
-
trunk/includes/Services/AI/AbilityResolver.php (modified) (5 diffs)
-
trunk/includes/Services/AI/DTO/AgentConfig.php (modified) (1 diff)
-
trunk/includes/Services/AI/HistoryCompactor.php (modified) (10 diffs)
-
trunk/includes/Services/AI/KnowledgeResolver.php (modified) (1 diff)
-
trunk/includes/Services/AI/PromptBuilder.php (modified) (5 diffs)
-
trunk/includes/Services/AbilityRegistrarService.php (modified) (17 diffs)
-
trunk/includes/Services/AskUserAbilityService.php (modified) (3 diffs)
-
trunk/includes/Services/FindAbilitiesAbilityService.php (added)
-
trunk/includes/Services/SettingsService.php (modified) (3 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
agent-mod/trunk/agent-mod.php
r3770241 r3775519 7 7 * Requires at least: 7.0 8 8 * Requires PHP: 7.4 9 * Version: 1.3. 39 * Version: 1.3.4 10 10 * Author: Kadim Gültekin 11 11 * Author URI: https://profiles.wordpress.org/arkenon/ -
agent-mod/trunk/includes/App.php
r3761762 r3775519 15 15 use AgentMod\Services\AbilityRegistrarService; 16 16 use AgentMod\Services\AskUserAbilityService; 17 use AgentMod\Services\FindAbilitiesAbilityService; 17 18 use AgentMod\Common\DI; 18 19 use AgentMod\Presentation\ControllerInit; … … 39 40 NavigationAbilityService::class, 40 41 AskUserAbilityService::class, 42 FindAbilitiesAbilityService::class, 41 43 LibraryService::class, 42 44 SettingsService::class, -
agent-mod/trunk/includes/Common/Constants.php
r3770241 r3775519 58 58 59 59 /** 60 * Category of AgentMod's system abilities: the tools that are part of how 61 * the agent works (ask-user, present-plan, request-approval, 62 * find-abilities) rather than site capabilities. AgentMod alone decides 63 * when they are offered, so they are never part of an ability selection. 64 * 65 * @since 1.3.4 66 */ 67 public const SYSTEM_ABILITY_CATEGORY = 'agent-mod-system'; 68 69 /** 70 * Default tool loading strategy: 'dynamic' sends a small tool set and lets 71 * the agent load the rest through agent-mod/find-abilities; 'all' sends 72 * every allowed ability on every request. 73 * 74 * @since 1.3.4 75 */ 76 public const AI_TOOL_LOADING = 'dynamic'; 77 78 /** 79 * Allowed-ability count up to which dynamic loading still sends every 80 * ability: a small set costs less than the extra find round trip. 81 * Filterable via `agent_mod_dynamic_tools_threshold`. 82 * 83 * @since 1.3.4 84 */ 85 public const AI_DYNAMIC_TOOLS_THRESHOLD = 15; 86 87 /** 88 * Most abilities one agent-mod/find-abilities call returns (and loads). 89 * 90 * @since 1.3.4 91 */ 92 public const AI_FIND_ABILITIES_LIMIT = 8; 93 94 /** 60 95 * HTTP timeout (seconds) for requests to AI provider APIs. 61 96 * … … 70 105 71 106 /** 72 * How many of the most recent tool-result messages are re-sent verbatim on73 * each iteration of the tool-calling loop; older ones are elided to stubs.74 * Filterable via`agent_mod_history_keep_tool_results`.107 * How many of the most recent tool-result messages are always re-sent 108 * verbatim, even once the history has been compacted. Filterable via 109 * `agent_mod_history_keep_tool_results`. 75 110 * 76 111 * @since 1.1.5 77 112 */ 78 public const AI_HISTORY_KEEP_TOOL_RESULTS = 4; 113 public const AI_HISTORY_KEEP_TOOL_RESULTS = 6; 114 115 /** 116 * Estimated history size (tokens) at which the tool-calling loop starts 117 * eliding old tool results. Below it the history is sent whole. Filterable 118 * via `agent_mod_history_compact_trigger_tokens`. 119 * 120 * @since 1.3.4 121 */ 122 public const AI_HISTORY_COMPACT_TRIGGER_TOKENS = 80000; 123 124 /** 125 * Estimated history size (tokens) a compaction elides down to. The gap to 126 * the trigger keeps compactions rare, so the sent prefix stays stable (and 127 * cacheable) between them. Filterable via 128 * `agent_mod_history_compact_target_tokens`. 129 * 130 * @since 1.3.4 131 */ 132 public const AI_HISTORY_COMPACT_TARGET_TOKENS = 40000; 79 133 80 134 /** … … 234 288 ), 235 289 __( 236 'When a tool can answer the user accurately, call it before responding . Base your answers on tool results rather than guessing.',290 'When a tool can answer the user accurately, call it before responding, unless the answer is already given in the site context or earlier in the conversation. Base your answers on tool results rather than guessing.', 237 291 'agent-mod' 238 292 ), -
agent-mod/trunk/includes/Presentation/Admin/Controllers/SettingsController.php
r3761762 r3775519 239 239 'fields' => [ 240 240 [ 241 'fieldType' => 'select', 242 'name' => 'ability_source', 243 'fieldLabel' => __('Ability Source', 'agent-mod'), 244 'fieldHelpText' => __('Choose whether to allow all abilities or only selected ones.', 'agent-mod'), 245 'options' => 'All Abilities : all, Selected Abilities : selected', 246 'default' => 'all', 247 ], 248 [ 249 'fieldType' => 'select', 250 'multiple' => true, 251 'name' => 'allowed_abilities', 252 'fieldLabel' => __('Allowed Abilities', 'agent-mod'), 253 'className' => 'agent-mod-wp-core-ui-select-multiple', 254 'fieldHelpText' => __('Select the abilities to allow. Used only when Ability Source is set to Selected Abilities.', 'agent-mod'), 255 'options' => $this->getAbilityOptions(), 256 'default' => [], 257 'dependencies' => [ 258 'relation' => 'and', 259 'conditions' => [ 260 [ 261 'field' => 'ability_source', 262 'operator' => '==', 263 'value' => 'selected', 264 ], 241 'fieldType' => 'select', 242 'name' => 'tool_loading', 243 'fieldLabel' => __('Tool Loading', 'agent-mod'), 244 'fieldHelpText' => __('Dynamic (recommended): the agent starts with a few tools and loads the abilities it needs by searching for them, which keeps every request small. All: every ability is sent with every request; costs far more tokens on sites with many abilities.', 'agent-mod'), 245 'options' => [ 246 [ 247 'label' => __('Dynamic (recommended)', 'agent-mod'), 248 'value' => 'dynamic', 249 ], 250 [ 251 'label' => __('All abilities', 'agent-mod'), 252 'value' => 'all', 265 253 ], 266 254 ], 255 'default' => Constants::AI_TOOL_LOADING, 267 256 ], 268 257 ], … … 357 346 ); 358 347 } 359 360 /**361 * Get allowed abilities options for the settings field.362 *363 * @return array<int, array<string, string>>364 * @since 1.0.0365 */366 private function getAbilityOptions(): array367 {368 $options = [];369 370 $abilities = wp_get_abilities();371 foreach ($abilities as $ability) {372 $options[] = [373 'label' => $ability->get_label(),374 'value' => $ability->get_name(),375 ];376 }377 378 return $options;379 }380 348 } -
agent-mod/trunk/includes/Services/AI/AIClientAdapter.php
r3770241 r3775519 88 88 89 89 /** 90 * Run-scoped ability catalog (dynamic tool loading). 91 * 92 * @var AbilityCatalog 93 * @since 1.3.4 94 */ 95 private AbilityCatalog $catalog; 96 97 /** 90 98 * Constructor (PHP-DI autowired). 91 99 * … … 95 103 * @param HistoryCompactor $historyCompactor In-loop history compactor. 96 104 * @param AskUserAbilityService $askUser Ask-user ability service. 105 * @param AbilityCatalog $catalog Run-scoped ability catalog. 97 106 * 98 107 * @since 1.0.0 … … 103 112 HttpTimeoutManager $httpTimeouts, 104 113 HistoryCompactor $historyCompactor, 105 AskUserAbilityService $askUser 114 AskUserAbilityService $askUser, 115 AbilityCatalog $catalog 106 116 ) { 107 117 $this->toolCallRepairs = $toolCallRepairs; … … 110 120 $this->historyCompactor = $historyCompactor; 111 121 $this->askUser = $askUser; 122 $this->catalog = $catalog; 112 123 } 113 124 … … 126 137 * confirmation gate entirely. 127 138 * @param bool $webSearchEnabled Whether to enable the provider's native web search tool. 139 * @param WP_Ability[]|null $tools Tools sent at the start under dynamic tool loading (see 140 * AbilityResolver::selectTools()); null sends every ability. 128 141 * 129 142 * @return AgentResponse … … 139 152 string $requestId = '', 140 153 array $autoApprovedAbilities = [], 141 bool $webSearchEnabled = false 154 bool $webSearchEnabled = false, 155 ?array $tools = null 142 156 ): AgentResponse { 143 157 if (! function_exists('wp_ai_client_prompt')) { … … 150 164 $this->toolCallRepairs->register(); 151 165 $this->httpTimeouts->register(); 166 $this->catalog->scope($abilities); 152 167 153 168 try { … … 161 176 $requestId, 162 177 $autoApprovedAbilities, 163 $webSearchEnabled 178 $webSearchEnabled, 179 [], 180 $tools 164 181 ); 165 182 } finally { 166 183 $this->toolCallRepairs->unregister(); 167 184 $this->httpTimeouts->unregister(); 185 $this->catalog->clear(); 168 186 169 187 // Progress and stop flags are only meaningful while the request is … … 202 220 * @param string $decision 'approve', 'decline' or 'answer'. 203 221 * @param array<string, mixed> $answerPayload Function response for the ask-user call ('answer' only). 222 * @param WP_Ability[]|null $tools Tools sent at the start under dynamic tool loading; null sends every ability. 204 223 * 205 224 * @return AgentResponse … … 218 237 array $executedCalls, 219 238 string $decision, 220 array $answerPayload = [] 239 array $answerPayload = [], 240 ?array $tools = null 221 241 ): AgentResponse { 222 242 if (! function_exists('wp_ai_client_prompt')) { … … 243 263 $this->toolCallRepairs->register(); 244 264 $this->httpTimeouts->register(); 265 $this->catalog->scope($abilities); 245 266 246 267 try { … … 288 309 $autoApprovedAbilities, 289 310 $webSearchEnabled, 290 $executedCalls 311 $executedCalls, 312 $tools 291 313 ); 292 314 } finally { 293 315 $this->toolCallRepairs->unregister(); 294 316 $this->httpTimeouts->unregister(); 317 $this->catalog->clear(); 295 318 296 319 if ('' !== $requestId) { … … 565 588 * confirmation chain; seeds the executed 566 589 * list so responses report the whole chain. 590 * @param WP_Ability[]|null $tools Tools sent at the start under dynamic tool loading; null sends every ability. 567 591 * 568 592 * @return AgentResponse … … 579 603 array $autoApprovedAbilities = [], 580 604 bool $webSearchEnabled = false, 581 array $initialToolCalls = [] 605 array $initialToolCalls = [], 606 ?array $tools = null 582 607 ): AgentResponse { 583 608 $resolver = new WP_AI_Client_Ability_Function_Resolver(...$abilities); … … 586 611 $tokenUsage = []; 587 612 $callCounts = []; 613 // History index of the latest result of each read call signature, and 614 // the index below which the compactor has elided results in this run. 615 $readIndexes = []; 616 $watermark = 0; 588 617 589 618 for ($i = 0; $i < $maxToolCalls; $i++) { … … 610 639 // The provider gets a compacted copy; $history itself stays intact 611 640 // for the AgentResponse / confirmation-resume flow. 641 $compacted = $this->historyCompactor->compact($history, $watermark); 642 643 // A read whose result was elided may run again: blocking it would 644 // leave the model with neither the data nor a way to get it back. 645 $this->forgetElidedReads($callCounts, $readIndexes, $watermark); 646 612 647 $builder = wp_ai_client_prompt() 613 ->with_history(...$ this->historyCompactor->compact($history))648 ->with_history(...$compacted) 614 649 ->using_system_instruction($systemInstruction); 615 650 … … 622 657 } 623 658 624 if (! empty($abilities)) { 625 $builder->using_abilities(...$abilities); 659 $sentTools = null === $tools ? $abilities : $this->withLoadedTools($tools, $abilities, $history); 660 661 if (! empty($sentTools)) { 662 $builder->using_abilities(...$sentTools); 626 663 } 627 664 … … 759 796 760 797 $resultMessage = $this->executeAbilities($resolver, $message, $callCounts); 761 $history[] = $resultMessage; 798 799 foreach ($requestedCalls as $call) { 800 if (! $this->isWriteAbility((string) $call['name'])) { 801 $readIndexes[$this->readSignature((string) $call['name'], $call['args'])] = count($history); 802 } 803 } 804 805 $history[] = $resultMessage; 762 806 763 807 $this->logToolFailures($resultMessage); … … 781 825 782 826 $finalBuilder = wp_ai_client_prompt() 783 ->with_history(...$this->historyCompactor->compact($history ))827 ->with_history(...$this->historyCompactor->compact($history, $watermark)) 784 828 ->using_system_instruction($systemInstruction); 785 829 … … 817 861 $this->extractStopReason($finalResult) 818 862 ); 863 } 864 865 /** 866 * Appends the abilities loaded during the run to the initial tool set. 867 * 868 * Loaded abilities (find-abilities matches and abilities the model called) 869 * go after the initial set, in the order they were loaded, and stay for the 870 * rest of the run. The tool block therefore only ever grows at its end, 871 * which keeps the provider's cached prompt prefix as long as possible. 872 * Only abilities of the run's allowed set are added. 873 * 874 * @param WP_Ability[] $tools Initial tool set. 875 * @param WP_Ability[] $abilities Abilities allowed in the run. 876 * @param Message[] $history Wire-format history of the run. 877 * 878 * @return WP_Ability[] 879 * @since 1.3.4 880 */ 881 private function withLoadedTools(array $tools, array $abilities, array $history): array 882 { 883 $allowed = []; 884 foreach ($abilities as $ability) { 885 if ($ability instanceof WP_Ability) { 886 $allowed[$ability->get_name()] = $ability; 887 } 888 } 889 890 $sent = []; 891 foreach ($tools as $tool) { 892 if ($tool instanceof WP_Ability) { 893 $sent[$tool->get_name()] = $tool; 894 } 895 } 896 897 foreach ($this->catalog->loadedNames($history) as $name) { 898 if (! isset($sent[$name]) && isset($allowed[$name])) { 899 $sent[$name] = $allowed[$name]; 900 } 901 } 902 903 return array_values($sent); 819 904 } 820 905 … … 1047 1132 $digest = (string) wp_json_encode($responses[$index]->getResponse(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 1048 1133 1049 $calls[$index]['result'] = $isAnswer ? $digest : mb_substr($digest, 0, $digestLength); 1134 if ($isAnswer) { 1135 $calls[$index]['result'] = $digest; 1136 continue; 1137 } 1138 1139 // A configuration the agent writes code against (e.g. a field 1140 // configuration) keeps the larger summary budget, so a follow-up 1141 // message can still use it instead of asking the user to paste it. 1142 if ($this->historyCompactor->isStickyTool((string) ($call['name'] ?? ''))) { 1143 $calls[$index]['result'] = mb_substr($digest, 0, max($digestLength, Constants::AI_LIST_RESULT_SUMMARY_LENGTH)); 1144 continue; 1145 } 1146 1147 // A nested result too long for the digest keeps its skeleton 1148 // rather than its first few hundred characters. 1149 $structureSummary = mb_strlen($digest) > $digestLength 1150 ? $this->historyCompactor->summarizeStructure($responses[$index]->getResponse()) 1151 : null; 1152 1153 $calls[$index]['result'] = $structureSummary ?? mb_substr($digest, 0, $digestLength); 1050 1154 } 1051 1155 … … 1136 1240 } 1137 1241 1138 $signature = $ abilityName . '|' . md5((string) wp_json_encode($this->normalizeArgs($functionCall->getArgs() ?? [])));1242 $signature = $this->readSignature($abilityName, $functionCall->getArgs()); 1139 1243 $seen = $callCounts[$signature] ?? 0; 1140 1244 … … 1170 1274 1171 1275 /** 1276 * Builds the repeat-guard signature of a read call. 1277 * 1278 * @param string $abilityName Ability name. 1279 * @param mixed $args Call arguments. 1280 * 1281 * @return string 1282 * @since 1.3.4 1283 */ 1284 private function readSignature(string $abilityName, $args): string 1285 { 1286 return $abilityName . '|' . md5((string) wp_json_encode($this->normalizeArgs($args ?? []))); 1287 } 1288 1289 /** 1290 * Resets the repeat-guard count of reads whose latest result was elided. 1291 * 1292 * The guard exists to stop a model re-fetching data it still has. Once the 1293 * compactor has elided that data, a repeat is the only way to get it back, 1294 * so the signature starts counting from zero again. 1295 * 1296 * @param array<string, int> $callCounts Per-run counts of read call signatures. 1297 * @param array<string, int> $readIndexes History index of the latest result per signature. 1298 * @param int $watermark History index below which results are elided. 1299 * 1300 * @return void 1301 * @since 1.3.4 1302 */ 1303 private function forgetElidedReads(array &$callCounts, array &$readIndexes, int $watermark): void 1304 { 1305 foreach ($readIndexes as $signature => $index) { 1306 if ($index < $watermark) { 1307 unset($callCounts[$signature], $readIndexes[$signature]); 1308 } 1309 } 1310 } 1311 1312 /** 1172 1313 * Normalizes call arguments for signature comparison (recursively sorted keys). 1173 1314 * -
agent-mod/trunk/includes/Services/AI/AIOrchestratorService.php
r3761762 r3775519 74 74 75 75 /** 76 * Ability catalog (category index for dynamic tool loading). 77 * 78 * @var AbilityCatalog 79 * @since 1.3.4 80 */ 81 private AbilityCatalog $abilityCatalog; 82 83 /** 76 84 * Constructor (PHP-DI autowired). 77 85 * … … 81 89 * @param AIClientAdapter $clientAdapter WP AI Client adapter. 82 90 * @param ConfirmationStore $confirmationStore Pending write-action store. 91 * @param AbilityCatalog $abilityCatalog Ability catalog. 83 92 * 84 93 * @since 1.0.0 … … 89 98 KnowledgeResolver $knowledgeResolver, 90 99 AIClientAdapter $clientAdapter, 91 ConfirmationStore $confirmationStore 100 ConfirmationStore $confirmationStore, 101 AbilityCatalog $abilityCatalog 92 102 ) { 93 103 $this->promptBuilder = $promptBuilder; … … 96 106 $this->clientAdapter = $clientAdapter; 97 107 $this->confirmationStore = $confirmationStore; 108 $this->abilityCatalog = $abilityCatalog; 98 109 } 99 110 … … 128 139 $siteContext = $agent->autoIncludeSiteContext ? $this->knowledgeResolver->getSiteContext() : []; 129 140 130 $systemInstruction = $this->promptBuilder->buildSystemInstruction($agent, $siteContext); 131 $abilities = $this->abilityResolver->resolve($agent); 141 // Before the tools are selected, and stored with the agent so a paused 142 // run resumes with the same tool list. 143 $agent->preloadedAbilities = $this->recentToolNames($history); 144 145 $toolset = $this->resolveToolset($agent); 146 147 $systemInstruction = $this->promptBuilder->buildSystemInstruction($agent, $siteContext, $toolset['catalog']); 132 148 133 149 $messages = $this->mapHistoryToMessages($history); … … 137 153 $systemInstruction, 138 154 $messages, 139 $ abilities,155 $toolset['abilities'], 140 156 $agent->provider, 141 157 $agent->model, … … 143 159 $requestId, 144 160 $autoApprovedAbilities, 145 $agent->webSearchEnabled 161 $agent->webSearchEnabled, 162 $toolset['tools'] 146 163 ); 147 164 … … 211 228 $siteContext = $agent->autoIncludeSiteContext ? $this->knowledgeResolver->getSiteContext() : []; 212 229 213 $systemInstruction = $this->promptBuilder->buildSystemInstruction($agent, $siteContext); 214 $abilities = $this->abilityResolver->resolve($agent); 230 $toolset = $this->resolveToolset($agent); 231 232 $systemInstruction = $this->promptBuilder->buildSystemInstruction($agent, $siteContext, $toolset['catalog']); 215 233 216 234 $response = $this->clientAdapter->resume( 217 235 $systemInstruction, 218 236 $messages, 219 $ abilities,237 $toolset['abilities'], 220 238 $agent->provider, 221 239 $agent->model, … … 226 244 $executedCalls, 227 245 $decision, 228 $answerPayload 246 $answerPayload, 247 $toolset['tools'] 229 248 ); 230 249 … … 235 254 236 255 return $response; 256 } 257 258 /** 259 * Resolves the abilities of a run and the tools sent up front. 260 * 261 * @param AgentConfig $agent The agent configuration. 262 * 263 * @return array{abilities: \WP_Ability[], tools: \WP_Ability[]|null, catalog: array<string, array{label: string, count: int}>} 264 * catalog is empty when every ability is sent as a tool. 265 * @since 1.3.4 266 */ 267 private function resolveToolset(AgentConfig $agent): array 268 { 269 $abilities = $this->abilityResolver->resolve($agent); 270 $toolset = $this->abilityResolver->selectTools($agent, $abilities); 271 272 $toolset['catalog'] = null === $toolset['tools'] ? [] : $this->abilityCatalog->categoryIndex($abilities); 273 274 return $toolset; 275 } 276 277 /** 278 * Ability names the agent used in the recent turns of the conversation. 279 * 280 * Covers the same turns whose tool digests are replayed to the model 281 * (agent_mod_history_tool_digest_turns), so the tools behind the results the 282 * model reads are loaded too. The history comes from the client; the names 283 * are only used after being intersected with the allowed abilities. 284 * 285 * @param array<int, array<string, mixed>> $history Prior turns. 286 * 287 * @return string[] 288 * @since 1.3.4 289 */ 290 private function recentToolNames(array $history): array 291 { 292 /** This filter is documented in AIOrchestratorService::mapHistoryToMessages(). */ 293 $turns = max(0, (int) apply_filters('agent_mod_history_tool_digest_turns', 6)); 294 295 $toolTurns = array_filter($history, static function ($turn): bool { 296 return is_array($turn) && ! empty($turn['toolCalls']) && is_array($turn['toolCalls']); 297 }); 298 299 $names = []; 300 301 foreach ($turns > 0 ? array_slice($toolTurns, -$turns) : [] as $turn) { 302 foreach ($turn['toolCalls'] as $call) { 303 $name = is_array($call) ? sanitize_text_field((string) ($call['name'] ?? '')) : ''; 304 305 if ('' !== $name && AbilityCatalog::FIND !== $name) { 306 $names[] = $name; 307 } 308 } 309 } 310 311 return array_slice(array_values(array_unique($names)), 0, 20); 237 312 } 238 313 -
agent-mod/trunk/includes/Services/AI/AbilityResolver.php
r3770241 r3775519 15 15 16 16 use WP_Ability; 17 use AgentMod\Common\Constants; 17 18 use AgentMod\Services\AI\DTO\AgentConfig; 18 19 use AgentMod\Services\AskUserAbilityService; 20 use AgentMod\Services\SettingsService; 19 21 20 22 defined('ABSPATH') || exit; … … 22 24 class AbilityResolver 23 25 { 26 /** 27 * Name prefix of the MCP Adapter plugin's abilities, which are never resolved. 28 * 29 * @var string 30 * @since 1.3.4 31 */ 32 private const MCP_ADAPTER_PREFIX = 'mcp-adapter/'; 33 34 /** 35 * Settings service; supplies the tool loading strategy. 36 * 37 * @var SettingsService 38 * @since 1.3.4 39 */ 40 private SettingsService $settings; 41 42 /** 43 * Constructor (PHP-DI autowired). 44 * 45 * @param SettingsService $settings Settings service. 46 * 47 * @since 1.3.4 48 */ 49 public function __construct(SettingsService $settings) 50 { 51 $this->settings = $settings; 52 } 53 24 54 /** 25 55 * Resolves the abilities available to an agent. … … 39 69 ? $this->resolveSelected($agent->allowedAbilities) 40 70 : array_values(wp_get_abilities()); 71 72 // System abilities are added below and in selectTools() exactly when they 73 // apply, never because they were selected or registered. 74 $abilities = array_values(array_filter($abilities, static function ($ability): bool { 75 return ! $ability instanceof WP_Ability || self::isSelectable($ability); 76 })); 41 77 42 78 // Ask/Plan modes only get read-only abilities; Pro keeps final say via the filter below. … … 61 97 62 98 /** 99 * Whether an ability can be part of an ability selection. 100 * 101 * Two kinds of abilities never are, so selection lists should not offer them 102 * and resolve() drops them from every list: 103 * 104 * - AgentMod's system abilities (Constants::SYSTEM_ABILITY_CATEGORY): part 105 * of how the agent works; AgentMod offers them when they apply. 106 * - The MCP Adapter's abilities: discover / get-info / execute are a proxy 107 * for every other ability. execute-ability would run any of them under 108 * its own name, past the per-name write confirmation, auto-approval and 109 * a PRO agent's allowlist; the agent calls abilities directly anyway. 110 * 111 * @param WP_Ability $ability The ability. 112 * 113 * @return bool 114 * @since 1.3.4 115 */ 116 public static function isSelectable(WP_Ability $ability): bool 117 { 118 return ! self::isSystemAbility($ability) && 0 !== strpos($ability->get_name(), self::MCP_ADAPTER_PREFIX); 119 } 120 121 /** 122 * Whether an ability is one of AgentMod's system abilities. 123 * 124 * @param WP_Ability $ability The ability. 125 * 126 * @return bool 127 * @since 1.3.4 128 */ 129 public static function isSystemAbility(WP_Ability $ability): bool 130 { 131 return Constants::SYSTEM_ABILITY_CATEGORY === $ability->get_category(); 132 } 133 134 /** 135 * Splits the resolved abilities into what may run and what is sent as tools. 136 * 137 * Under dynamic tool loading only a small set goes out as tool definitions: 138 * the system abilities (find-abilities, the interactive chat tools), the core abilities 139 * (agent_mod_core_abilities), the abilities the user highlighted and those 140 * preloaded from recent turns. Everything else is reached through 141 * find-abilities, which loads its matches into the tool list during the run. 142 * The executable set is the resolved set plus find-abilities, so the write 143 * gate, auto-approval and read-only rules keep working on real ability names. 144 * 145 * Small sets (up to agent_mod_dynamic_tools_threshold abilities) and the 146 * 'all' strategy send everything, as before. 147 * 148 * @param AgentConfig $agent The agent configuration. 149 * @param WP_Ability[] $abilities Abilities from resolve(). 150 * 151 * @return array{abilities: WP_Ability[], tools: WP_Ability[]|null} tools is null when every ability is sent. 152 * @since 1.3.4 153 */ 154 public function selectTools(AgentConfig $agent, array $abilities): array 155 { 156 $everything = ['abilities' => $abilities, 'tools' => null]; 157 158 /** 159 * Filters the tool loading strategy of a run: 'dynamic' or 'all'. 160 * 161 * @param string $strategy Strategy from the settings page. 162 * @param AgentConfig $agent The agent configuration. 163 * 164 * @since 1.3.4 165 */ 166 $strategy = (string) apply_filters('agent_mod_tool_loading', $this->settings->getToolLoading(), $agent); 167 168 /** 169 * Filters the ability count up to which dynamic loading still sends every ability. 170 * 171 * @param int $threshold Ability count. 172 * 173 * @since 1.3.4 174 */ 175 $threshold = (int) apply_filters('agent_mod_dynamic_tools_threshold', Constants::AI_DYNAMIC_TOOLS_THRESHOLD); 176 177 $find = function_exists('wp_get_ability') ? wp_get_ability(AbilityCatalog::FIND) : null; 178 179 if ('dynamic' !== $strategy || count($abilities) <= $threshold || ! $find instanceof WP_Ability) { 180 return $everything; 181 } 182 183 /** 184 * Filters the abilities that are always sent as tools under dynamic loading. 185 * 186 * Only names that are also allowed for the run are used. 187 * 188 * @param string[] $names Ability names. 189 * @param AgentConfig $agent The agent configuration. 190 * 191 * @since 1.3.4 192 */ 193 $core = (array) apply_filters('agent_mod_core_abilities', [], $agent); 194 195 $initial = array_merge($core, $agent->emphasizedAbilities, $agent->preloadedAbilities); 196 197 $abilities = array_values(array_filter($abilities, static function ($ability): bool { 198 return $ability instanceof WP_Ability; 199 })); 200 $abilities[] = $find; 201 202 usort($abilities, static function (WP_Ability $a, WP_Ability $b): int { 203 return strcmp($a->get_name(), $b->get_name()); 204 }); 205 206 // Filtering the name-sorted set keeps the tool block in the same order 207 // on every request, which provider prompt caching depends on. 208 $tools = array_values(array_filter($abilities, static function (WP_Ability $ability) use ($initial): bool { 209 return self::isSystemAbility($ability) || in_array($ability->get_name(), $initial, true); 210 })); 211 212 return ['abilities' => $abilities, 'tools' => $tools]; 213 } 214 215 /** 63 216 * Offers the interactive chat tools exactly when somebody can use them. 64 217 * 65 218 * ask-user is offered on an interactive channel; present-plan only there 66 219 * and in Plan mode; request-approval only there, in Execute mode with 67 * manual approval. On such a channel they are added even when the agent68 * uses a "selected" ability list, because they are part of the chat itself69 * rather than site capabilities. Everywhere else (widgets, scheduled runs)70 * they are removed, even when "all" abilities were resolved: nobody would71 * see the questions or thereview options.72 * 73 * @param WP_Ability[] $abilities Resolved abilities .220 * manual approval. They are system abilities, so resolve() has already 221 * dropped them from the list whether or not they were selected; on an 222 * interactive channel they are added back here. Everywhere else (widgets, 223 * scheduled runs) they stay out: nobody would see the questions or the 224 * review options. 225 * 226 * @param WP_Ability[] $abilities Resolved abilities, without system abilities. 74 227 * @param AgentConfig $agent The agent configuration. 75 228 * … … 79 232 private function applyInteractiveTools(array $abilities, AgentConfig $agent): array 80 233 { 81 $interactiveTools = [82 AskUserAbilityService::NAME,83 AskUserAbilityService::PRESENT_PLAN,84 AskUserAbilityService::REQUEST_APPROVAL,85 ];86 87 $abilities = array_values(array_filter($abilities, static function ($ability) use ($interactiveTools): bool {88 return ! ($ability instanceof WP_Ability && in_array($ability->get_name(), $interactiveTools, true));89 }));90 91 234 if (! $agent->interactive) { 92 235 return $abilities; -
agent-mod/trunk/includes/Services/AI/DTO/AgentConfig.php
r3770241 r3775519 203 203 */ 204 204 public string $approvalMode = 'manual'; 205 206 /** 207 * Ability names loaded up front under dynamic tool loading. 208 * 209 * Set server-side by the orchestrator from the tools used in recent turns, 210 * so a follow-up message ("now do the same for the other page") does not 211 * have to search for them again. Carried on the DTO because the DTO is what 212 * the confirmation store keeps for a paused run. Names are only ever 213 * intersected with the allowed abilities, never trusted on their own. 214 * 215 * @var string[] 216 * @since 1.3.4 217 */ 218 public array $preloadedAbilities = []; 205 219 206 220 /** -
agent-mod/trunk/includes/Services/AI/HistoryCompactor.php
r3770241 r3775519 9 9 * re-uploaded verbatim on every later iteration, and screenshot attachments on 10 10 * the user message are re-uploaded too. This service compacts the copy of the 11 * history that goes to the provider: old tool results are elided to short 12 * stubs (their call id and name are preserved — providers require a response 13 * for every outstanding call), and binary file parts are kept only on the most 14 * recent user message. The canonical history kept for persistence and the 15 * confirmation/resume flow is never modified. 11 * history that goes to the provider: binary file parts are kept only on the 12 * most recent user message, and once the history grows past a token threshold 13 * old tool results are elided to short summaries (their call id and name are 14 * preserved — providers require a response for every outstanding call). The 15 * canonical history kept for persistence and the confirmation/resume flow is 16 * never modified. 17 * 18 * Elision is threshold-based and sticky on purpose. Below the trigger nothing 19 * is elided, so the model keeps every result it fetched. When the trigger is 20 * crossed, results are elided oldest first down to a lower target and a 21 * watermark records how far it went; later iterations elide exactly the same 22 * messages until the next crossing. The sent prefix therefore stays byte- 23 * identical between compactions, which keeps provider prompt caches warm — 24 * a window sliding on every iteration would invalidate them each time. 16 25 * 17 26 * @package AgentMod … … 50 59 51 60 /** 61 * Keys kept at every depth in the structure summary of a nested result 62 * (e.g. a field configuration tree), so its skeleton survives elision. 63 * 64 * @var string[] 65 * @since 1.3.4 66 */ 67 private const STRUCTURE_KEYS = [ 68 'id', 'ID', 'post_id', 'term_id', 'user_id', 'name', 'slug', 'title', 'label', 'status', 'post_type', 'taxonomy', 69 'type', 'fieldType', 'fieldLabel', 'meta_box_id', 'meta_box_title', 'section_name', 'section_title', 'menu_slug', 70 'options', 'multiple', 'required', 'default', 71 ]; 72 73 /** 74 * Maximum length of a top-level string kept in a structure summary. 75 * 76 * @var int 77 * @since 1.3.4 78 */ 79 private const STRUCTURE_STRING_LENGTH = 200; 80 81 /** 82 * Rough characters-per-token ratio used to estimate history size. 83 * 84 * @var int 85 * @since 1.3.4 86 */ 87 private const CHARS_PER_TOKEN = 4; 88 89 /** 90 * Estimated token cost of one file part (image / PDF attachment). 91 * 92 * @var int 93 * @since 1.3.4 94 */ 95 private const FILE_PART_TOKENS = 1500; 96 97 /** 52 98 * Compacts a history for sending to the provider. 53 99 * 54 * @param Message[] $history Conversation history (never mutated). 100 * Pass the same $watermark variable on every iteration of one run: it 101 * records up to which history index tool results have been elided, so 102 * later calls elide the same messages and only move it on the next 103 * threshold crossing. 104 * 105 * @param Message[] $history Conversation history (never mutated). 106 * @param int $watermark History index below which tool results are elided; advanced in place. 55 107 * 56 108 * @return Message[] Compacted copy. 57 109 * @since 1.1.5 58 110 */ 59 public function compact(array $history ): array111 public function compact(array $history, int &$watermark = 0): array 60 112 { 61 113 /** 62 * Filters how many of the most recent tool-result messages are re-sent63 * verbatim; older ones are elided to stubs.114 * Filters how many of the most recent tool-result messages are always 115 * re-sent verbatim, even once the history has been compacted. 64 116 * 65 117 * @param int $keep Number of tool-result messages kept verbatim. … … 68 120 */ 69 121 $keep = max(0, (int) apply_filters('agent_mod_history_keep_tool_results', Constants::AI_HISTORY_KEEP_TOOL_RESULTS)); 122 123 /** 124 * Filters the estimated history size (tokens) at which old tool 125 * results start being elided. 126 * 127 * @param int $trigger Token threshold. 128 * 129 * @since 1.3.4 130 */ 131 $trigger = max(0, (int) apply_filters('agent_mod_history_compact_trigger_tokens', Constants::AI_HISTORY_COMPACT_TRIGGER_TOKENS)); 132 133 /** 134 * Filters the estimated history size (tokens) a compaction elides 135 * down to. Kept below the trigger so compactions stay rare. 136 * 137 * @param int $target Token target. 138 * 139 * @since 1.3.4 140 */ 141 $target = min($trigger, max(0, (int) apply_filters('agent_mod_history_compact_target_tokens', Constants::AI_HISTORY_COMPACT_TARGET_TOKENS))); 70 142 71 143 $toolResultIndexes = []; … … 84 156 } 85 157 86 // Tool-result messages recent enough to stay verbatim. (array_slice 87 // with -0 would return the whole array, so 0 is handled explicitly.) 88 $verbatim = $keep > 0 ? array_flip(array_slice($toolResultIndexes, -$keep)) : []; 158 // The first tool-result message recent enough to always stay verbatim. 159 // (array_slice with -0 would return the whole array, so 0 is handled 160 // explicitly.) 161 $recent = $keep > 0 ? array_slice($toolResultIndexes, -$keep) : []; 162 $firstRecent = empty($recent) ? PHP_INT_MAX : (int) reset($recent); 89 163 90 164 // Only the most recent attachment-bearing user message keeps its files. … … 97 171 foreach ($history as $index => $message) { 98 172 if (! $message instanceof Message) { 99 $compacted[] = $message; 100 continue; 101 } 102 103 if (in_array($index, $toolResultIndexes, true) && ! isset($verbatim[$index])) { 104 $compacted[] = $this->elideToolResults($message, $sticky[$index] ?? []); 173 $compacted[$index] = $message; 174 continue; 175 } 176 177 // Everything below the watermark was elided by an earlier 178 // compaction of this run and stays elided, so the prefix the 179 // provider sees does not change between compactions. 180 if ($index < $watermark && in_array($index, $toolResultIndexes, true)) { 181 $compacted[$index] = $this->elideToolResults($message, $sticky[$index] ?? []); 105 182 continue; 106 183 } … … 110 187 && $index !== $lastFileIndex 111 188 ) { 112 $compacted[] = $this->stripFiles($message); 113 continue; 114 } 115 116 $compacted[] = $message; 117 } 118 119 return $compacted; 189 $compacted[$index] = $this->stripFiles($message); 190 continue; 191 } 192 193 $compacted[$index] = $message; 194 } 195 196 $tokens = $this->estimateTokens($compacted); 197 198 if ($tokens < $trigger) { 199 return array_values($compacted); 200 } 201 202 // Over the threshold: elide the oldest results down to the target and 203 // move the watermark past the last one elided. 204 foreach ($toolResultIndexes as $index) { 205 if ($tokens <= $target || $index >= $firstRecent) { 206 break; 207 } 208 209 if ($index < $watermark) { 210 continue; 211 } 212 213 $before = $this->estimateTokens([$compacted[$index]]); 214 $compacted[$index] = $this->elideToolResults($history[$index], $sticky[$index] ?? []); 215 $tokens -= $before - $this->estimateTokens([$compacted[$index]]); 216 $watermark = $index + 1; 217 } 218 219 return array_values($compacted); 220 } 221 222 /** 223 * Estimates the token size of a history. 224 * 225 * A characters / 4 heuristic over text and JSON-encoded function call and 226 * response parts; each file part counts as a fixed amount. Only used to 227 * decide when to compact, so a rough figure is enough. 228 * 229 * @param Message[] $history Conversation history. 230 * 231 * @return int Estimated tokens. 232 * @since 1.3.4 233 */ 234 public function estimateTokens(array $history): int 235 { 236 $chars = 0; 237 $files = 0; 238 239 foreach ($history as $message) { 240 if (! $message instanceof Message) { 241 continue; 242 } 243 244 foreach ($message->getParts() as $part) { 245 $type = $part->getType(); 246 247 if ($type->isFile()) { 248 $files++; 249 } elseif ($type->isFunctionCall()) { 250 $call = $part->getFunctionCall(); 251 $chars += null === $call ? 0 : strlen((string) wp_json_encode($call->getArgs())) + strlen((string) $call->getName()); 252 } elseif ($type->isFunctionResponse()) { 253 $response = $part->getFunctionResponse(); 254 $chars += null === $response ? 0 : strlen((string) wp_json_encode($response->getResponse())); 255 } else { 256 $chars += strlen((string) $part->getText()); 257 } 258 } 259 } 260 261 return (int) ceil($chars / self::CHARS_PER_TOKEN) + $files * self::FILE_PART_TOKENS; 262 } 263 264 /** 265 * Whether an ability's results are exempt from elision. 266 * 267 * By default the Pro skill fetcher, the ask-user / request-approval 268 * answers and the Native Custom Fields configuration reads: a field 269 * configuration is what the agent writes code against, and losing it 270 * mid-task sends the agent back to re-read it or to guess meta keys. 271 * 272 * @param string $abilityName Ability name. 273 * 274 * @return bool 275 * @since 1.3.4 276 */ 277 public function isStickyTool(string $abilityName): bool 278 { 279 return in_array($abilityName, $this->stickyTools(), true); 280 } 281 282 /** 283 * Returns the ability names whose results are never elided. 284 * 285 * @return string[] 286 * @since 1.3.4 287 */ 288 private function stickyTools(): array 289 { 290 /** 291 * Filters the ability names whose most recent result per argument 292 * identity is never elided from the in-loop history. 293 * 294 * @param string[] $stickyTools Ability names. 295 * 296 * @since 1.1.5 297 */ 298 // The user's answers to ask-user / request-approval are their own input, 299 // so they are never elided either. 300 return (array) apply_filters( 301 'agent_mod_history_sticky_tools', 302 array_merge( 303 [ 304 'agent-mod-pro/get-skill-by-slug', 305 'native-custom-fields/get-post-meta-fields', 306 'native-custom-fields/get-term-meta-fields', 307 'native-custom-fields/get-user-meta-fields', 308 'native-custom-fields/get-options-page', 309 ], 310 AskUserAbilityService::PAUSING_TOOLS 311 ) 312 ); 120 313 } 121 314 … … 138 331 private function resolveStickyParts(array $history, array $toolResultIndexes): array 139 332 { 140 /** 141 * Filters the ability names whose most recent result per argument 142 * identity is never elided from the in-loop history. 143 * 144 * @param string[] $stickyTools Ability names. 145 * 146 * @since 1.1.5 147 */ 148 // The user's answers to ask-user / request-approval are their own input, 149 // so they are never elided either. 150 $stickyTools = (array) apply_filters( 151 'agent_mod_history_sticky_tools', 152 array_merge(['agent-mod-pro/get-skill-by-slug'], AskUserAbilityService::PAUSING_TOOLS) 153 ); 333 $stickyTools = $this->stickyTools(); 154 334 155 335 if (empty($stickyTools)) { … … 234 414 235 415 // A list keeps its IDs and titles: losing them is what sent the 236 // agent back to list the same posts again and again. 237 $listSummary = $this->summarizeListResult($response->getResponse()); 238 239 $payload = null !== $listSummary 240 ? [ 416 // agent back to list the same posts again and again. A nested 417 // result (e.g. a field configuration) keeps its skeleton. 418 $listSummary = $this->summarizeListResult($response->getResponse()); 419 $structureSummary = null === $listSummary ? $this->summarizeStructure($response->getResponse()) : null; 420 421 if (null !== $listSummary) { 422 $payload = [ 241 423 'elided' => true, 242 424 'list_summary' => $listSummary, 243 425 'note' => __('Older list result reduced to IDs and titles to save context. Reuse these IDs; do not list the same items again.', 'agent-mod'), 244 ] 245 : [ 426 ]; 427 } elseif (null !== $structureSummary) { 428 $payload = [ 429 'elided' => true, 430 'structure_summary' => $structureSummary, 431 'note' => __('Older result reduced to its structure (names, types, labels) to save context. If you need the omitted details, call the same tool again.', 'agent-mod'), 432 ]; 433 } else { 434 $payload = [ 246 435 'elided' => true, 247 436 'summary' => mb_substr( … … 249 438 0, 250 439 Constants::AI_TOOL_RESULT_STUB_LENGTH 251 ) . '… ' . __('(older tool result elided to save context; re-call the tool only if you truly need the full dataagain)', 'agent-mod'),440 ) . '… ' . __('(older tool result elided to save context; if you need the full data, call the same tool again)', 'agent-mod'), 252 441 ]; 442 } 253 443 254 444 $parts[] = new MessagePart( … … 322 512 323 513 /** 514 * Reduces a nested tool result to its structural skeleton. 515 * 516 * Applies to results that hold nested arrays but are not ID lists, such 517 * as a Native Custom Fields configuration (sections → fields → sub-fields). 518 * Top-level scalars are kept (long strings cut short); below the top level 519 * only identifying keys (name, type, fieldType, label, options …) are kept, 520 * at any depth. 521 * 522 * @param mixed $payload Tool result. 523 * 524 * @return string|null JSON summary capped at AI_LIST_RESULT_SUMMARY_LENGTH, or null when the result has no nested structure. 525 * @since 1.3.4 526 */ 527 public function summarizeStructure($payload): ?string 528 { 529 if (! is_array($payload) || empty(array_filter($payload, 'is_array'))) { 530 return null; 531 } 532 533 $skeleton = $this->skeleton($payload, 0); 534 535 if (empty($skeleton)) { 536 return null; 537 } 538 539 return mb_substr( 540 (string) wp_json_encode($skeleton, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 541 0, 542 Constants::AI_LIST_RESULT_SUMMARY_LENGTH 543 ); 544 } 545 546 /** 547 * Recursively keeps the structural keys of a nested array. 548 * 549 * @param array<mixed> $value Array to reduce. 550 * @param int $depth Current depth (0 = top level). 551 * 552 * @return array<mixed> 553 * @since 1.3.4 554 */ 555 private function skeleton(array $value, int $depth): array 556 { 557 $keep = array_flip(self::STRUCTURE_KEYS); 558 $out = []; 559 560 foreach ($value as $key => $item) { 561 if (is_array($item)) { 562 $child = $this->skeleton($item, $depth + 1); 563 564 if (! empty($child)) { 565 $out[$key] = $child; 566 } 567 } elseif (0 === $depth) { 568 $out[$key] = is_string($item) ? mb_substr($item, 0, self::STRUCTURE_STRING_LENGTH) : $item; 569 } elseif (is_int($key) || isset($keep[$key])) { 570 $out[$key] = $item; 571 } 572 } 573 574 return $out; 575 } 576 577 /** 324 578 * Whether a value is a non-empty list of items that carry an ID. 325 579 * -
agent-mod/trunk/includes/Services/AI/KnowledgeResolver.php
r3585895 r3775519 34 34 'wp_version' => (string) get_bloginfo('version'), 35 35 'language' => (string) get_bloginfo('language'), 36 'php_version' => PHP_VERSION, 36 37 ]; 37 38 } -
agent-mod/trunk/includes/Services/AI/PromptBuilder.php
r3761762 r3775519 37 37 * Builds the full system instruction for an agent. 38 38 * 39 * @param AgentConfig $agent The agent configuration. 40 * @param array<string, string> $siteContext Site context (may be empty). 39 * @param AgentConfig $agent The agent configuration. 40 * @param array<string, string> $siteContext Site context (may be empty). 41 * @param array<string, array{label: string, count: int}> $toolCatalog Categories of the abilities left to 42 * find under dynamic tool loading 43 * (AbilityCatalog::categoryIndex()); empty 44 * when every ability is sent as a tool. 41 45 * 42 46 * @return string 43 47 * @since 1.0.0 44 48 */ 45 public function buildSystemInstruction(AgentConfig $agent, array $siteContext = [] ): string49 public function buildSystemInstruction(AgentConfig $agent, array $siteContext = [], array $toolCatalog = []): string 46 50 { 47 51 $sections = []; … … 101 105 $sections[] = __('Earlier tool results are summarized in the conversation history as "[Tools used this turn]" blocks. Trust those summaries: do not re-call the same read tool with the same arguments to retrieve data you already have.', 'agent-mod'); 102 106 107 if (! empty($toolCatalog)) { 108 $sections[] = $this->buildToolLoadingDirective($toolCatalog); 109 } 110 103 111 $emphasized = $this->resolveEmphasized($agent); 104 112 if (! empty($emphasized)) { … … 113 121 114 122 return (string) apply_filters('agent_mod_system_prompt', $instruction, $agent); 123 } 124 125 /** 126 * Builds the directive for dynamic tool loading. 127 * 128 * Without it the model takes its short tool list for everything it can do 129 * and tells the user a task is impossible. The category list costs a few 130 * hundred tokens at most and tells the model what is worth searching for. 131 * 132 * @param array<string, array{label: string, count: int}> $toolCatalog Categories keyed by slug. 133 * 134 * @return string 135 * @since 1.3.4 136 */ 137 private function buildToolLoadingDirective(array $toolCatalog): string 138 { 139 $lines = [ 140 sprintf( 141 /* translators: %s: find-abilities ability name. */ 142 __('Only a few of your tools are loaded at the start. Whenever the loaded tools cannot do what the task needs, call %s with short English keywords first; the tools it returns are loaded for your next step. Never tell the user something cannot be done before searching for a tool. Tool categories you can search (slug: label, number of tools):', 'agent-mod'), 143 AbilityCatalog::FIND 144 ), 145 ]; 146 147 foreach ($toolCatalog as $slug => $category) { 148 $lines[] = sprintf('- %s: %s (%d)', '' !== $slug ? $slug : '-', $category['label'], $category['count']); 149 } 150 151 return implode("\n", $lines); 115 152 } 116 153 … … 331 368 'wp_version' => __('WordPress version', 'agent-mod'), 332 369 'language' => __('Language', 'agent-mod'), 370 'php_version' => __('PHP version', 'agent-mod'), 333 371 ]; 334 372 … … 341 379 } 342 380 381 // Without this the model treats the block as a hint and re-fetches the 382 // same values with the site/environment info tools on every turn. 383 $lines[] = __('This site context is current and authoritative. Use it directly; do not call tools only to retrieve these values again (for example the site name, URL, WordPress or PHP version).', 'agent-mod'); 384 343 385 return implode("\n", $lines); 344 386 } -
agent-mod/trunk/includes/Services/AbilityRegistrarService.php
r3770241 r3775519 20 20 use WP_Post; 21 21 use WP_Query; 22 use WP_Taxonomy; 23 use WP_Term; 22 24 use WP_Block_Patterns_Registry; 23 25 use WP_Theme_JSON; … … 642 644 [ 643 645 'label' => __('Get Post or Page', 'agent-mod'), 644 'description' => __('Returns a post raw block markup as html by post_id, plus its status and custom fields (meta). Use list-posts to find post_id first. For the same text change across many posts use preview-bulk-replace / bulk-replace-content instead of reading each post. The returned html can be modified and passed straight back to update-post as the html parameter. Navigation menus (wp_navigation, referenced by {"ref":ID} in wp:navigation blocks) have dedicated abilities: list-navigations / get-navigation / update-navigation.', 'agent-mod'),646 'description' => __('Returns a post raw block markup as html by post_id, plus its status, custom fields (meta) and assigned taxonomy terms (terms, keyed by taxonomy). Use list-posts to find post_id first. For the same text change across many posts use preview-bulk-replace / bulk-replace-content instead of reading each post. The returned html can be modified and passed straight back to update-post as the html parameter. Navigation menus (wp_navigation, referenced by {"ref":ID} in wp:navigation blocks) have dedicated abilities: list-navigations / get-navigation / update-navigation.', 'agent-mod'), 645 647 'category' => self::CATEGORY, 646 648 'execute_callback' => [$this, 'executeGetPost'], … … 679 681 'html' => ['type' => 'string'], 680 682 'meta' => ['type' => 'object'], 683 'terms' => ['type' => 'object'], 681 684 'error' => ['type' => 'string'], 682 685 ], … … 693 696 [ 694 697 'label' => __('Create Post or Page', 'agent-mod'), 695 'description' => __('Creates a new post/page. Provide title and optionally html and meta. The html parameter must be complete, valid serialized block markup; it is parsed with parse_blocks() and re-serialized as-is — attribute/innerHTML consistency is NOT validated server-side. Returns the new post_id for use with get-post/update-post.', 'agent-mod'),698 'description' => __('Creates a new post/page. Provide title and optionally html, meta and terms (categories, tags or custom taxonomy terms; create missing terms first with create-terms). The html parameter must be complete, valid serialized block markup; it is parsed with parse_blocks() and re-serialized as-is — attribute/innerHTML consistency is NOT validated server-side. Returns the new post_id for use with get-post/update-post.', 'agent-mod'), 696 699 'category' => self::CATEGORY, 697 700 'execute_callback' => [$this, 'executeCreatePost'], … … 721 724 ], 722 725 'meta' => $this->metaWriteSchema(), 726 'terms' => $this->termWriteSchema(), 723 727 ], 724 728 ], … … 745 749 [ 746 750 'label' => __('Update Post or Page', 'agent-mod'), 747 'description' => __('Updates a post. Provide post_id plus any of html, title, post_status and meta; fields left out stay unchanged. The html parameter must be complete, valid serialized block markup; it is parsed with parse_blocks() and re-serialized as-is — attribute/innerHTML consistency is NOT validated server-side. To edit a navigation menu (wp_navigation) use update-navigation instead.', 'agent-mod'),751 'description' => __('Updates a post. Provide post_id plus any of html, title, post_status, meta and terms (categories, tags or custom taxonomy terms; create missing terms first with create-terms); fields left out stay unchanged. The html parameter must be complete, valid serialized block markup; it is parsed with parse_blocks() and re-serialized as-is — attribute/innerHTML consistency is NOT validated server-side. To edit a navigation menu (wp_navigation) use update-navigation instead.', 'agent-mod'), 748 752 'category' => self::CATEGORY, 749 753 'execute_callback' => [$this, 'executeUpdatePost'], … … 777 781 ], 778 782 'meta' => $this->metaWriteSchema(), 783 'terms' => $this->termWriteSchema(), 779 784 ], 780 785 ], … … 2351 2356 'html' => $post->post_content, 2352 2357 'meta' => $this->readPostMeta($post->ID, $input['meta_keys'] ?? null), 2358 'terms' => $this->readPostTerms($post), 2353 2359 ]; 2354 2360 } … … 2357 2363 * Execute callback for agent-mod/create-post. 2358 2364 * 2359 * @param mixed $input Input data (expects 'title', optional 'html', 'post_type', 'post_status', 'meta' ).2365 * @param mixed $input Input data (expects 'title', optional 'html', 'post_type', 'post_status', 'meta', 'terms'). 2360 2366 * 2361 2367 * @return array<string, mixed> … … 2403 2409 } 2404 2410 2411 // Term checks need no post, so they run before anything is inserted. 2412 $termWrites = $this->prepareTermWrites($postType, $input['terms'] ?? null); 2413 if (is_wp_error($termWrites)) { 2414 return ['success' => false, 'error' => $termWrites->get_error_message()]; 2415 } 2416 2405 2417 // Meta capabilities can only be checked against an existing post, so a 2406 // post with meta starts as a draft: it can be rolled back without side 2407 // effects, and publish-time hooks (SEO, sharing) see the final meta. 2408 $hasMeta = ! empty($input['meta']); 2418 // post with meta or terms starts as a draft: it can be rolled back without 2419 // side effects, and publish-time hooks (SEO, sharing) see the final data. 2420 $hasMeta = ! empty($input['meta']); 2421 $deferSave = $hasMeta || ! empty($termWrites); 2409 2422 2410 2423 // wp_insert_post() unslashes its input; without wp_slash() the JSON … … 2415 2428 'post_content' => $serializedContent, 2416 2429 'post_type' => $postType, 2417 'post_status' => $ hasMeta? 'draft' : $postStatus,2430 'post_status' => $deferSave ? 'draft' : $postStatus, 2418 2431 ]), 2419 2432 true … … 2426 2439 $postId = (int) $result; 2427 2440 2428 if ($ hasMeta) {2429 $metaWrites = $this->prepareMetaWrites($postId, $input['meta'] );2441 if ($deferSave) { 2442 $metaWrites = $this->prepareMetaWrites($postId, $input['meta'] ?? null); 2430 2443 if (is_wp_error($metaWrites)) { 2431 2444 wp_delete_post($postId, true); … … 2434 2447 2435 2448 $this->applyMetaWrites($postId, $metaWrites); 2449 2450 // A new post only holds the default terms (e.g. Uncategorized), so 2451 // the requested terms always replace them. 2452 $termError = $this->applyTermWrites($postId, $termWrites, false); 2453 if (is_wp_error($termError)) { 2454 wp_delete_post($postId, true); 2455 return ['success' => false, 'error' => $termError->get_error_message()]; 2456 } 2436 2457 2437 2458 if ('draft' !== $postStatus) { … … 2455 2476 * Execute callback for agent-mod/update-post. 2456 2477 * 2457 * @param mixed $input Input data (expects 'post_id' plus any of 'html', 'title', 'post_status', 'meta' ).2478 * @param mixed $input Input data (expects 'post_id' plus any of 'html', 'title', 'post_status', 'meta', 'terms'). 2458 2479 * 2459 2480 * @return array<string, mixed> … … 2470 2491 $hasStatus = ! empty($input['post_status']); 2471 2492 $hasMeta = ! empty($input['meta']); 2472 2473 if (! $hasHtml && ! $hasTitle && ! $hasStatus && ! $hasMeta) { 2474 return ['success' => false, 'error' => __('Nothing to update: provide at least one of html, title, post_status or meta.', 'agent-mod')]; 2493 $hasTerms = ! empty($input['terms']); 2494 2495 if (! $hasHtml && ! $hasTitle && ! $hasStatus && ! $hasMeta && ! $hasTerms) { 2496 return ['success' => false, 'error' => __('Nothing to update: provide at least one of html, title, post_status, meta or terms.', 'agent-mod')]; 2475 2497 } 2476 2498 … … 2527 2549 } 2528 2550 2529 // Meta first, so publish-time hooks fired by a status change see it. 2551 $termWrites = $this->prepareTermWrites($post->post_type, $input['terms'] ?? null); 2552 if (is_wp_error($termWrites)) { 2553 return ['success' => false, 'error' => $termWrites->get_error_message()]; 2554 } 2555 2556 // Meta and terms first, so publish-time hooks fired by a status change see them. 2530 2557 $this->applyMetaWrites($postId, $metaWrites); 2558 2559 $termError = $this->applyTermWrites($postId, $termWrites, true); 2560 if (is_wp_error($termError)) { 2561 return ['success' => false, 'error' => $termError->get_error_message()]; 2562 } 2531 2563 2532 2564 if (count($updateArgs) > 1) { … … 3233 3265 3234 3266 /** 3267 * Input schema shared by the create-post and update-post terms parameter. 3268 * 3269 * Term references are strings so one fixed item type works with every 3270 * provider; numeric strings are read as term IDs. 3271 * 3272 * @return array<string, mixed> 3273 * @since 1.3.4 3274 */ 3275 private function termWriteSchema(): array 3276 { 3277 return [ 3278 'type' => 'array', 3279 'description' => __('Optional. Taxonomy terms (categories, tags, custom taxonomies) to assign, one item per taxonomy. Terms must already exist: create missing ones first with create-terms. Taxonomies left out keep their current terms.', 'agent-mod'), 3280 'items' => [ 3281 'type' => 'object', 3282 'required' => ['taxonomy', 'terms'], 3283 'properties' => [ 3284 'taxonomy' => [ 3285 'type' => 'string', 3286 'description' => __('Taxonomy slug, e.g. "category", "post_tag" or a custom taxonomy from list-taxonomies.', 'agent-mod'), 3287 ], 3288 'terms' => [ 3289 'type' => 'array', 3290 'items' => ['type' => 'string'], 3291 'description' => __('Term IDs (as strings, e.g. "12"), slugs or names. Replaces the post\'s terms in this taxonomy; an empty list removes them all.', 'agent-mod'), 3292 ], 3293 'append' => [ 3294 'type' => 'boolean', 3295 'description' => __('update-post only. true adds the terms and keeps the existing ones instead of replacing them. Default false.', 'agent-mod'), 3296 ], 3297 ], 3298 ], 3299 ]; 3300 } 3301 3302 /** 3303 * Validates term write items and resolves every term reference to an ID. 3304 * 3305 * Nothing is written here, so callers can reject the whole request before 3306 * touching the post. Missing terms are never created: wp_set_object_terms() 3307 * would silently add a new term for any unknown name. 3308 * 3309 * @param string $postType Post type the terms are assigned to. 3310 * @param mixed $items The 'terms' ability input. 3311 * 3312 * @return array<int, array{taxonomy: string, term_ids: int[], append: bool}>|WP_Error 3313 * @since 1.3.4 3314 */ 3315 private function prepareTermWrites(string $postType, $items) 3316 { 3317 if (empty($items)) { 3318 return []; 3319 } 3320 3321 if (! is_array($items)) { 3322 return new WP_Error('invalid_terms', __('terms must be a list of {taxonomy, terms} objects.', 'agent-mod')); 3323 } 3324 3325 $writes = []; 3326 foreach ($items as $item) { 3327 $slug = is_array($item) && isset($item['taxonomy']) ? sanitize_key((string) $item['taxonomy']) : ''; 3328 $taxonomy = '' !== $slug ? get_taxonomy($slug) : false; 3329 3330 if (! $taxonomy instanceof WP_Taxonomy) { 3331 return new WP_Error( 3332 'invalid_terms', 3333 /* translators: %s: taxonomy slug */ 3334 sprintf(__('The taxonomy "%s" is not registered on this site. Call list-taxonomies to see the available slugs.', 'agent-mod'), $slug) 3335 ); 3336 } 3337 3338 if (! is_object_in_taxonomy($postType, $taxonomy->name)) { 3339 return new WP_Error( 3340 'invalid_terms', 3341 /* translators: 1: taxonomy slug, 2: post type slug */ 3342 sprintf(__('The taxonomy "%1$s" is not registered for the post type "%2$s".', 'agent-mod'), $taxonomy->name, $postType) 3343 ); 3344 } 3345 3346 if (isset($writes[$taxonomy->name])) { 3347 return new WP_Error( 3348 'invalid_terms', 3349 /* translators: %s: taxonomy slug */ 3350 sprintf(__('The taxonomy "%s" is listed more than once. Put all of its terms in a single item.', 'agent-mod'), $taxonomy->name) 3351 ); 3352 } 3353 3354 if (! current_user_can($taxonomy->cap->assign_terms)) { 3355 return new WP_Error( 3356 'terms_forbidden', 3357 /* translators: %s: taxonomy slug */ 3358 sprintf(__('You are not allowed to assign terms in the "%s" taxonomy.', 'agent-mod'), $taxonomy->name) 3359 ); 3360 } 3361 3362 if (! isset($item['terms']) || ! is_array($item['terms'])) { 3363 return new WP_Error( 3364 'invalid_terms', 3365 /* translators: %s: taxonomy slug */ 3366 sprintf(__('The "%s" item needs a terms list (an empty list removes all of its terms).', 'agent-mod'), $taxonomy->name) 3367 ); 3368 } 3369 3370 $termIds = []; 3371 $missing = []; 3372 foreach ($item['terms'] as $reference) { 3373 if (! is_scalar($reference) || '' === trim((string) $reference)) { 3374 continue; 3375 } 3376 3377 $termId = $this->resolveTermReference(trim((string) $reference), $taxonomy->name); 3378 if (null === $termId) { 3379 $missing[] = (string) $reference; 3380 continue; 3381 } 3382 3383 $termIds[] = $termId; 3384 } 3385 3386 if (! empty($missing)) { 3387 return new WP_Error( 3388 'terms_not_found', 3389 sprintf( 3390 /* translators: 1: comma-separated term references, 2: taxonomy slug */ 3391 __('These terms do not exist in the "%2$s" taxonomy: %1$s. Check them with list-terms, or create them first with create-terms.', 'agent-mod'), 3392 implode(', ', $missing), 3393 $taxonomy->name 3394 ) 3395 ); 3396 } 3397 3398 $writes[$taxonomy->name] = [ 3399 'taxonomy' => $taxonomy->name, 3400 'term_ids' => array_values(array_unique($termIds)), 3401 'append' => ! empty($item['append']), 3402 ]; 3403 } 3404 3405 return array_values($writes); 3406 } 3407 3408 /** 3409 * Resolves a term reference (ID, slug or name) to a term ID. 3410 * 3411 * A numeric reference is tried as an ID first, then as a slug, so numeric 3412 * slugs such as a "2024" tag still resolve. 3413 * 3414 * @param string $reference Term ID, slug or name. 3415 * @param string $taxonomy Taxonomy slug. 3416 * 3417 * @return int|null Null when no term matches. 3418 * @since 1.3.4 3419 */ 3420 private function resolveTermReference(string $reference, string $taxonomy): ?int 3421 { 3422 if (ctype_digit($reference)) { 3423 $term = get_term((int) $reference, $taxonomy); 3424 if ($term instanceof WP_Term) { 3425 return (int) $term->term_id; 3426 } 3427 } 3428 3429 $term = get_term_by('slug', sanitize_title($reference), $taxonomy); 3430 if (! $term instanceof WP_Term) { 3431 $term = get_term_by('name', sanitize_text_field($reference), $taxonomy); 3432 } 3433 3434 return $term instanceof WP_Term ? (int) $term->term_id : null; 3435 } 3436 3437 /** 3438 * Saves term writes validated by prepareTermWrites(). 3439 * 3440 * @param int $postId Post ID. 3441 * @param array<int, array{taxonomy: string, term_ids: int[], append: bool}> $writes Validated writes. 3442 * @param bool $allowAppend Whether the items' append flag is honoured. 3443 * 3444 * @return WP_Error|null Null on success. 3445 * @since 1.3.4 3446 */ 3447 private function applyTermWrites(int $postId, array $writes, bool $allowAppend): ?WP_Error 3448 { 3449 foreach ($writes as $write) { 3450 // Integer IDs: wp_set_object_terms() reads strings as term names. 3451 $result = wp_set_object_terms($postId, $write['term_ids'], $write['taxonomy'], $allowAppend && $write['append']); 3452 if (is_wp_error($result)) { 3453 return $result; 3454 } 3455 } 3456 3457 return null; 3458 } 3459 3460 /** 3461 * Reads the terms assigned to a post for get-post, keyed by taxonomy. 3462 * 3463 * Only taxonomies an editor can see (public or shown in the admin UI) are 3464 * listed, which keeps internal ones such as wp_theme out of the result. 3465 * Taxonomies with no terms are listed empty, so the agent also learns 3466 * which taxonomies it can assign. 3467 * 3468 * @param WP_Post $post Post. 3469 * 3470 * @return array<string, array<int, array{id: int, name: string, slug: string}>> 3471 * @since 1.3.4 3472 */ 3473 private function readPostTerms(WP_Post $post): array 3474 { 3475 $result = []; 3476 foreach (get_object_taxonomies($post->post_type, 'objects') as $taxonomy) { 3477 if (! $taxonomy->public && ! $taxonomy->show_ui) { 3478 continue; 3479 } 3480 3481 $terms = get_the_terms($post, $taxonomy->name); 3482 3483 $result[$taxonomy->name] = is_array($terms) 3484 ? array_map(static function (WP_Term $term): array { 3485 return ['id' => (int) $term->term_id, 'name' => $term->name, 'slug' => $term->slug]; 3486 }, $terms) 3487 : []; 3488 } 3489 3490 return $result; 3491 } 3492 3493 /** 3235 3494 * Builds the get-post / update-post error for a missing post or a post_type mismatch. 3236 3495 * -
agent-mod/trunk/includes/Services/AskUserAbilityService.php
r3770241 r3775519 25 25 26 26 use WP_Error; 27 use AgentMod\Common\Constants; 27 28 28 29 defined('ABSPATH') || exit; … … 76 77 * @since 1.3.1 77 78 */ 78 private const CATEGORY = 'agent-mod';79 private const CATEGORY = Constants::SYSTEM_ABILITY_CATEGORY; 79 80 80 81 /** … … 121 122 $this->settings = $settings; 122 123 124 add_action('wp_abilities_api_categories_init', [$this, 'registerCategories']); 123 125 add_action('wp_abilities_api_init', [$this, 'registerAbilities']); 126 } 127 128 /** 129 * Registers the system ability category. 130 * 131 * Shared with agent-mod/find-abilities (FindAbilitiesAbilityService). 132 * 133 * @return void 134 * @since 1.3.4 135 */ 136 public function registerCategories(): void 137 { 138 if (! function_exists('wp_register_ability_category')) { 139 return; 140 } 141 142 wp_register_ability_category( 143 Constants::SYSTEM_ABILITY_CATEGORY, 144 [ 145 'label' => __('AgentMod — System', 'agent-mod'), 146 'description' => __('Tools that are part of how the AgentMod agent works, such as asking the user questions, requesting approval and finding other abilities. AgentMod offers them automatically when they apply; they are never selected per agent.', 'agent-mod'), 147 ] 148 ); 124 149 } 125 150 -
agent-mod/trunk/includes/Services/SettingsService.php
r3770241 r3775519 260 260 261 261 /** 262 * Ability source of the settings-based (default) agent. 263 * 264 * Always 'all' since dynamic tool loading: the agent finds the abilities it 265 * needs itself, so the settings page no longer offers a selection. A saved 266 * 'selected' value from earlier versions is ignored on purpose. Agents that 267 * carry their own ability list (e.g. PRO agents) still pass it explicitly. 268 * 262 269 * @return string 263 270 * @since 1.1.0 … … 265 272 public function getAbilitySource(): string 266 273 { 267 $saved = trim((string) ($this->getSettings()['agent_mod_abilities']['ability_source'] ?? '')); 268 $value = '' !== $saved ? $saved : 'all'; 269 return (string) $value; 270 } 271 272 /** 274 return 'all'; 275 } 276 277 /** 278 * Allowed abilities of the settings-based (default) agent. 279 * 280 * Always empty: see getAbilitySource(). Returning the stale saved list would 281 * let a selection the settings page no longer shows leak into agents that 282 * fall back to it. 283 * 273 284 * @return string[] 274 285 * @since 1.1.0 … … 276 287 public function getAllowedAbilities(): array 277 288 { 278 $saved = $this->getSettings()['agent_mod_abilities']['allowed_abilities'] ?? []; 279 return is_array($saved) ? $saved : []; 289 return []; 290 } 291 292 /** 293 * Tool loading strategy: 'dynamic' or 'all'. 294 * 295 * @return string 296 * @since 1.3.4 297 */ 298 public function getToolLoading(): string 299 { 300 $saved = (string) ($this->getSettings()['agent_mod_abilities']['tool_loading'] ?? ''); 301 302 return in_array($saved, ['dynamic', 'all'], true) ? $saved : Constants::AI_TOOL_LOADING; 280 303 } 281 304 -
agent-mod/trunk/readme.txt
r3770241 r3775519 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1.3. 37 Stable tag: 1.3.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 123 123 124 124 == Changelog == 125 = 1.3.4 = 126 * Added: Dynamic tool loading, the new default. Instead of sending every ability with every request, the agent starts with a few tools and loads the abilities a task needs through the new `agent-mod/find-abilities` tool; what it finds stays available for the rest of the run, and tools used in the recent messages are loaded up front. On a site with about 100 abilities, the tool definitions sent with each request drop from about 23,000 to about 1,100 tokens, so even a "hello" costs a fraction of what it did. Write confirmation, auto-approval and the read-only rule of Ask and Plan modes work exactly as before. Agents with 15 or fewer abilities still get all of them up front. The new "Tool Loading" setting (Settings > Abilities) switches back to sending every ability. 127 * Changed: The "Ability Source" and "Allowed Abilities" settings are removed. The default agent can use every ability and finds the ones it needs; a selection saved in an earlier version is no longer applied. 128 * Changed: AgentMod's own chat tools (`ask-user`, `present-plan`, `request-approval`, `find-abilities`) moved to a separate "AgentMod — System" ability category. They are offered automatically where they apply and are no longer part of ability selections, where selecting or leaving them out never had an effect. 129 * Fixed: The MCP Adapter plugin's abilities (`discover-abilities`, `get-ability-info`, `execute-ability`) are no longer offered to the agent. `execute-ability` could run any other ability under its own name, so the confirmation dialog showed "execute-ability" instead of the actual action, and a session approval for it covered every ability. 130 * Added: `create-post` and `update-post` can assign taxonomy terms (categories, tags and custom taxonomies) with the new `terms` parameter, one item per taxonomy. Terms are given by ID, slug or name and replace the post's terms in that taxonomy; an empty list removes them, and `append: true` on `update-post` adds to the existing ones. Taxonomies left out keep their terms. Missing terms are never created on the fly: the call fails and points to `create-terms`. The taxonomy, the post type and the user's permission to assign terms are checked before anything is saved, and terms are set before a post is published so publish-time hooks see them. Previously the agent could create terms but had no way to attach them to a post. 131 * Improved: `get-post` returns the post's assigned terms, grouped by taxonomy, including the taxonomies that have none, so the agent can see what is assigned and which taxonomies it can use. 132 * Improved: Older tool results are compacted only when the conversation grows large. Before, everything except the last four tool results was cut to a short stub on every step, even in a short run, so the agent lost data it had just fetched (for example a post type's custom field configuration) and had to ask for it or fetch it again. Now nothing is compacted until the history reaches about 80,000 tokens; it is then reduced to about 40,000, and the six most recent results are always kept in full. 133 * Improved: Compaction keeps the already compacted part of the history unchanged until the next compaction, so the providers' prompt cache (OpenAI, Gemini) is reused across steps instead of being invalidated on every step. 134 * Improved: A compacted nested result, such as a field configuration (sections, fields, sub-fields), keeps its structure (names, types, labels, options) instead of only its first 200 characters. The same summary is passed on to the following messages. 135 * Improved: Native Custom Fields configuration reads (`get-post-meta-fields`, `get-term-meta-fields`, `get-user-meta-fields`, `get-options-page`) are never compacted, and are passed on to the following messages with a larger summary. 136 * Fixed: A read-only tool whose earlier result had been compacted could not be called again, because the repeat guard counted it as an identical call. The agent then had neither the data nor a way to get it back. Such a call now runs again. 137 * Fixed: With "Enable site context" on, the agent still called `get-site-info` and `get-environment-info` on every request to fetch values that were already in the site context. The site context now includes the PHP version and tells the agent to use these values directly, and the default system prompt no longer asks for a tool call when the answer is already in the site context or the conversation. 138 125 139 = 1.3.3 = 126 140 * Added: Bulk text replace in posts. `preview-bulk-replace` (read-only) shows, for up to 100 posts at once, how many matches each post has with a before / after snippet, without saving anything. `bulk-replace-content` then applies the same change on the server; changed posts get a revision when their post type supports revisions. Literal text or regular expressions, an optional limit per post (e.g. only the leading label), case-insensitive matching that handles the Turkish i / ı / İ / I, and by default only the visible text is changed, never block comments or attributes. A change that would alter the block structure skips that post. Previously a task such as "remove the 'Short bio:' label from these 12 posts" needed a get-post and an update-post per post, and ran into the full-content read limit and the tool-call limit.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)