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
4 changes: 3 additions & 1 deletion .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 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
Expand Down Expand Up @@ -175,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
Expand Down
2 changes: 2 additions & 0 deletions Codev.Avalonia.Tests/MainWindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemsControl>(window.FindControl<ItemsControl>("ConversationFindResults"));
var resultButton = Assert.Single(results.GetVisualDescendants().OfType<Button>());
var resultText = Assert.Single(resultButton.GetVisualDescendants().OfType<TextBlock>());
Assert.Equal(AutomationProperties.GetName(resultButton), AutomationProperties.GetName(resultText));
await Dispatcher.UIThread.InvokeAsync(() => resultButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)));
await Dispatcher.UIThread.InvokeAsync(window.UpdateLayout, DispatcherPriority.Background);

Expand Down
2 changes: 1 addition & 1 deletion Codev.Avalonia/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@
<DataTemplate x:DataType="core:ConversationMessageMatch">
<Button Classes="nav" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Padding="8,5"
Click="ConversationFindResult_Click" AutomationProperties.Name="{Binding DisplayText}">
<TextBlock Text="{Binding DisplayText}" TextWrapping="Wrap" MaxWidth="370" />
<TextBlock Text="{Binding DisplayText}" AutomationProperties.Name="{Binding DisplayText}" TextWrapping="Wrap" MaxWidth="370" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
Expand Down
1 change: 1 addition & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
3 changes: 2 additions & 1 deletion docs/avalonia-smoke-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
57 changes: 56 additions & 1 deletion scripts/assert-linux-atspi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -61,8 +61,53 @@ 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
last_match = None
while time.monotonic() < deadline:
for node in accessible_nodes(desktop):
try:
if arguments.activate_contains not in (node.name or ""):
continue
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)
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()
last_matches = []
Expand All @@ -74,11 +119,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
Expand All @@ -89,6 +140,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)
Expand Down
Loading
Loading