From 59d2cc141bc73500a4a15bb7b60fe19a901f40e0 Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Mon, 27 Oct 2025 22:42:23 -0400 Subject: [PATCH 01/12] WIP: Agent and tools repository --- .gitignore | 4 + core/agents/post-taxonomy/instructions.txt | 7 ++ core/agents/post-taxonomy/post-taxonomy.php | 37 ++++++++++ core/tools/ensure-categories.php | 1 + installed/agents/.gitkeep | 0 installed/tools/.gitkeep | 0 lib/agent/class-abstract.php | 81 +++++++++++++++++++++ lib/agent/class-base.php | 5 ++ lib/llm/class-abstract.php | 67 ----------------- lib/providers/class-interface.php | 2 +- lib/providers/class-open-ai.php | 2 +- lib/services/class-agent-manager.php | 72 +++++++++++++++++- lib/system/class-agent-runner.php | 4 +- wp-agents.php | 26 ++++++- 14 files changed, 231 insertions(+), 77 deletions(-) create mode 100644 core/agents/post-taxonomy/instructions.txt create mode 100644 core/agents/post-taxonomy/post-taxonomy.php create mode 100644 core/tools/ensure-categories.php create mode 100644 installed/agents/.gitkeep create mode 100644 installed/tools/.gitkeep create mode 100644 lib/agent/class-abstract.php create mode 100644 lib/agent/class-base.php delete mode 100644 lib/llm/class-abstract.php diff --git a/.gitignore b/.gitignore index 3eb7bd6..a887e54 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ /logs/* !/logs/.gitkeep .DS_Store +/installed/agents/* +!/installed/agents/.gitkeep +/installed/tools/* +!/installed/tools/.gitkeep diff --git a/core/agents/post-taxonomy/instructions.txt b/core/agents/post-taxonomy/instructions.txt new file mode 100644 index 0000000..2453373 --- /dev/null +++ b/core/agents/post-taxonomy/instructions.txt @@ -0,0 +1,7 @@ +You are a WordPress editorial AI assistant +Analyze the post content and assign the most relevant category and tags +If the category and tags are already relevant enough, do not add extra +Try using existing categories and tags, only create new if needed. +You can call tools to create them if missing +Return only JSON in this exact format: +{"category": "Travel", "tags": ["Thailand", "Beaches", "Vacation"]}. diff --git a/core/agents/post-taxonomy/post-taxonomy.php b/core/agents/post-taxonomy/post-taxonomy.php new file mode 100644 index 0000000..224d530 --- /dev/null +++ b/core/agents/post-taxonomy/post-taxonomy.php @@ -0,0 +1,37 @@ +post_type ) { + $categories = wp_get_post_categories( $post_after->ID, array( 'fields' => 'names' ) ); + $tags = wp_get_post_tags( $post_after->ID, array( 'fields' => 'names' ) ); + + $category_list = empty( $categories ) ? 'none' : implode( ', ', $categories ); + $tag_list = empty( $tags ) ? 'none' : implode( ', ', $tags ); + + $input = "Post title: {$post_after->post_title}\n\n" + . "Post content: {$post_after->post_content}\n\n" + . "Existing category: {$category_list}\n" + . "Existing tags: {$tag_list}"; + + $response = wp_agents_get( 'post-taxonomy' ) + ->prompt( $input ) + ->chat(); + + $data = json_decode( $response->get_message(), true ); + + if ( ! empty( $data['category']['term_id'] ) ) { + wp_set_post_terms( $post_id, [ $data['category']['term_id'] ], 'category' ); + } + + if ( ! empty( $data['tags']['term_ids'] ) ) { + wp_set_post_terms( $post_id, $data['tags']['term_ids'], 'post_tag' ); + } + } +}, 10, 2 ); diff --git a/core/tools/ensure-categories.php b/core/tools/ensure-categories.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/core/tools/ensure-categories.php @@ -0,0 +1 @@ + 'gpt-4o-mini', + 'provider' => 'openai', + 'json' => false, + 'tools' => array(), + 'memory' => null, + 'memory_limit' => null, + 'instructions' => '', + 'name' => '', + 'version' => '', + 'description' => '', + 'title' => '', + 'hooks' => array(), + 'file' => null, + 'dir' => null + ); + + public function __construct( array $definition ) { + $this->definition = array_merge( + $this->definition, + $definition + ); + } + + public function instructions(): string { + return $this->definition['instructions']; + } + + public function prompt( mixed $input ): Wp_Agents_System_Agent_Runner { + return new Wp_Agents_System_Agent_Runner( + $input, + $this + ); + } + + public function name(): string { + return $this->definition['name']; + } + + public function get_model(): string { + return $this->definition['model']; + } + + public function get_provider(): string { + return $this->definition['provider']; + } + + public function json(): bool { + return $this->definition['json']; + } + + public function tools(): array { + return $this->definition['tools']; + } + + public function hooks(): array { + return $this->definition['hooks']; + } + + public function get_file(): ?string { + return $this->definition['file']; + } + + public function get_memory( string $session_id ): ?Wp_Agents_Memory_Abstract { + if ( $this->definition['memory'] ) { + $memory_class = $this->definition['memory']; + + return new $memory_class( $this->name(), $session_id ); + } + + return null; + } + + public function toArray(): array { + return $this->definition; + } +} diff --git a/lib/agent/class-base.php b/lib/agent/class-base.php new file mode 100644 index 0000000..0426661 --- /dev/null +++ b/lib/agent/class-base.php @@ -0,0 +1,5 @@ +name = $name; - } - - abstract public function instructions(): string; - - public function filters(): array { - return $this->filters; - } - - public function prompt( mixed $input ): Wp_Agents_System_Agent_Runner { - return new Wp_Agents_System_Agent_Runner( - $input, - $this - ); - } - - public function name(): string { - return $this->name; - } - - public function get_model(): string { - return $this->model; - } - - public function get_provider(): string { - return $this->provider; - } - - public function json(): string { - return $this->json; - } - - public function tools(): array { - return $this->tools; - } - - public function get_memory( string $session_id ): ?Wp_Agents_Memory_Abstract { - if ( $this->memory ) { - $memory_class = $this->memory; - - return new $memory_class( $this->name(), $session_id ); - } - - return null; - } -} diff --git a/lib/providers/class-interface.php b/lib/providers/class-interface.php index c7583be..3fb9e46 100644 --- a/lib/providers/class-interface.php +++ b/lib/providers/class-interface.php @@ -2,7 +2,7 @@ interface Wp_Agents_Providers_Interface { - public function chat( Wp_Agents_System_Message_Stack $message_stack, Wp_Agents_Llm_Abstract $agent ): void; + public function chat( Wp_Agents_System_Message_Stack $message_stack, Wp_Agents_Agent_Abstract $agent ): void; public function memorizable( Wp_Agents_System_Message $message ): bool; } diff --git a/lib/providers/class-open-ai.php b/lib/providers/class-open-ai.php index afdb1c7..d171221 100644 --- a/lib/providers/class-open-ai.php +++ b/lib/providers/class-open-ai.php @@ -8,7 +8,7 @@ public function __construct( string $api_key ) { $this->api_key = $api_key; } - public function chat( Wp_Agents_System_Message_Stack $message_stack, Wp_Agents_Llm_Abstract $agent ): void { + public function chat( Wp_Agents_System_Message_Stack $message_stack, Wp_Agents_Agent_Abstract $agent ): void { $tools = $agent->tools(); $message_stack->unshift( new Wp_Agents_System_Message( 'developer', $agent->instructions() ) ); diff --git a/lib/services/class-agent-manager.php b/lib/services/class-agent-manager.php index daa782a..78e0d2f 100644 --- a/lib/services/class-agent-manager.php +++ b/lib/services/class-agent-manager.php @@ -4,11 +4,11 @@ class Wp_Agents_Services_Agent_Manager { protected static array $agents = array(); - public static function register( string $name, string $agent_class ): void { - self::$agents[ $name ] = new $agent_class( $name ); + public static function register( array $definition ): void { + self::$agents[ $definition['name'] ] = new Wp_Agents_Agent_Base( $definition ); } - public static function get( string $name ): Wp_Agents_Llm_Abstract|WP_Error { + public static function get( string $name ): Wp_Agents_Agent_Abstract|WP_Error { if ( ! isset( self::$agents[ $name ] ) ) { return new WP_Error( 'wp_agents_agent_not_found', @@ -19,7 +19,73 @@ public static function get( string $name ): Wp_Agents_Llm_Abstract|WP_Error { return self::$agents[ $name ]; } + + public static function all(): array { + return static::$agents; + } + public static function boot(): void { + $agents_directories = array( + WP_PLUGIN_DIR . '/wp-agents/core/agents', + WP_PLUGIN_DIR . '/wp-agents/installed/agents', + ); + + foreach ( $agents_directories as $directory ) { + foreach ( glob( $directory . '/*/*.php' ) as $file ) { + $agent_name = basename( dirname( $file ) ); + if ( basename( $file, '.php' ) !== $agent_name ) { + continue; + } + + $definition = self::read_definition( $file ); + if ( empty( $definition['title'] ) ) { + continue; + } + + $instructions = dirname( $file ) . '/instructions.txt'; + if ( ! file_exists( $instructions ) || ! is_readable( $instructions ) ) { + continue; + } + + $definition['instructions'] = trim( file_get_contents( $instructions ) ); + $definition['name'] = $agent_name; + $definition['file'] = $file; + $definition['directory'] = dirname( $file ); + + wp_agents_register( $definition ); + } + } + do_action( 'wp_agents_register_agents' ); } + + protected static function read_definition( string $file ): array { + $headers = [ + 'Agent Name' => 'Agent Name', + 'Description' => 'Description', + 'Version' => 'Version', + 'Tools' => 'Tools', + 'Hooks' => 'Hooks', + ]; + + $data = get_file_data( $file, $headers, 'agent' ); + + foreach ( array( 'Tools', 'Hooks' ) as $list_key ) { + if ( ! empty( $data[ $list_key ] ) ) { + $data[ $list_key ] = array_filter( array_map( + 'trim', explode( ',', $data[ $list_key ] ) + ) ); + } else { + $data[ $list_key ] = array(); + } + } + + return array( + 'title' => $data['Agent Name'], + 'version' => $data['Version'], + 'description' => $data['Description'], + 'tools' => $data['Tools'], + 'hooks' => $data['Hooks'], + ); + } } diff --git a/lib/system/class-agent-runner.php b/lib/system/class-agent-runner.php index d6166bb..f27b889 100644 --- a/lib/system/class-agent-runner.php +++ b/lib/system/class-agent-runner.php @@ -6,11 +6,11 @@ class Wp_Agents_System_Agent_Runner { protected Wp_Agents_Providers_Interface $provider; - protected Wp_Agents_Llm_Abstract $agent; + protected Wp_Agents_Agent_Abstract $agent; protected ?string $session_id = null; - public function __construct( string $input, Wp_Agents_Llm_Abstract $agent ) { + public function __construct( string $input, Wp_Agents_Agent_Abstract $agent ) { $this->input = $input; $this->agent = $agent; $this->provider = Wp_Agents_Services_Provider_Manager::get( $agent->get_provider() ); diff --git a/wp-agents.php b/wp-agents.php index 0c34038..4e709b5 100644 --- a/wp-agents.php +++ b/wp-agents.php @@ -28,8 +28,8 @@ function wp_agents_install(): void { if ( ! function_exists( 'wp_agents_register' ) ) { - function wp_agents_register( string $name, string $agent_class ): void { - Wp_Agents_Services_Agent_Manager::register( $name, $agent_class ); + function wp_agents_register( array $definition ): void { + Wp_Agents_Services_Agent_Manager::register( $definition ); } } @@ -46,12 +46,20 @@ function wp_agents_register_provider( string $name, callable $callback ): void { if ( ! function_exists( 'wp_agents_get' ) ) { - function wp_agents_get( string $name ): Wp_Agents_Llm_Abstract|WP_Error { + function wp_agents_get( string $name ): Wp_Agents_Agent_Abstract|WP_Error { return Wp_Agents_Services_Agent_Manager::get( $name ); } } +if ( ! function_exists( 'wp_agents_all' ) ) { + + function wp_agents_all(): array { + return Wp_Agents_Services_Agent_Manager::all(); + } + +} + add_action( 'plugins_loaded', function () { @@ -60,3 +68,15 @@ function () { add_action( 'rest_api_init', array( Wp_Agents_System_Rest::class, 'register' ) ); } ); + +add_action( + 'plugins_loaded', + function () { + foreach ( wp_agents_all() as $agent ) { + if ( true && $file = $agent->get_file() ) { + require_once $file; + } + } + }, + 20 +); From ed2ab74d9d7c25370d34194209407e0e79d8b975 Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Sun, 2 Nov 2025 16:19:39 -0500 Subject: [PATCH 02/12] Add new Roadmap item MCP Server --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ebefdb1..423516c 100644 --- a/README.md +++ b/README.md @@ -72,4 +72,5 @@ https://github.com/santerref/wp-agents-demo - **Workflows:** Introduce workflows to connect and orchestrate multiple agents for complex, multi-step tasks. - **Tests:** Expand and complete Pest test coverage for agents, tools, and provider logic. - **Providers:** Add more LLM providers and improve compatibility with third-party APIs. -- **RAG:** Integrate Retrieval-Augmented Generation to allow agents to query custom datasets, documents, or WordPress content for more context-aware responses. +- **RAG:** Integrate Retrieval-Augmented Generation to allow agents to query custom datasets, documents, or WordPress content for more context-aware responses. +- **MCP Server:** Ship a local Message-Control-Protocol server to expose WordPress as a tool provider and allow external AI clients to execute WordPress actions and workflows securely. From f3eccc82c908fa0072a5c2a4e05a239f478c2264 Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Sun, 2 Nov 2025 18:29:10 -0500 Subject: [PATCH 03/12] Add base setup for TailwindCSS and VueJS for admin page --- .gitignore | 31 +- README.md | 77 +- inc/admin.php | 20 + inc/assets.php | 77 + lib/agent/class-abstract.php | 4 +- lib/services/class-agent-manager.php | 13 +- package-lock.json | 2044 ++++++++++++++++++++++++++ package.json | 24 + src/App.vue | 7 + src/assets/vue.svg | 1 + src/components/HelloWorld.vue | 6 + src/main.ts | 5 + src/style.css | 1 + tsconfig.app.json | 16 + tsconfig.json | 7 + tsconfig.node.json | 26 + vite.config.ts | 55 + wp-agents.php | 16 +- 18 files changed, 2340 insertions(+), 90 deletions(-) create mode 100644 inc/admin.php create mode 100644 inc/assets.php create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.vue create mode 100644 src/assets/vue.svg create mode 100644 src/components/HelloWorld.vue create mode 100644 src/main.ts create mode 100644 src/style.css create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index a887e54..38c5357 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,25 @@ -/vendor -/logs/* -!/logs/.gitkeep +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea .DS_Store -/installed/agents/* -!/installed/agents/.gitkeep -/installed/tools/* -!/installed/tools/.gitkeep +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +/vendor diff --git a/README.md b/README.md index 423516c..33895ab 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,5 @@ -# WP Agents – AI Agent Framework for WordPress +# Vue 3 + TypeScript + Vite -Build autonomous, hook-driven agents for WordPress — automate tasks and add LLM intelligence with clean, developer-first architecture. +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue new file mode 100644 index 0000000..a427957 --- /dev/null +++ b/src/components/HelloWorld.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..b12f490 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#wp-agents-app') diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..f1d8c73 --- /dev/null +++ b/src/style.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..8d16e42 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..4030cf6 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,55 @@ +import {defineConfig} from 'vite' +import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' +import fs from 'fs' + +let exitHandlersBound = false +const hotFile = './.hot' + +export default defineConfig({ + server: { + host: true, + cors: { + origin: '*', + }, + }, + build: { + manifest: true, + modulePreload: { + polyfill: false + }, + rollupOptions: { + input: './src/main.ts' + } + }, + plugins: [ + vue(), + tailwindcss(), + { + name: 'wp-agents-wp-hot', + configureServer(server) { + server.httpServer?.once('listening', () => { + fs.writeFileSync(hotFile, 'http://localhost:5173') + }) + server.httpServer?.once('close', () => { + if (fs.existsSync(hotFile)) fs.unlinkSync(hotFile) + }) + + if (!exitHandlersBound) { + const clean = () => { + if (fs.existsSync(hotFile)) { + fs.rmSync(hotFile) + } + } + + process.on('exit', clean) + process.on('SIGINT', () => process.exit()) + process.on('SIGTERM', () => process.exit()) + process.on('SIGHUP', () => process.exit()) + + exitHandlersBound = true + } + } + } + ], +}) diff --git a/wp-agents.php b/wp-agents.php index 4e709b5..9b88001 100644 --- a/wp-agents.php +++ b/wp-agents.php @@ -3,7 +3,7 @@ * Plugin Name: WP Agents * Plugin URI: https://santerref.com/ * Description: Build autonomous, hook-driven agents for WordPress — automate tasks and add LLM intelligence with clean, developer-first architecture. - * Version: 0.3.0 + * Version: 0.3.1 * Requires at least: 6.8 * Requires PHP: 8.4 * Author: Francis Santerre @@ -13,7 +13,17 @@ * Domain Path: /languages */ +if ( ! defined( 'WP_AGENTS_PLUGIN_DIR' ) ) { + define( 'WP_AGENTS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); +} + +if ( ! defined( 'WP_AGENTS_PLUGIN_URL' ) ) { + define( 'WP_AGENTS_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); +} + require_once 'autoload.php'; +require_once 'inc/assets.php'; +require_once 'inc/admin.php'; if ( ! function_exists( 'wp_agents_install' ) ) { @@ -73,7 +83,9 @@ function () { 'plugins_loaded', function () { foreach ( wp_agents_all() as $agent ) { - if ( true && $file = $agent->get_file() ) { + $file = $agent->get_file(); + + if ( $file ) { require_once $file; } } From 482b1d95f05663852ba35f60e7c548819fc9f0e1 Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Fri, 7 Nov 2025 18:21:20 -0500 Subject: [PATCH 04/12] Add basic agent listing in admin --- .gitignore | 1 + lib/agent/class-abstract.php | 21 +++++-- lib/services/class-agent-manager.php | 19 ++++--- lib/system/class-rest.php | 24 +++++++- package-lock.json | 24 +++++++- package.json | 3 +- src/App.vue | 16 +++++- src/components/AppLink.vue | 39 +++++++++++++ src/components/Layout.vue | 11 ++++ src/components/Nav.vue | 32 +++++++++++ src/main.ts | 7 ++- src/pages/Agents.vue | 57 +++++++++++++++++++ .../HelloWorld.vue => pages/Tools.vue} | 3 +- src/router.ts | 14 +++++ src/style.css | 12 +++- wp-agents.php | 4 +- 16 files changed, 260 insertions(+), 27 deletions(-) create mode 100644 src/components/AppLink.vue create mode 100644 src/components/Layout.vue create mode 100644 src/components/Nav.vue create mode 100644 src/pages/Agents.vue rename src/{components/HelloWorld.vue => pages/Tools.vue} (78%) create mode 100644 src/router.ts diff --git a/.gitignore b/.gitignore index 38c5357..5a2981e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ dist-ssr *.sln *.sw? /vendor +/.hot diff --git a/lib/agent/class-abstract.php b/lib/agent/class-abstract.php index a504956..c20867d 100644 --- a/lib/agent/class-abstract.php +++ b/lib/agent/class-abstract.php @@ -10,10 +10,10 @@ abstract class Wp_Agents_Agent_Abstract { 'memory' => null, 'memory_limit' => null, 'instructions' => '', - 'name' => '', + 'id' => '', 'version' => '', 'description' => '', - 'title' => '', + 'name' => '', 'hooks' => array(), 'file' => null, 'dir' => null, @@ -37,8 +37,8 @@ public function prompt( mixed $input ): Wp_Agents_System_Agent_Runner { ); } - public function name(): string { - return $this->definition['name']; + public function id(): string { + return $this->definition['id']; } public function get_model(): string { @@ -69,13 +69,22 @@ public function get_memory( string $session_id ): ?Wp_Agents_Memory_Abstract { if ( $this->definition['memory'] ) { $memory_class = $this->definition['memory']; - return new $memory_class( $this->name(), $session_id ); + return new $memory_class( $this->id(), $session_id ); } return null; } public function to_array(): array { - return $this->definition; + return array( + 'id' => $this->definition['id'], + 'model' => $this->definition['model'], + 'provider' => $this->definition['provider'], + 'name' => $this->definition['name'], + 'description' => $this->definition['description'], + 'hooks' => $this->definition['hooks'], + 'tools' => $this->definition['tools'], + 'version' => $this->definition['version'], + ); } } diff --git a/lib/services/class-agent-manager.php b/lib/services/class-agent-manager.php index 0476c8a..500c6a3 100644 --- a/lib/services/class-agent-manager.php +++ b/lib/services/class-agent-manager.php @@ -5,18 +5,18 @@ class Wp_Agents_Services_Agent_Manager { protected static array $agents = array(); public static function register( array $definition ): void { - self::$agents[ $definition['name'] ] = new Wp_Agents_Agent_Base( $definition ); + self::$agents[ $definition['id'] ] = new Wp_Agents_Agent_Base( $definition ); } - public static function get( string $name ): Wp_Agents_Agent_Abstract|WP_Error { - if ( ! isset( self::$agents[ $name ] ) ) { + public static function get( string $id ): Wp_Agents_Agent_Abstract|WP_Error { + if ( ! isset( self::$agents[ $id ] ) ) { return new WP_Error( 'wp_agents_agent_not_found', - "The agent with the name {$name} was not found." + "The agent with the name {$id} was not found." ); } - return self::$agents[ $name ]; + return self::$agents[ $id ]; } @@ -38,7 +38,7 @@ public static function boot(): void { } $definition = self::read_definition( $file ); - if ( empty( $definition['title'] ) ) { + if ( empty( $definition['name'] ) ) { continue; } @@ -46,9 +46,10 @@ public static function boot(): void { if ( ! file_exists( $instructions ) || ! is_readable( $instructions ) ) { continue; } + $instructions_content = file( $instructions, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); - $definition['instructions'] = trim( file_get_contents( $instructions ) ); - $definition['name'] = $agent_name; + $definition['instructions'] = trim( $instructions_content ? $instructions_content : '' ); + $definition['id'] = $agent_name; $definition['file'] = $file; $definition['directory'] = dirname( $file ); @@ -84,7 +85,7 @@ protected static function read_definition( string $file ): array { } return array( - 'title' => $data['Agent Name'], + 'name' => $data['Agent Name'], 'version' => $data['Version'], 'description' => $data['Description'], 'tools' => $data['Tools'], diff --git a/lib/system/class-rest.php b/lib/system/class-rest.php index 9866264..521e52a 100644 --- a/lib/system/class-rest.php +++ b/lib/system/class-rest.php @@ -8,13 +8,23 @@ public static function register(): void { '/chat', array( 'methods' => 'POST', - 'callback' => array( self::class, 'handle' ), + 'callback' => array( self::class, 'chat' ), + 'permission_callback' => '__return_true', + ) + ); + + register_rest_route( + 'wp-agents/v1', + '/agents', + array( + 'methods' => 'GET', + 'callback' => array( self::class, 'agents' ), 'permission_callback' => '__return_true', ) ); } - public static function handle( \WP_REST_Request $request ) { + public static function chat( \WP_REST_Request $request ) { $agent = Wp_Agents_Services_Agent_Manager::get( $request->get_param( 'agent' ) ); $message = $request->get_param( 'message' ); @@ -31,4 +41,14 @@ public static function handle( \WP_REST_Request $request ) { ) ); } + + public static function agents() { + $agents = array(); + + foreach ( wp_agents_all() as $agent ) { + $agents[] = $agent->to_array(); + } + + return rest_ensure_response( $agents ); + } } diff --git a/package-lock.json b/package-lock.json index 3b14929..79d247b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.16", "tailwindcss": "^4.1.16", - "vue": "^3.5.22" + "vue": "^3.5.22", + "vue-router": "^4.6.3" }, "devDependencies": { "@types/node": "^24.6.0", @@ -1239,6 +1240,12 @@ "@vue/shared": "3.5.22" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, "node_modules/@vue/language-core": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.2.tgz", @@ -2023,6 +2030,21 @@ } } }, + "node_modules/vue-router": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.3.tgz", + "integrity": "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/vue-tsc": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.2.tgz", diff --git a/package.json b/package.json index 3b113b3..0c0e985 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.16", "tailwindcss": "^4.1.16", - "vue": "^3.5.22" + "vue": "^3.5.22", + "vue-router": "^4.6.3" }, "devDependencies": { "@types/node": "^24.6.0", diff --git a/src/App.vue b/src/App.vue index c7590ff..ae5f1db 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,19 @@ diff --git a/src/components/AppLink.vue b/src/components/AppLink.vue new file mode 100644 index 0000000..b73e1ec --- /dev/null +++ b/src/components/AppLink.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/Layout.vue b/src/components/Layout.vue new file mode 100644 index 0000000..d7623d7 --- /dev/null +++ b/src/components/Layout.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/Nav.vue b/src/components/Nav.vue new file mode 100644 index 0000000..229fcbe --- /dev/null +++ b/src/components/Nav.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/main.ts b/src/main.ts index b12f490..ea025a0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,8 @@ -import { createApp } from 'vue' +import {createApp} from 'vue' import './style.css' import App from './App.vue' +import {router} from "./router.ts"; -createApp(App).mount('#wp-agents-app') +createApp(App) + .use(router) + .mount('#wp-agents-app') diff --git a/src/pages/Agents.vue b/src/pages/Agents.vue new file mode 100644 index 0000000..8352a49 --- /dev/null +++ b/src/pages/Agents.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/components/HelloWorld.vue b/src/pages/Tools.vue similarity index 78% rename from src/components/HelloWorld.vue rename to src/pages/Tools.vue index a427957..1d0ec53 100644 --- a/src/components/HelloWorld.vue +++ b/src/pages/Tools.vue @@ -1,6 +1,7 @@ diff --git a/src/router.ts b/src/router.ts new file mode 100644 index 0000000..8fd7124 --- /dev/null +++ b/src/router.ts @@ -0,0 +1,14 @@ +import { createMemoryHistory, createRouter } from 'vue-router' + +import AgentsPage from './pages/Agents.vue' +import ToolsPage from './pages/Tools.vue' + +const routes = [ + { path: '/', component: AgentsPage }, + { path: '/tools', component: ToolsPage }, +] + +export const router = createRouter({ + history: createMemoryHistory(), + routes, +}) diff --git a/src/style.css b/src/style.css index f1d8c73..e18830a 100644 --- a/src/style.css +++ b/src/style.css @@ -1 +1,11 @@ -@import "tailwindcss"; +@layer theme, base, components, utilities; +@import "tailwindcss/theme.css" layer(theme) prefix(tw) important; +@import "tailwindcss/utilities.css" layer(utilities) prefix(tw) important; + +@theme { + --color-blue-wp: #2271b1; +} + +.toplevel_page_wp-agents #wpcontent { + padding-left: 0; +} diff --git a/wp-agents.php b/wp-agents.php index 9b88001..2534167 100644 --- a/wp-agents.php +++ b/wp-agents.php @@ -46,8 +46,8 @@ function wp_agents_register( array $definition ): void { if ( ! function_exists( 'wp_agents_register_provider' ) ) { - function wp_agents_register_provider( string $name, callable $callback ): void { - Wp_Agents_Services_Provider_Manager::register( $name, $callback ); + function wp_agents_register_provider( string $id, callable $callback ): void { + Wp_Agents_Services_Provider_Manager::register( $id, $callback ); } require_once __DIR__ . '/inc/providers.php'; From 8d85e7c49f781c9960d8213e09f56af19f64b114 Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Fri, 7 Nov 2025 18:28:36 -0500 Subject: [PATCH 05/12] Add settings page and transition in router --- lib/services/class-agent-manager.php | 2 +- src/App.vue | 20 +++++++++++++++++++- src/components/Nav.vue | 4 ++++ src/pages/Settings.vue | 7 +++++++ src/router.ts | 2 ++ 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/pages/Settings.vue diff --git a/lib/services/class-agent-manager.php b/lib/services/class-agent-manager.php index 500c6a3..7960831 100644 --- a/lib/services/class-agent-manager.php +++ b/lib/services/class-agent-manager.php @@ -48,7 +48,7 @@ public static function boot(): void { } $instructions_content = file( $instructions, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ); - $definition['instructions'] = trim( $instructions_content ? $instructions_content : '' ); + $definition['instructions'] = trim( $instructions_content ? implode( "\n", $instructions_content ) : '' ); $definition['id'] = $agent_name; $definition['file'] = $file; $definition['directory'] = dirname( $file ); diff --git a/src/App.vue b/src/App.vue index ae5f1db..eb324e2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,7 +7,13 @@