diff --git a/.github/workflows/homeboy.yml b/.github/workflows/homeboy.yml index c7c6be8f..295c28f2 100644 --- a/.github/workflows/homeboy.yml +++ b/.github/workflows/homeboy.yml @@ -15,8 +15,20 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: mbstring, intl, pdo_sqlite, mysqli + tools: composer:v2 + coverage: none + + - name: Install project dependencies + run: composer install --no-interaction --prefer-dist + - uses: Extra-Chill/homeboy-action@v1 with: + version: '0.53.0' extension: wordpress commands: lint,test,audit component: data-machine diff --git a/composer.json b/composer.json index e4bb1a51..eba45406 100644 --- a/composer.json +++ b/composer.json @@ -3,12 +3,12 @@ "description": "WordPress plugin for automated data collection, AI processing, and multi-platform publishing", "version": "0.10.2", "license": "GPL-2.0-or-later", - "require": { - "php": ">=8.2", - "monolog/monolog": "^3.9", - "woocommerce/action-scheduler": "^3.9", - "chubes4/ai-http-client": "^2.0.13" - }, + "require": { + "php": ">=8.2", + "monolog/monolog": "^3.9", + "woocommerce/action-scheduler": "^3.9", + "chubes4/ai-http-client": "^2.0.13" + }, "require-dev": { "php-stubs/wordpress-stubs": "^6.9", "wp-coding-standards/wpcs": "^3.1", diff --git a/inc/Abilities/Engine/RunFlowAbility.php b/inc/Abilities/Engine/RunFlowAbility.php index beff7906..82a310cd 100644 --- a/inc/Abilities/Engine/RunFlowAbility.php +++ b/inc/Abilities/Engine/RunFlowAbility.php @@ -120,6 +120,7 @@ public function execute( array $input ): array { 'flow_id' => $flow_id, 'source' => 'pipeline', 'label' => $flow['flow_name'] ?? null, + 'user_id' => (int) ( $flow['user_id'] ?? 0 ), ) ); if ( ! $job_id ) { @@ -167,6 +168,7 @@ public function execute( array $input ): array { 'job_id' => $job_id, 'flow_id' => $flow_id, 'pipeline_id' => $pipeline_id, + 'user_id' => (int) ( $flow['user_id'] ?? 0 ), 'created_at' => current_time( 'mysql', true ), ), 'flow' => array( diff --git a/inc/Abilities/Flow/CreateFlowAbility.php b/inc/Abilities/Flow/CreateFlowAbility.php index 18b6495f..fa571856 100644 --- a/inc/Abilities/Flow/CreateFlowAbility.php +++ b/inc/Abilities/Flow/CreateFlowAbility.php @@ -84,18 +84,18 @@ private function registerAbility(): void { 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ), - 'flow_id' => array( 'type' => 'integer' ), - 'flow_name' => array( 'type' => 'string' ), - 'pipeline_id' => array( 'type' => 'integer' ), - 'synced_steps' => array( 'type' => 'integer' ), - 'flow_data' => array( 'type' => 'object' ), - 'created_count' => array( 'type' => 'integer' ), - 'failed_count' => array( 'type' => 'integer' ), - 'created' => array( 'type' => 'array' ), - 'errors' => array( 'type' => 'array' ), - 'partial' => array( 'type' => 'boolean' ), - 'message' => array( 'type' => 'string' ), - 'error' => array( 'type' => 'string' ), + 'flow_id' => array( 'type' => array( 'integer', 'null' ) ), + 'flow_name' => array( 'type' => array( 'string', 'null' ) ), + 'pipeline_id' => array( 'type' => array( 'integer', 'null' ) ), + 'synced_steps' => array( 'type' => array( 'integer', 'null' ) ), + 'flow_data' => array( 'type' => array( 'object', 'null' ) ), + 'created_count' => array( 'type' => array( 'integer', 'null' ) ), + 'failed_count' => array( 'type' => array( 'integer', 'null' ) ), + 'created' => array( 'type' => array( 'array', 'null' ) ), + 'errors' => array( 'type' => array( 'array', 'null' ) ), + 'partial' => array( 'type' => array( 'boolean', 'null' ) ), + 'message' => array( 'type' => array( 'string', 'null' ) ), + 'error' => array( 'type' => array( 'string', 'null' ) ), ), ), 'execute_callback' => array( $this, 'execute' ), diff --git a/inc/Abilities/Pipeline/CreatePipelineAbility.php b/inc/Abilities/Pipeline/CreatePipelineAbility.php index 41746fb0..c0759254 100644 --- a/inc/Abilities/Pipeline/CreatePipelineAbility.php +++ b/inc/Abilities/Pipeline/CreatePipelineAbility.php @@ -69,20 +69,20 @@ private function registerAbility(): void { 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ), - 'pipeline_id' => array( 'type' => 'integer' ), - 'pipeline_name' => array( 'type' => 'string' ), - 'flow_id' => array( 'type' => 'integer' ), - 'flow_name' => array( 'type' => 'string' ), - 'steps_created' => array( 'type' => 'integer' ), - 'flow_step_ids' => array( 'type' => 'array' ), - 'creation_mode' => array( 'type' => 'string' ), - 'created_count' => array( 'type' => 'integer' ), - 'failed_count' => array( 'type' => 'integer' ), - 'created' => array( 'type' => 'array' ), - 'errors' => array( 'type' => 'array' ), - 'partial' => array( 'type' => 'boolean' ), - 'message' => array( 'type' => 'string' ), - 'error' => array( 'type' => 'string' ), + 'pipeline_id' => array( 'type' => array( 'integer', 'null' ) ), + 'pipeline_name' => array( 'type' => array( 'string', 'null' ) ), + 'flow_id' => array( 'type' => array( 'integer', 'null' ) ), + 'flow_name' => array( 'type' => array( 'string', 'null' ) ), + 'steps_created' => array( 'type' => array( 'integer', 'null' ) ), + 'flow_step_ids' => array( 'type' => array( 'array', 'null' ) ), + 'creation_mode' => array( 'type' => array( 'string', 'null' ) ), + 'created_count' => array( 'type' => array( 'integer', 'null' ) ), + 'failed_count' => array( 'type' => array( 'integer', 'null' ) ), + 'created' => array( 'type' => array( 'array', 'null' ) ), + 'errors' => array( 'type' => array( 'array', 'null' ) ), + 'partial' => array( 'type' => array( 'boolean', 'null' ) ), + 'message' => array( 'type' => array( 'string', 'null' ) ), + 'error' => array( 'type' => array( 'string', 'null' ) ), ), ), 'execute_callback' => array( $this, 'execute' ), diff --git a/inc/Api/Chat/ChatOrchestrator.php b/inc/Api/Chat/ChatOrchestrator.php index b4f8fad7..18810d25 100644 --- a/inc/Api/Chat/ChatOrchestrator.php +++ b/inc/Api/Chat/ChatOrchestrator.php @@ -158,6 +158,7 @@ public static function processChat( 'max_turns' => $max_turns, 'selected_pipeline_id' => $selected_pipeline_id ? $selected_pipeline_id : null, 'agent_type' => AgentType::CHAT, + 'user_id' => $user_id, ) ); @@ -289,6 +290,7 @@ public static function processContinue( string $session_id, int $user_id ): arra 'max_turns' => $max_turns, 'selected_pipeline_id' => $selected_pipeline_id, 'agent_type' => AgentType::CHAT, + 'user_id' => (int) ( $session['user_id'] ?? 0 ), ) ); @@ -389,7 +391,10 @@ public static function processPing( string $message, string $provider, string $m $messages, $provider, $model, - array( 'agent_type' => AgentType::CHAT ) + array( + 'agent_type' => AgentType::CHAT, + 'user_id' => $user_id, + ) ); if ( is_wp_error( $result ) ) { @@ -544,7 +549,11 @@ public static function executeConversationTurn( $tool_manager = new ToolManager(); $all_tools = $tool_manager->getAvailableToolsForChat(); - $loop_context = array( 'session_id' => $session_id ); + $user_id = $options['user_id'] ?? 0; + $loop_context = array( + 'session_id' => $session_id, + 'user_id' => $user_id, + ); if ( $selected_pipeline_id ) { $loop_context['selected_pipeline_id'] = $selected_pipeline_id; } diff --git a/inc/Core/Steps/AI/AIStep.php b/inc/Core/Steps/AI/AIStep.php index 720b3a4d..7317f18f 100644 --- a/inc/Core/Steps/AI/AIStep.php +++ b/inc/Core/Steps/AI/AIStep.php @@ -176,12 +176,17 @@ protected function executeStep(): array { $max_turns = PluginSettings::get( 'max_turns', 12 ); + // Resolve user_id from engine snapshot (set by RunFlowAbility). + $job_snapshot = $this->engine->get( 'job' ); + $user_id = (int) ( $job_snapshot['user_id'] ?? 0 ); + $payload = array( 'job_id' => $this->job_id, 'flow_step_id' => $this->flow_step_id, 'step_id' => $pipeline_step_id, 'data' => $this->dataPackets, 'engine' => $this->engine, + 'user_id' => $user_id, ); $navigator = new \DataMachine\Engine\StepNavigator(); diff --git a/inc/Core/Steps/AI/Directives/FlowMemoryFilesDirective.php b/inc/Core/Steps/AI/Directives/FlowMemoryFilesDirective.php index 3aed9c16..0c76422a 100644 --- a/inc/Core/Steps/AI/Directives/FlowMemoryFilesDirective.php +++ b/inc/Core/Steps/AI/Directives/FlowMemoryFilesDirective.php @@ -54,7 +54,9 @@ public static function get_outputs( string $provider_name, array $tools, ?string $db_flows = new Flows(); $memory_files = $db_flows->get_flow_memory_files( $flow_id ); - return MemoryFilesReader::read( $memory_files, 'Flow', $flow_id ); + $user_id = (int) ( $payload['user_id'] ?? 0 ); + + return MemoryFilesReader::read( $memory_files, 'Flow', $flow_id, $user_id ); } } diff --git a/inc/Core/Steps/AI/Directives/PipelineMemoryFilesDirective.php b/inc/Core/Steps/AI/Directives/PipelineMemoryFilesDirective.php index 63878979..ac7d3d83 100644 --- a/inc/Core/Steps/AI/Directives/PipelineMemoryFilesDirective.php +++ b/inc/Core/Steps/AI/Directives/PipelineMemoryFilesDirective.php @@ -50,8 +50,9 @@ public static function get_outputs( string $provider_name, array $tools, ?string } $memory_files = $db_pipelines->get_pipeline_memory_files( (int) $pipeline_id ); + $user_id = (int) ( $payload['user_id'] ?? 0 ); - return MemoryFilesReader::read( $memory_files, 'Pipeline', (int) $pipeline_id ); + return MemoryFilesReader::read( $memory_files, 'Pipeline', (int) $pipeline_id, $user_id ); } } diff --git a/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php b/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php index 7e9fd15e..4f894b20 100644 --- a/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php +++ b/inc/Engine/AI/Directives/CoreMemoryFilesDirective.php @@ -49,8 +49,8 @@ public static function get_outputs( string $provider_name, array $tools, ?string DirectoryManager::ensure_agent_files(); $directory_manager = new DirectoryManager(); - // TODO: Multi-agent Phase 2 — resolve user_id from execution context (#565). - $agent_dir = $directory_manager->get_agent_directory(); + $user_id = (int) ( $payload['user_id'] ?? 0 ); + $agent_dir = $directory_manager->get_agent_directory( $user_id ); $outputs = array(); foreach ( $filenames as $filename ) { diff --git a/inc/Engine/AI/Directives/MemoryFilesReader.php b/inc/Engine/AI/Directives/MemoryFilesReader.php index 665aab7d..4e7243cc 100644 --- a/inc/Engine/AI/Directives/MemoryFilesReader.php +++ b/inc/Engine/AI/Directives/MemoryFilesReader.php @@ -23,16 +23,19 @@ class MemoryFilesReader { * @param array $memory_files Array of memory filenames. * @param string $scope_label Label for logging (e.g. 'Pipeline', 'Flow'). * @param int $scope_id Entity ID for logging (e.g. pipeline_id, flow_id). + * @param int $user_id WordPress user ID. 0 = legacy shared directory. * @return array Array of directive outputs (type => system_text, content => ...). */ - public static function read( array $memory_files, string $scope_label, int $scope_id ): array { + /** + * @since 0.37.0 Added $user_id parameter for multi-agent partitioning. + */ + public static function read( array $memory_files, string $scope_label, int $scope_id, int $user_id = 0 ): array { if ( empty( $memory_files ) ) { return array(); } $directory_manager = new DirectoryManager(); - // TODO: Multi-agent Phase 2 — resolve user_id from execution context (#565). - $agent_dir = $directory_manager->get_agent_directory(); + $agent_dir = $directory_manager->get_agent_directory( $user_id ); $outputs = array(); foreach ( $memory_files as $filename ) {