Skip to content

feat: group chat enhancements#559

Open
ets wants to merge 8 commits intoRunMaestro:mainfrom
ets:ets/group-chat-enhancements
Open

feat: group chat enhancements#559
ets wants to merge 8 commits intoRunMaestro:mainfrom
ets:ets/group-chat-enhancements

Conversation

@ets
Copy link

@ets ets commented Mar 12, 2026

Summary

  • Enhanced group chat routing with improved participant response handling, stability fixes, and better observability
  • Added group chat moderator system prompt for synthesis and auto-run registry for managing group chat workflows
  • Extended settings, stores, and IPC handlers to support new group chat capabilities including participant cards and right panel

Changes

Core group chat improvements:

  • src/main/group-chat/group-chat-router.ts — Major enhancements to routing logic (+321 lines)
  • src/main/ipc/handlers/groupChat.ts — Extended IPC handlers for group chat operations
  • src/main/preload/groupChat.ts — New preload bridge for group chat IPC
  • src/main/process-listeners/data-listener.ts / exit-listener.ts — Process lifecycle improvements

Renderer/UI:

  • src/renderer/App.tsx — Group chat integration into main app coordinator
  • src/renderer/components/GroupChat*.tsx — Updated header, list, panel, participants, and right panel components
  • src/renderer/components/ParticipantCard.tsx — Enhanced participant card with richer display
  • src/renderer/components/Settings/SettingsModal.tsx — New group chat settings

State management & utilities:

  • src/renderer/stores/groupChatStore.ts — New Zustand store for group chat state
  • src/renderer/hooks/groupChat/useGroupChatHandlers.ts — Group chat event handlers
  • src/renderer/utils/groupChatAutoRunRegistry.ts — Auto-run registry for group chat workflows

Prompts:

  • src/prompts/group-chat-moderator-system.md — New moderator system prompt
  • src/prompts/group-chat-participant-request.md — Updated participant request prompt

PR Checklist

  • Linting passes (npm run lint && npm run lint:eslint)
  • Tests pass (npm test) — 22,296 passed
  • No new console warnings or errors

Test plan

  • Verify group chat creation and participant management in the UI
  • Test moderator synthesis flow with multiple participants
  • Confirm participant response routing works correctly
  • Validate settings persistence for group chat configuration
  • Check keyboard shortcuts and focus flow in group chat panels

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Stop All button to halt active group chats
    • Added Auto-Run directive support for triggering participant document execution
    • Added live output peeking to monitor participant progress in real-time
    • Added Moderator Standing Instructions setting for persistent guidance
    • Introduced dedicated Group Chat settings panel
    • Group chat list now sorts by recency
  • Bug Fixes

    • Improved error handling for synthesis failures with user notifications
    • Enhanced timeout handling for participant responses
  • Documentation

    • Added Auto Run Execution and Commit & Switch Branch guidance sections to moderator documentation
  • Tests

    • Added comprehensive test coverage for stop-all and auto-run completion workflows

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

This PR introduces auto-run support for group chats, enabling moderators to trigger batch document execution via !autorun directives. It adds participant live output streaming, stop-all controls, moderator settings integration (standing instructions and conductor profile), and per-participant timeout handling with synthesis fallback.

Changes

Cohort / File(s) Summary
Configuration
.gitignore
Adds Claude Code local settings ignore pattern.
Core Group Chat Router
src/main/group-chat/group-chat-router.ts
Introduces per-participant timeout handling, auto-run directive parsing and propagation, moderator settings integration (standing instructions and conductor profile substitution), new public functions (clearPendingParticipants, routeAgentResponse, markParticipantResponded, spawnModeratorSynthesis), and expanded SessionInfo type.
IPC Handlers & Main Setup
src/main/ipc/handlers/groupChat.ts, src/main/index.ts
Adds groupChat:stopAll and groupChat:reportAutoRunComplete handlers, three new emitters (emitParticipantLiveOutput, emitAutoRunTriggered, emitAutoRunBatchComplete), and wires moderator settings callback to expose standing instructions and conductor profile.
Preload API
src/main/preload/groupChat.ts
Introduces stopAll() and reportAutoRunComplete() methods, plus three event listeners (onParticipantLiveOutput, onAutoRunTriggered, onAutoRunBatchComplete) with unsubscribe support.
Process Streaming & Events
src/main/process-manager/spawners/ChildProcessSpawner.ts, src/main/process-listeners/data-listener.ts, src/main/process-listeners/exit-listener.ts
Adds raw-stdout event emission for per-participant live output streaming; enhances exit-listener with synthesis spawn failure handling and error reporting via Sentry.
Renderer Components
src/renderer/components/GroupChatHeader.tsx, src/renderer/components/GroupChatPanel.tsx, src/renderer/components/GroupChatParticipants.tsx, src/renderer/components/GroupChatRightPanel.tsx, src/renderer/components/ParticipantCard.tsx
Adds Stop All button to GroupChatHeader, threads onStopAll prop through components, integrates participant live output display with peek toggle in ParticipantCard.
Renderer Stores
src/renderer/stores/groupChatStore.ts, src/renderer/stores/settingsStore.ts
Adds participantLiveOutput map state with append/clear actions; introduces moderatorStandingInstructions setting with persistence.
Renderer Hooks & Handlers
src/renderer/hooks/groupChat/useGroupChatHandlers.ts, src/renderer/hooks/settings/useSettings.ts, src/renderer/hooks/batch/useBatchHandlers.ts
Extends GroupChatHandlersReturn with handleStopAll, adds live output streaming subscription, integrates auto-run completion reporting via groupChatAutoRunRegistry consumption.
Auto-run Registry & Utilities
src/renderer/utils/groupChatAutoRunRegistry.ts
New module providing registry functions (registerGroupChatAutoRun, consumeGroupChatAutoRun, getAutoRunSessionsForGroupChat) to track batch runs triggered by group chat auto-run directives.
Renderer App Integration
src/renderer/App.tsx, src/renderer/components/GroupChatList.tsx, src/renderer/components/Settings/SettingsModal.tsx
Introduces group-chat auto-run bridge subscribing to onAutoRunTriggered, validates and registers auto-run context, initiates batch runs; adds Group Chat settings tab with moderator standing instructions textarea; switches GroupChatList sorting from alphabetic to recency-based (updatedAt/createdAt).
Type Definitions
src/renderer/global.d.ts
Updates MaestroAPI.groupChat interface with new methods (stopAll, reportAutoRunComplete) and event listeners (onParticipantLiveOutput, onAutoRunTriggered, onAutoRunBatchComplete).
Prompts & Documentation
src/prompts/group-chat-moderator-system.md
Adds two sections documenting Auto Run Execution (per-agent and batch document triggers) and Commit & Switch Branch commands.
Tests
src/__tests__/main/agents/session-storage.test.ts, src/__tests__/main/ipc/handlers/groupChat.test.ts, src/__tests__/renderer/components/GroupChatHeader.test.tsx
Adds tmpdir mock in session-storage test; comprehensive tests for groupChat:stopAll and groupChat:reportAutoRunComplete handlers; adds GroupChatHeader Stop All button tests with state-based visibility and click behavior.

Sequence Diagram

sequenceDiagram
    participant Moderator as Moderator (UI)
    participant Router as Group Chat Router
    participant Renderer as Renderer Auto-run<br/>Bridge
    participant BatchRunner as Batch Runner
    participant GroupChat as GroupChat IPC

    Moderator->>Router: Send message with !autorun directive
    Router->>Router: Extract & validate !autorun directives
    Router->>Renderer: Emit onAutoRunTriggered event
    Renderer->>Renderer: Validate session & auto-run folder
    Renderer->>Renderer: Register auto-run context in<br/>groupChatAutoRunRegistry
    Renderer->>BatchRunner: Start batch run with document config
    BatchRunner->>BatchRunner: Execute documents
    BatchRunner->>GroupChat: Report completion via<br/>reportAutoRunComplete
    GroupChat->>Router: Route auto-run completion response
    Router->>Moderator: Update group chat state & emit<br/>synthesis trigger
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #377: Modifies group chat IPC and preload APIs (src/main/ipc/handlers/groupChat.ts, src/main/preload/groupChat.ts) with overlapping changes to IPC channels and renderer handlers.
  • PR #444: Overlapping changes in renderer batch/worktree auto-run flow, worktree session utilities, and batch/auto-run dispatch wiring.
  • PR #439: Touches renderer settings hook/store API (src/renderer/hooks/settings/useSettings.ts) with group chat settings modifications.

Suggested reviewers

  • pedramamini
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: group chat enhancements' directly summarizes the main focus of the changeset, which spans extensive group chat feature additions across routing, IPC, UI components, stores, and utilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Mar 12, 2026

Greptile Summary

This PR substantially extends the group chat feature with autorun orchestration (moderator-driven !autorun @Agent:file.md directives that trigger full batch runs through the renderer pipeline), participant response timeouts (10-minute safety net so a hung process can never block synthesis indefinitely), live output streaming to a peek panel, a "Stop All" button, and moderator standing instructions in Settings. The design correctly keeps autorun batch execution in the renderer (useBatchProcessor) so that full progress UI, achievements, and multi-document sequencing continue to work as usual, while the main process only coordinates routing and synthesis.

Key observations:

  • Bug: routeAgentResponse failure blocks synthesis — In reportAutoRunComplete, if routeAgentResponse throws (e.g. participant removed from chat mid-run), withIpcErrorLogging re-throws before markParticipantResponded is called. The participant remains stuck in the pending set until the 10-minute timeout fires. markParticipantResponded should be called unconditionally (in a finally or after a try/catch) to ensure synthesis is never permanently blocked.
  • Bug: "Stop All" doesn't cancel in-flight autorun batchesclearAllParticipantSessions only kills group-chat-prefixed subprocess sessions. Autorun batches run in regular agent sessions and are not cancelled. When they eventually complete, reportAutoRunComplete is still invoked, which logs an orphaned participant response and emits it to the UI even though the user stopped the chat.
  • Style: Silent fallback for missing targetFilename — When !autorun @Agent:plan.md references a file that doesn't exist, the code falls back to running all documents with only a console.warn, leaving the moderator unaware of the substitution.
  • Notable fix: The pre-existing bug where participantStates.get(participant.sessionId) was used (a GUID that never matched the name-keyed map) has been corrected to participantStates.get(participant.name), making participant working/idle indicators display correctly for the first time.

Confidence Score: 3/5

  • Safe to merge for non-autorun group chat flows; autorun paths have two logic gaps (error recovery and Stop All cancellation) that can leave the UI in an inconsistent state or expose stale batch responses after the chat is stopped.
  • The core routing, timeout safety net, live output streaming, and moderator-settings injection are well-implemented. The participant state key bug fix is solid. However, the two logic issues in reportAutoRunComplete and stopAll mean that autorun-heavy group chats may hit a 10-minute synthesis stall on any routeAgentResponse failure, and "Stop All" can produce confusing orphaned messages from still-running batches. Neither is data-corrupting, but both affect the reliability of the primary new feature (auto-run orchestration).
  • src/main/ipc/handlers/groupChat.ts (reportAutoRunComplete error recovery and stopAll batch cancellation) and src/renderer/App.tsx (silent targetFilename fallback)

Important Files Changed

Filename Overview
src/main/group-chat/group-chat-router.ts Major enhancements: participant timeout tracking, !autorun directive extraction and routing, moderator settings injection, and auto-run participant filtering. The core logic is well-structured with good timeout recovery, but the double-call of extractAutoRunDirectives (flagged in a previous review) remains. The new timeout mechanism correctly cancels on normal completion and serves as a safety net for hung processes.
src/main/ipc/handlers/groupChat.ts Two new IPC handlers: stopAll (kills moderator + group-chat sessions but not autorun batches) and reportAutoRunComplete (logs summary + triggers synthesis). The reportAutoRunComplete handler has a gap: if routeAgentResponse throws, markParticipantResponded is never called and the participant hangs until the 10-minute timeout. The stopAll handler leaves in-flight autorun batches running, which post orphaned messages after the chat is stopped.
src/renderer/App.tsx Adds the group chat auto-run bridge: intercepts groupChat:autoRunTriggered events from the main process, validates the session and folder, then starts a proper batch run via useBatchProcessor. Correctly uses a ref for startBatchRun to avoid stale closures. Silent fallback when targetFilename is not found (runs all docs without notifying the moderator).
src/renderer/hooks/batch/useBatchHandlers.ts Adds group chat auto-run completion reporting to the batch onComplete callback. Uses consumeGroupChatAutoRun to look up context and calls reportAutoRunComplete with a human-readable summary. Correctly handles the stopped-batch case. Error handling shows a toast but the recovery path relies on the 10-minute server-side timeout.
src/renderer/hooks/groupChat/useGroupChatHandlers.ts Adds new IPC subscriptions for live output streaming, auto-run batch completion, and a handleStopAll handler. The onAutoRunBatchComplete listener ignores the groupChatId parameter — it dispatches COMPLETE_BATCH by session name only, which may produce unexpected results if two sessions share a name. Live output is correctly cleared when the participant returns to idle.
src/renderer/components/GroupChatParticipants.tsx Fixes a pre-existing bug where participantStates was looked up by participant.sessionId (a GUID) instead of participant.name, meaning participant working/idle states were never displayed. Also adds live output peek wiring.

Sequence Diagram

sequenceDiagram
    participant User
    participant Renderer
    participant MainProcess as Main Process (IPC)
    participant Router as group-chat-router
    participant BatchProc as useBatchProcessor

    User->>Renderer: Send message
    Renderer->>MainProcess: groupChat:sendToModerator
    MainProcess->>Router: routeUserMessage → spawns moderator process
    Router-->>Renderer: emitStateChange("agent-working")

    Note over Router: Moderator responds with @mentions / !autorun

    alt Regular @mention participant
        Router->>Router: spawns GROUP_CHAT_PREFIX process
        Router->>Router: setParticipantResponseTimeout (10 min)
        Router-->>Renderer: emitParticipantState("working")
        Note over Router: Process exits → exit-listener fires
        Router->>Router: routeAgentResponse → appendToLog
        Router->>Router: markParticipantResponded
        Router->>Router: [if last] spawnModeratorSynthesis
    else !autorun @AgentName directive
        Router-->>Renderer: emitAutoRunTriggered(groupChatId, name, file?)
        Router->>Router: setParticipantResponseTimeout (10 min)
        Renderer->>BatchProc: startBatchRun(sessionId, config)
        BatchProc-->>Renderer: onComplete(info)
        Renderer->>MainProcess: groupChat:reportAutoRunComplete(summary)
        MainProcess->>Router: routeAgentResponse → appendToLog
        MainProcess->>Router: markParticipantResponded (cancels timeout)
        Router-->>Renderer: emitAutoRunBatchComplete
        Router->>Router: [if last] spawnModeratorSynthesis
    end

    Router-->>Renderer: Synthesis result → emitMessage (moderator)
    Router-->>Renderer: emitStateChange("idle")
Loading

Last reviewed commit: ae7950d

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
src/__tests__/main/ipc/handlers/groupChat.test.ts (1)

172-173: Add behavior tests for the two new IPC channels.

This only proves the handler names were registered. It won't catch groupChat:stopAll or groupChat:reportAutoRunComplete calling the wrong dependency or sending the wrong payload. Please add focused tests for those handlers alongside the existing moderator/participant cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/ipc/handlers/groupChat.test.ts` around lines 172 - 173,
Add focused behavior tests that invoke the registered IPC handlers
'groupChat:stopAll' and 'groupChat:reportAutoRunComplete' (not just assert
registration): mock/spy the concrete dependencies those handlers call (the
service/method that stops chats and the reporter/method that logs auto-run
completion), trigger the handler via the same test harness used for
moderator/participant cases, and assert the correct dependency was called with
the expected payload and that the handler returns or sends the expected result;
mirror the structure of the existing moderator/participant tests so you cover
both happy paths and at least one error/edge case for each new handler.
src/__tests__/renderer/components/GroupChatHeader.test.tsx (1)

27-31: LGTM on the mock addition.

The StopCircle mock follows the established pattern for icon mocks in this file.

However, consider adding tests for the new state and onStopAll props that were added to GroupChatHeader. The current defaultProps (lines 48-57) doesn't include these required props, which may cause test failures. Tests should verify:

  • Stop All button renders when state !== 'idle'
  • Stop All button is hidden when state === 'idle'
  • onStopAll callback is invoked on click
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/renderer/components/GroupChatHeader.test.tsx` around lines 27 -
31, Add tests for the new GroupChatHeader props: update the test suite around
GroupChatHeader (use the existing defaultProps constant in the test file) to
include the required state and onStopAll props; add three tests: (1) render with
state !== 'idle' (e.g., 'running') and assert the "Stop All" button is present,
(2) render with state === 'idle' and assert the "Stop All" button is not
present, and (3) render with a mock onStopAll function, click the "Stop All"
button and assert the mock was called. Ensure the tests reference the
GroupChatHeader component and the defaultProps pattern used in this test file so
props are supplied consistently.
src/main/ipc/handlers/groupChat.ts (1)

515-516: Consider hoisting the dynamic import to a top-level import.

The dynamic import() inside the handler works but is inconsistent with the rest of the file where routeUserMessage and clearPendingParticipants are imported at the top level. This could cause subtle timing issues and makes the code harder to follow.

♻️ Proposed refactor
 // Group chat router imports
-import { routeUserMessage, clearPendingParticipants } from '../../group-chat/group-chat-router';
+import {
+	routeUserMessage,
+	clearPendingParticipants,
+	routeAgentResponse,
+	markParticipantResponded,
+	spawnModeratorSynthesis,
+} from '../../group-chat/group-chat-router';

Then update the handler:

-			const { routeAgentResponse, markParticipantResponded, spawnModeratorSynthesis } =
-				await import('../../group-chat/group-chat-router');
-
 			// Log the autorun summary as the participant's response
 			await routeAgentResponse(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/ipc/handlers/groupChat.ts` around lines 515 - 516, Hoist the dynamic
import by adding a top-level import for routeAgentResponse,
markParticipantResponded, and spawnModeratorSynthesis from
'../../group-chat/group-chat-router' (instead of using await import inside the
handler), then remove the await import call in the handler and use the imported
symbols directly; ensure you update any existing local references in the handler
that used the dynamically-loaded names so they reference the top-level imports
(routeAgentResponse, markParticipantResponded, spawnModeratorSynthesis) to keep
imports consistent with routeUserMessage and clearPendingParticipants.
src/renderer/components/ParticipantCard.tsx (1)

259-272: Consider adding aria-expanded for accessibility.

The peek button toggles visibility of the live output panel. Adding aria-expanded and aria-controls would improve screen reader support.

♿ Proposed accessibility improvement
 <button
 	onClick={() => setPeekOpen((v) => !v)}
 	className="flex items-center gap-0.5 text-[10px] px-1.5 py-0.5 rounded shrink-0 hover:opacity-80 transition-opacity cursor-pointer"
 	style={{
 		backgroundColor: peekOpen ? `${theme.colors.accent}25` : `${theme.colors.accent}10`,
 		color: peekOpen ? theme.colors.accent : theme.colors.textDim,
 		border: `1px solid ${peekOpen ? theme.colors.accent + '60' : theme.colors.border}`,
 	}}
 	title="Peek at live output"
+	aria-expanded={peekOpen}
+	aria-controls={`live-output-${participant.name}`}
 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/ParticipantCard.tsx` around lines 259 - 272, The Peek
button in ParticipantCard (the button that calls setPeekOpen and reads peekOpen)
should expose its state to assistive tech by adding aria-expanded={peekOpen} and
aria-controls referencing the live output panel's id; update the live output
panel element to have a matching id (e.g., "peek-panel-<uniqueParticipantId>" or
another stable identifier) so the button's aria-controls points to it, leaving
the existing onClick and visual logic unchanged.
src/main/group-chat/group-chat-router.ts (1)

170-172: Consider logging the error in the catch block.

The empty catch block silently swallows errors when appending the timeout message to the log. While non-critical, logging these errors aids debugging.

🔧 Suggested improvement
 		} catch {
-			// Non-critical — synthesize anyway
+			// Non-critical — synthesize anyway, but log for debugging
+			console.warn(`[GroupChat:Debug] Failed to append timeout log for ${participantName}:`, err);
 		}

Note: You'll need to capture the error: } catch (err) {

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/group-chat-router.ts` around lines 170 - 172, The silent
catch that swallows errors when appending the timeout message should capture and
log the error instead: change the empty catch to catch (err) and call the
appropriate logger (e.g., processLogger.warn or console.warn) with a clear
message like "Failed to append timeout message to log" plus the error details;
keep the non-critical flow (do not rethrow) so behavior remains unchanged.
Target the catch block around the timeout append logic in group-chat-router (the
block that currently reads "} catch { // Non-critical — synthesize anyway }")
and ensure the logged output includes the caught error object for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/process-listeners/exit-listener.ts`:
- Around line 246-252: Replace the user-facing message that currently
interpolates String(err) in groupChatEmitters.emitMessage with a generic retry
notice (e.g., "⚠️ Synthesis failed. You can send another message to continue.")
and do not expose internal error details in the chat transcript; instead, log
the full error and report it to Sentry by calling processLogger.error(...) with
the error and context and invoking Sentry.captureException(err) (or your
project's Sentry helper) so the detailed exception is recorded for debugging
while the chat only shows the generic message; keep the existing emitStateChange
call and use the same err variable when logging/reporting.

In `@src/main/process-manager/spawners/ChildProcessSpawner.ts`:
- Around line 430-434: Wrap the best-effort this.emitter.emit('raw-stdout',
sessionId, output) call in its own try/catch so any synchronous listener
exception cannot prevent this.stdoutHandler.handleData(sessionId, output) from
running; on catch, log the error and report it via the project's Sentry
utilities (e.g., Sentry.captureException or the repo's equivalent) with context
(sessionId, 'raw-stdout'), but do not rethrow so stdoutHandler still processes
the chunk. Ensure the change is applied in the ChildProcessSpawner stdout data
handler where emitter.emit and stdoutHandler.handleData are called.

In `@src/prompts/group-chat-participant-request.md`:
- Line 19: The prompt template uses the {{WORKTREE_BASE_PATH}} placeholder but
group-chat-router.ts currently replaces it with an empty string; find the
replacement in src/main/group-chat/group-chat-router.ts and stop hardcoding
''—instead pass the real worktree base path from the request/context into the
template renderer (e.g., use the existing worktreeBasePath or config variable
used elsewhere), ensuring the function that constructs participant prompts (the
handler that currently does the replace) forwards that value into the template
so participant prompts receive the correct worktree-path context.

In `@src/renderer/components/GroupChatPanel.tsx`:
- Line 33: The prop onStopAll on GroupChatPanel is currently optional and
defaulted to a no-op, which masks missing wiring and renders the stop action
nonfunctional; update GroupChatPanel to either require onStopAll (make its type
non-optional) or stop providing the default (() => {}) so the prop value can be
undefined and GroupChatHeader can hide the action when absent—locate the
onStopAll declaration in GroupChatPanel and remove the default no-op (or change
its type to mandatory), and ensure any parent components wiring this prop pass
the actual handler or omit it intentionally.

In `@src/renderer/components/GroupChatRightPanel.tsx`:
- Around line 84-85: participantLiveOutput is being keyed only by
participant.name which can collide across chats; update the lookup and any
set/write paths to use a chat-scoped key (for example groupChatId or
participant.sessionId) instead of plain participant.name. Locate uses of
participantLiveOutput.get(...) and the code that writes into
participantLiveOutput in GroupChatRightPanel (and related handlers) and change
them to compute a composite key (e.g.,
`${groupChatId}:${participant.sessionId}`) or directly use
participant.sessionId/groupChatId as the map key so the panel only shows live
output for the current chat's participant.

In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts`:
- Around line 72-73: The async handler handleStopAll currently returns
Promise<void> but is invoked synchronously by GroupChatHeader.tsx via onStopAll,
risking unhandled promise rejections; wrap the entire implementation of
handleStopAll in a try/catch, catch any thrown/rejected errors, log or report
them (using the existing logger or console.error), and do not rethrow so the
function resolves to void when called without awaiting; keep the exported name
handleStopAll unchanged so consumers (onStopAll) can call it without causing
unhandled rejections.

---

Nitpick comments:
In `@src/__tests__/main/ipc/handlers/groupChat.test.ts`:
- Around line 172-173: Add focused behavior tests that invoke the registered IPC
handlers 'groupChat:stopAll' and 'groupChat:reportAutoRunComplete' (not just
assert registration): mock/spy the concrete dependencies those handlers call
(the service/method that stops chats and the reporter/method that logs auto-run
completion), trigger the handler via the same test harness used for
moderator/participant cases, and assert the correct dependency was called with
the expected payload and that the handler returns or sends the expected result;
mirror the structure of the existing moderator/participant tests so you cover
both happy paths and at least one error/edge case for each new handler.

In `@src/__tests__/renderer/components/GroupChatHeader.test.tsx`:
- Around line 27-31: Add tests for the new GroupChatHeader props: update the
test suite around GroupChatHeader (use the existing defaultProps constant in the
test file) to include the required state and onStopAll props; add three tests:
(1) render with state !== 'idle' (e.g., 'running') and assert the "Stop All"
button is present, (2) render with state === 'idle' and assert the "Stop All"
button is not present, and (3) render with a mock onStopAll function, click the
"Stop All" button and assert the mock was called. Ensure the tests reference the
GroupChatHeader component and the defaultProps pattern used in this test file so
props are supplied consistently.

In `@src/main/group-chat/group-chat-router.ts`:
- Around line 170-172: The silent catch that swallows errors when appending the
timeout message should capture and log the error instead: change the empty catch
to catch (err) and call the appropriate logger (e.g., processLogger.warn or
console.warn) with a clear message like "Failed to append timeout message to
log" plus the error details; keep the non-critical flow (do not rethrow) so
behavior remains unchanged. Target the catch block around the timeout append
logic in group-chat-router (the block that currently reads "} catch { //
Non-critical — synthesize anyway }") and ensure the logged output includes the
caught error object for debugging.

In `@src/main/ipc/handlers/groupChat.ts`:
- Around line 515-516: Hoist the dynamic import by adding a top-level import for
routeAgentResponse, markParticipantResponded, and spawnModeratorSynthesis from
'../../group-chat/group-chat-router' (instead of using await import inside the
handler), then remove the await import call in the handler and use the imported
symbols directly; ensure you update any existing local references in the handler
that used the dynamically-loaded names so they reference the top-level imports
(routeAgentResponse, markParticipantResponded, spawnModeratorSynthesis) to keep
imports consistent with routeUserMessage and clearPendingParticipants.

In `@src/renderer/components/ParticipantCard.tsx`:
- Around line 259-272: The Peek button in ParticipantCard (the button that calls
setPeekOpen and reads peekOpen) should expose its state to assistive tech by
adding aria-expanded={peekOpen} and aria-controls referencing the live output
panel's id; update the live output panel element to have a matching id (e.g.,
"peek-panel-<uniqueParticipantId>" or another stable identifier) so the button's
aria-controls points to it, leaving the existing onClick and visual logic
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b763724-0e04-4124-b99e-08526d690db4

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff6839 and ae7950d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (28)
  • .gitignore
  • src/__tests__/main/agents/session-storage.test.ts
  • src/__tests__/main/ipc/handlers/groupChat.test.ts
  • src/__tests__/renderer/components/GroupChatHeader.test.tsx
  • src/main/group-chat/group-chat-router.ts
  • src/main/index.ts
  • src/main/ipc/handlers/groupChat.ts
  • src/main/preload/groupChat.ts
  • src/main/process-listeners/data-listener.ts
  • src/main/process-listeners/exit-listener.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts
  • src/prompts/group-chat-moderator-system.md
  • src/prompts/group-chat-participant-request.md
  • src/renderer/App.tsx
  • src/renderer/components/GroupChatHeader.tsx
  • src/renderer/components/GroupChatList.tsx
  • src/renderer/components/GroupChatPanel.tsx
  • src/renderer/components/GroupChatParticipants.tsx
  • src/renderer/components/GroupChatRightPanel.tsx
  • src/renderer/components/ParticipantCard.tsx
  • src/renderer/components/Settings/SettingsModal.tsx
  • src/renderer/global.d.ts
  • src/renderer/hooks/batch/useBatchHandlers.ts
  • src/renderer/hooks/groupChat/useGroupChatHandlers.ts
  • src/renderer/hooks/settings/useSettings.ts
  • src/renderer/stores/groupChatStore.ts
  • src/renderer/stores/settingsStore.ts
  • src/renderer/utils/groupChatAutoRunRegistry.ts

@ets
Copy link
Author

ets commented Mar 12, 2026

I'll work the greptile callouts. For context, this PR is from a branch I've been daily driving for a bit. I'm obviously heavily focused on Group Chat and having the moderator coordinate participant activity. I layered in an ability for each participant to work through Maestro's excellent autorun doc facilities as their SOP. This works well for my typical workflow. No hard feelings if you want to reject this - but it's been super useful for me so wanted to share if there's interest.

@ets
Copy link
Author

ets commented Mar 12, 2026

@greptileai review latest changes

- Protect raw-stdout emit from breaking stdout handling (try/catch)
- Sanitize error messages in chat transcript (Sentry instead of String(err))
- Scope live-output lookup by groupChatId:participantName composite key
- Remove dead WORKTREE_BASE_PATH template variable
- Hoist dynamic imports to top-level in groupChat IPC handler
- Deduplicate extractAutoRunDirectives call in router
- Make onStopAll required prop, add async error handling
- stopAll now cancels in-flight autorun batch runs via registry
- Report failure instead of silent fallback when autorun target file missing
- Add behavior tests for stopAll and reportAutoRunComplete IPC handlers
- Add tests for GroupChatHeader state/stop props

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/main/ipc/handlers/groupChat.test.ts`:
- Around line 1068-1071: The test mocks for
groupChatRouter.markParticipantResponded are returning an object but the real
API returns a boolean; change the mocks to return boolean values
(mockReturnValue(false) and mockReturnValue(true) where applicable) for
groupChatRouter.markParticipantResponded, update the two places that currently
return an object to return plain booleans, and then adjust the assertions to
validate spawn behavior based on the boolean (i.e., assert that synthesis is not
spawned when false and is spawned when true).

In `@src/main/group-chat/group-chat-router.ts`:
- Around line 1239-1248: The finalization branch currently checks only
mentionsToSpawn.length and autoRunParticipants.length, which can be non-zero
despite no actionable work; change the condition to use the actual actionable
set (participantsToRespond) so cleanup runs when there is no work: replace the
check `mentionsToSpawn.length === 0 && autoRunParticipants.length === 0` with
`mentionsToSpawn.length === 0 && participantsToRespond.size === 0` (and apply
the same change to the other similar block later), ensuring you still call
groupChatEmitters.emitStateChange?.(groupChatId, 'idle') and
powerManager.removeBlockReason(`groupchat:${groupChatId}`) when idle.
- Around line 177-179: The timeout path is unconditionally calling
groupChatEmitters.emitAutoRunBatchComplete which causes auto-run completion to
fire for non-autorun participants; change the call site so
emitAutoRunBatchComplete is only invoked for participants that are tracked as
autorun runs (e.g., check the autorun-tracking structure or a participant flag
such as participant.meta?.isAutoRun or
autorunTrackedParticipants.has(participantName) before calling it). After
emitting, clear the participant from the autorun tracking so it won't fire
again. Keep the existing emitParticipantState call unconditional.
- Around line 170-172: The empty catch in group-chat-router.ts that swallows
failures when writing timeout markers should be replaced with a proper error
handler: catch the error as "err", call processLogger.error with a descriptive
message and relevant context (e.g., chatId, userId, traceId or request id), call
Sentry.captureException(err) (or your app's telemetry/reporting helper) to
ensure it is reported, and then rethrow the error so it bubbles up; update the
catch block around the timeout-marker write to use these calls and include the
unique symbols/processLogger and Sentry.captureException in the change.

In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts`:
- Around line 237-245: The handler registered with
window.maestro.groupChat.onAutoRunBatchComplete currently resolves the session
via participantName (useSessionStore.getState().sessions.find(...)) which can
collide across chats and ignores the provided _groupChatId; instead look up the
sessionId from a group-chat-scoped autorun registry keyed by _groupChatId (or
maintain a map from groupChatId -> sessionId created when autoruns start) and
dispatch the COMPLETE_BATCH using that sessionId via
useBatchStore.getState().dispatchBatch; update the onAutoRunBatchComplete
callback to use the group-scoped registry (not global name lookup) and fall back
to participantName only if the registry entry is missing.
- Around line 620-622: The catch in handleStopAll currently only console.errors
the failure and swallows it; replace that with a recoverable UI failure and
proper error reporting: when catching the error from the IPC stop call in
handleStopAll, call the app's toast/notification helper (e.g., showToast or
notify) with a user-friendly message like "Failed to stop all conversations" and
include a brief action if available, and also pass the error to the centralized
error reporter (e.g., reportError or captureException) with context { location:
'handleStopAll', ipc: 'stopAll' } or similar; remove the bare console.error and
ensure the catch handles known error codes explicitly if present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c171e9c6-8a2c-4872-8747-761ff4b15eac

📥 Commits

Reviewing files that changed from the base of the PR and between ae7950d and 9d4d86e.

📒 Files selected for processing (11)
  • src/__tests__/main/ipc/handlers/groupChat.test.ts
  • src/__tests__/renderer/components/GroupChatHeader.test.tsx
  • src/main/group-chat/group-chat-router.ts
  • src/main/ipc/handlers/groupChat.ts
  • src/main/process-listeners/exit-listener.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts
  • src/renderer/App.tsx
  • src/renderer/components/GroupChatPanel.tsx
  • src/renderer/components/GroupChatRightPanel.tsx
  • src/renderer/hooks/groupChat/useGroupChatHandlers.ts
  • src/renderer/utils/groupChatAutoRunRegistry.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/utils/groupChatAutoRunRegistry.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts
  • src/tests/renderer/components/GroupChatHeader.test.tsx

Comment on lines +1068 to +1071
vi.mocked(groupChatRouter.markParticipantResponded).mockReturnValue({
allResponded: false,
isLastParticipant: false,
} as any);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mock return type for markParticipantResponded is incorrect.

The real router API returns boolean, but tests mock an object. That weakens branch validation and can hide regressions in synthesis-trigger logic. Mock false/true directly and assert spawn behavior accordingly.

Also applies to: 1086-1089

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/ipc/handlers/groupChat.test.ts` around lines 1068 - 1071,
The test mocks for groupChatRouter.markParticipantResponded are returning an
object but the real API returns a boolean; change the mocks to return boolean
values (mockReturnValue(false) and mockReturnValue(true) where applicable) for
groupChatRouter.markParticipantResponded, update the two places that currently
return an object to return plain booleans, and then adjust the assertions to
validate spawn behavior based on the boolean (i.e., assert that synthesis is not
spawned when false and is spawned when true).

Comment on lines +170 to +172
} catch {
// Non-critical — synthesize anyway
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Silent catch drops timeout-path failures from telemetry.

catch {} here suppresses failures when writing timeout markers, so production issues disappear silently. Please at least log + report with context.

As per coding guidelines, "Do not silently swallow errors with try-catch blocks that only log. Let exceptions bubble up to Sentry for error tracking in production."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/group-chat-router.ts` around lines 170 - 172, The empty
catch in group-chat-router.ts that swallows failures when writing timeout
markers should be replaced with a proper error handler: catch the error as
"err", call processLogger.error with a descriptive message and relevant context
(e.g., chatId, userId, traceId or request id), call Sentry.captureException(err)
(or your app's telemetry/reporting helper) to ensure it is reported, and then
rethrow the error so it bubbles up; update the catch block around the
timeout-marker write to use these calls and include the unique
symbols/processLogger and Sentry.captureException in the change.

Comment on lines +177 to +179
groupChatEmitters.emitParticipantState?.(groupChatId, participantName, 'idle');
groupChatEmitters.emitAutoRunBatchComplete?.(groupChatId, participantName);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

emitAutoRunBatchComplete is firing for all participant timeouts, not just autorun.

This timeout path is shared by normal participant spawns too. Emitting auto-run completion unconditionally can force-complete unrelated batch state in the renderer for same-named sessions. Gate this emit to autorun-tracked participants only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/group-chat-router.ts` around lines 177 - 179, The timeout
path is unconditionally calling groupChatEmitters.emitAutoRunBatchComplete which
causes auto-run completion to fire for non-autorun participants; change the call
site so emitAutoRunBatchComplete is only invoked for participants that are
tracked as autorun runs (e.g., check the autorun-tracking structure or a
participant flag such as participant.meta?.isAutoRun or
autorunTrackedParticipants.has(participantName) before calling it). After
emitting, clear the participant from the autorun tracking so it won't fire
again. Keep the existing emitParticipantState call unconditional.

Comment on lines +1239 to 1248
} else if (mentionsToSpawn.length === 0 && autoRunParticipants.length === 0) {
console.log(
`[GroupChat:Debug] No participant @mentions or autorun directives found - moderator response is final`
);
// Set state back to idle since no agents are being spawned
groupChatEmitters.emitStateChange?.(groupChatId, 'idle');
console.log(`[GroupChat:Debug] Emitted state change: idle`);
// Remove power block reason since round is complete
powerManager.removeBlockReason(`groupchat:${groupChatId}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Finalization check is based on raw directives, not actionable work.

If !autorun directives are present but all are invalid/skipped, autoRunParticipants.length > 0 prevents the idle/power-block cleanup branch from running, while participantsToRespond.size may still be 0. That can leak powerManager block reasons and leave lifecycle inconsistent.

Suggested fix
- } else if (mentionsToSpawn.length === 0 && autoRunParticipants.length === 0) {
+ }
+
+ // No actionable participant work was started (all mentions/directives resolved to no-op)
+ if (participantsToRespond.size === 0) {
   groupChatEmitters.emitStateChange?.(groupChatId, 'idle');
   powerManager.removeBlockReason(`groupchat:${groupChatId}`);
 }

Also applies to: 1251-1268

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/group-chat/group-chat-router.ts` around lines 1239 - 1248, The
finalization branch currently checks only mentionsToSpawn.length and
autoRunParticipants.length, which can be non-zero despite no actionable work;
change the condition to use the actual actionable set (participantsToRespond) so
cleanup runs when there is no work: replace the check `mentionsToSpawn.length
=== 0 && autoRunParticipants.length === 0` with `mentionsToSpawn.length === 0 &&
participantsToRespond.size === 0` (and apply the same change to the other
similar block later), ensuring you still call
groupChatEmitters.emitStateChange?.(groupChatId, 'idle') and
powerManager.removeBlockReason(`groupchat:${groupChatId}`) when idle.

Comment on lines +237 to +245
const unsubBatchComplete = window.maestro.groupChat.onAutoRunBatchComplete?.(
(_groupChatId, participantName) => {
const session = useSessionStore.getState().sessions.find((s) => s.name === participantName);
if (!session) return;
// Dispatch COMPLETE_BATCH — no-op if already completed, cleans up if stuck.
useBatchStore.getState().dispatchBatch({
type: 'COMPLETE_BATCH',
sessionId: session.id,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Batch completion resolution is ambiguous (participant name only).

On Line 239, selecting session by participantName can complete the wrong batch when names collide across sessions/chats; _groupChatId is ignored too. Resolve batch session IDs from a group-chat-scoped autorun registry mapping, not global name search.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts` around lines 237 - 245,
The handler registered with window.maestro.groupChat.onAutoRunBatchComplete
currently resolves the session via participantName
(useSessionStore.getState().sessions.find(...)) which can collide across chats
and ignores the provided _groupChatId; instead look up the sessionId from a
group-chat-scoped autorun registry keyed by _groupChatId (or maintain a map from
groupChatId -> sessionId created when autoruns start) and dispatch the
COMPLETE_BATCH using that sessionId via useBatchStore.getState().dispatchBatch;
update the onAutoRunBatchComplete callback to use the group-scoped registry (not
global name lookup) and fall back to participantName only if the registry entry
is missing.

Comment on lines +620 to +622
} catch (error) {
console.error('[GroupChat] Failed to stop all:', error);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

handleStopAll failure is swallowed with console-only logging.

If IPC stop fails, users get no feedback and the error is not explicitly reported. Handle this as recoverable UI failure (toast) and report exception context.

As per coding guidelines, "For expected/recoverable errors, catch them explicitly, check the error code, and handle gracefully (e.g., show offline message)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts` around lines 620 - 622,
The catch in handleStopAll currently only console.errors the failure and
swallows it; replace that with a recoverable UI failure and proper error
reporting: when catching the error from the IPC stop call in handleStopAll, call
the app's toast/notification helper (e.g., showToast or notify) with a
user-friendly message like "Failed to stop all conversations" and include a
brief action if available, and also pass the error to the centralized error
reporter (e.g., reportError or captureException) with context { location:
'handleStopAll', ipc: 'stopAll' } or similar; remove the bare console.error and
ensure the catch handles known error codes explicitly if present.

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