Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions inc/Cli/Commands/JobsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion inc/Engine/AI/ConversationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
5 changes: 5 additions & 0 deletions inc/Engine/AI/DataMachinePipelineTranscriptPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
14 changes: 9 additions & 5 deletions tests/duplicate-tool-call-mode-aware-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.'
Expand Down
51 changes: 51 additions & 0 deletions tests/pipeline-transcript-session-link-smoke.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
/**
* Source smoke coverage for pipeline transcript job linkage.
*
* Run with: php tests/pipeline-transcript-session-link-smoke.php
*
* @package DataMachine\Tests
*/

declare(strict_types=1);

$root = dirname( __DIR__ );
$persister = file_get_contents( $root . '/inc/Engine/AI/DataMachinePipelineTranscriptPersister.php' );
$jobs_cli = file_get_contents( $root . '/inc/Cli/Commands/JobsCommand.php' );

$failures = array();

$assert = static function ( bool $condition, string $message ) use ( &$failures ): void {
if ( $condition ) {
echo "PASS: {$message}\n";
return;
}

echo "FAIL: {$message}\n";
$failures[] = $message;
};

$assert(
false !== strpos( $persister, 'datamachine_merge_engine_data' )
&& false !== strpos( $persister, '\'transcript_session_id\' => $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 );
Loading