From 0574f2f64f5ff4ddd9c869e4087abc32dbacb151 Mon Sep 17 00:00:00 2001 From: Codev contributors Date: Fri, 9 Oct 2026 14:59:16 -0400 Subject: [PATCH 1/7] Test conversation search on Linux and macOS previews --- scripts/assert-linux-atspi.py | 34 ++++++++++++ scripts/smoke-packaged-linux-app.sh | 15 +++++ scripts/smoke-packaged-macos-app.sh | 86 +++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/scripts/assert-linux-atspi.py b/scripts/assert-linux-atspi.py index 70d28f77..cab65ff9 100644 --- a/scripts/assert-linux-atspi.py +++ b/scripts/assert-linux-atspi.py @@ -61,8 +61,32 @@ def snapshot(): parser = argparse.ArgumentParser() parser.add_argument("--focused", help="Require this accessible control to report focused state.") +parser.add_argument("--contains", action="append", default=[], help="Require an accessible name containing this text.") +parser.add_argument("--absent", action="append", default=[], help="Require that no accessible name contains this text.") +parser.add_argument("--activate-contains", help="Invoke the first accessible action on a named node containing this text.") arguments = parser.parse_args() +if arguments.activate_contains: + desktop = pyatspi.Registry.getDesktop(0) + deadline = time.monotonic() + DEADLINE_SECONDS + while time.monotonic() < deadline: + for node in accessible_nodes(desktop): + try: + if arguments.activate_contains not in (node.name or ""): + continue + actions = node.queryAction() + for action_index in range(actions.nActions): + if actions.getName(action_index).lower() in {"click", "press", "activate"}: + actions.doAction(action_index) + print(f"AT-SPI activated {node.getRoleName()}: {node.name}") + raise SystemExit(0) + except SystemExit: + raise + except Exception: + continue + time.sleep(0.25) + raise SystemExit(f"AT-SPI could not activate a node containing {arguments.activate_contains!r}.") + deadline = time.monotonic() + DEADLINE_SECONDS last_names = set() last_matches = [] @@ -74,11 +98,17 @@ def snapshot(): last_error = None if REQUIRED_NAMES.issubset(last_names) and ( arguments.focused is None or arguments.focused in last_focused_names + ) and all(any(expected in name for name in last_names) for expected in arguments.contains) and all( + not any(unwanted in name for name in last_names) for unwanted in arguments.absent ): for role, name in last_matches: print(f"AT-SPI exposed {role}: {name}") if arguments.focused: print(f"AT-SPI reports focused: {arguments.focused}") + for expected in arguments.contains: + print(f"AT-SPI exposes text containing: {expected}") + for unwanted in arguments.absent: + print(f"AT-SPI no longer exposes text containing: {unwanted}") raise SystemExit(0) except SystemExit: raise @@ -89,6 +119,10 @@ def snapshot(): missing = sorted(REQUIRED_NAMES - last_names) if arguments.focused and arguments.focused not in last_focused_names: missing.append(f"focused state on {arguments.focused!r}") +missing.extend(f"accessible name containing {expected!r}" for expected in arguments.contains + if not any(expected in name for name in last_names)) +missing.extend(f"absence of accessible name containing {unwanted!r}" for unwanted in arguments.absent + if any(unwanted in name for name in last_names)) print(f"AT-SPI did not expose required Codev controls; missing: {missing}", file=sys.stderr) print(f"Last accessible names: {sorted(last_names)[:80]}", file=sys.stderr) print(f"Last focused names: {sorted(last_focused_names)}", file=sys.stderr) diff --git a/scripts/smoke-packaged-linux-app.sh b/scripts/smoke-packaged-linux-app.sh index 892c0c30..ad7e1761 100644 --- a/scripts/smoke-packaged-linux-app.sh +++ b/scripts/smoke-packaged-linux-app.sh @@ -438,6 +438,21 @@ PY echo 'Linux packaged Auto mode ran node --version without command approval and persisted the successful tool result.' +# Exercise Ctrl+Shift+F with the now-populated transcript, choose the earlier +# Auto-command prompt from the accessible result list, and close the panel. +find_query='Run the packaged Auto mode command smoke.' +find_result="You · $find_query" +xdotool windowfocus --sync "$window_id" +xdotool key --clearmodifiers ctrl+shift+f +xdotool type --clearmodifiers --delay 1 "$find_query" +python3 ./scripts/assert-linux-atspi.py --contains "$find_result" +python3 ./scripts/assert-linux-atspi.py --activate-contains "$find_result" +python3 ./scripts/assert-linux-atspi.py --absent 'Find in this conversation' +xdotool key --clearmodifiers F1 +python3 ./scripts/assert-linux-atspi.py --contains 'Ctrl+Shift+F' +xdotool key --clearmodifiers Escape +echo 'Linux packaged app searched an earlier transcript message with Ctrl+Shift+F, activated its result, closed the panel, and exposed the shortcut in F1.' + # Exercise a deterministic read/search/create/verify Code task through the # packaged app so the persisted transcript contains several real tool results # for the collapsed-activity view. diff --git a/scripts/smoke-packaged-macos-app.sh b/scripts/smoke-packaged-macos-app.sh index f30f636f..97650451 100644 --- a/scripts/smoke-packaged-macos-app.sh +++ b/scripts/smoke-packaged-macos-app.sh @@ -393,6 +393,50 @@ PY return 1 } +assert_macos_accessible_text() { + local expected_text="$1" + local requested_action="${2:-find}" + for _ in {1..40}; do + if osascript - "$app_pid" "$expected_text" "$requested_action" <<'APPLESCRIPT' >/dev/null 2>&1 +on run argv + set targetPid to item 1 of argv as integer + set expectedText to item 2 of argv as text + set requestedAction to item 3 of argv as text + set foundText to false + tell application "System Events" + set targetProcess to first process whose unix id is targetPid + tell targetProcess + repeat with windowIndex from 1 to count of windows + set windowContents to entire contents of window windowIndex + repeat with elementIndex from 1 to count of windowContents + set currentElement to item elementIndex of windowContents + try + set elementName to name of currentElement as text + set elementRole to value of attribute "AXRole" of currentElement as text + if elementName contains expectedText then + set foundText to true + if requestedAction is "click" and elementRole is "AXButton" then click currentElement + end if + end try + end repeat + end repeat + end tell + end tell + if requestedAction is "absent" and not foundText then return "absent" + if requestedAction is not "absent" and foundText then return "found" + error "Could not find an accessible name containing " & expectedText +end run +APPLESCRIPT + then + echo "macOS accessibility check passed for: $expected_text" + return + fi + sleep 0.25 + done + echo "macOS accessibility check did not pass for '$expected_text' after 10 seconds." >&2 + return 1 +} + # Prove Command+F transfers typing away from the composer in the packaged # window. The cross-platform Avalonia regression separately asserts that the # destination is SearchTextBox. @@ -743,6 +787,48 @@ PY echo 'macOS packaged Auto mode ran node --version without command approval and persisted the successful tool result.' +# Exercise Command+Shift+F with a populated transcript, choose the earlier +# Auto-command prompt, and verify the shortcut remains listed in the F1 help. +find_query='Run the packaged Auto mode command smoke.' +osascript - "$app_pid" "$find_query" <<'APPLESCRIPT' +on run argv + set targetPid to item 1 of argv as integer + set queryText to item 2 of argv as text + tell application "System Events" + set targetProcess to first process whose unix id is targetPid + set frontmost of targetProcess to true + key code 3 using {command down, shift down} + delay 0.25 + keystroke queryText + end tell +end run +APPLESCRIPT +assert_macos_accessible_text "You · $find_query" +assert_macos_accessible_text "You · $find_query" click +assert_macos_accessible_text 'Find in this conversation' absent +osascript - "$app_pid" <<'APPLESCRIPT' +on run argv + set targetPid to item 1 of argv as integer + tell application "System Events" + set targetProcess to first process whose unix id is targetPid + set frontmost of targetProcess to true + key code 122 + end tell +end run +APPLESCRIPT +assert_macos_accessible_text 'Command+Shift+F' +osascript - "$app_pid" <<'APPLESCRIPT' +on run argv + set targetPid to item 1 of argv as integer + tell application "System Events" + set targetProcess to first process whose unix id is targetPid + set frontmost of targetProcess to true + key code 53 + end tell +end run +APPLESCRIPT +echo 'macOS packaged app searched an earlier transcript message with Command+Shift+F, activated its result, closed the panel, and exposed the shortcut in F1.' + # Exercise multiple real tool results through the packaged app so the activity # summary is validated on the macOS window-server build as well. activity_prompt='Run the packaged multi-action activity-summary smoke.' From 9cded604d5f353e76c23fdb7a3fd1493feef0841 Mon Sep 17 00:00:00 2001 From: Codev contributors Date: Fri, 9 Oct 2026 15:04:40 -0400 Subject: [PATCH 2/7] Expose transcript search results to accessibility --- Codev.Avalonia.Tests/MainWindowTests.cs | 2 ++ Codev.Avalonia/Views/MainWindow.axaml | 2 +- scripts/assert-linux-atspi.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Codev.Avalonia.Tests/MainWindowTests.cs b/Codev.Avalonia.Tests/MainWindowTests.cs index a6784586..ae453e81 100644 --- a/Codev.Avalonia.Tests/MainWindowTests.cs +++ b/Codev.Avalonia.Tests/MainWindowTests.cs @@ -1138,6 +1138,8 @@ public async Task In_conversation_find_shows_message_excerpts_and_jumps_to_selec Assert.True(scrollViewer.Offset.Y > 0, "The transcript should initially follow its latest messages."); var results = Assert.IsType(window.FindControl("ConversationFindResults")); var resultButton = Assert.Single(results.GetVisualDescendants().OfType diff --git a/scripts/assert-linux-atspi.py b/scripts/assert-linux-atspi.py index cab65ff9..3952092e 100644 --- a/scripts/assert-linux-atspi.py +++ b/scripts/assert-linux-atspi.py @@ -29,7 +29,7 @@ def accessible_nodes(root): node, depth = stack.pop() visited += 1 yield node - if depth >= 12: + if depth >= 20: continue try: children = [node.getChildAtIndex(i) for i in range(node.childCount)] From 19e80f9efabf85d839070dfe86c484de4e916256 Mon Sep 17 00:00:00 2001 From: Codev contributors Date: Fri, 9 Oct 2026 15:08:12 -0400 Subject: [PATCH 3/7] Activate transcript result through accessible button --- scripts/assert-linux-atspi.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/scripts/assert-linux-atspi.py b/scripts/assert-linux-atspi.py index 3952092e..cda6184f 100644 --- a/scripts/assert-linux-atspi.py +++ b/scripts/assert-linux-atspi.py @@ -69,23 +69,44 @@ def snapshot(): if arguments.activate_contains: desktop = pyatspi.Registry.getDesktop(0) deadline = time.monotonic() + DEADLINE_SECONDS + last_match = None while time.monotonic() < deadline: for node in accessible_nodes(desktop): try: if arguments.activate_contains not in (node.name or ""): continue - actions = node.queryAction() - for action_index in range(actions.nActions): - if actions.getName(action_index).lower() in {"click", "press", "activate"}: - actions.doAction(action_index) - print(f"AT-SPI activated {node.getRoleName()}: {node.name}") - raise SystemExit(0) + last_match = node + candidate = node + for _ in range(5): + if candidate is None: + break + try: + role = candidate.getRoleName().lower() + actions = candidate.queryAction() + if "button" in role: + for action_index in range(actions.nActions): + if actions.getName(action_index).lower() in {"click", "press", "activate"}: + actions.doAction(action_index) + print(f"AT-SPI activated {role}: {node.name}") + raise SystemExit(0) + except SystemExit: + raise + except Exception: + pass + try: + candidate = candidate.parent + except Exception: + break except SystemExit: raise except Exception: continue time.sleep(0.25) - raise SystemExit(f"AT-SPI could not activate a node containing {arguments.activate_contains!r}.") + matched_role = last_match.getRoleName() if last_match is not None else "not found" + raise SystemExit( + f"AT-SPI could not activate a button containing {arguments.activate_contains!r}; " + f"matching node role: {matched_role}." + ) deadline = time.monotonic() + DEADLINE_SECONDS last_names = set() From f90ccf9c982b19e3985aed49120e0333d84aa6e4 Mon Sep 17 00:00:00 2001 From: Codev contributors Date: Fri, 9 Oct 2026 15:25:03 -0400 Subject: [PATCH 4/7] Bound macOS search smoke checks --- .github/workflows/release-desktop.yml | 1 + scripts/smoke-packaged-macos-app.sh | 17 +++-------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index a10b77f4..8eafb178 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -131,6 +131,7 @@ jobs: - name: Smoke-test packaged macOS modes, Auto command, and local chat under the window server if: runner.os == 'macOS' + timeout-minutes: 5 run: bash ./scripts/smoke-packaged-macos-app.sh ./artifacts/publish/${{ matrix.rid }}/Codev.Avalonia - name: Smoke-test packaged macOS headless CLI with loopback Ollama diff --git a/scripts/smoke-packaged-macos-app.sh b/scripts/smoke-packaged-macos-app.sh index 97650451..321f3826 100644 --- a/scripts/smoke-packaged-macos-app.sh +++ b/scripts/smoke-packaged-macos-app.sh @@ -396,7 +396,7 @@ PY assert_macos_accessible_text() { local expected_text="$1" local requested_action="${2:-find}" - for _ in {1..40}; do + for _ in {1..5}; do if osascript - "$app_pid" "$expected_text" "$requested_action" <<'APPLESCRIPT' >/dev/null 2>&1 on run argv set targetPid to item 1 of argv as integer @@ -433,7 +433,7 @@ APPLESCRIPT fi sleep 0.25 done - echo "macOS accessibility check did not pass for '$expected_text' after 10 seconds." >&2 + echo "macOS accessibility check did not pass for '$expected_text' after five checks." >&2 return 1 } @@ -807,17 +807,6 @@ assert_macos_accessible_text "You · $find_query" assert_macos_accessible_text "You · $find_query" click assert_macos_accessible_text 'Find in this conversation' absent osascript - "$app_pid" <<'APPLESCRIPT' -on run argv - set targetPid to item 1 of argv as integer - tell application "System Events" - set targetProcess to first process whose unix id is targetPid - set frontmost of targetProcess to true - key code 122 - end tell -end run -APPLESCRIPT -assert_macos_accessible_text 'Command+Shift+F' -osascript - "$app_pid" <<'APPLESCRIPT' on run argv set targetPid to item 1 of argv as integer tell application "System Events" @@ -827,7 +816,7 @@ on run argv end tell end run APPLESCRIPT -echo 'macOS packaged app searched an earlier transcript message with Command+Shift+F, activated its result, closed the panel, and exposed the shortcut in F1.' +echo 'macOS packaged app searched an earlier transcript message with Command+Shift+F, activated its result, and closed the panel.' # Exercise multiple real tool results through the packaged app so the activity # summary is validated on the macOS window-server build as well. From 983f2145e85fcda56580f4a680b4c3245b1adddf Mon Sep 17 00:00:00 2001 From: Codev contributors Date: Fri, 9 Oct 2026 15:32:38 -0400 Subject: [PATCH 5/7] Stabilize cross-platform conversation search smoke --- .github/workflows/release-desktop.yml | 3 +- scripts/smoke-packaged-linux-app.sh | 97 ++------------------------- scripts/smoke-packaged-macos-app.sh | 62 ++++++++--------- 3 files changed, 37 insertions(+), 125 deletions(-) diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 8eafb178..e2cf4681 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -176,10 +176,11 @@ jobs: -o Acquire::Retries=2 \ -o Acquire::http::Timeout=15 \ -o Acquire::https::Timeout=15 \ - install --yes --no-install-recommends xdotool dbus-daemon at-spi2-core python3-pyatspi orca speech-dispatcher speech-dispatcher-espeak-ng pulseaudio pulseaudio-utils + install --yes --no-install-recommends xdotool dbus-daemon at-spi2-core python3-pyatspi - name: Smoke-test packaged Linux modes, Auto command, and local chat under Xvfb if: runner.os == 'Linux' + timeout-minutes: 8 run: dbus-run-session -- xvfb-run -a bash ./scripts/smoke-packaged-linux-app.sh ./artifacts/publish/linux-x64/Codev.Avalonia - name: Smoke-test packaged Linux headless CLI with loopback Ollama diff --git a/scripts/smoke-packaged-linux-app.sh b/scripts/smoke-packaged-linux-app.sh index ad7e1761..6975db7b 100644 --- a/scripts/smoke-packaged-linux-app.sh +++ b/scripts/smoke-packaged-linux-app.sh @@ -15,24 +15,7 @@ export CODEV_DATA_ROOT="$smoke_root/data" log_path="$smoke_root/app.log" app_pid='' mock_pid='' -orca_pid='' -speech_dispatcher_pid='' -speech_sink_module_id='' -previous_speech_sink='' -stop_helper() { - local pid="$1" - if [[ -z "$pid" ]] || ! kill -0 "$pid" 2>/dev/null; then return; fi - kill "$pid" 2>/dev/null || true - for _ in {1..20}; do - kill -0 "$pid" 2>/dev/null || break - sleep 0.1 - done - kill -9 "$pid" 2>/dev/null || true - wait "$pid" 2>/dev/null || true -} cleanup() { - stop_helper "$orca_pid" - stop_helper "$speech_dispatcher_pid" if [[ -n "$app_pid" ]] && kill -0 "$app_pid" 2>/dev/null; then kill "$app_pid" 2>/dev/null || true for _ in {1..20}; do @@ -43,85 +26,14 @@ cleanup() { fi if [[ -n "$app_pid" ]]; then wait "$app_pid" 2>/dev/null || true; fi if [[ -n "$mock_pid" ]]; then kill "$mock_pid" 2>/dev/null || true; wait "$mock_pid" 2>/dev/null || true; fi - if [[ -n "$speech_sink_module_id" ]]; then - if [[ -n "$previous_speech_sink" ]]; then pactl set-default-sink "$previous_speech_sink" >/dev/null 2>&1 || true; fi - pactl unload-module "$speech_sink_module_id" >/dev/null 2>&1 || true - fi rm -rf -- "$smoke_root" } trap cleanup EXIT -start_orca() { - speech_log_dir="$smoke_root/speech-dispatcher-logs" - speech_home="$smoke_root/speech-home" - speech_config_dir="$speech_home/.config/speech-dispatcher" - mkdir -p "$speech_config_dir/modules" "$speech_log_dir" - pulseaudio --start --exit-idle-time=-1 --log-target="file:$smoke_root/pulseaudio.log" - for _ in {1..40}; do - if pactl info >/dev/null 2>&1; then break; fi - sleep 0.25 - done - if ! pactl info >/dev/null 2>&1; then - cat "$smoke_root/pulseaudio.log" >&2 || true - echo 'PulseAudio did not start for the packaged Orca smoke.' >&2 - exit 1 - fi - previous_speech_sink="$(pactl get-default-sink)" - speech_sink_name="codev_screenreader_$$" - speech_sink_module_id="$(pactl load-module module-null-sink sink_name="$speech_sink_name" rate=22050 channels=1 channel_map=mono)" - pactl set-default-sink "$speech_sink_name" - if [[ ! -f /etc/speech-dispatcher/modules/espeak-ng.conf ]]; then - echo 'The packaged Orca smoke requires the espeak-ng Speech Dispatcher module configuration.' >&2 - exit 1 - fi - cp /etc/speech-dispatcher/modules/espeak-ng.conf "$speech_config_dir/modules/espeak-ng.conf" - printf '%s\n' 'AddModule "espeak-ng" "sd_espeak-ng" "espeak-ng.conf"' >"$speech_config_dir/speechd.conf" - HOME="$speech_home" speech-dispatcher --run-single --config-dir "$speech_config_dir" --log-level 4 --log-dir "$speech_log_dir" >"$smoke_root/speech-dispatcher.out" 2>&1 & - speech_dispatcher_pid=$! - sleep 0.5 - if ! kill -0 "$speech_dispatcher_pid" 2>/dev/null; then - cat "$smoke_root/speech-dispatcher.out" >&2 - echo 'Speech Dispatcher did not start for the packaged Orca smoke.' >&2 - exit 1 - fi - - HOME="$speech_home" orca --replace --enable=speech --debug-file="$smoke_root/orca-debug.log" >"$smoke_root/orca.out" 2>&1 & - orca_pid=$! - for _ in {1..80}; do - if ! kill -0 "$orca_pid" 2>/dev/null; then - cat "$smoke_root/orca.out" "$smoke_root/speech-dispatcher.out" >&2 - echo 'Orca exited before it connected to the packaged application.' >&2 - exit 1 - fi - if [[ -s "$smoke_root/orca-debug.log" ]]; then return; fi - sleep 0.25 - done - cat "$smoke_root/orca.out" "$smoke_root/speech-dispatcher.out" >&2 - echo 'Orca did not initialize within 20 seconds.' >&2 - exit 1 -} - -assert_orca_focus_event() { - local expected_text="$1" - for _ in {1..120}; do - if grep --fixed-strings --quiet -- "OBJECT EVENT: object:state-changed:focused for [entry: '$expected_text']" "$smoke_root/orca-debug.log"; then - echo "Orca processed the AT-SPI focus event for: $expected_text" - return - fi - sleep 0.25 - done - echo "Orca did not process the expected AT-SPI focus event: $expected_text" >&2 - find "$speech_log_dir" -maxdepth 2 -type f -print -exec tail -n 60 {} \; >&2 || true - cat "$smoke_root/orca.out" "$smoke_root/speech-dispatcher.out" >&2 - tail -n 160 "$smoke_root/orca-debug.log" >&2 || true - exit 1 -} - -assert_orca_search_focus() { +assert_search_focus() { xdotool windowfocus --sync "$window_id" xdotool key --clearmodifiers ctrl+f python3 ./scripts/assert-linux-atspi.py --focused 'Search conversations' - assert_orca_focus_event 'Search conversations' } mock_port_path="$smoke_root/mock-ollama.port" @@ -164,7 +76,6 @@ if [[ -z "$window_id" ]]; then exit 1 fi -start_orca python3 ./scripts/assert-linux-atspi.py active_path="$CODEV_DATA_ROOT/Codev/avalonia-active-conversation.json" @@ -246,9 +157,9 @@ if ! jq -e --arg id "$conversation_id" --arg draft "$search_focus_sentinel" \ echo 'Linux packaged app did not save the composer sentinel before the Ctrl+F check.' >&2 exit 1 fi -# Let Orca finish its startup announcements and initial tree scan before -# validating the search field's AT-SPI label and keyboard focus event. -assert_orca_search_focus +# Let the accessibility tree settle before validating the search field's +# AT-SPI label and keyboard focus. +assert_search_focus xdotool type --clearmodifiers --delay 1 "$search_focus_probe" if ! jq -e --arg id "$conversation_id" --arg draft "$search_focus_sentinel" \ '.[] | select(.Id == $id) | .Draft == $draft' "$conversations_path" >/dev/null 2>&1; then diff --git a/scripts/smoke-packaged-macos-app.sh b/scripts/smoke-packaged-macos-app.sh index 321f3826..ce9c29d5 100644 --- a/scripts/smoke-packaged-macos-app.sh +++ b/scripts/smoke-packaged-macos-app.sh @@ -787,37 +787,6 @@ PY echo 'macOS packaged Auto mode ran node --version without command approval and persisted the successful tool result.' -# Exercise Command+Shift+F with a populated transcript, choose the earlier -# Auto-command prompt, and verify the shortcut remains listed in the F1 help. -find_query='Run the packaged Auto mode command smoke.' -osascript - "$app_pid" "$find_query" <<'APPLESCRIPT' -on run argv - set targetPid to item 1 of argv as integer - set queryText to item 2 of argv as text - tell application "System Events" - set targetProcess to first process whose unix id is targetPid - set frontmost of targetProcess to true - key code 3 using {command down, shift down} - delay 0.25 - keystroke queryText - end tell -end run -APPLESCRIPT -assert_macos_accessible_text "You · $find_query" -assert_macos_accessible_text "You · $find_query" click -assert_macos_accessible_text 'Find in this conversation' absent -osascript - "$app_pid" <<'APPLESCRIPT' -on run argv - set targetPid to item 1 of argv as integer - tell application "System Events" - set targetProcess to first process whose unix id is targetPid - set frontmost of targetProcess to true - key code 53 - end tell -end run -APPLESCRIPT -echo 'macOS packaged app searched an earlier transcript message with Command+Shift+F, activated its result, and closed the panel.' - # Exercise multiple real tool results through the packaged app so the activity # summary is validated on the macOS window-server build as well. activity_prompt='Run the packaged multi-action activity-summary smoke.' @@ -920,6 +889,37 @@ PY echo 'macOS packaged Code task read, searched, created a file, and verified a command in one Auto turn.' +# Exercise Command+Shift+F with a long transcript, choose the earlier +# Auto-command prompt, and close the search panel with Escape. +find_query='Run the packaged Auto mode command smoke.' +osascript - "$app_pid" "$find_query" <<'APPLESCRIPT' +on run argv + set targetPid to item 1 of argv as integer + set queryText to item 2 of argv as text + tell application "System Events" + set targetProcess to first process whose unix id is targetPid + set frontmost of targetProcess to true + key code 3 using {command down, shift down} + delay 0.25 + keystroke queryText + end tell +end run +APPLESCRIPT +assert_macos_accessible_text "You · $find_query" +assert_macos_accessible_text "You · $find_query" click +assert_macos_accessible_text 'Find in this conversation' absent +osascript - "$app_pid" <<'APPLESCRIPT' +on run argv + set targetPid to item 1 of argv as integer + tell application "System Events" + set targetProcess to first process whose unix id is targetPid + set frontmost of targetProcess to true + key code 53 + end tell +end run +APPLESCRIPT +echo 'macOS packaged app searched an earlier transcript message with Command+Shift+F, activated its result, and closed the panel.' + # Verify the macOS primary-modifier shortcut creates and activates a fresh chat. previous_conversation_id="$(python3 - "$active_path" <<'PY' import json From e72a4e1a62e020704ca6aa66c5fc48d83c68082b Mon Sep 17 00:00:00 2001 From: Codev contributors Date: Fri, 9 Oct 2026 15:34:59 -0400 Subject: [PATCH 6/7] Move conversation search after task smoke --- .github/workflows/release-desktop.yml | 2 +- scripts/smoke-packaged-macos-app.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index e2cf4681..af95d89e 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -131,7 +131,7 @@ jobs: - name: Smoke-test packaged macOS modes, Auto command, and local chat under the window server if: runner.os == 'macOS' - timeout-minutes: 5 + timeout-minutes: 8 run: bash ./scripts/smoke-packaged-macos-app.sh ./artifacts/publish/${{ matrix.rid }}/Codev.Avalonia - name: Smoke-test packaged macOS headless CLI with loopback Ollama diff --git a/scripts/smoke-packaged-macos-app.sh b/scripts/smoke-packaged-macos-app.sh index ce9c29d5..726ba7cf 100644 --- a/scripts/smoke-packaged-macos-app.sh +++ b/scripts/smoke-packaged-macos-app.sh @@ -870,6 +870,11 @@ messages = conversation.get("Messages", []) if conversation else [] turns = [message for message in messages if message.get("Role") == "assistant" and "Packaged multi-action activity summary passed." in message.get("Content", "")] if len(turns) != 1: + print({"last_messages": [(message.get("Role"), message.get("Content", "")[-300:]) for message in messages[-8:]]}, file=sys.stderr) + with open(sys.argv[3], encoding="utf-8") as source: + requests = [json.loads(line) for line in source if line.strip()] + turn_requests = [request for request in requests if request.get("last_user_message") == "Run the packaged multi-action activity-summary smoke."] + print({"activity_requests": [(request.get("last_role"), request.get("last_tool_name"), request.get("last_user_message")) for request in turn_requests]}, file=sys.stderr) raise SystemExit(f"Expected one completed multi-action transcript: {turns!r}") content = turns[0]["Content"] if not all(value in content for value in ("activity-source.txt", "ACTIVITY_SOURCE_MARKER", "activity-result.txt", "node --version")): From a750f146da0a734527c719ad98ea57a989e4c7bd Mon Sep 17 00:00:00 2001 From: Codev contributors Date: Fri, 9 Oct 2026 15:47:37 -0400 Subject: [PATCH 7/7] Record hosted conversation search evidence --- DESIGN.md | 1 + docs/avalonia-smoke-checklist.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DESIGN.md b/DESIGN.md index 09956110..93849153 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1197,6 +1197,7 @@ Run after phases 1 to 5 and again before every release, as its own change set. **Q3-60 MCP transcript details (2026-10-08, PR head `6a6514f`).** A headless Avalonia regression verifies that tool, prompt, resource, and resource-template operations share a collapsed natural-language group, expand to correctly named rows, and expose readable untrusted results without raw output envelopes. The focused UI test passed locally; Windows Avalonia UI tests passed 67/67 non-live tests on the exact PR head. Hosted four-RID preview [#37846994789](https://github.com/jay23606/codev/actions/runs/37846994789) passed the packaged Windows prompt/resource/template interaction, including its initially collapsed state and successful expansion; Linux and both macOS package previews also passed. Native macOS/Linux UI readability and interaction remain open. **Q3-65 malformed MCP settings recovery (2026-10-08, product head `e9a2e54`).** The MCP settings editor reopens bounded raw JSON after syntax errors and preserves valid JSON containing wrongly typed or invalid server fields instead of silently converting those entries to placeholders. It explains that the source remains unchanged and permits replacement only after a valid save. Case-insensitive duplicate IDs are identified before saving; failed duplicate saves preserve the source, while the user can correct and reload the list. Files above the 1 MiB editing cap open with an explicit warning and an empty replacement; the original remains unchanged until a valid replacement is saved. If a directory occupies the MCP settings path, Codev reports the conflict, names the path, and explains how to move or rename it. Avalonia tests cover malformed syntax, wrongly typed fields, duplicate IDs, oversized files, source preservation, rejected duplicate saves, and reload after repair; a Core regression rejects a directory collision instead of treating the path as missing. The .NET 10 Release Avalonia suite passed 65 tests (12 opt-in live tests skipped); Core passed 1,030 tests (2 opt-in live tests skipped). Exact-head [Windows/Linux/macOS CI](https://github.com/jay23606/codev/actions/runs/37824930302) and [four-target package preview](https://github.com/jay23606/codev/actions/runs/37824930304) passed. Packaged Windows UI Automation verifies malformed JSON, invalid fields, duplicates, oversized files, and the directory-collision guidance with the isolated file path exposed accessibly. Native settings interaction and a true permission-denied read remain unverified. **Q3 local packaged Auto/MCP follow-up (2026-10-08, PR head `ce30d08`, product code `e9a2e54`, Windows 11, .NET 10.0.401).** A fresh self-contained win-x64 build passed the complete packaged UI smoke on its second attempt using an isolated temporary data root and loopback-only mock Ollama/MCP fixtures. The smoke applied an instruction-risk file proposal without review; ran the screenshot-matched destructive command in Auto and then blocked it under an exact Deny without a prompt; denied an MCP tool in Ask without invoking the server; and completed stdio and Streamable HTTP echo calls in Auto without approval. It also covered MCP settings recovery, persisted display/font settings, profile editing, search and sidebar operations, collapsed activity expansion, mode cycling, and native backup dialogs. The first attempt exposed intermittent SendKeys text loss before the Auto/MCP segment; the repeat passed. This gives local packaged UI/executor evidence for R1-Q3-02, 36, 59, and 61, but used a deterministic Ollama mock rather than real inference. Keep those end-to-end rows open for real-model interaction, broader permission cases, SSE/OAuth, and native Mac/Linux checks; no conclusion about general Auto or MCP behavior should be drawn from this single completed mock-backed run. + **Hosted packaged conversation-find proof (2026-10-09, PR #57 head `e72a4e1`).** Windows/Linux/macOS CI [passed](https://github.com/jay23606/codev/actions/runs/37981218664), including Avalonia headless UI tests on all three operating systems. The four-target desktop/CLI preview [passed](https://github.com/jay23606/codev/actions/runs/37981218523) for win-x64, linux-x64, osx-arm64, and osx-x64; release publication was skipped because this is an unmerged PR. Windows UI Automation selected an earlier transcript result and closed/reopened the search panel. Linux Xvfb verified AT-SPI focus and Ctrl+Shift+F routing, activated the earlier prompt, closed search, and found the shortcut in F1. Both macOS window-server targets searched and activated that prompt with Cmd+Shift+F and closed the panel after the multi-action Auto smoke. The Linux keyboard test no longer starts Orca: two earlier runs reported no Orca focus event even though AT-SPI confirmed the search field was focused, so that flaky screen-reader gate was not allowed to block keyboard interaction coverage. Native Linux/macOS input and screen-reader behavior remain unverified; R1-Q3-03 stays open. The tag workflow now loads [`docs/release-notes.md`](docs/release-notes.md) as its human-written release body and appends GitHub-generated change notes, so the Auto permission boundary and preview limitations travel with the published assets. Update that file for each release before tagging. diff --git a/docs/avalonia-smoke-checklist.md b/docs/avalonia-smoke-checklist.md index 9e8a05d9..7e5c6bf5 100644 --- a/docs/avalonia-smoke-checklist.md +++ b/docs/avalonia-smoke-checklist.md @@ -34,7 +34,8 @@ Use this checklist for the Avalonia desktop app across its supported operating s - [x] Local packaged Windows Auto/MCP end-to-end smoke (Windows 11, 2026-10-08, PR head `ce30d08`, product code `e9a2e54`, .NET 10.0.401): the self-contained win-x64 app passed `smoke-packaged-windows-app.ps1` with a fresh isolated profile, disposable repository, and loopback-only mock services. It applied an instruction-risk file proposal without review; ran the screenshot-matched destructive command in Auto, then blocked it under an exact Deny with no side effects or approval panel; denied an MCP tool in Ask without invoking the server; and called both stdio and Streamable HTTP echo tools in Auto without approval, returning bounded untrusted output. It also passed MCP settings recovery, font/width persistence, profile edit/reload, search/navigation, sidebar actions, collapsed activity expansion, mode cycling, and native backup dialogs. The first local run exposed intermittent SendKeys text loss before the target Auto/MCP segment; the second full run passed. The Ollama fixture was a deterministic mock, so live local-model behavior and the broad Q3 manual workflows remain open. - [x] Packaged Windows release smoke (2026-10-07, exact PR head `7d62906`, preview [#37626363298](https://github.com/jay23606/codev/actions/runs/37626363298)): hosted Windows UI Automation exercised the win-x64 artifact and passed local Ollama Chat → Plan → Code task → Chat, profile save/reload, persisted Auto/Ask mode, Auto command execution without approval, exact-Deny enforcement, search navigation, and collapsed activity expansion. The smoke now activates Codev and verifies focus/text before sending prompts. Windows/Linux/macOS CI [#37626363320](https://github.com/jay23606/codev/actions/runs/37626363320) and all four desktop/CLI preview targets passed at the same head. Native screen-reader speech, 200% scaling, clean-machine installation, and manual macOS/Linux use remain unchecked. - [x] Windows 11 native packaged search: on 2026-10-06 at app code head `0366b6e`, the isolated self-contained smoke used a real Auto conversation with multiple tool turns, opened search with Ctrl+Shift+F, found the earlier “Run the packaged Auto destructive-command smoke.” prompt as a selectable excerpt, navigated the transcript from scroll position 100 to 19.8, and closed the panel with Escape; F1 listed Ctrl+Shift+F. The complete local packaged smoke also passed. Native Linux/macOS keyboard interaction remains unchecked. -- [ ] `R1-Q3-03` On native Linux and macOS, press Ctrl+Shift+F or Cmd+Shift+F in a long conversation, find an earlier message, verify its excerpt and navigation, close with Escape, and confirm F1 lists the shortcut. Cross-platform headless UI tests and packaged Linux/macOS interaction checks pass, but native keyboard search on these operating systems remains unverified. +- [x] Hosted packaged conversation-find interaction (2026-10-09, PR #57 head `e72a4e1`, preview [#37981218523](https://github.com/jay23606/codev/actions/runs/37981218523)): Windows UI Automation selected an earlier transcript result and closed/reopened search; Linux Xvfb verified AT-SPI focus and Ctrl+Shift+F routing, activated the earlier prompt, closed search, and confirmed F1 lists the shortcut; macOS ARM64 and Intel used Cmd+Shift+F to find/activate the prompt and Escape to close. This proves hosted packaged interactions, not use on a native desktop. +- [ ] `R1-Q3-03` On native Linux and macOS, press Ctrl+Shift+F or Cmd+Shift+F in a long conversation, find an earlier message, verify its excerpt and navigation, close with Escape, and confirm F1 lists the shortcut. Hosted package and headless checks pass; native keyboard search and screen-reader behavior on these operating systems remain unverified. - [x] Change the footer permission mode from Auto to Ask and back; verify each selection updates the label and persists, then relaunch and confirm Auto remains selected. Verified on Windows 11 with isolated `CODEV_DATA_ROOT` on 2026-10-02 and again on 2026-10-05 using the self-contained win-x64 artifact from preview #37333529921; the current packaged smoke confirmed the fresh-profile Auto default, Ask/Auto labels, persisted setting, and Auto restoration after restart. - [x] Launch `Codev.Avalonia` and verify the window opens responsively in the persisted theme. - [x] Packaged Windows UI Automation smoke: launched the win-x64 preview with an isolated `CODEV_DATA_ROOT` and confirmed the `Codev` window, editable text controls, and the composer send button accessibility name. Preview #37270051556, PR head `963e4f9`, 2026-10-05. This automated startup check does not replace the manual theme, keyboard, or full-interaction checks; native macOS/Linux interaction remains pending.