Skip to content

feat: group chat enhancements#559

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

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

Conversation

@ets
Copy link
Copy Markdown

@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

  • New Features

    • Stop All control to halt moderator and participant activity
    • Auto‑Run directives to trigger per‑participant batch runs, lifecycle events, and reporting
    • Live participant output streaming with peek panel and related event hooks
    • Moderator Standing Instructions setting and Group Chat settings tab
    • Group chat list now sorts by recency
  • Bug Fixes

    • Improved synthesis failure handling and participant timeout cleanup
    • Reduced duplicate autorun completion reporting
  • Documentation

    • Added Auto Run Execution and Commit & Switch Branch guidance
  • Tests

    • Expanded tests for stop‑all and auto‑run workflows

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds per-participant autorun parsing/tracking and timeouts, streams raw process stdout as per-participant live output, introduces stop-all and autorun-complete IPC paths and emitters, and wires renderer stores, hooks, UI, and a registry to start/cancel/report autorun batch runs.

Changes

Cohort / File(s) Summary
Core router & IPC (main)
src/main/group-chat/group-chat-router.ts, src/main/ipc/handlers/groupChat.ts, src/main/index.ts
Adds autorun directive parsing, per-participant autorun tracking and timeouts, moderator-settings callback, new IPC handlers groupChat:stopAll and groupChat:reportAutoRunComplete, and new emitters for participant live output and autorun lifecycle. Review parsing, timeout lifecycle, and state transitions.
Process I/O & spawn integration
src/main/process-manager/spawners/ChildProcessSpawner.ts, src/main/process-listeners/data-listener.ts, src/main/process-listeners/exit-listener.ts
Emits raw-stdout events before normal stdout processing and routes chunked output to group-chat live-output emitter; adds error capture and idle reset on synthesis spawn failure. Check non-blocking emission and error handling.
Preload & renderer IPC surface
src/main/preload/groupChat.ts, src/renderer/global.d.ts
Adds preload methods stopAll and reportAutoRunComplete plus event subscriptions onParticipantLiveOutput, onAutoRunTriggered, onAutoRunBatchComplete. Verify typings, unsubscribe behavior, and channel names.
Renderer app wiring & autorun registry
src/renderer/App.tsx, src/renderer/utils/groupChatAutoRunRegistry.ts
Registers autorun bridge to start batch runs on autoRunTriggered and a registry mapping batch session → originating group chat/participant. Verify registration ordering and race conditions before starting batches.
Renderer hooks & batch integration
src/renderer/hooks/groupChat/useGroupChatHandlers.ts, src/renderer/hooks/batch/useBatchHandlers.ts
Adds handleStopAll, per-participant live-output handling, autorun batch-complete reporting. Note: duplicated report block in batch handler — deduplicate. Ensure cleanup and error paths.
Renderer stores & UI components
src/renderer/stores/groupChatStore.ts, src/renderer/stores/settingsStore.ts, src/renderer/components/... (GroupChatHeader, GroupChatPanel, GroupChatParticipants, GroupChatRightPanel, ParticipantCard, GroupChatList, SettingsModal.tsx)
Introduces participantLiveOutput state/actions (50KB cap), live-output peek UI, Stop All button and wiring, new moderatorStandingInstructions setting and Settings tab. Review store cap/merge semantics, prop surface changes, and UI state propagation.
Tests, docs, misc
src/__tests__/*, src/prompts/group-chat-moderator-system.md, .gitignore
Extended tests and mocks for new handlers, added autorun docs and settings text, updated gitignore. Check test mocks and new coverage for autorun/stop-all flows.

Sequence Diagram(s)

sequenceDiagram
  participant R as Renderer (App / UI)
  participant S as Main (group-chat-router / IPC)
  participant P as ProcessManager / Worker
  participant Store as Renderer Store

  R->>S: send moderator message (may include !autorun ...)
  S->>S: extractAutoRunDirectives -> emit autoRunTriggered(groupChatId, participant, filename?)
  S-->>R: autoRunTriggered event
  R->>R: registerGroupChatAutoRun(sessionId, groupChatId, participant)
  R->>P: start batch run (spawn processes)
  P->>S: emit raw-stdout(sessionId, chunk)
  S->>S: resolve participant from chunk -> emit participantLiveOutput(groupChatId, participant, chunk)
  S-->>R: participantLiveOutput event
  R->>Store: appendParticipantLiveOutput(participant, chunk)
  P-->>R: batch completes
  R->>S: reportAutoRunComplete(groupChatId, participant, summary)
  S->>S: routeAgentResponse / markParticipantResponded / spawnModeratorSynthesis?
  S-->>R: autoRunBatchComplete(groupChatId, participant)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

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

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title 'feat: group chat enhancements' is vague and overly broad, using the non-descriptive term 'enhancements' that doesn't convey the specific scope or primary changes of this comprehensive changeset. Consider a more specific title that highlights the primary feature or capability added, such as 'feat: add auto-run directives and participant response handling for group chat' or 'feat: implement group chat auto-run with moderator synthesis and live output streaming'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

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
Copy Markdown

@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
Copy Markdown
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
Copy Markdown
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
Copy Markdown

@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

@jeffscottward
Copy link
Copy Markdown
Contributor

jeffscottward commented Mar 14, 2026

@ets please run the following on your machine for this PR so it can move toward acceptance.

Here is the prompt you should run:

You are responsible for getting this PR in RunMaestro/Maestro to an accept-ready state.

PR: https://github.com/RunMaestro/Maestro/pull/559
Branch/Head: inspect the PR branch locally before making changes.

Goal:
Drive this PR to zero unresolved CodeRabbit and Greptile review threads.

Process:
1. Fetch the PR locally and work from a clean git worktree on the PR branch.
2. List all unresolved CodeRabbit and Greptile review threads for this PR.
3. For each unresolved bot thread:
   - verify whether the issue is still real on the current PR head
   - if it is already fixed or the thread is outdated, resolve the GitHub review thread immediately
   - if it is real, implement the smallest correct fix on the PR branch
4. After each fix batch:
   - run the relevant validation commands locally
   - push the branch
   - re-poll the PR’s unresolved CodeRabbit/Greptile threads
   - resolve any newly stale/fixed bot threads
5. Repeat until this PR has zero unresolved CodeRabbit and Greptile threads.

Rules:
- Resolve bot threads only when the current branch state actually fixes them.
- Do not resolve human review threads unless explicitly asked.
- Be surgical. Do not broaden scope beyond the review findings.
- If local validation differs from CI because of environment issues, state the exact failing test/check and the exact missing dependency/file/path.
- If a bot comment is a design tradeoff rather than a bug, explain why and leave it unresolved only if it truly still applies.

Definition of done:
- zero unresolved CodeRabbit threads
- zero unresolved Greptile threads
- branch pushed
- latest validation/check status re-polled
- concise summary written with:
  - PR URL
  - commits pushed
  - threads resolved
  - any remaining blockers

Keep looping until the PR is clean.

- Fix mock return type for markParticipantResponded (returns boolean, not object)
- Add logging and Sentry reporting to timeout-path catch block
- Gate emitAutoRunBatchComplete to autorun-tracked participants only
- Use participantsToRespond.size for finalization check instead of raw directive count
- Scope batch completion resolution to group chat via autorun registry
- Surface handleStopAll failures to user via toast notification

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/group-chat/group-chat-router.ts (2)

257-286: ⚠️ Potential issue | 🟠 Major

Clear autorun tracker state on normal completion and reset.

autoRunParticipantTracker is only pruned in the timeout path. After a successful reportAutoRunComplete() or stopAll(), the stale entry survives, so a later ordinary timeout for the same participant/chat can still emit an unrelated emitAutoRunBatchComplete().

Suggested fix
 export function clearPendingParticipants(groupChatId: string): void {
 	// Cancel all timeouts for this chat before clearing
 	const pending = pendingParticipantResponses.get(groupChatId);
 	if (pending) {
 		for (const name of pending) {
 			clearParticipantResponseTimeout(groupChatId, name);
 		}
 	}
 	pendingParticipantResponses.delete(groupChatId);
+	autoRunParticipantTracker.delete(groupChatId);
 }
@@
 export function markParticipantResponded(groupChatId: string, participantName: string): boolean {
 	clearParticipantResponseTimeout(groupChatId, participantName);
+
+	const autoRunSet = autoRunParticipantTracker.get(groupChatId);
+	if (autoRunSet?.delete(participantName) && autoRunSet.size === 0) {
+		autoRunParticipantTracker.delete(groupChatId);
+	}
 
 	const pending = pendingParticipantResponses.get(groupChatId);
 	if (!pending) return false;
🤖 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 257 - 286,
autoRunParticipantTracker entries are only removed on timeout, leaving stale
entries after normal completion paths; update reportAutoRunComplete() and
stopAll() to remove the corresponding key from autoRunParticipantTracker when a
run finishes or is reset so later timeouts don't emit spurious
emitAutoRunBatchComplete(). Locate autoRunParticipantTracker usage and add a
delete for the groupChatId (or participant-specific key used) in both
reportAutoRunComplete() and stopAll() (and any other normal-completion code
paths), ensuring consistency with how timeouts currently prune the tracker.

1520-1608: ⚠️ Potential issue | 🟠 Major

Moderator synthesis still bypasses SSH wrapping.

routeUserMessage() already routes moderator spawns through wrapSpawnWithSsh(), but this synthesis path still goes straight to processManager.spawn(). Any chat with moderatorConfig.sshRemoteConfig will execute the follow-up synthesis locally instead of on the configured remote.

As per coding guidelines, "When implementing features that spawn agent processes (context grooming, group chat, batch operations), you MUST support SSH remote execution."

🤖 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 1520 - 1608, Synthesis
spawn bypasses SSH wrapping by calling processManager.spawn() directly; update
the synthesis path to use the same SSH wrapping as routeUserMessage() by calling
wrapSpawnWithSsh(...) with the original command, finalArgs, winConfig
(shell/runInShell/sendPromptViaStdin flags) and
chat.moderatorConfig?.sshRemoteConfig, then pass the wrapped command/args into
processManager.spawn instead of the unwrapped values; ensure you preserve
prompt, readOnlyMode, customEnvVars, promptArgs, contextWindow and other spawn
options when replacing the direct call so synthesis runs on the configured
remote.
♻️ Duplicate comments (2)
src/renderer/hooks/groupChat/useGroupChatHandlers.ts (1)

633-640: ⚠️ Potential issue | 🟠 Major

Report stop-all failures explicitly.

The toast is good, but this catch still turns every failure into console.error + UI feedback and drops the error from telemetry. Please capture the exception with activeGroupChatId / groupChat:stopAll context, and only special-case known recoverable error codes here.

As per coding guidelines, "For expected/recoverable errors, catch them explicitly, check the error code, and handle gracefully." and "Use Sentry utilities for explicit reporting."

🤖 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 633 - 640,
In the catch block inside useGroupChatHandlers.ts for the stop-all flow, replace
the blanket console.error + toast by explicitly reporting the exception to
Sentry (use your project Sentry utility, e.g., Sentry.captureException or the
app's report function) with context/tags { operation: 'groupChat:stopAll',
activeGroupChatId } and include the original error; then special-case known
recoverable errors by checking error.code (handle those without reporting or
with a lower-severity log and keep the UI toast), and only send telemetry for
unexpected failures while still showing the same user-facing toast on error.
src/__tests__/main/ipc/handlers/groupChat.test.ts (1)

1065-1094: ⚠️ Potential issue | 🟡 Minor

Assert the synthesis branch, not just the mock return type.

These cases still pass if reportAutoRunComplete always skips synthesis or always triggers it. Add explicit spawnModeratorSynthesis expectations for the false and true branches so the boolean contract is actually covered.

Suggested test assertions
		expect(groupChatRouter.routeAgentResponse).toHaveBeenCalledWith(
			'gc-autorun',
			'Worker 1',
			'Task completed successfully',
			mockProcessManager
		);
+		expect(groupChatRouter.spawnModeratorSynthesis).not.toHaveBeenCalled();
@@
		expect(groupChatRouter.markParticipantResponded).toHaveBeenCalledWith(
			'gc-autorun-done',
			'Worker 1'
		);
+		expect(groupChatRouter.spawnModeratorSynthesis).toHaveBeenCalledTimes(1);
🤖 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 1065 - 1094,
The tests for the "groupChat:reportAutoRunComplete" handler currently assert
routeAgentResponse and markParticipantResponded but don't assert the synthesis
branch; update the two cases to explicitly assert spawnModeratorSynthesis
behavior: in the first test (when markParticipantResponded returns false) add an
expectation that groupChatRouter.spawnModeratorSynthesis was not called, and in
the second test (when markParticipantResponded returns true) add an expectation
that groupChatRouter.spawnModeratorSynthesis was called (and optionally called
with the expected gc id and mockProcessManager) so both boolean branches of
markParticipantResponded are actually validated; locate the handler via
handlers.get('groupChat:reportAutoRunComplete') and the router mocks
groupChatRouter.routeAgentResponse, groupChatRouter.markParticipantResponded and
groupChatRouter.spawnModeratorSynthesis to modify the assertions.
🤖 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/group-chat/group-chat-router.ts`:
- Around line 204-209: The console-only catch for spawnModeratorSynthesis should
instead report the error to Sentry and perform the same cleanup the successful
path would do so the power manager lock is cleared; replace the current .catch
block on spawnModeratorSynthesis(groupChatId, processManager, agentDetector) to
call the project's Sentry reporting util (e.g., reportExceptionToSentry or
Sentry.captureException) with the error, then call
groupChatEmitters.emitStateChange?.(groupChatId, 'idle') and explicitly clear
any power/process lock for this chat (use processManager or powerManager helper
to release/unblock the key `groupchat:${groupChatId}` or call the existing
cleanup helper used by spawnModeratorSynthesis), ensuring no errors are
swallowed.

In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts`:
- Around line 237-259: The callback currently completes every autorun session
from getAutoRunSessionsForGroupChat(groupChatId) when any participant emits
completion; instead, resolve a single session by matching participantName to a
session id inside that registry: when autoRunSessionIds.length > 0, lookup
useSessionStore.getState().sessions to find the session whose id is in
autoRunSessionIds and whose name === participantName and call
useBatchStore.getState().dispatchBatch({ type: 'COMPLETE_BATCH', sessionId })
only for that session (if found); keep the existing participantName-only lookup
(using useSessionStore.sessions.find(s => s.name === participantName)) as the
fallback branch when autoRunSessionIds is empty.

---

Outside diff comments:
In `@src/main/group-chat/group-chat-router.ts`:
- Around line 257-286: autoRunParticipantTracker entries are only removed on
timeout, leaving stale entries after normal completion paths; update
reportAutoRunComplete() and stopAll() to remove the corresponding key from
autoRunParticipantTracker when a run finishes or is reset so later timeouts
don't emit spurious emitAutoRunBatchComplete(). Locate autoRunParticipantTracker
usage and add a delete for the groupChatId (or participant-specific key used) in
both reportAutoRunComplete() and stopAll() (and any other normal-completion code
paths), ensuring consistency with how timeouts currently prune the tracker.
- Around line 1520-1608: Synthesis spawn bypasses SSH wrapping by calling
processManager.spawn() directly; update the synthesis path to use the same SSH
wrapping as routeUserMessage() by calling wrapSpawnWithSsh(...) with the
original command, finalArgs, winConfig (shell/runInShell/sendPromptViaStdin
flags) and chat.moderatorConfig?.sshRemoteConfig, then pass the wrapped
command/args into processManager.spawn instead of the unwrapped values; ensure
you preserve prompt, readOnlyMode, customEnvVars, promptArgs, contextWindow and
other spawn options when replacing the direct call so synthesis runs on the
configured remote.

---

Duplicate comments:
In `@src/__tests__/main/ipc/handlers/groupChat.test.ts`:
- Around line 1065-1094: The tests for the "groupChat:reportAutoRunComplete"
handler currently assert routeAgentResponse and markParticipantResponded but
don't assert the synthesis branch; update the two cases to explicitly assert
spawnModeratorSynthesis behavior: in the first test (when
markParticipantResponded returns false) add an expectation that
groupChatRouter.spawnModeratorSynthesis was not called, and in the second test
(when markParticipantResponded returns true) add an expectation that
groupChatRouter.spawnModeratorSynthesis was called (and optionally called with
the expected gc id and mockProcessManager) so both boolean branches of
markParticipantResponded are actually validated; locate the handler via
handlers.get('groupChat:reportAutoRunComplete') and the router mocks
groupChatRouter.routeAgentResponse, groupChatRouter.markParticipantResponded and
groupChatRouter.spawnModeratorSynthesis to modify the assertions.

In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts`:
- Around line 633-640: In the catch block inside useGroupChatHandlers.ts for the
stop-all flow, replace the blanket console.error + toast by explicitly reporting
the exception to Sentry (use your project Sentry utility, e.g.,
Sentry.captureException or the app's report function) with context/tags {
operation: 'groupChat:stopAll', activeGroupChatId } and include the original
error; then special-case known recoverable errors by checking error.code (handle
those without reporting or with a lower-severity log and keep the UI toast), and
only send telemetry for unexpected failures while still showing the same
user-facing toast on error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6e5fd41b-1a81-41b6-974c-f6cf42fa20e2

📥 Commits

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

📒 Files selected for processing (3)
  • src/__tests__/main/ipc/handlers/groupChat.test.ts
  • src/main/group-chat/group-chat-router.ts
  • src/renderer/hooks/groupChat/useGroupChatHandlers.ts

- Add Sentry reporting and powerManager cleanup to timeout synthesis catch
- Scope batch completion to matching participant session, not all sessions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 2

♻️ Duplicate comments (2)
src/renderer/hooks/groupChat/useGroupChatHandlers.ts (2)

621-643: ⚠️ Potential issue | 🟠 Major

handleStopAll still swallows unexpected errors and misses Sentry reporting.

This catch handles every failure identically, logs to console, and never escalates unexpected errors.

Suggested fix aligned with error-handling guidelines
+import { captureException } from '../../utils/sentry';
...
 	const handleStopAll = useCallback(async () => {
 		const { activeGroupChatId } = useGroupChatStore.getState();
 		if (!activeGroupChatId) return;
 		try {
 			// Cancel any in-flight autorun batch runs for this group chat.
 			// These run in the agent's own Maestro session (not group-chat-prefixed),
 			// so the main process's clearAllParticipantSessions won't reach them.
 			const autoRunSessionIds = getAutoRunSessionsForGroupChat(activeGroupChatId);
 			for (const sessionId of autoRunSessionIds) {
 				useBatchStore.getState().dispatchBatch({
 					type: 'COMPLETE_BATCH',
 					sessionId,
 				});
 			}
 			await window.maestro.groupChat.stopAll(activeGroupChatId);
 		} catch (error) {
-			console.error('[GroupChat] Failed to stop all:', error);
-			notifyToast({
-				type: 'error',
-				title: 'Stop Failed',
-				message: 'Failed to stop all group chat conversations. Please try again.',
-			});
+			const errorCode =
+				typeof error === 'object' && error !== null && 'code' in error
+					? String((error as { code?: unknown }).code)
+					: '';
+
+			if (errorCode === 'NETWORK_ERROR') {
+				notifyToast({
+					type: 'error',
+					title: 'Stop Failed',
+					message: 'You appear to be offline. Please reconnect and try again.',
+				});
+				return;
+			}
+
+			captureException(error, {
+				operation: 'groupChat:stopAll',
+				groupChatId: activeGroupChatId,
+			});
+			throw error;
 		}
 	}, []);

As per coding guidelines, "For expected/recoverable errors, catch them explicitly, check the error code, and handle gracefully" and "For unexpected errors, rethrow to let Sentry capture them."

🤖 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 621 - 643,
The catch block in handleStopAll swallows all errors; change it to handle
expected/recoverable errors explicitly (e.g., inspect error.code or instance for
known Maestro/group-chat errors) and only show the notifyToast and suppress the
error for those cases; for any unexpected errors, rethrow the error so Sentry
can capture it. Keep the existing cleanup of autoRunSessionIds via
useBatchStore.dispatchBatch and the call to window.maestro.groupChat.stopAll,
but replace the generic catch to branch: if error matches known/recoverable
cases then log and notifyToast as now, otherwise console.error and throw error
(or rethrow) so the error propagates to global handlers/Sentry.

241-257: ⚠️ Potential issue | 🟠 Major

Avoid global name-only fallback while group-scoped autorun entries still exist.

When autoRunSessionIds is non-empty but no participant match is found, the current fallback can still complete an unrelated same-named session from another chat.

Suggested minimal fix
 				const autoRunSessionIds = getAutoRunSessionsForGroupChat(groupChatId);
 				if (autoRunSessionIds.length > 0) {
 					const sessions = useSessionStore.getState().sessions;
 					const matchingSessionId = autoRunSessionIds.find((sid) =>
 						sessions.some((s) => s.id === sid && s.name === participantName)
 					);
 					if (matchingSessionId) {
 						useBatchStore.getState().dispatchBatch({
 							type: 'COMPLETE_BATCH',
 							sessionId: matchingSessionId,
 						});
 						return;
 					}
+					// Prevent cross-chat collisions from global name-only fallback.
+					return;
 				}
 				// Fallback: resolve by participant name if registry entry was already consumed
 				const session = useSessionStore.getState().sessions.find((s) => s.name === participantName);
🤖 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 241 - 257,
The code currently falls back to resolving a session by name even when
group-scoped autoRun entries exist but none matched, which can complete a
same-named session from another chat; update the logic in useGroupChatHandlers
by using getAutoRunSessionsForGroupChat(groupChatId) and after computing
matchingSessionId (using useSessionStore.getState().sessions) if
autoRunSessionIds.length > 0 and no matchingSessionId was found then return
early (do not fall back), so only perform the name-only lookup
(useSessionStore.getState().sessions.find by participantName) when
autoRunSessionIds is empty.
🤖 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/group-chat/group-chat-router.ts`:
- Around line 1049-1054: The autoRunParticipantTracker map is populated for
autorun participants (autoRunParticipantTracker.set/get with groupChatId and
participant.name) but never cleared on successful completion, leaking entries
and causing future emitAutoRunBatchComplete to misfire; fix by removing the
participant from autoRunParticipantTracker in the non-timeout success path (the
code path that calls emitBatchComplete / handles successful batch processing for
participant) — call
autoRunParticipantTracker.get(groupChatId)!.delete(participant.name) and if the
Set becomes empty, remove the groupChatId key
(autoRunParticipantTracker.delete(groupChatId)); apply the same cleanup wherever
successful completion is handled so timeout is the only path that triggers
emitAutoRunBatchComplete for autorun entries.
- Around line 860-883: The code currently appends/emits/creates history for a
moderator message even when displayMessage is empty after stripping !autorun
directives; update the block around
appendToLog/groupChatEmitters.emitMessage/addGroupChatHistoryEntry to first
check whether displayMessage (after trimming) is non-empty and only proceed if
it has content—otherwise skip appending, emitting, and history creation (or
return early) to avoid empty chat bubbles and history rows; reference symbols:
displayMessage, appendToLog, groupChatEmitters.emitMessage,
addGroupChatHistoryEntry, extractFirstSentence, and groupChatId.

---

Duplicate comments:
In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts`:
- Around line 621-643: The catch block in handleStopAll swallows all errors;
change it to handle expected/recoverable errors explicitly (e.g., inspect
error.code or instance for known Maestro/group-chat errors) and only show the
notifyToast and suppress the error for those cases; for any unexpected errors,
rethrow the error so Sentry can capture it. Keep the existing cleanup of
autoRunSessionIds via useBatchStore.dispatchBatch and the call to
window.maestro.groupChat.stopAll, but replace the generic catch to branch: if
error matches known/recoverable cases then log and notifyToast as now, otherwise
console.error and throw error (or rethrow) so the error propagates to global
handlers/Sentry.
- Around line 241-257: The code currently falls back to resolving a session by
name even when group-scoped autoRun entries exist but none matched, which can
complete a same-named session from another chat; update the logic in
useGroupChatHandlers by using getAutoRunSessionsForGroupChat(groupChatId) and
after computing matchingSessionId (using useSessionStore.getState().sessions) if
autoRunSessionIds.length > 0 and no matchingSessionId was found then return
early (do not fall back), so only perform the name-only lookup
(useSessionStore.getState().sessions.find by participantName) when
autoRunSessionIds is empty.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab44283f-5590-46fe-8c29-5324a7bc609b

📥 Commits

Reviewing files that changed from the base of the PR and between 185874b and d79707a.

📒 Files selected for processing (2)
  • src/main/group-chat/group-chat-router.ts
  • src/renderer/hooks/groupChat/useGroupChatHandlers.ts

- Skip log/emit/history for empty moderator messages after stripping !autorun
- Clean up autoRunParticipantTracker in markParticipantResponded and clearPendingParticipants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ets
Copy link
Copy Markdown
Author

ets commented Mar 14, 2026

@jeffscottward my model confirms completion.

ets and others added 2 commits March 15, 2026 11:52
…ion feedback

Root cause: AI moderators often output markdown-formatted mentions like
**@name** or _@name_. The mention regex captured trailing formatting chars
(e.g. "controlplane**"), which failed participant matching silently — no
agents were spawned, no visual indicators changed, and no synthesis triggered.

Changes:
- Add stripMarkdownFormatting() to clean *, _, `, ~ from mention names
- Apply to extractMentions, extractAllMentions, and extractAutoRunDirectives
- Emit system message when moderator mentions resolve to zero actionable work
- Add 12 tests covering markdown-formatted mention extraction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: pendingParticipantResponses was only populated AFTER the spawn
loop completed (deferred to post-loop block). If a participant process
exited before that registration, the exit listener's markParticipantResponded
call found no pending entry, returned false, and synthesis never triggered.
This also meant emitStateChange('agent-working') fired late, so the sidebar
and chat indicators didn't reflect participant activity during the window
between spawn and post-loop registration.

Fix: Register each participant in pendingParticipantResponses immediately
after spawn (both @mention and !autorun paths). Install per-participant
timeouts and emit 'agent-working' state on first spawn, not after loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@josecsotomorales
Copy link
Copy Markdown

This is a great contribution to group chats, and I would love to use it in main 🚀

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.

3 participants