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
1 change: 1 addition & 0 deletions data-machine-code.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ function datamachine_code_load_chat_tools() {
add_filter(
'datamachine_tasks', function ( array $tasks ): array {
$tasks['github_create_issue'] = \DataMachineCode\Tasks\GitHubIssueTask::class;
$tasks['github_update_issue_labels'] = \DataMachineCode\Tasks\GitHubIssueLabelsTask::class;
$tasks['worktree_cleanup_chunk'] = \DataMachineCode\Tasks\WorktreeCleanupChunkTask::class;
$tasks['worktree_cleanup'] = \DataMachineCode\Tasks\WorktreeCleanupTask::class;
$tasks['workspace_disk_emergency_cleanup'] = \DataMachineCode\Tasks\WorkspaceDiskEmergencyCleanupTask::class;
Expand Down
2 changes: 1 addition & 1 deletion inc/Abilities/GitHubAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -1943,7 +1943,7 @@ private static function getCurrentAgentSlug(): string {
}
}

$agent_slug = PermissionHelper::get_acting_agent_slug();
$agent_slug = (string) PermissionHelper::get_acting_agent_slug();
if ( '' !== trim($agent_slug) ) {
return sanitize_text_field($agent_slug);
}
Expand Down
207 changes: 207 additions & 0 deletions inc/Tasks/GitHubIssueLabelsTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<?php
/**
* GitHub issue label update system task.
*
* @package DataMachineCode\Tasks
*/

namespace DataMachineCode\Tasks;

use DataMachine\Engine\AI\System\Tasks\SystemTask;
use DataMachineCode\Abilities\GitHubAbilities;

defined('ABSPATH') || exit;

class GitHubIssueLabelsTask extends SystemTask {

/**
* Task type identifier.
*
* @return string
*/
public function getTaskType(): string {
return 'github_update_issue_labels';
}

/**
* Task metadata for Data Machine system-task surfaces.
*
* @return array<string,mixed>
*/
public static function getTaskMeta(): array {
return array(
'label' => 'GitHub Issue Label Update',
'description' => 'Deterministically add and remove GitHub issue labels without replacing the full label set.',
'setting_key' => null,
'default_enabled' => true,
'supports_run' => false,
'mutates' => true,
'params_schema' => array(
'type' => 'object',
'required' => array( 'repo', 'issue_number' ),
'properties' => array(
'repo' => array( 'type' => 'string' ),
'issue_number' => array( 'type' => 'integer' ),
'add_labels' => array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
),
'remove_labels' => array(
'type' => 'array',
'items' => array( 'type' => 'string' ),
),
),
),
);
}

/**
* This task can infer the fetched GitHub issue when embedded after a fetch step.
*
* @return bool
*/
public function needsPipelineContext(): bool {
return true;
}

/**
* Execute surgical GitHub issue label updates.
*
* @param int $jobId Job ID.
* @param array $params Task params.
* @return void
*/
public function executeTask( int $jobId, array $params ): void {
$params = $this->withFetchedGitHubIssueContext($params);
$repo = trim( (string) ( $params['repo'] ?? '' ) );
$issue_number = (int) ( $params['issue_number'] ?? 0 );
$add_labels = $this->normalizeLabels($params['add_labels'] ?? array());
$remove_labels = $this->normalizeLabels($params['remove_labels'] ?? array());

if ( '' === $repo || $issue_number <= 0 ) {
$this->failJob($jobId, 'GitHub issue label update requires repo and issue_number.');
return;
}

if ( array() === $add_labels && array() === $remove_labels ) {
$this->failJob($jobId, 'GitHub issue label update requires at least one add_labels or remove_labels entry.');
return;
}

$removed = array();
$results = array();

foreach ( $remove_labels as $label ) {
$result = GitHubAbilities::removeLabel(
array(
'repo' => $repo,
'issue_number' => $issue_number,
'label' => $label,
)
);
if ( is_wp_error($result) ) {
$this->failJob($jobId, $result->get_error_message());
return;
}
$removed[] = $label;
$results[] = array(
'action' => 'remove',
'label' => $label,
'result' => $result,
);
}

$added = array();
if ( array() !== $add_labels ) {
$result = GitHubAbilities::addLabels(
array(
'repo' => $repo,
'issue_number' => $issue_number,
'labels' => $add_labels,
)
);
if ( is_wp_error($result) ) {
$this->failJob($jobId, $result->get_error_message());
return;
}
$added = $add_labels;
$results[] = array(
'action' => 'add',
'labels' => $add_labels,
'result' => $result,
);
}

$this->completeJob(
$jobId,
array(
'success' => true,
'repo' => $repo,
'issue_number' => $issue_number,
'added_labels' => $added,
'removed_labels' => $removed,
'results' => $results,
)
);
}

/**
* Normalize a scalar or array of labels to a de-duplicated list.
*
* @param mixed $labels Raw labels.
* @return array<int,string>
*/
private function normalizeLabels( mixed $labels ): array {
if ( is_string($labels) ) {
$labels = array( $labels );
}
if ( ! is_array($labels) ) {
return array();
}

$normalized = array();
foreach ( $labels as $label ) {
$label = trim( (string) $label );
if ( '' !== $label ) {
$normalized[] = $label;
}
}

return array_values(array_unique($normalized));
}

/**
* Fill repo and issue_number from the first GitHub issue packet when omitted.
*
* @param array<string,mixed> $params Raw params.
* @return array<string,mixed>
*/
private function withFetchedGitHubIssueContext( array $params ): array {
$repo_missing = '' === trim( (string) ( $params['repo'] ?? '' ) );
$number_missing = (int) ( $params['issue_number'] ?? 0 ) <= 0;
if ( ! $repo_missing && ! $number_missing ) {
return $params;
}

$data_packets = is_array($params['data_packets'] ?? null) ? $params['data_packets'] : array();
foreach ( $data_packets as $packet ) {
if ( ! is_array($packet) ) {
continue;
}
$metadata = is_array($packet['metadata'] ?? null) ? $packet['metadata'] : array();
if ( 'github' !== (string) ( $metadata['source_type'] ?? '' ) || 'issues' !== (string) ( $metadata['github_type'] ?? '' ) ) {
continue;
}

if ( $repo_missing && ! empty($metadata['github_repo']) ) {
$params['repo'] = (string) $metadata['github_repo'];
}
if ( $number_missing && (int) ( $metadata['github_number'] ?? 0 ) > 0 ) {
$params['issue_number'] = (int) $metadata['github_number'];
}
return $params;
}

return $params;
}
}
36 changes: 36 additions & 0 deletions tests/smoke-github-create-abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,42 @@ public static function get( string $key, string $default_value = '' ): string
}

namespace DataMachineCode\Support {
class PermissionHelper
{
public static function can_manage(): bool
{
return \DataMachine\Abilities\PermissionHelper::can_manage();
}

public static function get_acting_agent_slug(): ?string
{
return \DataMachine\Abilities\PermissionHelper::get_acting_agent_slug();
}

public static function get_acting_agent_id(): ?int
{
return \DataMachine\Abilities\PermissionHelper::get_acting_agent_id();
}

public static function get_runtime_context(): array
{
return \DataMachine\Abilities\PermissionHelper::get_runtime_context();
}

public static function get_execution_principal(): mixed
{
return null;
}
}

class PluginSettings
{
public static function get( string $key, string $default_value = '' ): string
{
return \DataMachine\Core\PluginSettings::get($key, $default_value);
}
}

class GitHubCredentialResolver
{
public static string $mode = 'pat';
Expand Down
Loading
Loading