From d2a555193ad67b13bbd167212a8a0f25c18cbb32 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Tue, 5 May 2026 13:34:25 -0400 Subject: [PATCH] Fix pipeline transcript lookup --- inc/Cli/Commands/JobsCommand.php | 27 ++++++++++ inc/Engine/AI/ConversationManager.php | 2 +- ...DataMachinePipelineTranscriptPersister.php | 5 ++ .../duplicate-tool-call-mode-aware-smoke.php | 14 +++-- ...pipeline-transcript-session-link-smoke.php | 51 +++++++++++++++++++ 5 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 tests/pipeline-transcript-session-link-smoke.php diff --git a/inc/Cli/Commands/JobsCommand.php b/inc/Cli/Commands/JobsCommand.php index e2aeab6b..5ab73b99 100644 --- a/inc/Cli/Commands/JobsCommand.php +++ b/inc/Cli/Commands/JobsCommand.php @@ -465,6 +465,9 @@ public function transcript( array $args, array $assoc_args ): void { $job = $jobs[0]; $engine_data = $job['engine_data'] ?? array(); $transcript_session_id = $engine_data['transcript_session_id'] ?? ''; + if ( empty( $transcript_session_id ) ) { + $transcript_session_id = $this->findTranscriptSessionIdForJob( $job_id ); + } if ( empty( $transcript_session_id ) ) { WP_CLI::error( @@ -523,6 +526,30 @@ public function transcript( array $args, array $assoc_args ): void { $this->renderTranscriptText( $job_id, (string) $transcript_session_id, $session, $messages, $metadata ); } + /** + * Locate a pipeline transcript by metadata for jobs created before engine_data + * stored transcript_session_id. + * + * @param int $job_id Job ID. + * @return string Transcript session ID, or empty string when none exists. + */ + private function findTranscriptSessionIdForJob( int $job_id ): string { + global $wpdb; + + $table = $wpdb->prefix . 'datamachine_chat_sessions'; + $like = '%"job_id":' . $job_id . '%'; + $row = $wpdb->get_var( + $wpdb->prepare( + 'SELECT session_id FROM %i WHERE mode = %s AND metadata LIKE %s ORDER BY created_at DESC LIMIT 1', + $table, + 'pipeline', + $like + ) + ); + + return is_string( $row ) ? $row : ''; + } + /** * Render a transcript as a human-readable turn-by-turn text block. * diff --git a/inc/Engine/AI/ConversationManager.php b/inc/Engine/AI/ConversationManager.php index b077eb90..5f1beeab 100644 --- a/inc/Engine/AI/ConversationManager.php +++ b/inc/Engine/AI/ConversationManager.php @@ -433,7 +433,7 @@ public static function generateDuplicateToolCallMessage( private static function buildDuplicateToolCallError( string $tool_name, string $mode ): string { switch ( $mode ) { case 'pipeline': - return "DUPLICATE REJECTED: You already called {$tool_name} with these exact parameters earlier in this conversation. Do NOT call it again. Move on — call the publish handler tool now to complete this step."; + return "DUPLICATE REJECTED: You already called {$tool_name} with these exact parameters earlier in this conversation. Do NOT call it again. Move on to the next required pipeline action using the result you already have."; case 'bridge': return "DUPLICATE REJECTED: You already called {$tool_name} with these exact parameters. Do NOT call it again. Continue your response to the user using the result you already have."; case 'chat': diff --git a/inc/Engine/AI/DataMachinePipelineTranscriptPersister.php b/inc/Engine/AI/DataMachinePipelineTranscriptPersister.php index 14f0c400..7e6d4b97 100644 --- a/inc/Engine/AI/DataMachinePipelineTranscriptPersister.php +++ b/inc/Engine/AI/DataMachinePipelineTranscriptPersister.php @@ -105,6 +105,11 @@ public function persist( array $messages, AgentConversationRequest $request, arr return ''; } + $job_id = (int) ( $runtime_context['job_id'] ?? 0 ); + if ( $job_id > 0 ) { + datamachine_merge_engine_data( $job_id, array( 'transcript_session_id' => $session_id ) ); + } + do_action( 'datamachine_log', 'debug', diff --git a/tests/duplicate-tool-call-mode-aware-smoke.php b/tests/duplicate-tool-call-mode-aware-smoke.php index 2b3c2b15..039b3ab5 100644 --- a/tests/duplicate-tool-call-mode-aware-smoke.php +++ b/tests/duplicate-tool-call-mode-aware-smoke.php @@ -3,8 +3,8 @@ * Smoke tests for the mode-aware duplicate-tool-call correction message. * * Regression coverage for #1441: when a pipeline AI step double-calls a tool, - * the correction message must instruct the model to keep going (call the - * publish handler) rather than the chat-shaped "task is done — end the + * the correction message must instruct the model to keep going to the next + * pipeline action rather than the chat-shaped "task is done — end the * conversation". The chat-mode behavior must remain identical to the * pre-fix-1441 message so existing chat callers are unaffected. * @@ -68,19 +68,23 @@ function datamachine_dup_msg_assert( bool $condition, string $message ): void { 'Explicit chat mode does NOT mention the publish handler.' ); -// --- Pipeline mode tells the AI to call the publish handler. ----------------- +// --- Pipeline mode tells the AI to continue the pipeline. -------------------- $pipeline_envelope = ConversationManager::generateDuplicateToolCallMessage( 'foo', 0, 'pipeline' ); $pipeline_error = $extract_error( $pipeline_envelope ); datamachine_dup_msg_assert( - str_contains( $pipeline_error, 'publish handler' ), - 'Pipeline mode instructs the AI to call the publish handler.' + str_contains( $pipeline_error, 'next required pipeline action' ), + 'Pipeline mode instructs the AI to move to the next required pipeline action.' ); datamachine_dup_msg_assert( ! str_contains( $pipeline_error, 'end the conversation' ), 'Pipeline mode does NOT tell the AI to end the conversation.' ); +datamachine_dup_msg_assert( + ! str_contains( $pipeline_error, 'publish handler' ), + 'Pipeline mode does NOT assume a publish handler is configured.' +); datamachine_dup_msg_assert( str_contains( $pipeline_error, 'foo' ), 'Pipeline mode renders the duplicated tool name into the error.' diff --git a/tests/pipeline-transcript-session-link-smoke.php b/tests/pipeline-transcript-session-link-smoke.php new file mode 100644 index 00000000..627f3804 --- /dev/null +++ b/tests/pipeline-transcript-session-link-smoke.php @@ -0,0 +1,51 @@ + $session_id' ), + 'persister stores transcript_session_id on the originating job' +); + +$assert( + false !== strpos( $jobs_cli, 'findTranscriptSessionIdForJob' ), + 'jobs transcript command has metadata fallback for old transcript rows' +); + +$assert( + false !== strpos( $jobs_cli, 'metadata LIKE' ) && false !== strpos( $jobs_cli, 'datamachine_chat_sessions' ), + 'metadata fallback searches pipeline transcript rows by job_id' +); + +echo "\n"; +if ( empty( $failures ) ) { + echo "OK: pipeline transcript session link smoke assertions passed.\n"; + exit( 0 ); +} + +echo sprintf( "FAILED: %d assertion(s) failed.\n", count( $failures ) ); +exit( 1 );