[codex] Improve Companion runtime console#120
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughExtends the Companion UI and client: adds automations/workflows HTTP APIs, confirmation dialog services, UI styles, many MainWindowViewModel feature sections (Automations, Workflows, Sessions, Runtime Events, Payments, Plugins, Profiles, Providers, Dashboard), runtime-console helpers, and tests. ChangesCompanion Runtime Console Feature Expansion
🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
🤖 Augment PR SummarySummary: Refactors the Avalonia Companion into a Runtime Console-style shell and extends the integration client surface to support new operational screens. Changes:
Technical Notes: Risky mutation actions (approvals/automations/payments/workflows) now route through a confirmation prompt before calling the integration API. 🤖 Was this summary useful? React with 👍 or 👎 |
| public bool IsAssistant => Role == ChatRole.Assistant; | ||
| public bool IsSystem => Role == ChatRole.System && !IsToolEvent && !IsError; | ||
| public bool IsToolEvent => Role == ChatRole.System && | ||
| (Text.StartsWith("Agent invoked tool:", StringComparison.OrdinalIgnoreCase) || |
There was a problem hiding this comment.
ChatMessage.IsToolEvent/IsError call Text.StartsWith/Contains without a null guard; if Text is ever null (e.g., from deserialization), this will throw and break chat rendering. Consider defensively handling null (similar to how IsStreamingPlaceholder uses string.IsNullOrWhiteSpace).
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| } | ||
| }; | ||
|
|
||
| await dialog.ShowDialog(owner); |
There was a problem hiding this comment.
ConfirmAsync checks cancellationToken before showing the dialog, but cancellation while the dialog is open won’t interrupt ShowDialog(owner), so callers can still block waiting for UI input. This can cause hangs in flows that expect cancellation (shutdown/timeouts) to abort the confirmation prompt.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| partial void OnSelectedSessionChanged(SessionRow? value) | ||
| { | ||
| if (value is not null) | ||
| _ = LoadSelectedSessionAsync(value); |
There was a problem hiding this comment.
This selection-change handler starts LoadSelectedSessionAsync as fire-and-forget, which can race: a slower request for an older selection can complete later and overwrite SelectedSessionDetail/timeline with stale data. Consider guarding the apply step (e.g., compare current selection or use cancellation) to keep the UI consistent.
Severity: medium
Other Locations
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cs:35src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Profiles.cs:35
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (1)
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Providers.cs (1)
47-50: ⚡ Quick winHandle expected failures explicitly and avoid swallowing unexpected exceptions.
Catching all exceptions here makes diagnostics harder and can hide programming bugs. Prefer specific catches for operational failures and let unknown exceptions propagate.
Proposed fix
+using System.Net.Http; @@ - catch (Exception ex) + catch (OperationCanceledException ex) + { + ProvidersStatus = $"Providers load canceled: {ex.Message}"; + } + catch (HttpRequestException ex) + { + ProvidersStatus = $"Providers load failed: {ex.Message}"; + } + catch (TaskCanceledException ex) + { + ProvidersStatus = $"Providers load timed out: {ex.Message}"; + } + catch (Exception) { - ProvidersStatus = $"Providers load failed: {ex.Message}"; + throw; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Providers.cs` around lines 47 - 50, The current blanket catch(Exception ex) that sets ProvidersStatus swallows unexpected bugs; change it to catch only expected operational exceptions (e.g., IOException, ProviderLoadException or InvalidDataException) and set ProvidersStatus inside those specific catch blocks, and either remove the broad catch or rethrow for unhandled exceptions (use catch (Exception ex) { ProvidersStatus = $"Providers load failed: {ex.Message}"; throw; } if you need the message but must not swallow). Update the method containing this block (the provider-loading routine that assigns ProvidersStatus) to explicitly handle known failure types and let other exceptions propagate so they surface during debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/OpenClaw.Companion/Services/WindowConfirmationDialogService.cs`:
- Around line 15-16: The modal ShowDialog call in
WindowConfirmationDialogService (the code around the
cancellationToken.IsCancellationRequested check) doesn't honor cancellation
because ShowDialog blocks; register the provided CancellationToken to close the
dialog when cancelled by attaching a callback that calls
window.Dispatcher.Invoke(() => window.Close()) (or Dispatcher.BeginInvoke) so
the UI thread closes the modal; ensure you store and dispose/unregister the
registration (e.g., via token.Register returning a
CancellationTokenRegistration) so you remove it after ShowDialog returns; apply
the same change to the other occurrence handling the token near the ShowDialog
call around the later block (lines 73-74).
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cs`:
- Around line 32-36: OnSelectedAutomationChanged fires a fire-and-forget
LoadAutomationDetailAsync which can return out-of-order and overwrite
AutomationDetail/AutomationRunRows for a new selection; change the flow so each
load is cancellable or validated: introduce a per-viewmodel
CancellationTokenSource (or capture the requested automationId) before calling
LoadAutomationDetailAsync, pass the token (or capture id) into the async loader
(LoadAutomationDetailAsync and the code that sets
AutomationDetail/AutomationRunRows) and when the call completes verify the token
has not been cancelled (or the returned AutomationDetail.AutomationId matches
the current SelectedAutomation?.AutomationId) before applying results to
AutomationDetail/AutomationRunRows; also cancel the previous token (or bump a
request id) at the start of OnSelectedAutomationChanged to prevent stale
responses from applying.
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Payments.cs`:
- Around line 92-95: The catch block that sets PaymentsStatus on failure should
also clear stale funding options: in the catch handling around the Payments
setup load (where PaymentsStatus = $"Payment setup load failed: {ex.Message}";)
ensure FundingSourceRows is reset (e.g., clear the collection or replace it with
an empty list/observable) so the UI no longer shows previously loaded
providers/environments, then set PaymentsStatus with the error message as
before.
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Profiles.cs`:
- Around line 32-35: OnSelectedProfileChanged currently launches
LoadProfileDetailAsync without guarding against out-of-order completions; change
the flow so when SelectedProfile is null you immediately clear
SelectedProfileDetail, and inside the async load (LoadProfileDetailAsync / the
GetProfileAsync continuation) capture the actorId you requested and before
assigning SelectedProfileDetail verify that SelectedProfile is non-null and
SelectedProfile.ActorId equals that captured actorId, skipping the update if
they differ (thus preventing stale A results from overwriting B).
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Sessions.cs`:
- Around line 57-170: The PR added substantial sessions logic but lacks focused
unit tests for VM behaviors; add tests covering LoadSessionsAsync (filter
application, deduping via SessionRow.FromSummary, SessionsStatus text),
pagination via NextSessionsPageAsync/PreviousSessionsPageAsync (SessionsPage
increment/decrement and SessionsHasMore behavior), snippet enrichment via
LoadSessionSnippetsAsync (search triggers and snippet mapping), and selection
detail loading via LoadSelectedSessionAsync (SelectedSessionDetail content and
timeline rows via ReplaceItems SessionTimelineRows). Use mocks/stubs of
OpenClawHttpClient to return controlled ListSessionsAsync, SearchSessionsAsync,
GetSessionAsync, and GetSessionTimelineAsync responses, and assert
SessionRows/HasSessionRows, SessionsHasMore, SessionsStatus, SessionsPage and
SelectedSessionDetail/HasSessionTimelineRows are updated as expected.
- Around line 51-55: The async load for session details can be overwritten by
stale responses; modify OnSelectedSessionChanged and LoadSelectedSessionAsync to
guard against this by capturing an identifier for the selection (e.g.,
SelectedSession?.Id or the SessionRow reference) at the start of
LoadSelectedSessionAsync and verifying it still matches SelectedSession before
applying results; alternatively add a CancellationTokenSource field (e.g.,
_loadSelectedSessionCts) that you cancel in OnSelectedSessionChanged and pass
into LoadSelectedSessionAsync to abort prior work. Also, when an awaited call
fails, only clear or set TimelineRows (or the prior timeline UI) if the
selection still matches the captured id/token to avoid leaving prior timeline
rows visible.
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Workflows.cs`:
- Around line 70-111: The RunSelectedWorkflowAsync and RespondWorkflowInputAsync
methods lack an in-flight guard and can be re-entered; add a boolean
flag/property (reuse the existing IsWorkflowsBusy pattern) and at the top of
each method check if IsWorkflowsBusy then return, set IsWorkflowsBusy = true
before doing external calls (before
RequireIntegrationClient/RunWorkflowAsync/any client mutation) and reset
IsWorkflowsBusy = false in a finally block so it always clears; ensure
UI-updates (WorkflowsStatus, WorkflowRunId, ReplaceItems, OnPropertyChanged)
remain the same and do not run when the guard prevents re-entry.
- Around line 174-182: ApplyWorkflowSnapshot replaces the WorkflowPendingInputs
collection but doesn't validate SelectedWorkflowPendingInput, allowing a stale
selection; update ApplyWorkflowSnapshot to reconcile the selection after calling
ReplaceItems(WorkflowPendingInputs, ...) by finding a matching item (e.g., by Id
or Port) in the new WorkflowPendingInputs (items produced by
WorkflowPendingInputRow.FromInput) and keep SelectedWorkflowPendingInput if
found, otherwise set SelectedWorkflowPendingInput to null, then raise property
change for SelectedWorkflowPendingInput as needed.
In `@src/OpenClaw.Tests/CompanionCanvasUiTests.cs`:
- Around line 59-60: Replace brittle numeric selection and plain text checks by
locating the TabItem whose Header equals the target label (e.g., "Sessions"),
set tabControl.SelectedItem to that TabItem, run Dispatcher.UIThread.RunJobs(),
and then assert the tabControl.SelectedItem (or SelectedIndex) equals that
TabItem and that its Content (or the view/control inside SelectedContent) is the
expected active section. Apply the same change for the other occurrences around
lines 113-117 so tests select by header and verify the selected TabItem and its
content rather than relying on header text anywhere in the UI chrome.
---
Nitpick comments:
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Providers.cs`:
- Around line 47-50: The current blanket catch(Exception ex) that sets
ProvidersStatus swallows unexpected bugs; change it to catch only expected
operational exceptions (e.g., IOException, ProviderLoadException or
InvalidDataException) and set ProvidersStatus inside those specific catch
blocks, and either remove the broad catch or rethrow for unhandled exceptions
(use catch (Exception ex) { ProvidersStatus = $"Providers load failed:
{ex.Message}"; throw; } if you need the message but must not swallow). Update
the method containing this block (the provider-loading routine that assigns
ProvidersStatus) to explicitly handle known failure types and let other
exceptions propagate so they surface during debugging.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 31b84ea1-faa1-4b9a-a840-fdcc17f35e47
📒 Files selected for processing (23)
src/OpenClaw.Client/OpenClawHttpClient.cssrc/OpenClaw.Companion/App.axamlsrc/OpenClaw.Companion/App.axaml.cssrc/OpenClaw.Companion/Models/ChatMessage.cssrc/OpenClaw.Companion/Services/IConfirmationDialogService.cssrc/OpenClaw.Companion/Services/WindowConfirmationDialogService.cssrc/OpenClaw.Companion/Styles/CompanionStyles.axamlsrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Approvals.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Dashboard.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.ManagedGateway.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Payments.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Plugins.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Profiles.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Providers.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeConsoleHelpers.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeEvents.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Sessions.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Workflows.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.cssrc/OpenClaw.Companion/Views/MainWindow.axamlsrc/OpenClaw.Tests/CompanionCanvasUiTests.cssrc/OpenClaw.Tests/CompanionRuntimeConsoleTests.cs
| [RelayCommand] | ||
| private async Task LoadSessionsAsync() | ||
| { | ||
| if (IsSessionsBusy) | ||
| return; | ||
|
|
||
| IsSessionsBusy = true; | ||
| try | ||
| { | ||
| if (!RequireIntegrationClient(out var client, status => SessionsStatus = status) || client is null) | ||
| return; | ||
|
|
||
| var query = new SessionListQuery | ||
| { | ||
| Search = EmptyToNull(SessionsSearchText), | ||
| ChannelId = EmptyToNull(SessionsChannelId), | ||
| SenderId = EmptyToNull(SessionsSenderId), | ||
| State = TryParseSessionState(SessionsState), | ||
| Starred = SessionsStarredOnly ? true : null, | ||
| Tag = EmptyToNull(SessionsTag) | ||
| }; | ||
| var response = await client.ListSessionsAsync(SessionsPage, pageSize: 25, query, CancellationToken.None); | ||
| var snippets = await LoadSessionSnippetsAsync(client); | ||
| var rows = response.Active.Select(item => SessionRow.FromSummary(item, "active", snippets)) | ||
| .Concat(response.Persisted.Items.Select(item => SessionRow.FromSummary(item, "persisted", snippets))) | ||
| .GroupBy(static row => row.SessionId, StringComparer.Ordinal) | ||
| .Select(static group => group.First()) | ||
| .ToList(); | ||
|
|
||
| SessionsHasMore = response.Persisted.HasMore; | ||
| ReplaceItems(SessionRows, rows); | ||
| OnPropertyChanged(nameof(HasSessionRows)); | ||
| SessionsStatus = rows.Count == 0 | ||
| ? "No sessions match the current filters." | ||
| : $"{rows.Count} session{(rows.Count == 1 ? "" : "s")} loaded."; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| SessionsStatus = $"Sessions load failed: {ex.Message}"; | ||
| } | ||
| finally | ||
| { | ||
| IsSessionsBusy = false; | ||
| } | ||
| } | ||
|
|
||
| [RelayCommand] | ||
| private async Task NextSessionsPageAsync() | ||
| { | ||
| if (!SessionsHasMore) | ||
| return; | ||
|
|
||
| SessionsPage++; | ||
| await LoadSessionsAsync(); | ||
| } | ||
|
|
||
| [RelayCommand] | ||
| private async Task PreviousSessionsPageAsync() | ||
| { | ||
| if (SessionsPage <= 1) | ||
| return; | ||
|
|
||
| SessionsPage--; | ||
| await LoadSessionsAsync(); | ||
| } | ||
|
|
||
| private async Task<Dictionary<string, string>> LoadSessionSnippetsAsync(OpenClaw.Client.OpenClawHttpClient client) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(SessionsSearchText)) | ||
| return new Dictionary<string, string>(StringComparer.Ordinal); | ||
|
|
||
| var search = await client.SearchSessionsAsync(new SessionSearchQuery | ||
| { | ||
| Text = SessionsSearchText, | ||
| ChannelId = EmptyToNull(SessionsChannelId), | ||
| SenderId = EmptyToNull(SessionsSenderId), | ||
| Limit = 25, | ||
| SnippetLength = 180 | ||
| }, CancellationToken.None); | ||
|
|
||
| return search.Result.Items | ||
| .GroupBy(static item => item.SessionId, StringComparer.Ordinal) | ||
| .ToDictionary(static group => group.Key, static group => group.First().Snippet, StringComparer.Ordinal); | ||
| } | ||
|
|
||
| private async Task LoadSelectedSessionAsync(SessionRow row) | ||
| { | ||
| try | ||
| { | ||
| if (!RequireIntegrationClient(out var client, status => SelectedSessionDetail = status) || client is null) | ||
| return; | ||
|
|
||
| var detail = await client.GetSessionAsync(row.SessionId, CancellationToken.None); | ||
| var timeline = await client.GetSessionTimelineAsync(row.SessionId, 100, CancellationToken.None); | ||
| SelectedSessionDetail = string.Join(Environment.NewLine, new[] | ||
| { | ||
| $"Session: {row.SessionId}", | ||
| $"Channel: {row.ChannelId}", | ||
| $"Sender: {row.SenderId}", | ||
| $"State: {row.State}", | ||
| $"Active: {detail.IsActive}", | ||
| $"Branches: {detail.BranchCount}", | ||
| $"History turns: {row.HistoryTurns}", | ||
| $"Tokens: {row.TotalTokens}" | ||
| }); | ||
| ReplaceItems(SessionTimelineRows, timeline.Events.Select(RuntimeEventRow.FromEntry)); | ||
| OnPropertyChanged(nameof(HasSessionTimelineRows)); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| SelectedSessionDetail = $"Session detail load failed: {ex.Message}"; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
Add focused tests for sessions query/pagination/selection behavior.
This introduces substantial sessions logic (filters, paging, dedupe, snippet enrichment, detail/timeline loading) but the provided test changes don’t include targeted sessions VM coverage.
As per coding guidelines, **/*.{cs,csproj}: Update tests where appropriate when making changes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Sessions.cs` around
lines 57 - 170, The PR added substantial sessions logic but lacks focused unit
tests for VM behaviors; add tests covering LoadSessionsAsync (filter
application, deduping via SessionRow.FromSummary, SessionsStatus text),
pagination via NextSessionsPageAsync/PreviousSessionsPageAsync (SessionsPage
increment/decrement and SessionsHasMore behavior), snippet enrichment via
LoadSessionSnippetsAsync (search triggers and snippet mapping), and selection
detail loading via LoadSelectedSessionAsync (SelectedSessionDetail content and
timeline rows via ReplaceItems SessionTimelineRows). Use mocks/stubs of
OpenClawHttpClient to return controlled ListSessionsAsync, SearchSessionsAsync,
GetSessionAsync, and GetSessionTimelineAsync responses, and assert
SessionRows/HasSessionRows, SessionsHasMore, SessionsStatus, SessionsPage and
SelectedSessionDetail/HasSessionTimelineRows are updated as expected.
There was a problem hiding this comment.
Pull request overview
This PR refactors the Avalonia Companion into a broader Runtime Console shell, adding navigation and operator-facing screens for runtime administration, workflows, automations, sessions, payments, plugins, providers, and memory/profile surfaces.
Changes:
- Reworks the Companion UI shell with a left-nav tab layout, shared styles, dashboard, and expanded admin/setup/chat/canvas/WhatsApp/payment screens.
- Adds typed runtime-console view-model surfaces and client methods for sessions, events, plugins, providers, workflows, automations, profiles, and payments.
- Adds confirmation dialog plumbing for risky actions plus focused UI/view-model tests.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
src/OpenClaw.Tests/CompanionRuntimeConsoleTests.cs |
Adds VM/runtime-console behavior tests. |
src/OpenClaw.Tests/CompanionCanvasUiTests.cs |
Updates canvas tab index and adds runtime shell UI coverage. |
src/OpenClaw.Companion/Views/MainWindow.axaml |
Rebuilds Companion UI into Runtime Console shell and screens. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Workflows.cs |
Adds workflow list/run/response VM support. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Sessions.cs |
Adds session list/detail/timeline VM support. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeEvents.cs |
Adds runtime event query/detail VM support. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeConsoleHelpers.cs |
Adds navigation, badges, confirmation, and shared helpers. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Providers.cs |
Adds providers/tool-presets VM support. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Profiles.cs |
Adds profiles, memory notes, and learning proposal VM support. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Plugins.cs |
Adds plugins, compatibility, and channel readiness VM support. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Payments.cs |
Adds Payment Lab VM commands and rows. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.ManagedGateway.cs |
Updates derived setup/local-model notifications. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Dashboard.cs |
Adds dashboard metrics and summary collections. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.cs |
Adds message collection notifications. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cs |
Adds automation list/detail/run/delete VM support. |
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Approvals.cs |
Adds confirmation gates for approval decisions and badge updates. |
src/OpenClaw.Companion/Styles/CompanionStyles.axaml |
Adds shared Companion UI styles. |
src/OpenClaw.Companion/Services/WindowConfirmationDialogService.cs |
Adds Avalonia modal confirmation service. |
src/OpenClaw.Companion/Services/IConfirmationDialogService.cs |
Adds confirmation service abstraction and deny-by-default implementation. |
src/OpenClaw.Companion/Models/ChatMessage.cs |
Adds chat message classification helpers. |
src/OpenClaw.Companion/App.axaml.cs |
Wires confirmation dialog service into the main window. |
src/OpenClaw.Companion/App.axaml |
Includes shared Companion styles. |
src/OpenClaw.Client/OpenClawHttpClient.cs |
Adds workflow and automation-run client APIs. |
| client = CreateIntegrationClient(out var error); | ||
| if (client is not null) | ||
| return true; | ||
|
|
||
| setStatus(error ?? "Invalid gateway URL."); | ||
| return false; |
| partial void OnSelectedSessionChanged(SessionRow? value) | ||
| { | ||
| if (value is not null) | ||
| _ = LoadSelectedSessionAsync(value); |
| partial void OnSelectedProfileChanged(ProfileRow? value) | ||
| { | ||
| if (value is not null) | ||
| _ = LoadProfileDetailAsync(value.ActorId); |
| partial void OnSelectedAutomationChanged(AutomationRow? value) | ||
| { | ||
| if (value is not null) | ||
| _ = LoadAutomationDetailAsync(value.AutomationId); |
| var query = new SessionListQuery | ||
| { | ||
| Search = EmptyToNull(SessionsSearchText), | ||
| ChannelId = EmptyToNull(SessionsChannelId), | ||
| SenderId = EmptyToNull(SessionsSenderId), | ||
| State = TryParseSessionState(SessionsState), | ||
| Starred = SessionsStarredOnly ? true : null, | ||
| Tag = EmptyToNull(SessionsTag) | ||
| }; | ||
| var response = await client.ListSessionsAsync(SessionsPage, pageSize: 25, query, CancellationToken.None); |
| WorkflowRunDetail = BuildWorkflowRunText(result.WorkflowId, result.RunId, result.Status, result.BackendId, result.Output, result.Error); | ||
| ReplaceItems(WorkflowEventRows, result.Events.Select(WorkflowEventRow.FromEvent)); | ||
| ReplaceItems(WorkflowPendingInputs, []); |
| private static string ToIndentedJson<T>(T value) | ||
| { | ||
| try | ||
| { | ||
| return JsonSerializer.Serialize(value, new JsonSerializerOptions { WriteIndented = true }); | ||
| } |
| <Grid Grid.Row="0" ColumnDefinitions="*,Auto"><StackPanel><TextBlock Text="Workflows" Classes="page-title" /><TextBlock Text="{Binding WorkflowsStatus}" Classes="muted" /></StackPanel><Button Grid.Column="1" Content="Load" Command="{Binding LoadWorkflowsCommand}" Classes="primary" /></Grid> | ||
| <Grid Grid.Row="1" ColumnDefinitions="2*,3*" ColumnSpacing="12"> | ||
| <Border Grid.Column="0" Classes="section-card"><ListBox ItemsSource="{Binding WorkflowRows}" SelectedItem="{Binding SelectedWorkflow}" Background="Transparent" BorderThickness="0"><ListBox.ItemTemplate><DataTemplate DataType="vm:WorkflowRow"><StackPanel Margin="0,0,0,8"><TextBlock Text="{Binding DisplayName}" FontWeight="SemiBold" /><TextBlock Text="{Binding WorkflowName}" Classes="muted" /><TextBlock Text="{Binding Status}" Classes="muted" /></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox></Border> | ||
| <Border Grid.Column="1" Classes="section-card"><Grid RowDefinitions="Auto,Auto,*,Auto" RowSpacing="8"><Grid ColumnDefinitions="*,Auto"><TextBox Text="{Binding WorkflowInput}" Watermark="Workflow input" AcceptsReturn="True" MinHeight="58" TextWrapping="Wrap" /><Button Grid.Column="1" Content="Run" Command="{Binding RunSelectedWorkflowCommand}" Margin="8,0,0,0" /></Grid><Grid Grid.Row="1" ColumnDefinitions="*,Auto"><TextBox Text="{Binding WorkflowRunId}" Watermark="Run id" /><Button Grid.Column="1" Content="Load Run" Command="{Binding LoadWorkflowRunCommand}" Margin="8,0,0,0" /></Grid><TextBox Grid.Row="2" Text="{Binding WorkflowRunDetail}" IsReadOnly="True" AcceptsReturn="True" Classes="monospace" TextWrapping="Wrap" /><Grid Grid.Row="3" ColumnDefinitions="*,Auto"><ComboBox ItemsSource="{Binding WorkflowPendingInputs}" SelectedItem="{Binding SelectedWorkflowPendingInput}" MinWidth="180"><ComboBox.ItemTemplate><DataTemplate DataType="vm:WorkflowPendingInputRow"><TextBlock Text="{Binding PortId}" /></DataTemplate></ComboBox.ItemTemplate></ComboBox><Button Grid.Column="1" Content="Send Response" Command="{Binding RespondWorkflowInputCommand}" Margin="8,0,0,0" /></Grid></Grid></Border> |
| <Grid RowDefinitions="Auto,*" RowSpacing="10" Margin="12,0,0,0"> | ||
| <Grid Grid.Row="0" ColumnDefinitions="*,Auto"><StackPanel><TextBlock Text="Automations" Classes="page-title" /><TextBlock Text="{Binding AutomationsStatus}" Classes="muted" /></StackPanel><Button Grid.Column="1" Content="Load" Command="{Binding LoadAutomationsCommand}" Classes="primary" /></Grid> | ||
| <Grid Grid.Row="1" ColumnDefinitions="2*,3*" ColumnSpacing="12"> | ||
| <Border Grid.Column="0" Classes="section-card"><ListBox ItemsSource="{Binding AutomationRows}" SelectedItem="{Binding SelectedAutomation}" Background="Transparent" BorderThickness="0"><ListBox.ItemTemplate><DataTemplate DataType="vm:AutomationRow"><StackPanel Margin="0,0,0,8"><TextBlock Text="{Binding Name}" FontWeight="SemiBold" /><TextBlock Text="{Binding Schedule}" Classes="muted" /><TextBlock Text="{Binding State}" Classes="muted" /></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox></Border> |
| </Style> | ||
|
|
||
| <Style Selector="Button.danger"> | ||
| <Setter Property="Foreground" Value="#ff453a" /> |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cs (1)
33-44:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClear stale run state immediately when automation selection changes.
When selection changes to a new automation, previous
SelectedAutomationRun/AutomationRunRowsstay active until the async detail load finishes. In that window, replay can pair a stale run ID with the newly selected automation ID.Suggested fix
partial void OnSelectedAutomationChanged(AutomationRow? value) { + SelectedAutomationRun = null; + ReplaceItems(AutomationRunRows, []); + OnPropertyChanged(nameof(HasAutomationRunRows)); + if (value is null) { AutomationDetail = "Select an automation to inspect run state."; - ReplaceItems(AutomationRunRows, []); - OnPropertyChanged(nameof(HasAutomationRunRows)); return; } _ = LoadAutomationDetailAsync(value.AutomationId); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cs` around lines 33 - 44, OnSelectedAutomationChanged should clear any stale run state immediately when selection changes: when value is null do as now, and when value is non-null first set SelectedAutomationRun = null, clear AutomationRunRows via ReplaceItems(AutomationRunRows, Array.Empty<AutomationRunRow>()), update AutomationDetail to a loading/placeholder string and raise OnPropertyChanged(nameof(HasAutomationRunRows)) before calling _ = LoadAutomationDetailAsync(value.AutomationId); this ensures SelectedAutomationRun/AutomationRunRows are reset synchronously so an in-flight LoadAutomationDetailAsync cannot leave a stale run ID paired with the new automation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeConsoleHelpers.cs`:
- Around line 30-31: AttachConfirmationDialogService should guard against a null
injection: validate the dialogService parameter in
AttachConfirmationDialogService and throw an ArgumentNullException (or similar)
if it's null instead of assigning it blindly to _confirmationDialogService;
update the method that sets _confirmationDialogService
(AttachConfirmationDialogService) to perform the null check so later
confirmation requests won't hit a NullReferenceException.
---
Duplicate comments:
In `@src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cs`:
- Around line 33-44: OnSelectedAutomationChanged should clear any stale run
state immediately when selection changes: when value is null do as now, and when
value is non-null first set SelectedAutomationRun = null, clear
AutomationRunRows via ReplaceItems(AutomationRunRows,
Array.Empty<AutomationRunRow>()), update AutomationDetail to a
loading/placeholder string and raise
OnPropertyChanged(nameof(HasAutomationRunRows)) before calling _ =
LoadAutomationDetailAsync(value.AutomationId); this ensures
SelectedAutomationRun/AutomationRunRows are reset synchronously so an in-flight
LoadAutomationDetailAsync cannot leave a stale run ID paired with the new
automation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 84bbe229-a74b-43cd-a187-3c9189e482c2
📒 Files selected for processing (18)
src/OpenClaw.Companion/Models/ChatMessage.cssrc/OpenClaw.Companion/Services/WindowConfirmationDialogService.cssrc/OpenClaw.Companion/Styles/CompanionStyles.axamlsrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Automations.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Dashboard.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Payments.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Plugins.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Profiles.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Providers.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeConsoleHelpers.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeEvents.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Sessions.cssrc/OpenClaw.Companion/ViewModels/MainWindowViewModel.Workflows.cssrc/OpenClaw.Companion/Views/MainWindow.axamlsrc/OpenClaw.Tests/CompanionCanvasUiTests.cssrc/OpenClaw.Tests/CompanionRuntimeConsoleTests.cssrc/OpenClaw.Tests/LocalInferenceSupervisorTests.cssrc/OpenClaw.Tests/ManagedGatewayServiceTests.cs
✅ Files skipped from review due to trivial changes (1)
- src/OpenClaw.Companion/Styles/CompanionStyles.axaml
🚧 Files skipped from review as they are similar to previous changes (10)
- src/OpenClaw.Companion/Models/ChatMessage.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Providers.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.RuntimeEvents.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Dashboard.cs
- src/OpenClaw.Companion/Services/WindowConfirmationDialogService.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Plugins.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Payments.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Profiles.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Workflows.cs
- src/OpenClaw.Companion/ViewModels/MainWindowViewModel.Sessions.cs
| private bool _isAutomationsBusy; | ||
|
|
||
| [ObservableProperty] | ||
| private string _automationsStatus = "Automations not loaded."; |
| private AutomationRunRow? _selectedAutomationRun; | ||
|
|
||
| [ObservableProperty] | ||
| private string _automationDetail = "Select an automation to inspect run state."; |
| private bool _isDashboardBusy; | ||
|
|
||
| [ObservableProperty] | ||
| private string _dashboardStatus = "Dashboard not loaded."; |
| private int _dashboardChannelsReady; | ||
|
|
||
| [ObservableProperty] | ||
| private string _dashboardLastRefreshed = "never"; |
| private bool _isPaymentsBusy; | ||
|
|
||
| [ObservableProperty] | ||
| private string _paymentsStatus = "Payment Lab not loaded."; |
| private string _paymentsStatus = "Payment Lab not loaded."; | ||
|
|
||
| [ObservableProperty] | ||
| private string _paymentProvider = ""; |
| private string _paymentProvider = ""; | ||
|
|
||
| [ObservableProperty] | ||
| private string _paymentEnvironment = PaymentEnvironments.Test; |
| private string _paymentEnvironment = PaymentEnvironments.Test; | ||
|
|
||
| [ObservableProperty] | ||
| private string _paymentSetupSummary = "Load setup status before running payment actions."; |
| private string _paymentSetupSummary = "Load setup status before running payment actions."; | ||
|
|
||
| [ObservableProperty] | ||
| private string _virtualCardMerchantName = ""; |
Summary
Validation
dotnet format OpenClaw.Net.slnx --include ...scoped to touched C# filesdotnet build OpenClaw.Net.slnxdotnet test src/OpenClaw.Tests/OpenClaw.Tests.csproj --filter "FullyQualifiedName~Companion|FullyQualifiedName~Integration|FullyQualifiedName~Payment"dotnet test src/OpenClaw.Tests/OpenClaw.Tests.csproj --no-buildgit diff --checkSummary by CodeRabbit
New Features
UI/Style
Tests