Skip to content

feat: surface CLI subagent/skill events in chat + /agent command#445

Open
PureWeen wants to merge 7 commits intomainfrom
feature/cli-agent-visibility
Open

feat: surface CLI subagent/skill events in chat + /agent command#445
PureWeen wants to merge 7 commits intomainfrom
feature/cli-agent-visibility

Conversation

@PureWeen
Copy link
Copy Markdown
Owner

Summary

Improves Copilot CLI feature parity by making the CLI's built-in agent system visible to PolyPilot users.

What's new

1. Subagent activity shown in chat

When the Copilot CLI automatically invokes a specialized agent (e.g. code-review, security-review) in response to a prompt, PolyPilot now shows system messages in the chat thread:

  • 🤖 Agent: **Code Review** — agent selected by CLI
  • ▶️ Starting agent: **Code Review** — description — agent invocation began
  • Agent completed: **Code Review** — success
  • Agent failed: **Code Review**: error — failure with error detail

The active agent name/display name is also tracked on AgentSessionInfo and cleared on completion/deselect.

Previously these events fell through to LogUnhandledSessionEvent with no user-visible feedback.

2. SkillInvokedEvent shown in chat

When the CLI invokes a skill plugin, a ⚡ system message is shown: Skill: **skillName (plugin)**.

3. Active agent badge in session info panel

When a subagent is active, a 🤖 AgentName badge appears in the ExpandedSessionView info strip alongside the model selector.

4. /agent slash command

New /agent command with three modes:

  • /agent — lists available agents from both local discovery (.github/agents/, .claude/agents/, .copilot/agents/) and the live SDK AgentApi.ListAsync()
  • /agent <name> — selects an agent via AgentApi.SelectAsync(name)
  • /agent deselect — deselects the current agent via AgentApi.DeselectAsync()

Added to autocomplete in index.html and updated /help output.

SdkEventMatrix updates

  • SubagentSelectedEvent, SubagentDeselectedEvent, SubagentStartedEvent, SubagentCompletedEvent, SubagentFailedEvent: TimelineOnlyChatVisible
  • SkillInvokedEvent: TimelineOnlyChatVisible

Tests

  • SlashCommandAutocompleteTests updated to include /agent in the withArgs list
  • All 2987 tests pass; MAUI Mac Catalyst build succeeds with 0 errors

Copy link
Copy Markdown
Owner Author

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #445 — feat: surface CLI subagent/skill events in chat + /agent command

⚠️ Breaking change: /reflect command removed

The diff removes the /reflect case from the command switch and replaces it with /fleet. The HandleReflectCommand method is deleted from Dashboard.razor. The InsertReflectCommand shortcut button is also removed from ExpandedSessionView.razor.

This is a significant behavioral removal. /reflect was the user-facing entry point for reflection cycles. However:

  • The underlying reflection cycle infrastructure (StopReflectionCycle, ReflectionCycle model, OrchestratorReflect mode) still exists and is referenced in both files
  • The reflection pill UI (showing iteration progress, stop button) still renders in ExpandedSessionView.razor
  • /reflect is removed from autocomplete in index.html and from the /help text

The user can no longer start a reflection cycle via /reflect, but the existing infrastructure for running them (e.g., via multi-agent orchestrator mode) remains. If this removal is intentional, the leftover ReflectionCycle UI and references should be cleaned up. If it was accidental, /reflect needs to be re-added.


✅ Event handling — correct

The new subagent/skill event cases are well-structured:

  • SubagentSelectedEvent: Sets ActiveAgentName/ActiveAgentDisplayName on AgentSessionInfo and adds a system message. Correct.
  • SubagentDeselectedEvent: Clears both fields. Correct.
  • SubagentStartedEvent: Adds a ▶️ message. The description is appended only when non-empty. Correct.
  • SubagentCompletedEvent / SubagentFailedEvent: Clears ActiveAgentName only when the name matches (OrdinalIgnoreCase). This is a nice correctness guard against stale state from rapid sequential agent invocations.
  • SkillInvokedEvent: Adds ⚡ message with plugin name. Correct.
  • CommandsChangedEvent: Added as TimelineOnly — a reasonable default for an event that has no user-visible meaning yet.

All cases use Invoke(() => ...) for correct UI-thread marshaling. All use NotifyStateChangedCoalesced() consistently with surrounding code.


✅ /agent command — correct

HandleAgentCommand in Dashboard.razor correctly handles three cases:

  • Empty arg → list agents (SDK API + local discovery)
  • deselect → calls DeselectAgentAsync
  • Any other arg → calls SelectAgentAsync

The service methods (ListAgentsFromApiAsync, SelectAgentAsync, DeselectAgentAsync) all have proper null guards and exception handling (catch-and-log). ✅


✅ /fleet command — correct but minimal

HandleFleetCommand calls StartFleetAsync which calls state.Session.Rpc.Fleet.StartAsync. The "fleet started" message fires before the API result is confirmed:

session.History.Add(ChatMessage.SystemMessage($"🚀 Starting fleet for: *{arg}*"));
var started = await CopilotService.StartFleetAsync(sessionName, arg);
    session.History.Add(ChatMessage.ErrorMessage("Failed to start fleet mode."));

This is a minor UX issue — "Starting fleet..." appears briefly even on failure. Low severity, but worth noting.


✅ Active agent badge — correct

The badge in ExpandedSessionView.razor uses Session.ActiveAgentDisplayName ?? Session.ActiveAgentName with a null guard. CSS uses var(--type-footnote) — consistent with font-sizing conventions. ✅


Minor: AgentSessionInfo.ActiveAgentName not persisted

ActiveAgentName/ActiveAgentDisplayName are not included in session persistence/bridge sync. After an app restart or remote-mode reconnect, the badge won't show. This is acceptable (the agent state is runtime-only and the SDK will re-emit events on reconnect), but worth documenting.


Summary

Issue Severity
/reflect removed — breaking change, or leftover UI needs cleanup High — needs explicit decision
"Starting fleet" message before API confirms Low
ActiveAgentName not persisted Low/Info

All new event handling, API methods, and UI additions are correct. The /reflect removal is the only issue requiring a decision before merging.

@PureWeen
Copy link
Copy Markdown
Owner Author

Thanks for the thorough review @PureWeen!

On /reflect removal: This was intentional per the user's explicit request. The reflection pill UI (iteration counter, stop button) and all underlying infrastructure (ReflectionCycle, OrchestratorReflect, StopReflectionCycle) are intentionally retained — they still work when reflection is triggered via multi-agent orchestration. The removal only affects the user-facing /reflect slash command entry point. The leftover UI is not dead code — it activates when multi-agent orchestration starts a reflection cycle.

On fleet message before API confirms: Fixed in 00bff88 — the 🚀 message now only appears after StartFleetAsync returns true, error message on false.

On ActiveAgentName not persisted: Agreed this is acceptable — the SDK will re-emit subagent events on reconnect/resume. Not persisting is the right call to avoid stale badge state.

PureWeen and others added 7 commits March 28, 2026 15:34
- Show 🤖/▶️/✅/❌ system messages when CLI subagents activate
  (SubagentSelectedEvent, SubagentStartedEvent, SubagentCompletedEvent,
  SubagentFailedEvent, SubagentDeselectedEvent)
- Show ⚡ system message when SkillInvokedEvent fires
- Update SdkEventMatrix: subagent + skill events → ChatVisible
- Handle CommandsChangedEvent: store CLI commands in AgentSessionInfo.CliSlashCommands
- Add ActiveAgentName/ActiveAgentDisplayName to AgentSessionInfo
- Show active CLI agent badge in ExpandedSessionView info panel
- Add /agent slash command: list agents (local + SDK API) or select/deselect
- Wire AgentApi: ListAgentsFromApiAsync, SelectAgentAsync, DeselectAgentAsync
- Add /agent to index.html autocomplete + SlashCommandAutocompleteTests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove CommandsChangedEvent case handler (type not exported from SDK 0.2.0)
  SdkEventMatrix string entry remains for future use when SDK exposes the type
- Replace session.AvailableAgents (nonexistent property) with
  CopilotService.DiscoverAvailableAgents(session.WorkingDirectory) in /agent handler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CommandsChangedEvent handler was removed (SDK 0.2.0 doesn't export the type),
so this field was never set. Remove to avoid dead code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add /fleet <prompt> slash command that calls FleetApi.StartAsync() via
  the SDK, enabling parallel subagent execution. Shows a system message on
  success/failure. Added to index.html autocomplete and /help output.
  Added CopilotService.StartFleetAsync() wrapping state.Session.Rpc.Fleet.StartAsync().

- Remove /reflect slash command: remove case handler, delete
  HandleReflectCommand() method (~130 lines), remove from COMMANDS array and /help.
  The underlying reflection machinery (ReflectionCycle model,
  StartReflectionCycleAsync, OrchestratorReflect multi-agent mode,
  all reflection UI in ChatMessageItem/SessionCard) is UNTOUCHED --
  reflection still works via multi-agent orchestration.

- Update SlashCommandAutocompleteTests: replace /reflect with /fleet in
  both the minimum-commands and the hasArgs assertions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove 'Reflect' button from ExpandedSessionView toolbar
- Remove dead InsertReflectCommand() method
- Remove '/reflect stop' suggestion from context-full warning message

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only show 🚀 success message after StartFleetAsync returns true,
show error on false. Avoids premature success indicator on failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…leet guard

- SubagentCompleted/Failed: always clear ActiveAgent state even when
  displayName is empty (prevents permanently stuck badge)
- DeselectAgentAsync: return bool, surface failures in /agent deselect
- StartFleetAsync: reject when IsProcessing to prevent concurrent turns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen force-pushed the feature/cli-agent-visibility branch from 00bff88 to 09bc16e Compare March 28, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant