fix: group chat process cleanup, Codex cost tracking, moderator @mention enforcement#437
fix: group chat process cleanup, Codex cost tracking, moderator @mention enforcement#437openasocket wants to merge 24 commits intoRunMaestro:mainfrom
Conversation
Enable group chat to spawn new agent instances by type when no matching session exists in the sidebar. When a user or moderator @mentions an agent name that doesn't match an existing session, the system now falls back to matching against AGENT_DEFINITIONS and spawns a fresh instance with default configuration. - Add addFreshParticipant() to group-chat-agent.ts (clean spawn, no session overrides) - Add agent type fallback in routeUserMessage() and routeModeratorResponse() in group-chat-router.ts - Add groupChat:addFreshParticipant IPC handler and preload exposure - Update test to include new IPC handler channel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "+" button in participants panel header and AddParticipantModal with two modes: "Use existing session" (inherits config) and "Create fresh agent" (clean defaults). Wires modal through modalStore, App.tsx, AppModals, and useGroupChatHandlers. Adds missing addFreshParticipant type to global.d.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…arsing Add skipBatchForReadOnly guard in buildAgentArgs to prevent --dangerously-bypass-approvals-and-sandbox from conflicting with --sandbox read-only when Codex runs as a read-only moderator. Handle item.completed type:"error" events in Codex output parser (e.g., account warnings, sandbox violations) instead of silently swallowing them as system events. Replace OpenCode-specific error patterns in Codex with proper OpenAI patterns: model not found, model access denied, stream disconnection, and thread not found. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rticipant categories Add agents:getAvailable IPC endpoint that returns installed, non-hidden agent types. Enhance GroupChatInput mention dropdown to show three categories: existing participants (disabled), available sessions, and agent types that can be spawned fresh (with "New" badge). Section headers appear when multiple categories are present. Keyboard navigation skips disabled participant items. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added 6 test cases in a new describe('addFreshParticipant') block:
- spawns fresh agent with default config (no session overrides)
- generates unique session ID with group-chat- prefix
- rejects duplicate participant names
- requires moderator to be active
- throws if agent is not available
- uses os.homedir() as default cwd
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…outer
Add 6 tests in describe('agent type fallback') covering: session-first
resolution (backward compat), agent type fallback when no session exists,
fresh participant default cwd (os.homedir()), ignoring unavailable agents,
both ID and name mention matching via mentionMatches(), and moderator
response fallback. Mocked getVisibleAgentDefinitions via vi.mock with
importActual passthrough.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 13 tests covering rendering, session filtering (terminal exclusion, already-added exclusion), fresh agent selection, existing session selection, error handling, and modal close. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Input Add 5 tests covering agent type items with "New" badge, participant disabled state, session/agent-type deduplication, and dropdown filtering for the enhanced @mention autocomplete feature. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updated 7 generic error messages across group-chat-agent.ts and group-chat-router.ts to provide actionable guidance: agent unavailable now suggests installing or using a different agent, spawn failures suggest checking configuration, and moderator-not-active suggests restarting the moderator. Updated corresponding test assertions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oup chat Add validation module (validation.ts) with UUID, participant name, message content, base64 image, custom args, and env vars validators. Apply validation at the top of every group chat IPC handler. Add defense-in-depth path containment check to getGroupChatDir(). 45 new unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent state corruption from concurrent operations: - Chat-level locks prevent delete/update during message processing - Synthesis guards prevent double-trigger from duplicate exit handlers - Pending participants merge instead of overwrite across rounds - Spawn failure resets participant UI state to idle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…roup chat - Add explicit null guards for processManager/agentDetector in groupChat IPC handlers (sendToModerator, addParticipant, sendToParticipant, addFreshParticipant) replacing silent ?? undefined coalescing with thrown errors - Add killAllModerators() to group-chat-moderator.ts that kills all active moderator processes and clears session tracking maps - Wire group chat cleanup (clearAllParticipantSessionsGlobal + killAllModerators) into the quit handler's performCleanup() to prevent zombie processes on app exit - Add SSH wrapping to spawnModeratorSynthesis() matching the pattern used in routeUserMessage() so synthesis runs remotely on SSH-configured moderators - Add tests for killAllModerators and quit handler group chat cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…g in group chat Wrap untrusted content (chat history, user messages, agent responses) in XML-style boundary tags to prevent prompt injection attacks in moderator and participant prompts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… remove debug logging
- Wrap deleteGroupChatHistoryEntry() and clearGroupChatHistory() in
enqueueWrite() to prevent read-modify-write race conditions on
concurrent history operations
- Add missing fields to resetGroupChatState() (moderatorUsage,
groupChatExecutionQueue, groupChatStagedImages, groupChatReadOnlyMode)
to prevent stale state when switching chats
- Remove ~100 console.log('[GroupChat:Debug]') statements from
group-chat-router.ts, replacing key state transitions with
logger.debug() and converting console.error/warn to logger equivalents
- Remove 11 debug console.log/warn statements from groupChat.ts IPC
handler, replace emitParticipantState warning with logger.warn()
- Add TODO comment for unimplemented image embedding in sendToModerator
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt buffer loss When Codex uses tool calls during a moderator turn, the first agent_message sets resultEmitted=true after flushing. If a second agent_message arrives (the final one with @mentions), the subsequent usage event skips the flush because resultEmitted is already true, leaving stale reasoning text in the buffer. Reset the flag on each new agent_message so the latest text is always flushed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ew agent_message Covers the scenario where Codex tool calls cause an interim buffer flush, then a second agent_message with @mentions arrives. Verifies that resultEmitted is reset so the subsequent usage event flushes the updated text (preventing stale reasoning from being routed instead of the real response). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ngs) Moderator and participant processes now resume prior agent sessions instead of rebuilding full prompts each turn. On resume, only the new message/delegation is sent (~200-400 tokens vs ~2,200 tokens full prompt). Falls back to full prompt when no stored session ID or agent lacks resume support. Exit listener detects session_not_found errors and clears stored session ID for automatic fallback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y sandbox bypass, improve moderator @mention rules - Add new wrapped format support for Codex CLI v0.103.0+ (task_started, agent_reasoning, agent_message, token_count, tool_call, tool_result events) - Fix agent-args readOnlyMode to only strip sandbox-bypass flags, preserving --skip-git - Enhance moderator system prompt with explicit @mention routing rules and agent addition flow - Add conductorProfile callback for group chat conductor customization - Add groupChatLock mock to exit-listener tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-json output Adds 8 new tests to StdoutHandler.test.ts verifying the outputParser path for session-id emission, which was previously untested (all mocks returned null). Includes 3 integration tests using real ClaudeOutputParser. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…resume Codex wrapped format (v0.103.0+) `task_started` events were generating synthetic `codex-0-<timestamp>` session IDs. These IDs can't be used with `codex exec resume` — instead of erroring, Codex silently starts a new session with no prior context (verified manually on v0.103.0). Real session IDs come from `thread.started` events (legacy format), which Codex v0.103.0 uses by default. Removing synthetic ID generation ensures: - Legacy format: real thread_id UUIDs are captured and resume works - Wrapped format: no session ID stored, safe fallback to full prompt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… participant) Adds 4 tests verifying that Claude Code and Codex agents resume independently in group chat with their distinct resume arg formats: - Claude: --resume <session_id> - Codex: resume <session_id> (no -- prefix) Tests cover: independent resume args, batch mode prefixes, synthesis round isolation, and full-prompt-to-resume lifecycle transitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…on resume Automates the previously manual "app restart" test checklist item with 4 new tests verifying session IDs survive disk persistence and resume works after simulated restart: single-agent moderator+participant resume, synthesis resume, listGroupChats enumeration, and mixed Claude+Codex restart flow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4 new tests verify resume args (`--resume <sessionId>`) pass through the SSH wrapper correctly for moderator routing, participant delegation, synthesis spawning, and the negative case (first turn without resume). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…isplay, moderator @mention enforcement Three fixes for group chat reliability: 1. Process cleanup on delete: killModerator() and clearAllParticipantSessions() used exact session ID matching but batch mode spawns processes with timestamp suffixes. Added killByPrefix() to ProcessManager so delete kills all matching processes instead of silently failing (root cause of zombie processes and y-flood). 2. Codex session ID and cost: Restored display-only synthetic session IDs (codex-0-*) so UI shows an ID instead of "pending". Resume logic skips these prefixed IDs. Added OpenAI model pricing table to calculate cost from token counts since Codex CLI doesn't report cost directly. 3. Moderator prompt: Added mandatory @mention enforcement section and delegation strategy to prevent moderator from discussing agent tasks without actually routing messages. Updated synthesis prompt for critical analysis and fusion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughIntroduces a comprehensive group chat participant management system with fresh participant addition, operation locking, input validation, process batch termination, and moderator synthesis enhancements. Spans new modules (locking, validation), expanded IPC handlers, process manager capabilities, UI components for participant addition, and updated output parsing to support new Codex format versions. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR fixes three critical group chat issues: Process cleanup on delete
Codex session ID and cost tracking
Moderator @mention enforcement
Additional improvements
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User sends message to group chat] --> B{Acquire chat lock}
B -->|Lock failed| C[Error: Chat busy]
B -->|Lock acquired| D[Validate input]
D --> E{Auto-add @mentioned agents?}
E -->|Session exists| F[Add from existing session]
E -->|Agent type only| G[Add fresh agent instance]
E -->|No match| H[Continue]
F --> H
G --> H
H --> I[Spawn moderator batch process]
I --> J[Moderator processes message]
J --> K{Contains @mentions?}
K -->|Yes| L[Route to participants]
K -->|No| M[Direct response to user]
L --> N[Spawn participant batch processes]
N --> O{All responses received?}
O -->|No| P[Wait for responses]
O -->|Yes| Q{Synthesis guard check}
Q -->|Already running| R[Skip duplicate synthesis]
Q -->|Not running| S[Mark synthesis started]
S --> T[Spawn synthesis process]
T --> U[Moderator fuses responses]
U --> V[Clear synthesis guard]
V --> M
M --> W[Release chat lock]
X[Delete group chat] --> Y{Check lock}
Y -->|Locked| Z[Error: Chat busy]
Y -->|Unlocked| AA[Kill moderator by prefix]
AA --> AB[Kill all participants by prefix]
AB --> AC[Delete chat data]
style B fill:#fff3cd
style Q fill:#fff3cd
style AA fill:#d1ecf1
style AB fill:#d1ecf1
Last reviewed commit: f7b1265 |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/__tests__/main/ipc/handlers/groupChat.test.ts (1)
100-132:⚠️ Potential issue | 🟠 MajorType the mock to include
killByPrefix.The object literal includes
killByPrefix, but the declared type formockProcessManagerdoesn’t, which will fail TypeScript’s excess property checks.✅ Proposed fix
let mockProcessManager: { spawn: ReturnType<typeof vi.fn>; write: ReturnType<typeof vi.fn>; kill: ReturnType<typeof vi.fn>; + killByPrefix: ReturnType<typeof vi.fn>; };🤖 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 100 - 132, The mockProcessManager declared type is missing the killByPrefix property while the object literal sets it; update the mockProcessManager type to include killByPrefix (e.g., add killByPrefix: ReturnType<typeof vi.fn>) so the declaration matches the object created in beforeEach and TypeScript no longer errors on excess/missing properties for mockProcessManager.
🧹 Nitpick comments (13)
src/renderer/global.d.ts (1)
1776-1786: Parameter order differs fromaddParticipant.
addParticipantuses(id, name, agentId, cwd)(lines 1765-1770) whileaddFreshParticipantuses(id, agentId, name, cwd). This inconsistency could cause confusion for callers. If intentional (perhaps to emphasize that agentId is more central for fresh spawns), consider documenting this difference. Otherwise, aligning the parameter order would improve API consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/global.d.ts` around lines 1776 - 1786, The two APIs have inconsistent parameter order: addParticipant(id, name, agentId, cwd) vs addFreshParticipant(id, agentId, name, cwd); change addFreshParticipant’s declaration and any implementations/callsites to use the same order as addParticipant (id, name, agentId, cwd) to keep the API consistent, and update any documentation/tests that rely on the old order; if the original order was intentional, instead add a comment/docstring to addFreshParticipant clarifying the different ordering.src/main/preload/groupChat.ts (1)
115-116: Consider consistency: addFreshParticipant argument order differs from addParticipant.The preload method
addParticipantuses(id, name, agentId, cwd)whileaddFreshParticipantuses(id, agentId, name, cwd). While the handler correctly receives and reorders these arguments before invoking the actual function, the inconsistent parameter ordering in the preload API is confusing for developers. Consider aligning both methods to use the same argument order for better API consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/preload/groupChat.ts` around lines 115 - 116, The preload API is inconsistent: addParticipant(id, name, agentId, cwd) vs addFreshParticipant(id, agentId, name, cwd); change addFreshParticipant to the same parameter order as addParticipant (id, name, agentId, cwd), update its ipcRenderer.invoke call to pass arguments in that order, and update the corresponding IPC handler for 'groupChat:addFreshParticipant' (or the code that reorders its args before calling the underlying function) so it expects and forwards (id, name, agentId, cwd) consistently with addParticipant.src/main/ipc/handlers/groupChat.ts (2)
770-771: Incorrect validator used forentryId.
validateGroupChatId(entryId)validates thatentryIdis a UUID, which is correct if entry IDs are UUIDs. However, the function name is misleading since it's being used for a history entry ID, not a group chat ID. Consider creating a separatevalidateEntryIdfunction or a more genericvalidateUUIDfunction for clarity.Proposed refactor to use a generic UUID validator
In
src/main/group-chat/validation.ts, add:+/** + * Validate that a value is a valid UUID v4. + */ +export function validateUUID(value: unknown, fieldName = 'value'): string { + if (typeof value !== 'string' || !UUID_V4_REGEX.test(value)) { + throw new Error(`Invalid ${fieldName}: must be a valid UUID`); + } + return value; +}Then in the handler:
- validateGroupChatId(entryId); + validateUUID(entryId, 'entry ID');🤖 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 770 - 771, The code incorrectly reuses validateGroupChatId for the history entry ID; add a clear UUID validator (e.g., validateEntryId or validateUUID) in the group-chat validation module and replace the validateGroupChatId(entryId) call with the new validator; update any imports/usages in the handler (groupChatId = validateGroupChatId(groupChatId); validateEntryId(entryId) or validateUUID(entryId)) so intent is clear and validators are not misleading.
440-441: Acknowledged TODO for image embedding.The TODO comment about image embedding in moderator prompts is clear. Consider tracking this in an issue if not already done.
Would you like me to help create an issue to track the image embedding feature for moderator prompts?
🤖 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 440 - 441, Create a tracked issue for implementing image embedding in moderator prompts: describe the current state (images saved separately via the groupChat:saveImage handler and a TODO in the async moderator prompt handler with signature async (id: string, message: string, images?: string[], readOnly?: boolean)), list acceptance criteria (embed images inline in moderator prompt payloads, preserve image ordering and alt text, fallback to separate storage when embedding fails), include required API changes (modify the moderator prompt handler to accept/serialize embedded image data or URLs), add tests and UI/UX notes, and tag with a feature/enhancement label and assign or link to the relevant maintainer for prioritization.src/main/group-chat/group-chat-lock.ts (1)
75-82: Consider consolidatingforceReleaseChatLockwithreleaseChatLock.Both functions have identical implementations (calling
chatLocks.delete(chatId)). The semantic distinction ("unconditional" vs regular release) doesn't manifest in the code. Consider whether one function with clear documentation would suffice.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/group-chat/group-chat-lock.ts` around lines 75 - 82, Both forceReleaseChatLock and releaseChatLock simply call chatLocks.delete(chatId), so consolidate by removing one of them (preferably keep releaseChatLock for the simpler name) and update all references to the removed symbol to call the retained function; adjust JSDoc on the retained function to document unconditional behavior if needed and run/update any callers/tests that referenced forceReleaseChatLock to use releaseChatLock instead.src/main/group-chat/validation.ts (2)
95-109: Consider validating base64 format, not just size.The function validates type and size but doesn't verify the string is valid base64. Invalid base64 will fail later during decode. A regex check could catch malformed input earlier.
🛡️ Add base64 format validation
+const BASE64_REGEX = /^[A-Za-z0-9+/]*={0,2}$/; + export function validateBase64Image(data: unknown, maxSizeBytes = 20 * 1024 * 1024): string { if (typeof data !== 'string') { throw new Error('Invalid image data: must be a string'); } + + // Strip data URL prefix if present (e.g., "data:image/png;base64,") + const base64Data = data.includes(',') ? data.split(',')[1] : data; + + if (!BASE64_REGEX.test(base64Data)) { + throw new Error('Invalid image data: not valid base64'); + } // Approximate decoded size: base64 uses ~4/3 ratio - const estimatedSize = data.length * 0.75; + const estimatedSize = base64Data.length * 0.75;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/group-chat/validation.ts` around lines 95 - 109, The validateBase64Image function currently only checks type and estimated size but doesn't confirm the string is valid base64; add a format validation step in validateBase64Image that verifies the input matches a base64 pattern (optionally allowing data URI prefix removal) and rejects malformed strings before size checking, or attempt a safe decode (e.g., try Buffer.from(..., 'base64') and ensure re-encoding matches) to catch invalid base64 and throw a clear error message when validation fails.
10-10: UUID validation regex allows UUID v1-5, not just v4.The
UUID_V4_REGEXpattern matches any valid UUID format (v1-v5) since it doesn't validate the version nibble. While this works, the name is misleading.🧹 Either rename to UUID_REGEX or add version validation
-const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +// Matches standard UUID format (any version) +const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;Or for strict v4 validation:
-const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +// Strict UUID v4: version nibble = 4, variant nibble = 8/9/a/b +const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/group-chat/validation.ts` at line 10, The constant UUID_V4_REGEX currently matches any UUID version because it doesn't validate the version nibble; update the code so the name and behavior align: either rename UUID_V4_REGEX to UUID_REGEX (and update any references) if you intend to accept all UUID versions, or change the regex to enforce v4 specifically by requiring the version character to be "4" (e.g., the 13th hex digit) and adjust the constant name to remain UUID_V4_REGEX; locate and edit the UUID_V4_REGEX declaration in validation.ts and update usages accordingly.src/renderer/components/AddParticipantModal.tsx (2)
108-129: Minor: Early returns don't resetisSubmittingstate.The early returns on lines 114, 116, and 119 don't reset
isSubmittingtofalse. WhilecanSubmitshould prevent reaching these paths, if logic changes, the button could remain permanently disabled.🧹 Add isSubmitting reset to early returns
try { if (mode === 'existing') { - if (!selectedSessionId) return; + if (!selectedSessionId) { + setIsSubmitting(false); + return; + } const session = sessions.find((s) => s.id === selectedSessionId); - if (!session) return; + if (!session) { + setIsSubmitting(false); + return; + } onAddExisting(session.id, session.name, session.toolType, session.cwd); } else { - if (!selectedAgentId) return; + if (!selectedAgentId) { + setIsSubmitting(false); + return; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/components/AddParticipantModal.tsx` around lines 108 - 129, In handleSubmit ensure isSubmitting is reset on the early-return paths: before each early return in the 'existing' branch when selectedSessionId is falsy or session not found, and before the early return in the 'fresh' branch when selectedAgentId is falsy, call setIsSubmitting(false) (and optionally setError with a helpful message) so the component doesn't remain stuck in submitting state; update the closure that defines handleSubmit to call setIsSubmitting(false) right before those return points (symbols: handleSubmit, setIsSubmitting, selectedSessionId, sessions, selectedAgentId, AGENT_TILES, onAddExisting, onAddFresh).
22-22: Unused prop:groupChatIdis declared but never referenced.The
groupChatIdprop is defined inAddParticipantModalPropsand destructured in the component parameters but is never used in the component body.🧹 Remove unused prop
interface AddParticipantModalProps { theme: Theme; isOpen: boolean; - groupChatId: string; sessions: Session[]; participants: GroupChatParticipant[]; onClose: () => void; onAddExisting: (sessionId: string, name: string, agentId: string, cwd: string) => void; onAddFresh: (agentId: string, name: string) => void; } export function AddParticipantModal({ theme, isOpen, - groupChatId, sessions, participants,Also applies to: 30-39
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/components/AddParticipantModal.tsx` at line 22, Remove the unused prop "groupChatId": delete it from the AddParticipantModalProps interface and from the AddParticipantModal component's parameter destructuring, and remove any other references or propTypes/defaults named groupChatId in that component. Ensure the component compiles after removing the identifier and update any callers only if they were passing groupChatId (remove the argument) to avoid unused prop warnings.src/main/parsers/codex-output-parser.ts (2)
45-71: Pricing data is hardcoded and will become stale.Model pricing changes frequently. Consider externalizing this to a config file or adding a comment with the last-updated date and a reminder to verify periodically.
The comment on line 37-38 mentions "updated 2026-02-21" which is helpful. Consider also adding a TODO or linking to the source URL for easier verification:
/** * OpenAI model pricing per million tokens (USD) - * Source: https://openai.com/api/pricing/ (updated 2026-02-21) + * Source: https://openai.com/api/pricing/ + * Last verified: 2026-02-21 + * TODO: Consider moving to external config or fetching dynamically */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/parsers/codex-output-parser.ts` around lines 45 - 71, The MODEL_PRICING constant contains hardcoded rates that will go stale; either move this object into a configuration source (e.g., JSON/YAML loaded at runtime) and update code that references MODEL_PRICING to read from that config, or at minimum add a clear metadata comment above MODEL_PRICING with the last-updated date, a TODO to verify pricing regularly, and a link to the authoritative pricing URL so maintainers can refresh values; refer to the MODEL_PRICING symbol in src/main/parsers/codex-output-parser.ts when making changes.
76-86: Prefix matching order may cause incorrect pricing for new model variants.The prefix matching iterates in insertion order, so
gpt-5will match beforegpt-5.1for any model starting withgpt-5that isn't an exact match. For example, a hypotheticalgpt-5.1-codex-turbowould matchgpt-5pricing instead ofgpt-5.1-codex.🔧 Sort prefixes by length (longest first) for correct matching
function getModelPricing(model: string): ModelPricing { if (MODEL_PRICING[model]) { return MODEL_PRICING[model]; } - for (const [prefix, pricing] of Object.entries(MODEL_PRICING)) { + // Sort by prefix length descending to match most specific first + const sortedEntries = Object.entries(MODEL_PRICING).sort( + ([a], [b]) => b.length - a.length + ); + for (const [prefix, pricing] of sortedEntries) { if (model.startsWith(prefix)) { return pricing; } } return MODEL_PRICING['default']; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/parsers/codex-output-parser.ts` around lines 76 - 86, getModelPricing may return the wrong tier because prefix iteration uses insertion order; change the fallback loop that iterates Object.entries(MODEL_PRICING) to first collect the prefixes (excluding exact matches), sort them by descending length, then iterate that sorted list and use model.startsWith(prefix) to find the most specific match; keep the existing exact lookup MODEL_PRICING[model] and final default return the same.src/renderer/components/AppModals.tsx (1)
1541-1557: Consider extracting the IIFE into a local variable for clarity.The immediately-invoked function expression (IIFE) pattern inside JSX works but is less readable than computing the value before the return. This is a minor style suggestion.
♻️ Optional refactor
+ const addParticipantGroupChat = showAddParticipantModal + ? groupChats.find((c) => c.id === showAddParticipantModal) + : null; + return ( <> {/* ... other modals ... */} {/* --- ADD PARTICIPANT MODAL --- */} - {showAddParticipantModal && (() => { - const addParticipantGroupChat = groupChats.find( - (c) => c.id === showAddParticipantModal - ); - return ( - <AddParticipantModal - theme={theme} - isOpen={!!showAddParticipantModal} - groupChatId={showAddParticipantModal} - sessions={sessions} - participants={addParticipantGroupChat?.participants || []} - onClose={onCloseAddParticipantModal} - onAddExisting={onAddExistingParticipant} - onAddFresh={onAddFreshParticipant} - /> - ); - })()} + {showAddParticipantModal && ( + <AddParticipantModal + theme={theme} + isOpen={!!showAddParticipantModal} + groupChatId={showAddParticipantModal} + sessions={sessions} + participants={addParticipantGroupChat?.participants || []} + onClose={onCloseAddParticipantModal} + onAddExisting={onAddExistingParticipant} + onAddFresh={onAddFreshParticipant} + /> + )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/components/AppModals.tsx` around lines 1541 - 1557, Compute the JSX for the add-participant modal before the component return instead of using the IIFE: read showAddParticipantModal and find the matching group chat with groupChats.find(...) (store into a local addParticipantGroupChat variable), build the <AddParticipantModal ... /> element (setting isOpen based on !!showAddParticipantModal and passing groupChatId, participants from addParticipantGroupChat?.participants || [], sessions, onCloseAddParticipantModal, onAddExistingParticipant, onAddFreshParticipant), store that element in a local variable (e.g., addParticipantModalElement) and then render it in JSX with {showAddParticipantModal && addParticipantModalElement}; this removes the inline IIFE while preserving all props and behavior.src/main/group-chat/group-chat-router.ts (1)
501-503: Empty catch blocks silently swallow all errors, not just "unavailable" agents.The empty
catch { }blocks will suppress all exceptions including unexpected ones (e.g., network errors, parsing failures). Consider logging at debug level for observability.🔧 Proposed fix
} catch { - // Skip unavailable agents + // Skip unavailable agents - expected when agent is not installed + logger.debug(`Agent ${def.id} not available, skipping`, LOG_CONTEXT); }Also applies to: 1341-1344
🤖 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 501 - 503, Replace the empty catch that contains the comment "// Skip unavailable agents" with a catch that captures the error (e.g., catch (err)) and logs it at debug level instead of swallowing it, then continue skipping the agent; e.g., call an existing debug logger (logger.debug or this.logger.debug) with a concise message and the error/context (agent id or index) so unexpected failures are observable. Apply the same change to the other empty catch at the second occurrence (the block around lines 1341-1344).
🤖 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-moderator.ts`:
- Around line 280-296: The killAllModerators function currently calls
processManager.kill(sessionId) but activeModeratorSessions holds prefix IDs so
timestamp-suffixed processes won’t be terminated; change the call to
processManager.killByPrefix(sessionId) when processManager is provided, keep the
powerManager.removeBlockReason(`groupchat:${groupChatId}`) and the
session/sessionActivity clears, and update the killAllModerators unit tests to
expect killByPrefix to be invoked for each sessionId prefix instead of kill.
In `@src/main/group-chat/group-chat-router.ts`:
- Around line 1108-1109: The synthesis error paths call clearSynthesisInProgress
but never release the chat lock, leaving the conversation locked; update each
synthesis failure branch (the code paths that currently call
clearSynthesisInProgress at the synthesis spawn/error locations tied to
routeModeratorResponse and the synthesis flow) to also call
releaseChatLock(groupChatId) before returning/throwing so the chat lock is
always released; specifically add releaseChatLock(groupChatId) alongside
clearSynthesisInProgress in the error handlers found around the synthesis
spawn/error sites (the branches referenced in your review at the lines that call
clearSynthesisInProgress) to guarantee locks are freed on all failure paths.
In `@src/main/process-manager/ProcessManager.ts`:
- Around line 194-208: killByPrefix currently increments killed for every
sessionId that startsWith(prefix) even though this.kill(sessionId) can return
false; change the logic to call this.kill(sessionId) and only increment killed
when that call returns a truthy/success value. Update the loop in killByPrefix
(referencing killByPrefix, this.kill, this.processes, and sessionId) so it
checks the boolean result of this.kill(sessionId) and increments killed only on
success, then return the accurate killed count.
In `@src/renderer/components/AddParticipantModal.tsx`:
- Around line 76-80: The catch block in AddParticipantModal currently only logs
failures to console (inside the try/catch around agent detection) so users still
see "No agents available"; update the catch to set a user-facing error state
(e.g., setAgentDetectionError or setDetectionError) and leave
setIsDetecting(false) in finally, and also forward the exception to your error
monitoring (e.g., captureException or Sentry.captureException) so it’s recorded;
then update the component render logic that shows "No agents available" to
prefer displaying the new error state message when present.
In `@src/renderer/components/GroupChatRightPanel.tsx`:
- Line 10: The "Add Participant" button in the GroupChatRightPanel component
lacks keyboard focus handling; update the element that renders the Plus
icon/button inside GroupChatRightPanel to include tabIndex={0} (or tabIndex={-1}
if it should be programmatically focusable only), add the "outline-none" class
to match renderer focus rules, and add an accessible label via aria-label (e.g.,
aria-label="Add participant"); apply the same pattern (tabIndex + outline-none
and optional aria-label) to the other interactive elements referenced in this
file (the ones rendering PanelRightClose and other similar buttons).
In `@src/renderer/hooks/groupChat/useGroupChatHandlers.ts`:
- Around line 658-677: The parameter sessionId in handleAddExistingParticipant
is unused; either remove it from the function signature and any callers (keeping
the function to derive id from useGroupChatStore.getState().activeGroupChatId),
or if the caller should supply the session id, replace the local id assignment
with const id = sessionId and ensure callers pass the sessionId; update
references to useGroupChatStore, window.maestro.groupChat.addParticipant, and
useModalStore.getState().closeModal('addGroupChatParticipant') accordingly so
the function signature and implementation remain consistent.
---
Outside diff comments:
In `@src/__tests__/main/ipc/handlers/groupChat.test.ts`:
- Around line 100-132: The mockProcessManager declared type is missing the
killByPrefix property while the object literal sets it; update the
mockProcessManager type to include killByPrefix (e.g., add killByPrefix:
ReturnType<typeof vi.fn>) so the declaration matches the object created in
beforeEach and TypeScript no longer errors on excess/missing properties for
mockProcessManager.
---
Nitpick comments:
In `@src/main/group-chat/group-chat-lock.ts`:
- Around line 75-82: Both forceReleaseChatLock and releaseChatLock simply call
chatLocks.delete(chatId), so consolidate by removing one of them (preferably
keep releaseChatLock for the simpler name) and update all references to the
removed symbol to call the retained function; adjust JSDoc on the retained
function to document unconditional behavior if needed and run/update any
callers/tests that referenced forceReleaseChatLock to use releaseChatLock
instead.
In `@src/main/group-chat/group-chat-router.ts`:
- Around line 501-503: Replace the empty catch that contains the comment "//
Skip unavailable agents" with a catch that captures the error (e.g., catch
(err)) and logs it at debug level instead of swallowing it, then continue
skipping the agent; e.g., call an existing debug logger (logger.debug or
this.logger.debug) with a concise message and the error/context (agent id or
index) so unexpected failures are observable. Apply the same change to the other
empty catch at the second occurrence (the block around lines 1341-1344).
In `@src/main/group-chat/validation.ts`:
- Around line 95-109: The validateBase64Image function currently only checks
type and estimated size but doesn't confirm the string is valid base64; add a
format validation step in validateBase64Image that verifies the input matches a
base64 pattern (optionally allowing data URI prefix removal) and rejects
malformed strings before size checking, or attempt a safe decode (e.g., try
Buffer.from(..., 'base64') and ensure re-encoding matches) to catch invalid
base64 and throw a clear error message when validation fails.
- Line 10: The constant UUID_V4_REGEX currently matches any UUID version because
it doesn't validate the version nibble; update the code so the name and behavior
align: either rename UUID_V4_REGEX to UUID_REGEX (and update any references) if
you intend to accept all UUID versions, or change the regex to enforce v4
specifically by requiring the version character to be "4" (e.g., the 13th hex
digit) and adjust the constant name to remain UUID_V4_REGEX; locate and edit the
UUID_V4_REGEX declaration in validation.ts and update usages accordingly.
In `@src/main/ipc/handlers/groupChat.ts`:
- Around line 770-771: The code incorrectly reuses validateGroupChatId for the
history entry ID; add a clear UUID validator (e.g., validateEntryId or
validateUUID) in the group-chat validation module and replace the
validateGroupChatId(entryId) call with the new validator; update any
imports/usages in the handler (groupChatId = validateGroupChatId(groupChatId);
validateEntryId(entryId) or validateUUID(entryId)) so intent is clear and
validators are not misleading.
- Around line 440-441: Create a tracked issue for implementing image embedding
in moderator prompts: describe the current state (images saved separately via
the groupChat:saveImage handler and a TODO in the async moderator prompt handler
with signature async (id: string, message: string, images?: string[], readOnly?:
boolean)), list acceptance criteria (embed images inline in moderator prompt
payloads, preserve image ordering and alt text, fallback to separate storage
when embedding fails), include required API changes (modify the moderator prompt
handler to accept/serialize embedded image data or URLs), add tests and UI/UX
notes, and tag with a feature/enhancement label and assign or link to the
relevant maintainer for prioritization.
In `@src/main/parsers/codex-output-parser.ts`:
- Around line 45-71: The MODEL_PRICING constant contains hardcoded rates that
will go stale; either move this object into a configuration source (e.g.,
JSON/YAML loaded at runtime) and update code that references MODEL_PRICING to
read from that config, or at minimum add a clear metadata comment above
MODEL_PRICING with the last-updated date, a TODO to verify pricing regularly,
and a link to the authoritative pricing URL so maintainers can refresh values;
refer to the MODEL_PRICING symbol in src/main/parsers/codex-output-parser.ts
when making changes.
- Around line 76-86: getModelPricing may return the wrong tier because prefix
iteration uses insertion order; change the fallback loop that iterates
Object.entries(MODEL_PRICING) to first collect the prefixes (excluding exact
matches), sort them by descending length, then iterate that sorted list and use
model.startsWith(prefix) to find the most specific match; keep the existing
exact lookup MODEL_PRICING[model] and final default return the same.
In `@src/main/preload/groupChat.ts`:
- Around line 115-116: The preload API is inconsistent: addParticipant(id, name,
agentId, cwd) vs addFreshParticipant(id, agentId, name, cwd); change
addFreshParticipant to the same parameter order as addParticipant (id, name,
agentId, cwd), update its ipcRenderer.invoke call to pass arguments in that
order, and update the corresponding IPC handler for
'groupChat:addFreshParticipant' (or the code that reorders its args before
calling the underlying function) so it expects and forwards (id, name, agentId,
cwd) consistently with addParticipant.
In `@src/renderer/components/AddParticipantModal.tsx`:
- Around line 108-129: In handleSubmit ensure isSubmitting is reset on the
early-return paths: before each early return in the 'existing' branch when
selectedSessionId is falsy or session not found, and before the early return in
the 'fresh' branch when selectedAgentId is falsy, call setIsSubmitting(false)
(and optionally setError with a helpful message) so the component doesn't remain
stuck in submitting state; update the closure that defines handleSubmit to call
setIsSubmitting(false) right before those return points (symbols: handleSubmit,
setIsSubmitting, selectedSessionId, sessions, selectedAgentId, AGENT_TILES,
onAddExisting, onAddFresh).
- Line 22: Remove the unused prop "groupChatId": delete it from the
AddParticipantModalProps interface and from the AddParticipantModal component's
parameter destructuring, and remove any other references or propTypes/defaults
named groupChatId in that component. Ensure the component compiles after
removing the identifier and update any callers only if they were passing
groupChatId (remove the argument) to avoid unused prop warnings.
In `@src/renderer/components/AppModals.tsx`:
- Around line 1541-1557: Compute the JSX for the add-participant modal before
the component return instead of using the IIFE: read showAddParticipantModal and
find the matching group chat with groupChats.find(...) (store into a local
addParticipantGroupChat variable), build the <AddParticipantModal ... /> element
(setting isOpen based on !!showAddParticipantModal and passing groupChatId,
participants from addParticipantGroupChat?.participants || [], sessions,
onCloseAddParticipantModal, onAddExistingParticipant, onAddFreshParticipant),
store that element in a local variable (e.g., addParticipantModalElement) and
then render it in JSX with {showAddParticipantModal &&
addParticipantModalElement}; this removes the inline IIFE while preserving all
props and behavior.
In `@src/renderer/global.d.ts`:
- Around line 1776-1786: The two APIs have inconsistent parameter order:
addParticipant(id, name, agentId, cwd) vs addFreshParticipant(id, agentId, name,
cwd); change addFreshParticipant’s declaration and any implementations/callsites
to use the same order as addParticipant (id, name, agentId, cwd) to keep the API
consistent, and update any documentation/tests that rely on the old order; if
the original order was intentional, instead add a comment/docstring to
addFreshParticipant clarifying the different ordering.
| /** | ||
| * Kills all active moderator processes and clears session tracking. | ||
| * Used during application shutdown to prevent zombie processes. | ||
| * | ||
| * @param processManager - The process manager for killing processes (optional) | ||
| */ | ||
| export function killAllModerators(processManager?: IProcessManager): void { | ||
| for (const [groupChatId, sessionId] of activeModeratorSessions) { | ||
| if (processManager) { | ||
| processManager.kill(sessionId); | ||
| } | ||
| // Remove power block reason for each moderator | ||
| powerManager.removeBlockReason(`groupchat:${groupChatId}`); | ||
| } | ||
| activeModeratorSessions.clear(); | ||
| sessionActivityTimestamps.clear(); | ||
| } |
There was a problem hiding this comment.
killAllModerators still uses exact IDs; batch-mode moderators will survive.
activeModeratorSessions stores prefixes, so kill(sessionId) won’t match timestamp-suffixed processes. Use killByPrefix here too (and update the killAllModerators test expectations accordingly).
🔧 Proposed fix
-export function killAllModerators(processManager?: IProcessManager): void {
- for (const [groupChatId, sessionId] of activeModeratorSessions) {
- if (processManager) {
- processManager.kill(sessionId);
- }
- // Remove power block reason for each moderator
- powerManager.removeBlockReason(`groupchat:${groupChatId}`);
- }
- activeModeratorSessions.clear();
- sessionActivityTimestamps.clear();
-}
+export function killAllModerators(processManager?: IProcessManager): void {
+ for (const [groupChatId, sessionIdPrefix] of activeModeratorSessions) {
+ if (processManager) {
+ processManager.killByPrefix(sessionIdPrefix);
+ }
+ // Remove power block reason for each moderator
+ powerManager.removeBlockReason(`groupchat:${groupChatId}`);
+ }
+ activeModeratorSessions.clear();
+ sessionActivityTimestamps.clear();
+}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/group-chat/group-chat-moderator.ts` around lines 280 - 296, The
killAllModerators function currently calls processManager.kill(sessionId) but
activeModeratorSessions holds prefix IDs so timestamp-suffixed processes won’t
be terminated; change the call to processManager.killByPrefix(sessionId) when
processManager is provided, keep the
powerManager.removeBlockReason(`groupchat:${groupChatId}`) and the
session/sessionActivity clears, and update the killAllModerators unit tests to
expect killByPrefix to be invoked for each sessionId prefix instead of kill.
| // Release chat lock - conversation round is complete | ||
| releaseChatLock(groupChatId); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find all calls to releaseChatLock
rg -n 'releaseChatLock' src/main/group-chat/Repository: RunMaestro/Maestro
Length of output: 610
🏁 Script executed:
#!/bin/bash
# Read the file around the specified lines to understand the context
wc -l src/main/group-chat/group-chat-router.tsRepository: RunMaestro/Maestro
Length of output: 107
🏁 Script executed:
#!/bin/bash
# Get context around lines 1108-1109
sed -n '1050,1150p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 4279
🏁 Script executed:
#!/bin/bash
# View context around the other releaseChatLock calls
sed -n '600,750p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 6556
🏁 Script executed:
#!/bin/bash
# Search for clearSynthesisInProgress
rg -n 'clearSynthesisInProgress' src/main/group-chat/Repository: RunMaestro/Maestro
Length of output: 675
🏁 Script executed:
#!/bin/bash
# Search for synthesis-related functions and lock handling
rg -n 'synthesis|pendingParticipantResponses' src/main/group-chat/group-chat-router.ts | head -40Repository: RunMaestro/Maestro
Length of output: 2986
🏁 Script executed:
#!/bin/bash
# View the synthesis spawn function
sed -n '1227,1500p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 12412
🏁 Script executed:
#!/bin/bash
# Check if there's any lock release in the synthesis-related code after line 1484
sed -n '1484,1550p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 2956
🏁 Script executed:
#!/bin/bash
# Check what happens after a synthesis moderator response - search for synthesis-related response handling
rg -n 'synthesis.*response|isSynthesisRound' src/main/group-chat/group-chat-router.tsRepository: RunMaestro/Maestro
Length of output: 140
🏁 Script executed:
#!/bin/bash
# Search for all places where routeModeratorResponse is called to see the full synthesis flow
rg -n 'routeModeratorResponse' src/main/group-chat/ -B 2 -A 2Repository: RunMaestro/Maestro
Length of output: 1052
🏁 Script executed:
#!/bin/bash
# Check the moderator response routing to verify if synthesis responses properly release locks
sed -n '850,920p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 2882
🏁 Script executed:
#!/bin/bash
# Verify if there's any lock release in synthesis error handling or check the flow completely
sed -n '1240,1265p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 1255
🏁 Script executed:
#!/bin/bash
# View the full routeModeratorResponse function starting from line 691
sed -n '691,850p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 6902
🏁 Script executed:
#!/bin/bash
# Continue viewing routeModeratorResponse
sed -n '850,950p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 4133
🏁 Script executed:
#!/bin/bash
# Continue viewing to see the end of routeModeratorResponse and lock release logic
sed -n '950,1050p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 5013
🏁 Script executed:
#!/bin/bash
# Continue to see the end of routeModeratorResponse
sed -n '1050,1150p' src/main/group-chat/group-chat-router.ts | cat -nRepository: RunMaestro/Maestro
Length of output: 4279
Verify lock is released in all synthesis error paths.
The lock is correctly released when the synthesis moderator responds with no mentions (which goes through routeModeratorResponse's final path at line 1060). However, synthesis spawn error paths at lines 1252, 1261, 1275, 1293, and 1484 call clearSynthesisInProgress but do not call releaseChatLock. If synthesis fails to spawn or encounters errors, the lock remains held indefinitely.
🤖 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 1108 - 1109, The
synthesis error paths call clearSynthesisInProgress but never release the chat
lock, leaving the conversation locked; update each synthesis failure branch (the
code paths that currently call clearSynthesisInProgress at the synthesis
spawn/error locations tied to routeModeratorResponse and the synthesis flow) to
also call releaseChatLock(groupChatId) before returning/throwing so the chat
lock is always released; specifically add releaseChatLock(groupChatId) alongside
clearSynthesisInProgress in the error handlers found around the synthesis
spawn/error sites (the branches referenced in your review at the lines that call
clearSynthesisInProgress) to guarantee locks are freed on all failure paths.
| /** | ||
| * Kill all processes whose session ID starts with a given prefix. | ||
| * Used by group chat to kill batch-spawned processes (moderator/participant) | ||
| * whose session IDs have timestamp/UUID suffixes. | ||
| */ | ||
| killByPrefix(prefix: string): number { | ||
| let killed = 0; | ||
| for (const [sessionId] of this.processes) { | ||
| if (sessionId.startsWith(prefix)) { | ||
| this.kill(sessionId); | ||
| killed++; | ||
| } | ||
| } | ||
| return killed; | ||
| } |
There was a problem hiding this comment.
Count only successful kills.
kill() can return false; incrementing unconditionally may overreport. Safer to increment only on success.
🔧 Suggested tweak
killByPrefix(prefix: string): number {
let killed = 0;
for (const [sessionId] of this.processes) {
- if (sessionId.startsWith(prefix)) {
- this.kill(sessionId);
- killed++;
- }
+ if (sessionId.startsWith(prefix) && this.kill(sessionId)) {
+ killed++;
+ }
}
return killed;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/process-manager/ProcessManager.ts` around lines 194 - 208,
killByPrefix currently increments killed for every sessionId that
startsWith(prefix) even though this.kill(sessionId) can return false; change the
logic to call this.kill(sessionId) and only increment killed when that call
returns a truthy/success value. Update the loop in killByPrefix (referencing
killByPrefix, this.kill, this.processes, and sessionId) so it checks the boolean
result of this.kill(sessionId) and increments killed only on success, then
return the accurate killed count.
| } catch (err) { | ||
| console.error('Failed to detect agents:', err); | ||
| } finally { | ||
| setIsDetecting(false); | ||
| } |
There was a problem hiding this comment.
Error is logged but user receives no feedback.
When agent detection fails, the error is logged to console but the user sees "No agents available" which is misleading. Per coding guidelines, exceptions should either bubble up to Sentry or be handled with user-facing feedback.
🛠️ Suggested fix: Set error state on detection failure
} catch (err) {
- console.error('Failed to detect agents:', err);
+ setError('Failed to detect available agents. Please try again.');
} finally {
setIsDetecting(false);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (err) { | |
| console.error('Failed to detect agents:', err); | |
| } finally { | |
| setIsDetecting(false); | |
| } | |
| } catch (err) { | |
| setError('Failed to detect available agents. Please try again.'); | |
| } finally { | |
| setIsDetecting(false); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/AddParticipantModal.tsx` around lines 76 - 80, The
catch block in AddParticipantModal currently only logs failures to console
(inside the try/catch around agent detection) so users still see "No agents
available"; update the catch to set a user-facing error state (e.g.,
setAgentDetectionError or setDetectionError) and leave setIsDetecting(false) in
finally, and also forward the exception to your error monitoring (e.g.,
captureException or Sentry.captureException) so it’s recorded; then update the
component render logic that shows "No agents available" to prefer displaying the
new error state message when present.
|
|
||
| import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; | ||
| import { PanelRightClose } from 'lucide-react'; | ||
| import { PanelRightClose, Plus } from 'lucide-react'; |
There was a problem hiding this comment.
Add focus handling to the new Add Participant button.
Please add tabIndex and an outline-none class (and optionally an aria-label) to align with the renderer focus rules.
♿ Suggested update
{onAddParticipant && (
<button
onClick={onAddParticipant}
- className="flex items-center justify-center p-2 rounded hover:bg-white/5 transition-colors w-10 shrink-0"
+ tabIndex={0}
+ className="flex items-center justify-center p-2 rounded hover:bg-white/5 transition-colors w-10 shrink-0 outline-none"
+ aria-label="Add participant"
title="Add participant"
>
<Plus className="w-4 h-4 opacity-50" />
</button>
)}Also applies to: 59-60, 84-85, 281-289
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/GroupChatRightPanel.tsx` at line 10, The "Add
Participant" button in the GroupChatRightPanel component lacks keyboard focus
handling; update the element that renders the Plus icon/button inside
GroupChatRightPanel to include tabIndex={0} (or tabIndex={-1} if it should be
programmatically focusable only), add the "outline-none" class to match renderer
focus rules, and add an accessible label via aria-label (e.g., aria-label="Add
participant"); apply the same pattern (tabIndex + outline-none and optional
aria-label) to the other interactive elements referenced in this file (the ones
rendering PanelRightClose and other similar buttons).
| const handleAddExistingParticipant = useCallback( | ||
| async (sessionId: string, name: string, agentId: string, cwd: string) => { | ||
| const { activeGroupChatId } = useGroupChatStore.getState(); | ||
| const id = activeGroupChatId; | ||
| if (!id) return; | ||
|
|
||
| try { | ||
| await window.maestro.groupChat.addParticipant(id, name, agentId, cwd); | ||
| useModalStore.getState().closeModal('addGroupChatParticipant'); | ||
| } catch (err) { | ||
| notifyToast({ | ||
| type: 'error', | ||
| title: 'Group Chat', | ||
| message: err instanceof Error ? err.message : 'Failed to add participant', | ||
| }); | ||
| throw err; // Re-throw so the modal can display the error | ||
| } | ||
| }, | ||
| [] | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the addParticipant IPC handler signature in main process
rg -n 'addParticipant' --type ts -A 15 src/main/ipc/handlers/groupChat.ts | head -60
# Also check preload
echo "=== Preload definitions ==="
rg -n 'addParticipant' --type ts src/main/preload/groupChat.tsRepository: RunMaestro/Maestro
Length of output: 2136
Unused parameter: sessionId should be removed or used.
The sessionId parameter is declared in the function signature but never used. The function derives id from activeGroupChatId instead and passes it to the IPC call. Either remove the unused sessionId parameter or replace the store-derived id with sessionId if that was the intended pattern.
🤖 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 658 - 677,
The parameter sessionId in handleAddExistingParticipant is unused; either remove
it from the function signature and any callers (keeping the function to derive
id from useGroupChatStore.getState().activeGroupChatId), or if the caller should
supply the session id, replace the local id assignment with const id = sessionId
and ensure callers pass the sessionId; update references to useGroupChatStore,
window.maestro.groupChat.addParticipant, and
useModalStore.getState().closeModal('addGroupChatParticipant') accordingly so
the function signature and implementation remain consistent.
Summary
killModerator()andclearAllParticipantSessions()silently failed because batch mode spawns processes with timestamp-suffixed session IDs while the active sessions map stores prefix-only IDs. AddedkillByPrefix()toProcessManager— fixes zombie processes and the observedy\nflood after delete.codex-0-*) so participant cards show an ID instead of "pending". Resume logic skips these prefixed IDs. Added OpenAI model pricing table (GPT-4o through GPT-5.3-codex) to calculate cost from token counts since Codex CLI doesn't report cost directly.## MANDATORY: Always @mention agents when delegatingsection to moderator system prompt. Prevents the moderator from discussing agent tasks without actually routing messages via @mentions. Updated synthesis prompt for critical analysis and fusion instead of simple concatenation.Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes