diff --git a/docs/channels-workflows-operations.md b/docs/channels-workflows-operations.md index 5a7e882..4382e60 100644 --- a/docs/channels-workflows-operations.md +++ b/docs/channels-workflows-operations.md @@ -91,6 +91,8 @@ array( Cancellation is best-effort. A runtime that can abort provider work immediately may do so; a runtime that cannot should mark the run `cancelling` and let its conversation loop stop at the next interrupt check. `WP_Agent_Chat_Run_Control::cancellation_interrupt_message()` builds the message shape expected by `WP_Agent_Conversation_Loop` `interrupt_source` callbacks. +Read abilities return observer-safe run envelopes for non-operators. Stored run state may contain runtime diagnostics, package/workflow selectors, raw refs, provenance, output, or caller metadata for audit/debugging, but `agents/get-chat-run`, `agents/list-chat-run-events`, `agents/get-task-run`, `agents/get-runtime-package-run`, and `agents/list-runtime-package-run-events` redact high-risk keys unless the caller passes the explicit unredacted read gate. Managers keep full access by default; hosts can extend that path with `agents_chat_run_unredacted_read_permission`, `agents_task_unredacted_read_permission`, or `agents_runtime_package_run_unredacted_read_permission` while leaving broad read access observer-safe. + Queued messages return the same run payload plus `queued_message_id` and `position`. Async runtimes can drain queued messages through their worker, cron, or Action Scheduler integration. Synchronous runtimes can expose queued state and require polling or an explicit continue operation in the consuming product; the substrate does not force a background runner. ## Session, webhook, and idempotency helpers diff --git a/docs/runtime-and-tools.md b/docs/runtime-and-tools.md index c08fb44..b90a039 100644 --- a/docs/runtime-and-tools.md +++ b/docs/runtime-and-tools.md @@ -433,7 +433,9 @@ Canonical keys: | `capability_scope` | `runtime_local` or `control_plane` | Whether a host may expose the tool to a delegated runtime or should keep it in the parent/control-plane runtime. | | `environment` | `runtime_local` or `control_plane` | The intended execution environment for a declaration or result. | -The substrate treats `runtime` as a JSON-friendly associative array. It preserves scalar and nested array values with string keys, drops unsupported values, and leaves product-specific interpretation to callers. +The substrate treats `runtime` as a JSON-friendly associative array. It preserves scalar and nested array values with string keys, drops unsupported values, redacts sensitive key names such as tokens, secrets, cookies, nonces, passwords, authorization headers, and API keys, and leaves product-specific interpretation to callers. + +`parameter_defaults` is a model/request-facing declaration field, not private secret storage. Defaults for sensitive-looking parameter names are normalized to `[redacted]`; hosts that need credentials should resolve them inside the concrete executor or through an explicit binding/authorization layer that is not serialized into the tool declaration. Delegated runtime consumers should advertise runtime-local tools with `capability_scope: runtime_local` and `environment: runtime_local`. Control-plane tools such as repository mutation, deployment, approval, or parent orchestration actions should use `capability_scope: control_plane` and stay out of runtime-local declarations. Agents API records and propagates this vocabulary; hosts still own the concrete allow/deny policy and execution adapter. diff --git a/src/Channels/register-agents-chat-run-control-abilities.php b/src/Channels/register-agents-chat-run-control-abilities.php index 95a878a..49f4b77 100644 --- a/src/Channels/register-agents-chat-run-control-abilities.php +++ b/src/Channels/register-agents-chat-run-control-abilities.php @@ -98,7 +98,12 @@ static function (): void { function agents_get_chat_run( array $input ) { $result = agents_chat_run_control_adapter()->get_run( $input ); if ( null !== $result ) { - return is_wp_error( $result ) ? $result : agents_chat_run_control_normalize_result( $result, 'agents_chat_run_invalid_status' ); + if ( is_wp_error( $result ) ) { + return $result; + } + + $result = agents_chat_run_control_normalize_result( $result, 'agents_chat_run_invalid_status' ); + return is_wp_error( $result ) ? $result : agents_chat_run_observer_payload( $result, $input ); } $run = WP_Agent_Chat_Run_Control::get_run( agents_chat_run_control_string( $input['run_id'] ?? '' ) ); @@ -107,7 +112,7 @@ function agents_get_chat_run( array $input ) { return agents_chat_run_control_no_handler( 'agents_chat_run_not_found', 'No chat run was found for the requested session_id and run_id.' ); } if ( null !== $run ) { - return $run; + return agents_chat_run_observer_payload( $run, $input ); } return agents_chat_run_control_no_handler( 'agents_chat_run_not_found', 'No chat run was found for the requested run_id.' ); @@ -118,7 +123,8 @@ function agents_get_chat_run( array $input ) { * @return array|\WP_Error */ function agents_list_chat_run_events( array $input ) { - return agents_chat_run_events_normalize_result( agents_chat_run_control_adapter()->list_events( $input ) ); + $result = agents_chat_run_events_normalize_result( agents_chat_run_control_adapter()->list_events( $input ) ); + return is_wp_error( $result ) ? $result : agents_chat_run_observer_payload( $result, $input ); } /** @@ -242,6 +248,30 @@ function agents_chat_run_write_permission( array $input ): bool { return $allowed || agents_chat_run_current_user_owns_session( $input ); } +/** @param array $input Ability input. */ +function agents_chat_run_unredacted_read_permission( array $input ): bool { + $allowed = function_exists( 'current_user_can' ) ? current_user_can( 'manage_options' ) : false; + $agent = sanitize_title( agents_chat_run_control_string( $input['agent'] ?? '' ) ); + if ( '' !== $agent && class_exists( '\WP_Agent_Access' ) && class_exists( '\WP_Agent_Access_Grant' ) ) { + $allowed = $allowed || \WP_Agent_Access::can_current_principal_access_agent( + $agent, + \WP_Agent_Access_Grant::ROLE_OPERATOR, + agents_chat_run_control_request_scope( $input ) + ); + } + + return (bool) apply_filters( 'agents_chat_run_unredacted_read_permission', $allowed, $input ); +} + +/** + * @param array $payload Run or event-page payload. + * @param array $input Ability input. + * @return array + */ +function agents_chat_run_observer_payload( array $payload, array $input ): array { + return agents_chat_run_unredacted_read_permission( $input ) ? $payload : WP_Agent_Run_Control::redacted_observer_payload( $payload ); +} + /** @param array $input Ability input. */ function agents_chat_run_current_user_owns_session( array $input ): bool { $owner = is_array( $input['session_owner'] ?? null ) ? agents_chat_run_control_string_keyed_array( $input['session_owner'] ) : array(); diff --git a/src/Runtime/class-wp-agent-run-control.php b/src/Runtime/class-wp-agent-run-control.php index fffa07c..9e88bc3 100644 --- a/src/Runtime/class-wp-agent-run-control.php +++ b/src/Runtime/class-wp-agent-run-control.php @@ -215,6 +215,17 @@ public static function normalize_event( array $event ): array { return $normalized; } + /** + * Build an observer-safe run/event payload without changing stored state. + * + * @param array $payload Normalized run or event-page payload. + * @return array Redacted payload for non-operator readers. + */ + public static function redacted_observer_payload( array $payload ): array { + $redacted = self::redact_payload_value( $payload ); + return is_array( $redacted ) ? self::string_keyed_array( $redacted ) : array(); + } + /** * Start or update an addressable run in the selected store. * @@ -413,6 +424,31 @@ private static function int_value( mixed $value ): int { return is_int( $value ) || is_float( $value ) || is_string( $value ) || is_bool( $value ) ? (int) $value : 0; } + /** + * @param mixed $value Raw value. + * @return mixed Redacted value. + */ + private static function redact_payload_value( $value, string $key = '' ) { + if ( '' !== $key && self::is_sensitive_payload_key( $key ) ) { + return is_array( $value ) ? array( 'redacted' => true ) : '[redacted]'; + } + + if ( ! is_array( $value ) ) { + return $value; + } + + $redacted = array(); + foreach ( $value as $item_key => $item_value ) { + $redacted[ $item_key ] = self::redact_payload_value( $item_value, is_string( $item_key ) ? $item_key : '' ); + } + + return $redacted; + } + + private static function is_sensitive_payload_key( string $key ): bool { + return 1 === preg_match( '/(api[_-]?key|authorization|auth[_-]?token|bearer|cookie|credential|diagnostics|nonce|output|package|password|private[_-]?key|provenance|raw|secret|token|workflow)/i', $key ); + } + /** * @param array{runs:array>,queues:array>>,events:array>>} $state * @param array $metadata Event metadata. diff --git a/src/Runtime/register-runtime-package-run-ability.php b/src/Runtime/register-runtime-package-run-ability.php index 576be59..3b4e02b 100644 --- a/src/Runtime/register-runtime-package-run-ability.php +++ b/src/Runtime/register-runtime-package-run-ability.php @@ -260,7 +260,8 @@ function agents_runtime_package_run_dispatch( array $input ) { function agents_get_runtime_package_run( array $input ) { $handler = apply_filters( 'wp_agent_runtime_package_run_status_handler', null, $input ); if ( is_callable( $handler ) ) { - return WP_Agent_Run_Control::normalize_run_result( call_user_func( $handler, $input ), 'agents_runtime_package_run_invalid_status' ); + $result = WP_Agent_Run_Control::normalize_run_result( call_user_func( $handler, $input ), 'agents_runtime_package_run_invalid_status' ); + return is_wp_error( $result ) ? $result : agents_runtime_package_run_observer_payload( $result, $input ); } $run = WP_Agent_Run_Control::get_run( AGENTS_RUNTIME_PACKAGE_RUN_CONTROL_STORE, agents_runtime_package_run_string( $input['run_id'] ?? '' ) ); @@ -268,7 +269,7 @@ function agents_get_runtime_package_run( array $input ) { return new \WP_Error( 'agents_runtime_package_run_not_found', 'No runtime package run was found for the requested run_id.' ); } - return $run; + return agents_runtime_package_run_observer_payload( $run, $input ); } /** @@ -297,7 +298,8 @@ function agents_cancel_runtime_package_run( array $input ) { function agents_list_runtime_package_run_events( array $input ) { $handler = apply_filters( 'wp_agent_runtime_package_run_events_handler', null, $input ); if ( is_callable( $handler ) ) { - return WP_Agent_Run_Control::normalize_events_result( call_user_func( $handler, $input ), 'agents_runtime_package_run_invalid_events_result' ); + $result = WP_Agent_Run_Control::normalize_events_result( call_user_func( $handler, $input ), 'agents_runtime_package_run_invalid_events_result' ); + return is_wp_error( $result ) ? $result : agents_runtime_package_run_observer_payload( $result, $input ); } $result = WP_Agent_Run_Control::list_events( @@ -310,7 +312,7 @@ function agents_list_runtime_package_run_events( array $input ) { return new \WP_Error( 'agents_runtime_package_run_not_found', 'No runtime package run was found for the requested run_id.' ); } - return $result; + return agents_runtime_package_run_observer_payload( $result, $input ); } /** @@ -332,10 +334,25 @@ function agents_runtime_package_run_permission( array $input ): bool { /** @param array $input Ability input. */ function agents_runtime_package_run_read_permission( array $input ): bool { - $allowed = function_exists( 'current_user_can' ) ? current_user_can( 'read' ) : false; + $allowed = function_exists( 'current_user_can' ) ? current_user_can( 'manage_options' ) : false; return (bool) apply_filters( 'agents_runtime_package_run_read_permission', $allowed, $input ); } +/** @param array $input Ability input. */ +function agents_runtime_package_run_unredacted_read_permission( array $input ): bool { + $allowed = function_exists( 'current_user_can' ) ? current_user_can( 'manage_options' ) : false; + return (bool) apply_filters( 'agents_runtime_package_run_unredacted_read_permission', $allowed, $input ); +} + +/** + * @param array $payload Run or event-page payload. + * @param array $input Ability input. + * @return array + */ +function agents_runtime_package_run_observer_payload( array $payload, array $input ): array { + return agents_runtime_package_run_unredacted_read_permission( $input ) ? $payload : WP_Agent_Run_Control::redacted_observer_payload( $payload ); +} + /** @param array $input Ability input. */ function agents_runtime_package_run_cancel_permission( array $input ): bool { return agents_runtime_package_run_permission( $input ); diff --git a/src/Tasks/register-agents-task-abilities.php b/src/Tasks/register-agents-task-abilities.php index 983263b..e7c5a60 100644 --- a/src/Tasks/register-agents-task-abilities.php +++ b/src/Tasks/register-agents-task-abilities.php @@ -240,7 +240,8 @@ function agents_list_execution_targets( array $input ) { function agents_get_task_run( array $input ) { $handler = apply_filters( 'wp_agent_task_run_status_handler', null, $input ); if ( is_callable( $handler ) ) { - return agents_task_normalize_run_control_result( call_user_func( $handler, $input ), 'agents_task_run_invalid_status' ); + $result = agents_task_normalize_run_control_result( call_user_func( $handler, $input ), 'agents_task_run_invalid_status' ); + return is_wp_error( $result ) ? $result : agents_task_run_observer_payload( $result, $input ); } $run = WP_Agent_Task_Run_Control::get_run( agents_task_string( $input['run_id'] ?? '' ) ); @@ -249,7 +250,7 @@ function agents_get_task_run( array $input ) { return new \WP_Error( 'agents_task_run_not_found', 'No task run was found for the requested session_id and run_id.' ); } if ( null !== $run ) { - return $run; + return agents_task_run_observer_payload( $run, $input ); } return new \WP_Error( 'agents_task_run_not_found', 'No task run was found for the requested run_id.' ); @@ -453,6 +454,21 @@ function agents_task_read_permission( array $input ): bool { return (bool) apply_filters( 'agents_task_permission', $allowed, $input ); } +/** @param array $input Ability input. */ +function agents_task_unredacted_read_permission( array $input ): bool { + $allowed = function_exists( 'current_user_can' ) ? current_user_can( 'manage_options' ) : false; + return (bool) apply_filters( 'agents_task_unredacted_read_permission', $allowed, $input ); +} + +/** + * @param array $payload Run payload. + * @param array $input Ability input. + * @return array + */ +function agents_task_run_observer_payload( array $payload, array $input ): array { + return agents_task_unredacted_read_permission( $input ) ? $payload : \AgentsAPI\AI\WP_Agent_Run_Control::redacted_observer_payload( $payload ); +} + /** @param array $input Ability input. */ function agents_run_task_permission( array $input ): bool { $allowed = agents_task_write_permission( $input ); diff --git a/src/Tools/class-wp-agent-tool-declaration.php b/src/Tools/class-wp-agent-tool-declaration.php index fe76ecc..cc0a5e0 100644 --- a/src/Tools/class-wp-agent-tool-declaration.php +++ b/src/Tools/class-wp-agent-tool-declaration.php @@ -485,6 +485,10 @@ public static function normalizeRuntimeMetadata( $runtime ): array { if ( ! is_string( $key ) || '' === $key ) { continue; } + if ( WP_Agent_Tool_Parameters::sensitiveKey( $key ) ) { + $normalized[ $key ] = WP_Agent_Tool_Parameters::REDACTED_VALUE; + continue; + } $normalized_value = self::normalizeRuntimeMetadataValue( $value ); if ( null === $normalized_value ) { diff --git a/src/Tools/class-wp-agent-tool-parameters.php b/src/Tools/class-wp-agent-tool-parameters.php index c6b6a32..9460113 100644 --- a/src/Tools/class-wp-agent-tool-parameters.php +++ b/src/Tools/class-wp-agent-tool-parameters.php @@ -160,7 +160,8 @@ public static function normalizeParameterDefaults( array $tool_definition ): arr throw new \InvalidArgumentException( 'invalid_parameter_bindings: parameter_defaults' ); } - $normalized[ trim( $parameter_name ) ] = $value; + $parameter_name = trim( $parameter_name ); + $normalized[ $parameter_name ] = self::sensitiveKey( $parameter_name ) ? self::REDACTED_VALUE : $value; } return $normalized; @@ -522,7 +523,7 @@ private static function redactValue( $value, string $path, array $paths ) { * @param string $key Parameter key. * @return bool */ - private static function sensitiveKey( string $key ): bool { + public static function sensitiveKey( string $key ): bool { return '' !== $key && 1 === preg_match( '/(api[_-]?key|authorization|auth[_-]?token|bearer|cookie|credential|nonce|password|private[_-]?key|secret|session[_-]?id|token)/i', $key ); } diff --git a/tests/chat-run-control-smoke.php b/tests/chat-run-control-smoke.php index af28a72..b2868ff 100644 --- a/tests/chat-run-control-smoke.php +++ b/tests/chat-run-control-smoke.php @@ -216,7 +216,7 @@ static function ( $handler, array $input ) use ( &$captured_chat_input ) { 'status' => 'running', 'started_at' => '2026-01-01T00:00:00Z', 'updated_at' => '2026-01-01T00:00:01Z', - 'metadata' => array( 'provider' => 'test' ), + 'metadata' => array( 'provider' => 'test', 'token' => 'secret-token' ), ), 10, 2 @@ -225,6 +225,12 @@ static function ( $handler, array $input ) use ( &$captured_chat_input ) { $status = AgentsAPI\AI\Channels\agents_get_chat_run( array( 'session_id' => 'session-1', 'run_id' => 'run-1' ) ); agents_api_smoke_assert_equals( 'running', $status['status'] ?? null, 'get-run normalizes status payload', $failures, $passes ); agents_api_smoke_assert_equals( 'test', $status['metadata']['provider'] ?? null, 'get-run preserves metadata', $failures, $passes ); +agents_api_smoke_assert_equals( 'secret-token', $status['metadata']['token'] ?? null, 'manager get-run preserves operator metadata', $failures, $passes ); +$GLOBALS['__agents_api_smoke_caps']['manage_options'] = false; +$observer_status = AgentsAPI\AI\Channels\agents_get_chat_run( array( 'session_id' => 'session-1', 'run_id' => 'run-1' ) ); +agents_api_smoke_assert_equals( 'test', $observer_status['metadata']['provider'] ?? null, 'observer get-run preserves safe metadata', $failures, $passes ); +agents_api_smoke_assert_equals( '[redacted]', $observer_status['metadata']['token'] ?? null, 'observer get-run redacts metadata secrets', $failures, $passes ); +$GLOBALS['__agents_api_smoke_caps']['manage_options'] = true; add_filter( 'wp_agent_chat_run_cancel_handler', diff --git a/tests/no-product-imports-smoke.php b/tests/no-product-imports-smoke.php index a4a112f..0255afb 100644 --- a/tests/no-product-imports-smoke.php +++ b/tests/no-product-imports-smoke.php @@ -60,6 +60,11 @@ 'wp-site generator', 'WPSG', 'wpsg', + 'Codebox', + 'codebox', + 'Studio Native', + 'studio-native', + 'studio_native', ); $forbidden_admin_apis = array( diff --git a/tests/runtime-package-run-contract-smoke.php b/tests/runtime-package-run-contract-smoke.php index 9ec6fe2..81c0bfb 100644 --- a/tests/runtime-package-run-contract-smoke.php +++ b/tests/runtime-package-run-contract-smoke.php @@ -29,6 +29,28 @@ function is_wp_error( $value ): bool { } } +if ( ! function_exists( 'current_user_can' ) ) { + function current_user_can( string $capability ): bool { + return ! empty( $GLOBALS['__agents_api_smoke_caps'][ $capability ] ); + } +} + +$GLOBALS['__agents_api_smoke_options'] = array(); + +if ( ! function_exists( 'get_option' ) ) { + function get_option( string $option, $default = false ) { + return $GLOBALS['__agents_api_smoke_options'][ $option ] ?? $default; + } +} + +if ( ! function_exists( 'update_option' ) ) { + function update_option( string $option, $value, $autoload = null ): bool { + unset( $autoload ); + $GLOBALS['__agents_api_smoke_options'][ $option ] = $value; + return true; + } +} + require_once __DIR__ . '/agents-api-smoke-helpers.php'; if ( ! class_exists( 'WP_Ability' ) ) { @@ -331,6 +353,27 @@ static function ( $handler, WP_Agent_Runtime_Package_Run_Request $handler_reques agents_api_smoke_assert_equals( 'build-site', is_array( $dispatch ) ? $dispatch['result']['workflow_id'] ?? '' : '', 'dispatcher passes workflow to handler', $failures, $passes ); agents_api_smoke_assert_equals( 'runtime log', is_array( $dispatch ) ? $dispatch['evidence_refs'][0]['label'] ?? '' : '', 'dispatcher preserves evidence refs', $failures, $passes ); +$observer_run_id = 'observer-runtime-run'; +AgentsAPI\AI\WP_Agent_Run_Control::save_run( + AgentsAPI\AI\AGENTS_RUNTIME_PACKAGE_RUN_CONTROL_STORE, + array( + 'run_id' => $observer_run_id, + 'status' => 'succeeded', + 'metadata' => array( + 'package' => array( 'slug' => 'site-builder' ), + 'workflow' => array( 'id' => 'build-site' ), + ), + ) +); +$GLOBALS['__agents_api_smoke_caps'] = array( 'read' => true ); +agents_api_smoke_assert_equals( false, AgentsAPI\AI\agents_runtime_package_run_read_permission( array( 'run_id' => $observer_run_id ) ), 'runtime package read defaults to operators', $failures, $passes ); +$observer_run = AgentsAPI\AI\agents_get_runtime_package_run( array( 'run_id' => $observer_run_id ) ); +agents_api_smoke_assert_equals( 'succeeded', $observer_run['status'] ?? '', 'runtime package get-run still returns observer status when called directly', $failures, $passes ); +agents_api_smoke_assert_equals( true, $observer_run['metadata']['package']['redacted'] ?? false, 'runtime package observer envelope redacts nested package metadata', $failures, $passes ); +$GLOBALS['__agents_api_smoke_caps']['manage_options'] = true; +$operator_run = AgentsAPI\AI\agents_get_runtime_package_run( array( 'run_id' => $observer_run_id ) ); +agents_api_smoke_assert_equals( 'site-builder', $operator_run['metadata']['package']['slug'] ?? '', 'runtime package manager get-run preserves operator metadata', $failures, $passes ); + echo "\n[4] Public host helper invokes the canonical runtime package boundary:\n"; $helper_dispatch = wp_agent_run_runtime_package( array( diff --git a/tests/task-execution-smoke.php b/tests/task-execution-smoke.php index dee8a04..a496c79 100644 --- a/tests/task-execution-smoke.php +++ b/tests/task-execution-smoke.php @@ -207,8 +207,9 @@ static function ( $handler, array $input, array $target ) use ( &$captured_input 'type' => 'completed', ), ), - 'provenance' => array( 'source' => 'fake' ), - 'output' => array( 'answer' => 'done' ), + 'provenance' => array( 'source' => 'fake', 'api_key' => 'secret-key' ), + 'output' => array( 'answer' => 'done', 'token' => 'secret-token' ), + 'metadata' => array( 'token' => 'secret-token', 'safe' => 'yes' ), ); }; }, @@ -261,6 +262,20 @@ static function ( $handler, array $input, array $target ) use ( &$captured_input agents_api_smoke_assert_equals( 'agents-api/execution-metrics/v1', $stored['execution_metrics']['schema'] ?? null, 'get-task-run preserves stored metrics schema', $failures, $passes ); agents_api_smoke_assert_equals( 300, $stored['execution_metrics']['artifact_bytes'] ?? null, 'get-task-run preserves stored metrics artifact bytes', $failures, $passes ); agents_api_smoke_assert_equals( 'metrics-raw-1', $stored['execution_metrics']['raw_refs'][0]['id'] ?? null, 'get-task-run preserves stored metrics raw refs', $failures, $passes ); +agents_api_smoke_assert_equals( 'secret-token', $stored['metadata']['token'] ?? null, 'manager get-task-run keeps full operator metadata', $failures, $passes ); + +$GLOBALS['__agents_api_smoke_caps']['manage_options'] = false; +$observer_stored = AgentsAPI\AI\Tasks\agents_get_task_run( + array( + 'session_id' => $result['session_id'], + 'run_id' => $result['run_id'], + ) +); +agents_api_smoke_assert_equals( 'yes', $observer_stored['metadata']['safe'] ?? null, 'observer get-task-run preserves safe metadata', $failures, $passes ); +agents_api_smoke_assert_equals( '[redacted]', $observer_stored['metadata']['token'] ?? null, 'observer get-task-run redacts metadata secrets', $failures, $passes ); +agents_api_smoke_assert_equals( true, $observer_stored['provenance']['redacted'] ?? false, 'observer get-task-run redacts provenance', $failures, $passes ); +agents_api_smoke_assert_equals( true, $observer_stored['output']['redacted'] ?? false, 'observer get-task-run redacts output', $failures, $passes ); +$GLOBALS['__agents_api_smoke_caps']['manage_options'] = true; AgentsAPI\AI\Tasks\WP_Agent_Task_Run_Control::start_run( 'task-run-cancel', 'task-session-cancel', 'fake-executor' ); $cancelled = AgentsAPI\AI\Tasks\agents_cancel_task_run( diff --git a/tests/tool-runtime-smoke.php b/tests/tool-runtime-smoke.php index fec0709..64c4f49 100644 --- a/tests/tool-runtime-smoke.php +++ b/tests/tool-runtime-smoke.php @@ -97,6 +97,7 @@ 'scope' => AgentsAPI\AI\Tools\WP_Agent_Tool_Declaration::SCOPE_RUN, 'runtime' => array( 'duplicate_policy' => 'repeatable', + 'api_key' => 'secret-key', 'unsupported' => new stdClass(), ), ) @@ -106,6 +107,7 @@ agents_api_smoke_assert_equals( 'host', $host_declaration['executor'], 'request declaration records host executor', $failures, $passes ); agents_api_smoke_assert_equals( 'run', $host_declaration['scope'], 'request declaration records run scope for host tools', $failures, $passes ); agents_api_smoke_assert_equals( 'repeatable', $host_declaration['runtime']['duplicate_policy'] ?? '', 'request declaration preserves host runtime metadata', $failures, $passes ); +agents_api_smoke_assert_equals( '[redacted]', $host_declaration['runtime']['api_key'] ?? '', 'request declaration redacts sensitive runtime metadata', $failures, $passes ); agents_api_smoke_assert_equals( false, array_key_exists( 'unsupported', $host_declaration['runtime'] ?? array() ), 'request declaration drops unsupported host metadata values', $failures, $passes ); $strict_rejected_host = false; @@ -256,7 +258,7 @@ static function () { agents_api_smoke_assert_equals( 42, $dot_path_parameters['post_id'] ?? null, 'parameter bindings read values from allowed dot-path context sources', $failures, $passes ); $default_definition = array( - 'parameter_defaults' => array( 'query' => 'top-level-default' ), + 'parameter_defaults' => array( 'query' => 'top-level-default', 'api_key' => 'secret-key' ), 'parameter_bindings' => array( 'query' => array( 'source' => 'client_context', @@ -267,6 +269,7 @@ static function () { ); $defaulted = AgentsAPI\AI\Tools\WP_Agent_Tool_Parameters::buildParameters( array(), array(), $default_definition ); agents_api_smoke_assert_equals( 'binding-default', $defaulted['query'] ?? null, 'binding defaults override top-level parameter defaults', $failures, $passes ); +agents_api_smoke_assert_equals( '[redacted]', $defaulted['api_key'] ?? null, 'sensitive parameter defaults redact before model-facing declarations', $failures, $passes ); $default_context = AgentsAPI\AI\Tools\WP_Agent_Tool_Parameters::buildParameters( array(), array( 'client_context' => array( 'search' => array( 'query' => 'bound-context' ) ) ),