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
63 changes: 59 additions & 4 deletions lib/homeboy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ homeboy_run() {
[ -n "${SERVICE_HOME:-}" ] && \
{ [ "${WP_CODING_AGENTS_TEST_ASSUME_ROOT:-false}" = true ] || [ "$(id -u)" -eq 0 ]; } && \
command -v sudo >/dev/null 2>&1; then
if [ -n "${HOMEBOY_DATA_DIR:-}" ]; then
sudo -n -H -u "$SERVICE_USER" env HOME="$SERVICE_HOME" PATH="$PATH" HOMEBOY_DATA_DIR="$HOMEBOY_DATA_DIR" homeboy "$@"
return $?
fi
sudo -n -H -u "$SERVICE_USER" env HOME="$SERVICE_HOME" PATH="$PATH" homeboy "$@"
return $?
fi
Expand Down Expand Up @@ -126,6 +130,12 @@ homeboy_dmc_command_json() {
# probing its safety fields; the adapter refuses any continuation.
homeboy_json_array php "$SCRIPT_DIR/scripts/homeboy-dmc-provider.php" resolve_task '{task_url}' "${wp_argv[@]}" datamachine-code workspace worktree list '--task-ref={task_url}' --with-status --limit=200 --envelope --format=json "${wp_flags[@]}"
;;
task_attachment_preview)
homeboy_json_array php "$SCRIPT_DIR/scripts/homeboy-dmc-provider.php" task_attachment_preview "$provider" "$DM_WORKSPACE_DIR" '{handle}' '{task_url}'
;;
task_attachment_apply)
homeboy_json_array php "$SCRIPT_DIR/scripts/homeboy-dmc-provider.php" task_attachment_apply '{handle}' '{task_url}' "${wp_argv[@]}" datamachine-code workspace worktree attach-tracker '{handle}' '--task-url={task_url}' --format=json "${wp_flags[@]}"
;;
ensure)
# Homeboy owns each fanout worktree lifecycle. DMC verifies this complete
# contract on creation and on owner-identical retries.
Expand All @@ -150,19 +160,61 @@ homeboy_dmc_command_json() {

homeboy_dmc_worktree_provider_json() {
local provider="$1"
printf '{"enabled":true,"kind":"command","apply_enabled":true,"lookup_timeout_ms":12000,"lookup_output_limit_bytes":262144,"mutation_timeout_ms":120000,"list_result_mapping":{"items":"$","handle":"$.handle","path":"$.path","branch":"$.branch","task_url":"$.task_url","dirty":"$.safety.dirty","unpushed":"$.safety.unpushed","primary":"$.safety.primary"},"commands":{"resolve_identity":%s,"attest_safety":%s,"resolve":%s,"resolve_path":%s,"resolve_task":%s,"resolve_not_found_exit_codes":[42],"resolve_task_not_found_exit_codes":[42],"ensure":%s,"converge":%s,"plan":%s,"cleanup_preview":%s,"cleanup_apply":%s}}' \
local task_attachment_supported="${2:-false}"
printf '{"enabled":true,"kind":"command","apply_enabled":true,"lookup_timeout_ms":12000,"lookup_output_limit_bytes":262144,"mutation_timeout_ms":120000,"list_result_mapping":{"items":"$","handle":"$.handle","path":"$.path","branch":"$.branch","task_url":"$.task_url","dirty":"$.safety.dirty","unpushed":"$.safety.unpushed","primary":"$.safety.primary"},"commands":{"resolve_identity":%s,"attest_safety":%s,"resolve":%s,"resolve_path":%s,"resolve_task":%s' \
"$(homeboy_dmc_command_json resolve_identity "$provider")" \
"$(homeboy_dmc_command_json attest_safety "$provider")" \
"$(homeboy_dmc_command_json resolve "$provider")" \
"$(homeboy_dmc_command_json resolve_path "$provider")" \
"$(homeboy_dmc_command_json resolve_task "$provider")" \
"$(homeboy_dmc_command_json resolve_task "$provider")"
if [ "$task_attachment_supported" = true ]; then
printf ',"task_attachment_preview":%s,"task_attachment_apply":%s' \
"$(homeboy_dmc_command_json task_attachment_preview "$provider")" \
"$(homeboy_dmc_command_json task_attachment_apply "$provider")"
fi
printf ',"resolve_not_found_exit_codes":[42],"resolve_task_not_found_exit_codes":[42],"ensure":%s,"converge":%s,"plan":%s,"cleanup_preview":%s,"cleanup_apply":%s}}' \
"$(homeboy_dmc_command_json ensure "$provider")" \
"$(homeboy_dmc_command_json converge "$provider")" \
"$(homeboy_dmc_command_json plan "$provider")" \
"$(homeboy_dmc_command_json cleanup_preview "$provider")" \
"$(homeboy_dmc_command_json cleanup_apply "$provider")"
}

homeboy_dmc_task_attachment_capable() {
local provider="$1" response
response="$(php "$provider" capabilities 2>/dev/null)" || return 1
printf '%s' "$response" | python3 -c '
import json
import sys

try:
payload = json.load(sys.stdin)
except Exception:
sys.exit(1)

sys.exit(0 if (
payload.get("schema") == "datamachine-code/worktree-provider-capabilities/v1"
and "task_url" in payload.get("tracker_fields", [])
and payload.get("attachment_operation") == "datamachine-code/workspace-worktree-attach-tracker"
and payload.get("attachment_standalone") is False
) else 1)
' >/dev/null 2>&1
}

homeboy_task_attachment_commands_supported() {
local probe_root probe_provider
probe_root="$(mktemp -d)" || return 1
probe_provider='{"enabled":false,"kind":"command","apply_enabled":false,"commands":{"task_attachment_preview":["true"],"task_attachment_apply":["true"]}}'
if HOMEBOY_DATA_DIR="$probe_root" homeboy_run config set /worktree_providers/wpca-task-attachment-probe "$probe_provider" >/dev/null 2>&1 &&
HOMEBOY_DATA_DIR="$probe_root" homeboy_run config show /worktree_providers/wpca-task-attachment-probe/commands/task_attachment_preview >/dev/null 2>&1 &&
HOMEBOY_DATA_DIR="$probe_root" homeboy_run config show /worktree_providers/wpca-task-attachment-probe/commands/task_attachment_apply >/dev/null 2>&1; then
rm -rf "$probe_root"
return 0
fi
rm -rf "$probe_root"
return 1
}

homeboy_dmc_worktree_provider_ready() {
local provider="$1" response

Expand Down Expand Up @@ -551,12 +603,15 @@ configure_homeboy_dmc_worktree_provider() {
return 0
fi

local provider provider_json
local provider provider_json task_attachment_supported=false
provider="$(homeboy_dmc_worktree_provider_executable)" || {
homeboy_handle_failure "Data Machine Code standalone worktree provider source contract is unavailable; skipping Homeboy DMC worktree provider setup."
return 0
}
provider_json="$(homeboy_dmc_worktree_provider_json "$provider")"
if homeboy_dmc_task_attachment_capable "$provider" && homeboy_task_attachment_commands_supported; then
task_attachment_supported=true
fi
provider_json="$(homeboy_dmc_worktree_provider_json "$provider" "$task_attachment_supported")"

if [ "${DRY_RUN:-false}" = true ]; then
echo -e "${BLUE}[dry-run]${NC} homeboy config set /worktree_providers/dmc '$provider_json'"
Expand Down
138 changes: 135 additions & 3 deletions scripts/homeboy-dmc-provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// and report a typed adapter failure instead of being terminated externally.
const HOMEBOY_DMC_TASK_LOOKUP_TIMEOUT_SECONDS = 8;
const HOMEBOY_DMC_TASK_TERMINATION_GRACE_SECONDS = 1;
const HOMEBOY_DMC_ATTACHMENT_TIMEOUT_SECONDS = 30;

if ( '_session_exec' === $operation ) {
$command = array_slice($argv, 2);
Expand Down Expand Up @@ -43,7 +44,7 @@
}

/** @return array{status:int,stdout:string,stderr:string,failure:?string} */
$run_bounded_command = static function ( array $command, int $stdout_limit, int $stderr_limit ): array {
$run_bounded_command = static function ( array $command, int $stdout_limit, int $stderr_limit, int $timeout_seconds = HOMEBOY_DMC_TASK_LOOKUP_TIMEOUT_SECONDS ): array {
$launcher = array_merge(array( PHP_BINARY, __FILE__, '_session_exec' ), $command);
$process = proc_open($launcher, array( 1 => array( 'pipe', 'w' ), 2 => array( 'pipe', 'w' ), 3 => array( 'pipe', 'w' ) ), $pipes);
if ( ! is_resource($process) ) {
Expand All @@ -69,7 +70,7 @@
$failure = 'session';
$termination_started_at = microtime(true);
}
if ( null === $failure && microtime(true) - $started_at >= HOMEBOY_DMC_TASK_LOOKUP_TIMEOUT_SECONDS ) {
if ( null === $failure && microtime(true) - $started_at >= $timeout_seconds ) {
$failure = 'timeout';
$termination_started_at = microtime(true);
}
Expand Down Expand Up @@ -134,7 +135,7 @@
// Reap the leader before probing its group so a Linux zombie cannot keep kill(-pgid, 0) true.
$status = proc_close($process);
while ( $session_ready && null !== $pid && $pid > 0 && @posix_kill(-$pid, 0) ) {
if ( null === $failure && microtime(true) - $started_at >= HOMEBOY_DMC_TASK_LOOKUP_TIMEOUT_SECONDS ) {
if ( null === $failure && microtime(true) - $started_at >= $timeout_seconds ) {
$failure = 'timeout';
$termination_started_at = microtime(true);
}
Expand Down Expand Up @@ -172,6 +173,137 @@ static function ( array $matches ): string {
) ?? '';
};

$task_attachment_result = static function ( array $identity, string $task_url, string $status ): array {
return array(
'schema' => 'homeboy/worktree-provider-task-attachment/v1',
'provider_id' => 'dmc',
'handle' => $identity['handle'],
'task_url' => $task_url,
'path' => $identity['path'],
'branch' => $identity['branch'],
'primary' => false,
'status' => $status,
);
};

if ( 'task_attachment_preview' === $operation ) {
$provider = (string) ( $argv[2] ?? '' );
$workspace = (string) ( $argv[3] ?? '' );
$handle = (string) ( $argv[4] ?? '' );
$task_url = $canonical_task_url((string) ( $argv[5] ?? '' ));
if ( '' === $provider || '' === $workspace || '' === $handle || '' === $task_url ) {
fwrite(STDERR, "Usage: homeboy-dmc-provider.php task_attachment_preview <dmc-provider> <workspace-root> <handle> <task-url>\n");
exit(2);
}
try {
$identity_capture = $run_bounded_command(array( PHP_BINARY, $provider, 'identity', $workspace, $handle ), HOMEBOY_DMC_TASK_MAX_OUTPUT_BYTES, HOMEBOY_DMC_TASK_MAX_DMC_STDERR_BYTES);
} catch (Throwable $error) {
fwrite(STDERR, $error->getMessage() . "\n");
exit(1);
}
if ( null !== $identity_capture['failure'] ) {
fwrite(STDERR, "DMC tracker-attachment preview exceeded its bounded execution or capture.\n");
exit(1);
}
if ( 0 !== $identity_capture['status'] ) {
fwrite(STDERR, $identity_capture['stderr']);
exit($identity_capture['status'] > 0 && $identity_capture['status'] < 256 ? $identity_capture['status'] : 1);
}
try {
$identity = json_decode($identity_capture['stdout'], true, 512, JSON_THROW_ON_ERROR);
} catch (Throwable $error) {
fwrite(STDERR, "DMC tracker-attachment preview returned invalid identity JSON.\n");
exit(1);
}
if ( is_array($identity) && in_array((string) ( $identity['status'] ?? '' ), array( 'not_owned', 'not_found' ), true) ) {
fwrite(STDOUT, "{\"status\":\"not_owned\"}\n");
exit(0);
}
if (
! is_array($identity) || 'datamachine-code/worktree-identity/v1' !== ( $identity['schema'] ?? null )
|| $handle !== ( $identity['handle'] ?? null ) || ! is_string($identity['path'] ?? null) || '' === $identity['path']
|| ! is_string($identity['branch'] ?? null) || '' === $identity['branch'] || false !== ( $identity['primary'] ?? null )
) {
fwrite(STDERR, "DMC tracker-attachment preview returned an incomplete exact identity.\n");
exit(1);
}
$existing_task_url = $canonical_task_url((string) ( $identity['task_url'] ?? '' ));
if ( '' !== $existing_task_url && $task_url !== $existing_task_url ) {
fwrite(STDERR, "DMC worktree already has conflicting tracker ownership.\n");
exit(1);
}
if ( '' === $existing_task_url ) {
try {
$safety_capture = $run_bounded_command(array( PHP_BINARY, $provider, 'safety', $workspace, (string) ( $identity['token'] ?? '' ) ), HOMEBOY_DMC_TASK_MAX_OUTPUT_BYTES, HOMEBOY_DMC_TASK_MAX_DMC_STDERR_BYTES);
} catch (Throwable $error) {
fwrite(STDERR, $error->getMessage() . "\n");
exit(1);
}
if ( null !== $safety_capture['failure'] ) {
fwrite(STDERR, "DMC tracker-attachment preview exceeded its bounded execution or capture.\n");
exit(1);
}
if ( 0 !== $safety_capture['status'] ) {
fwrite(STDERR, $safety_capture['stderr']);
exit($safety_capture['status'] > 0 && $safety_capture['status'] < 256 ? $safety_capture['status'] : 1);
}
$safety = json_decode($safety_capture['stdout'], true);
if ( ! is_array($safety) || 'datamachine-code/worktree-safety/v1' !== ( $safety['schema'] ?? null ) || true !== ( $safety['fresh'] ?? null ) || false !== ( $safety['dirty'] ?? null ) ) {
fwrite(STDERR, "DMC tracker-attachment preview requires a fresh clean worktree.\n");
exit(1);
}
}
fwrite(STDOUT, json_encode($task_attachment_result($identity, $task_url, '' === $existing_task_url ? 'eligible' : 'already_attached'), JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");
exit(0);
}

if ( 'task_attachment_apply' === $operation ) {
$handle = (string) ( $argv[2] ?? '' );
$task_url = $canonical_task_url((string) ( $argv[3] ?? '' ));
$command = array_slice($argv, 4);
if ( '' === $handle || '' === $task_url || array() === $command ) {
fwrite(STDERR, "Usage: homeboy-dmc-provider.php task_attachment_apply <handle> <task-url> <dmc-attach-tracker-command...>\n");
exit(2);
}
foreach ( $command as $index => $argument ) {
$command[ $index ] = str_replace(array( '{handle}', '{task_url}' ), array( $handle, $task_url ), $argument);
}
try {
$capture = $run_bounded_command($command, HOMEBOY_DMC_TASK_MAX_DMC_STDOUT_BYTES, HOMEBOY_DMC_TASK_MAX_DMC_STDERR_BYTES, HOMEBOY_DMC_ATTACHMENT_TIMEOUT_SECONDS);
} catch (Throwable $error) {
fwrite(STDERR, $error->getMessage() . "\n");
exit(1);
}
if ( null !== $capture['failure'] ) {
fwrite(STDERR, "DMC tracker attachment exceeded its bounded execution or capture.\n");
exit(1);
}
if ( 0 !== $capture['status'] ) {
fwrite(STDERR, $capture['stderr']);
exit($capture['status'] > 0 && $capture['status'] < 256 ? $capture['status'] : 1);
}
try {
$attached = json_decode($capture['stdout'], true, 512, JSON_THROW_ON_ERROR);
} catch (Throwable $error) {
fwrite(STDERR, "DMC tracker attachment returned invalid JSON.\n");
exit(1);
}
$identity = is_array($attached) && is_array($attached['provider_resolution'] ?? null) ? $attached['provider_resolution'] : null;
$status = is_array($attached) ? (string) ( $attached['status'] ?? '' ) : '';
if (
! is_array($identity) || ! in_array($status, array( 'attached', 'already_attached' ), true)
|| $handle !== ( $attached['handle'] ?? null ) || $handle !== ( $identity['handle'] ?? null )
|| $task_url !== $canonical_task_url((string) ( $identity['task_url'] ?? '' ))
|| ! is_string($identity['path'] ?? null) || '' === $identity['path'] || ! is_string($identity['branch'] ?? null) || '' === $identity['branch']
|| false !== ( $identity['primary'] ?? null )
) {
fwrite(STDERR, "DMC tracker attachment returned incomplete or mismatched exact evidence.\n");
exit(1);
}
fwrite(STDOUT, json_encode($task_attachment_result($identity, $task_url, $status), JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");
exit(0);
}

if ( 'resolve_task' === $operation ) {
$task_url = $canonical_task_url((string) ( $argv[2] ?? '' ));
$command = array_slice($argv, 3);
Expand Down
Loading
Loading