feat: wire Bus Inspector into shell#206
Open
runyaga wants to merge 4 commits into
Open
Conversation
Adds an additive `addObserver` hook on `StateBus` that fires after every successful commit, plus an optional `tag` argument on `setAgentState` and `update` so writers can label the source of each write. Observers receive `(tag, snapshot)`; the snapshot is the frozen post-commit map already exposed via `agentState`. `addObserver` returns a disposer for symmetric tear-down. Detaching during dispatch is safe (siblings still fire). Adding to a disposed bus is a no-op. Pure additive: existing call sites continue to work without modification, and the new `BusObserver` typedef is exported via the existing `application` barrel. 11 tests cover the observer dispatch ordering, dispose semantics, self-detaching observers, tag preservation, and disposed-bus safety. Foundation for an upcoming Bus Inspector debug surface that needs to record every state commit on a per-thread basis without forcing a bus dependency on the agent layer's diagnostics.
Adds two optional observer hooks at the runtime layer for diagnostics consumers (e.g. an in-app Bus Inspector) without coupling the agent to any specific consumer: - `ThreadBusObserver` — receives every per-thread `StateBus` commit with the originating `ThreadKey`. Wired into the bus the first time the runtime materialises a `ThreadState`. Supports the `tag:` argument added in the StateBus observer PR so seed paths and other writers label their commits. - `ThreadEventObserver` — receives every raw AG-UI `BaseEvent` paired with the `ThreadKey` it was processed on. Forwarded from `AgentSession._bridgeBaseEvent` via a package-private `notifyThreadEvent` helper. The runtime makes no behavioural decision on the event; it only fans out. Tags `seedThreadState` and `seedThreadHistory` writes as `seed.initial` and `seed.history` respectively so consumers can distinguish initial state restoration from in-session updates. Bus writes inside `AgentSession._onStateChange` remain untagged at this layer — diagnostics consumers correlate them with the most recent AG-UI event observed via `eventObserver` to infer whether the commit came from a `StateSnapshotEvent`, `StateDeltaEvent`, or a non-state run-state transition. This keeps the agent diagnostics-agnostic. Test exercises the bus-observer wiring end-to-end via the seed APIs and verifies the per-thread context (key + tag + snapshot) is delivered to the observer.
Self-contained diagnostics module under `lib/src/modules/diagnostics/` providing an in-app inspector for the per-thread `StateBus` plus the AG-UI event stream that drives it. All new files; no existing call sites change. Components: - `BusInspector` — `ChangeNotifier` recorder with two bounded queues (commits + raw events). Sinks shaped to match `ThreadBusObserver` (`record`) and `ThreadEventObserver` (`recordEvent`) so the host flavor wires it through `AgentRuntime` directly. - Tag inference — `recordEvent` tracks the most recent `StateSnapshotEvent` / `StateDeltaEvent` per thread; when an untagged bus commit arrives, the inspector labels it `agui.snapshot`, `agui.delta`, or `agui.run-state` accordingly. Explicit tags (e.g. `seed.initial` / `seed.history` from `AgentRuntime`) pass through verbatim. Event records derive their own tag from the runtime type (`ActivitySnapshotEvent` → `agui.activitysnapshot`). - `snapshot_diff.dart` — pure-Dart structural differ. Compares two `Map<String, dynamic>` snapshots and emits added/removed/replaced changes with slash-joined paths. Walks added/removed subtrees recursively so a fresh nested branch surfaces as one leaf path per value, not a single coarse `/parent` change. - `bus_filter.dart` — small filter mini-language with prefixes `thread:`, `room:`, `server:`, `tag:`, `path:`, and `kind:bus|event`, plus bare-text fallback that matches against any of the above (and changed paths for bus rows). `tag:foo*` is a prefix glob. `suggestionsFor(...)` powers the autocomplete chip strip in the screen, drawing values from the live event buffer. - `BusInspectorScreen` — master-detail UI. Sidebar lists "All events" plus one row per active thread (color dot, room id, short thread id, last-event time, count). Right pane shows a filter `TextField` with autocomplete chips and active-filter pills (each removable via X), then a unified timestamp-ordered timeline mixing bus rows and event rows distinguished by a `bus`/`event` kind badge. Bus row detail renders the diff plus a collapsible full snapshot via the existing `JsonTreeView`; event row detail renders the event's `toJson()` payload via the same view. `TextField.onTapOutside` is a no-op so the field keeps focus while users tap suggestion chips — without this the rebuild triggered by the focus change unmounts the chip before its `onPressed` can fire. Tests: ~50 unit tests across the differ, filter parser/matcher, suggestion generator, and `BusInspector` recording semantics.
Surfaces the diagnostics module as a live debug UI: - `DiagnosticsAppModule` accepts a `BusInspector` alongside the existing `NetworkInspector`. Override exposes `busInspectorProvider`; new `GoRoute` at `/diagnostics/bus` builds `BusInspectorScreen`. Inspector is disposed when the module disposes. - `AgentRuntimeManager` accepts and forwards optional `busObserver` and `eventObserver` parameters to every `AgentRuntime` it materialises, so all per-thread bus commits and raw AG-UI events flow into the inspector regardless of which server hosts the thread. - `standard()` constructs `BusInspector`, wires `busInspector.record` as the runtime manager's `busObserver`, `busInspector.recordEvent` as the `eventObserver`, and passes the inspector to `DiagnosticsAppModule`. - `ThreadSidebar` and `ServerSidebar` render a "Bus Inspector" `TextButton.icon` directly below the existing "Network Inspector" entry. `RoomScreen` and `LobbyScreen` push `/diagnostics/bus` via `go_router` from the new callback. - Existing widget tests updated to pass an `onBusInspector` no-op so the new required parameter does not break them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #202 (4 of 4 stacked PRs). Depends on #203, #204, and #205 — merge those first then rebase this.
What this changes
Surfaces the Bus Inspector module as a live debug UI in the running
app. Mirrors how Network Inspector is wired today.
existing `NetworkInspector`. Override exposes
`busInspectorProvider`; new `GoRoute` at `/diagnostics/bus` builds
`BusInspectorScreen`. Inspector is disposed when the module
disposes.
and `eventObserver` parameters to every `AgentRuntime` it
materialises, so all per-thread bus commits and raw AG-UI events
flow into the inspector regardless of which server hosts the
thread.
`busInspector.record` as the runtime manager's `busObserver`,
`busInspector.recordEvent` as the `eventObserver`, and passes the
inspector to `DiagnosticsAppModule`.
`TextButton.icon` directly below the existing "Network Inspector"
entry. `RoomScreen` and `LobbyScreen` push `/diagnostics/bus` via
`go_router` from the new callback.
so the new required parameter does not break them.
Testing
`flutter analyze` clean, `flutter test` 1206/1206 pass on the full
stack. Manual smoke tested on macOS: open a room, click "Bus
Inspector", filter by `tag:agui.activitysnapshot` etc., verify the
unified timeline shows both bus commits and AG-UI events with
correct tags and per-thread color stripes.
Stack