diff --git a/bridges/kimaki/plugins/kimaki-session-attribution.ts b/bridges/kimaki/plugins/kimaki-session-attribution.ts index a91f859..f206299 100644 --- a/bridges/kimaki/plugins/kimaki-session-attribution.ts +++ b/bridges/kimaki/plugins/kimaki-session-attribution.ts @@ -12,7 +12,7 @@ type SessionAwareHooks = Awaited> & { }; const DISCORD_THREAD_URL = /^https:\/\/discord\.com\/channels\/\d{17,20}\/(\d{17,20})\/?$/; -const LOOKUP_TIMEOUT_MS = 2_000; +const LOOKUP_TIMEOUT_MS = 5_000; const OUTPUT_LIMIT = 1_024; const sessionAttribution = (async (_input: PluginInput): Promise => { diff --git a/lib/external-wordpress.sh b/lib/external-wordpress.sh index 5637c8c..e7b60e0 100644 --- a/lib/external-wordpress.sh +++ b/lib/external-wordpress.sh @@ -24,7 +24,10 @@ value = json.loads(sys.argv[1]) if not isinstance(value, list) or not value or any(not isinstance(item, str) or not item or "\0" in item for item in value): raise SystemExit(1) PY - mapfile -d '' -t WP_CONTROL_TRANSPORT < <(python3 - "$WP_CONTROL_TRANSPORT_JSON" <<'PY' + WP_CONTROL_TRANSPORT=() + while IFS= read -r -d '' transport_argument; do + WP_CONTROL_TRANSPORT+=("$transport_argument") + done < <(python3 - "$WP_CONTROL_TRANSPORT_JSON" <<'PY' import json, sys for item in json.loads(sys.argv[1]): sys.stdout.buffer.write(item.encode() + b"\0") diff --git a/lib/wordpress.sh b/lib/wordpress.sh index 2de62f9..420394b 100644 --- a/lib/wordpress.sh +++ b/lib/wordpress.sh @@ -6,7 +6,7 @@ wp_cmd() { if [ "${EXTERNAL_WORDPRESS:-false}" = true ]; then local user_args=() [ -z "${WORDPRESS_USER:-}" ] || user_args=("--user=$WORDPRESS_USER") - run_cmd "${WP_CONTROL_TRANSPORT[@]}" "$@" "${user_args[@]}" "--path=$WORDPRESS_PATH" + run_cmd "${WP_CONTROL_TRANSPORT[@]}" "${user_args[@]}" "--path=$WORDPRESS_PATH" "$@" return fi if [ "$IS_STUDIO" = true ]; then diff --git a/scripts/wp-control-transport.py b/scripts/wp-control-transport.py index 287f948..7f737c5 100644 --- a/scripts/wp-control-transport.py +++ b/scripts/wp-control-transport.py @@ -30,9 +30,10 @@ def fail(message: str) -> None: if not wordpress_path: fail("WORDPRESS_PATH is required") -command = [*transport, *sys.argv[1:]] wordpress_user = os.environ.get("WORDPRESS_USER", "") or profile.get("wordpress_user", "") +global_args = [] if wordpress_user: - command.append(f"--user={wordpress_user}") -command.append(f"--path={wordpress_path}") + global_args.append(f"--user={wordpress_user}") +global_args.append(f"--path={wordpress_path}") +command = [*transport, *global_args, *sys.argv[1:]] os.execvpe(command[0], command, os.environ) diff --git a/tests/external-wordpress-runtime.sh b/tests/external-wordpress-runtime.sh index 7b9fbc5..2a30cca 100644 --- a/tests/external-wordpress-runtime.sh +++ b/tests/external-wordpress-runtime.sh @@ -36,13 +36,16 @@ PY cat > "$TRANSPORT" <<'SH' #!/bin/bash printf '%s\n' "$@" >> "$WP_TEST_ARGS" -case "$3:$4" in - core:is-installed) exit 0 ;; +case "$#:$1:$2:$3:$4:$5:$6" in + "6:--identity:secret value with spaces:--user=$WORDPRESS_USER:--path=$WORDPRESS_PATH:core:is-installed") exit 0 ;; + "5:--identity:secret value with spaces:--path=$WORDPRESS_PATH:core:is-installed:") exit 0 ;; +esac +case "$5:$6" in datamachine:memory) - case "$5" in + case "$7" in injectable-files) printf '%s\n' '[{"filename":"SITE.md","layer":"shared","priority":10,"path":"/remote/wp-content/uploads/datamachine-files/shared/SITE.md"},{"filename":"SOUL.md","layer":"agent","priority":20,"path":"/remote/wp-content/uploads/datamachine-files/agents/remote/SOUL.md"}]' ;; read) - case "$6" in + case "$8" in SITE.md) printf '%s\n' 'site context' ;; SOUL.md) printf '%s\n' 'agent context' ;; *) exit 7 ;; @@ -51,10 +54,11 @@ case "$3:$4" in esac ;; eval:*) - [ "$#" -eq 6 ] && [ "$5" = "--user=$WORDPRESS_USER" ] && [ "$6" = "--path=$WORDPRESS_PATH" ] || { + [ "$#" -eq 6 ] && [ "$3" = "--user=$WORDPRESS_USER" ] && [ "$4" = "--path=$WORDPRESS_PATH" ] || { echo "wp eval received unsupported positional arguments" >&2 exit 9 } + set -- "$1" "$2" "$3" "$6" case "$4" in *"require base64_decode"*) echo "decoded PHP source was treated as a filename" >&2; exit 9 ;; esac @@ -111,6 +115,7 @@ error() { printf '%s\n' "$*" >&2; return 1; } external_wordpress_prepare_transport external_wordpress_validate +"$RUNTIME_PROJECT_ROOT/.wp-coding-agents/bin/wp-control" core is-installed >/dev/null external_wordpress_project_context runtime_generate_config WITH_AI_GATEWAY=true @@ -193,6 +198,13 @@ after_wrapper_calls="$(wc -l < "$ARGS" | tr -d ' ')" [ "$(sed -n '3p' "$KIMAKI_ENV")" = "agent user" ] || { echo "FAIL: Kimaki launcher lost WordPress user"; exit 1; } export WORDPRESS_PATH="/remote/site root" WORDPRESS_USER="agent user" +# Userless profiles retain the transport prefix and emit only the path global. +printf '%s\n' '{"wordpress_path":"/remote/site root","wordpress_user":""}' > "$RUNTIME_PROJECT_ROOT/.wp-coding-agents/wordpress.json" +unset WORDPRESS_USER +"$RUNTIME_PROJECT_ROOT/.wp-coding-agents/bin/wp-control" core is-installed >/dev/null +external_wordpress_validate +export WORDPRESS_USER="agent user" + if grep -R -F -- "secret value with spaces" "$RUNTIME_PROJECT_ROOT" >/dev/null 2>&1; then echo "FAIL: transport credential persisted below runtime root" exit 1