diff --git a/docs/cli/acw.md b/docs/cli/acw.md index def4f505..e45fae14 100644 --- a/docs/cli/acw.md +++ b/docs/cli/acw.md @@ -175,7 +175,7 @@ autoload -Uz compinit && compinit - `--stdout` behavior: - Without `--chat`: merges provider stderr into stdout so progress and output can be piped together. - With `--chat`: provider stderr is appended to `.tmp/acw-sessions/.stderr` to keep stdout clean for piping. Empty sidecar files created by `acw` are automatically removed. - - With `--chat --editor`: when stdout is a TTY, the editor prompt is echoed to stdout before assistant output. + - With `--chat --editor`: when stdout is a TTY, the user prompt is echoed immediately before provider invocation, so it appears before assistant output. - In file mode (no `--stdout`), provider stderr is written to `.stderr`. Empty sidecar files are removed after the provider exits. ## See Also diff --git a/src/cli/acw/dispatch.md b/src/cli/acw/dispatch.md index bbfd1e57..0094353b 100644 --- a/src/cli/acw/dispatch.md +++ b/src/cli/acw/dispatch.md @@ -74,7 +74,8 @@ and turn appending: output is captured to a temp file. After the provider exits, the captured content is emitted to stdout and the assistant response is appended to the session file. If `--editor` is used and stdout is a TTY, the editor prompt -is echoed to stdout before the assistant output. +is echoed to stdout before provider invocation so it appears before assistant +output. **Stderr sidecar**: When `--stdout` is combined with `--chat`, provider stderr is appended to `.stderr` beside the session file rather than diff --git a/src/cli/acw/dispatch.sh b/src/cli/acw/dispatch.sh index fcf34955..f75a9857 100644 --- a/src/cli/acw/dispatch.sh +++ b/src/cli/acw/dispatch.sh @@ -368,6 +368,12 @@ acw() { fi fi + if [ "$chat_mode" -eq 1 ] && [ "$stdout_mode" -eq 1 ] && [ "$use_editor" -eq 1 ] && [ -t 1 ]; then + echo "User Prompt:" + cat "$original_input_file" + echo "" + fi + # Remaining arguments are provider options local provider_exit=0 local stderr_file="" @@ -452,11 +458,6 @@ acw() { local assistant_response="" if [ "$stdout_mode" -eq 1 ]; then assistant_response="$chat_output_capture" - if [ "$use_editor" -eq 1 ] && [ -t 1 ]; then - echo "User Prompt:" - cat "$original_input_file" - echo "" - fi # Emit captured output to stdout cat "$chat_output_capture" else diff --git a/tests/cli/test-acw-chat.md b/tests/cli/test-acw-chat.md index 53d1e453..79424484 100644 --- a/tests/cli/test-acw-chat.md +++ b/tests/cli/test-acw-chat.md @@ -24,6 +24,7 @@ End-to-end tests for `acw` chat session functionality (`--chat` and `--chat-list ### Stdout Capture - `--chat --stdout` captures and emits assistant output - Captured output is also appended to session file +- `--chat --editor --stdout` on TTY echoes the user prompt before assistant output ### Stderr Sidecar (--chat --stdout) - Provider stderr is written to `.stderr` sidecar file diff --git a/tests/cli/test-acw-chat.sh b/tests/cli/test-acw-chat.sh index 9bc38eb3..068ad1a0 100755 --- a/tests/cli/test-acw-chat.sh +++ b/tests/cli/test-acw-chat.sh @@ -18,6 +18,18 @@ mkdir -p "$TEST_TMP/.tmp/acw-sessions" source "$ACW_CLI" +TEST_BIN="$TEST_TMP/bin" +mkdir -p "$TEST_BIN" + +# Stub claude provider binary for chat stdout coverage +cat > "$TEST_BIN/claude" << 'STUB' +#!/usr/bin/env bash +echo "Stub assistant output" +STUB +chmod +x "$TEST_BIN/claude" + +export PATH="$TEST_BIN:$PATH" + # ============================================================ # Test 1: Chat helper functions exist # ============================================================ @@ -385,4 +397,77 @@ if [ ! -f "$preexist_stderr" ]; then test_fail "Pre-existing empty stderr sidecar should have been preserved" fi +# ============================================================ +# Test 16: TTY prompt echo appears before assistant output in chat stdout +# ============================================================ +test_info "Checking chat stdout TTY prompt echo ordering" + +TTY_EDITOR="$TEST_TMP/tty-editor.sh" +cat > "$TTY_EDITOR" << 'STUB' +#!/usr/bin/env bash +cat > "$1" << 'EOF' +TTY prompt content +EOF +STUB +chmod +x "$TTY_EDITOR" + +if ! command -v script >/dev/null 2>&1; then + test_fail "script command is required for TTY stdout testing" +fi + +TTY_RUNNER="$TEST_TMP/tty-run.sh" +cat > "$TTY_RUNNER" << STUB +#!/usr/bin/env bash +source "$ACW_CLI" +acw --chat --editor --stdout claude test-model +STUB +chmod +x "$TTY_RUNNER" + +script_flavor="bsd" +if script --version >/dev/null 2>&1; then + script_flavor="util-linux" +fi + +export EDITOR="$TTY_EDITOR" + +set +e +if [ "$script_flavor" = "util-linux" ]; then + tty_output=$(script -q -c "bash \"$TTY_RUNNER\"" /dev/null 2>/dev/null) +else + tty_output=$(script -q /dev/null bash "$TTY_RUNNER" 2>/dev/null) +fi +exit_code=$? +set -e + +if [ "$exit_code" -ne 0 ]; then + test_info "script stdout (first 40 lines):" + printf "%s\n" "$tty_output" | sed -n '1,40p' + test_fail "--chat --editor --stdout should succeed on TTY stdout" +fi + +clean_tty_output=$(printf "%s\n" "$tty_output" | tr -d '\r') + +if ! printf "%s\n" "$clean_tty_output" | grep -q "^User Prompt:"; then + test_fail "TTY stdout should include User Prompt header" +fi + +if ! printf "%s\n" "$clean_tty_output" | grep -q "TTY prompt content"; then + test_fail "TTY stdout should include editor prompt content" +fi + +if ! printf "%s\n" "$clean_tty_output" | grep -q "Stub assistant output"; then + test_fail "TTY stdout should include assistant output" +fi + +prompt_line=$(printf "%s\n" "$clean_tty_output" | awk '/^User Prompt:/{print NR; exit}') +assistant_line=$(printf "%s\n" "$clean_tty_output" | awk '/Stub assistant output/{print NR; exit}') + +if [ -z "$prompt_line" ] || [ -z "$assistant_line" ]; then + test_fail "TTY stdout should include prompt and assistant output" +fi + +if [ "$prompt_line" -gt "$assistant_line" ]; then + test_fail "TTY stdout should echo prompt before assistant output" +fi + test_pass "All chat session tests passed"