=> {
+ logger.info(
+ `Auto Run complete for participant ${participantName} in ${groupChatId}`,
+ LOG_CONTEXT
+ );
+ const processManager = getProcessManager();
+
+ // Log the autorun summary as the participant's response
+ await routeAgentResponse(
+ groupChatId,
+ participantName,
+ summary,
+ processManager ?? undefined
+ );
+
+ // Reset participant state to idle (mirrors what exit-listener does for regular participants).
+ // Without this the participant card stays "Working" because no process exit fires for
+ // autorun participants (the batch runs in a separate Maestro session, not a group-chat session).
+ groupChatEmitters.emitParticipantState?.(groupChatId, participantName, 'idle');
+
+ // Signal the renderer to definitively complete the batch run for this participant.
+ // In the happy path this is a no-op (COMPLETE_BATCH was already dispatched by startBatchRun).
+ // In edge cases (synthesis re-triggered a second batch, or the process was slow to exit)
+ // this ensures the AUTO badge and progress bar are always cleared.
+ groupChatEmitters.emitAutoRunBatchComplete?.(groupChatId, participantName);
+
+ // Mark participant as done and trigger synthesis if all participants have responded.
+ // Unlike regular participants (whose process exit triggers this via exit-listener),
+ // autorun participants never exit a group-chat process — the batch runs as a separate
+ // Maestro session — so we must call markParticipantResponded here.
+ const agentDetector = getAgentDetector();
+ const isLast = markParticipantResponded(groupChatId, participantName);
+ if (isLast && processManager && agentDetector) {
+ logger.info(
+ `All participants responded after autorun, spawning synthesis for ${groupChatId}`,
+ LOG_CONTEXT
+ );
+ await spawnModeratorSynthesis(groupChatId, processManager, agentDetector);
+ }
+ }
+ )
+ );
+
// Get the moderator session ID (for checking if active)
ipcMain.handle(
'groupChat:getModeratorSessionId',
@@ -872,5 +963,62 @@ Respond with ONLY the summary text, no additional commentary.`;
}
};
+ /**
+ * Emit an Auto Run trigger event to the renderer.
+ * Called when the moderator issues !autorun @AgentName so the renderer can
+ * start a proper batch run through useBatchProcessor for full UI feedback.
+ */
+ groupChatEmitters.emitAutoRunTriggered = (
+ groupChatId: string,
+ participantName: string,
+ filename?: string
+ ): void => {
+ const mainWindow = getMainWindow();
+ if (isWebContentsAvailable(mainWindow)) {
+ mainWindow.webContents.send(
+ 'groupChat:autoRunTriggered',
+ groupChatId,
+ participantName,
+ filename
+ );
+ }
+ };
+
+ /**
+ * Tell the renderer to force-complete the batch run for an autorun participant.
+ * Fired on both normal completion (reportAutoRunComplete) and on the timeout path,
+ * so the AUTO badge and progress bar are always cleaned up regardless of how the
+ * participant's involvement ends.
+ */
+ groupChatEmitters.emitAutoRunBatchComplete = (
+ groupChatId: string,
+ participantName: string
+ ): void => {
+ const mainWindow = getMainWindow();
+ if (isWebContentsAvailable(mainWindow)) {
+ mainWindow.webContents.send('groupChat:autoRunBatchComplete', groupChatId, participantName);
+ }
+ };
+
+ /**
+ * Emit live output chunks from a participant to the renderer.
+ * Called as data streams in from participant processes.
+ */
+ groupChatEmitters.emitParticipantLiveOutput = (
+ groupChatId: string,
+ participantName: string,
+ chunk: string
+ ): void => {
+ const mainWindow = getMainWindow();
+ if (isWebContentsAvailable(mainWindow)) {
+ mainWindow.webContents.send(
+ 'groupChat:participantLiveOutput',
+ groupChatId,
+ participantName,
+ chunk
+ );
+ }
+ };
+
logger.info('Registered Group Chat IPC handlers', LOG_CONTEXT);
}
diff --git a/src/main/preload/groupChat.ts b/src/main/preload/groupChat.ts
index 692883c45b..9dfd8c791d 100644
--- a/src/main/preload/groupChat.ts
+++ b/src/main/preload/groupChat.ts
@@ -108,6 +108,11 @@ export function createGroupChatApi() {
stopModerator: (id: string) => ipcRenderer.invoke('groupChat:stopModerator', id),
+ stopAll: (id: string) => ipcRenderer.invoke('groupChat:stopAll', id),
+
+ reportAutoRunComplete: (groupChatId: string, participantName: string, summary: string) =>
+ ipcRenderer.invoke('groupChat:reportAutoRunComplete', groupChatId, participantName, summary),
+
getModeratorSessionId: (id: string) =>
ipcRenderer.invoke('groupChat:getModeratorSessionId', id),
@@ -204,6 +209,31 @@ export function createGroupChatApi() {
return () => ipcRenderer.removeListener('groupChat:participantState', handler);
},
+ onParticipantLiveOutput: (
+ callback: (groupChatId: string, participantName: string, chunk: string) => void
+ ) => {
+ const handler = (_: any, groupChatId: string, participantName: string, chunk: string) =>
+ callback(groupChatId, participantName, chunk);
+ ipcRenderer.on('groupChat:participantLiveOutput', handler);
+ return () => ipcRenderer.removeListener('groupChat:participantLiveOutput', handler);
+ },
+
+ onAutoRunTriggered: (
+ callback: (groupChatId: string, participantName: string, filename?: string) => void
+ ) => {
+ const handler = (_: any, groupChatId: string, participantName: string, filename?: string) =>
+ callback(groupChatId, participantName, filename);
+ ipcRenderer.on('groupChat:autoRunTriggered', handler);
+ return () => ipcRenderer.removeListener('groupChat:autoRunTriggered', handler);
+ },
+
+ onAutoRunBatchComplete: (callback: (groupChatId: string, participantName: string) => void) => {
+ const handler = (_: any, groupChatId: string, participantName: string) =>
+ callback(groupChatId, participantName);
+ ipcRenderer.on('groupChat:autoRunBatchComplete', handler);
+ return () => ipcRenderer.removeListener('groupChat:autoRunBatchComplete', handler);
+ },
+
onModeratorSessionIdChanged: (callback: (groupChatId: string, sessionId: string) => void) => {
const handler = (_: any, groupChatId: string, sessionId: string) =>
callback(groupChatId, sessionId);
diff --git a/src/main/process-listeners/data-listener.ts b/src/main/process-listeners/data-listener.ts
index b958029467..32ff39bb2d 100644
--- a/src/main/process-listeners/data-listener.ts
+++ b/src/main/process-listeners/data-listener.ts
@@ -5,6 +5,7 @@
import type { ProcessManager } from '../process-manager';
import { GROUP_CHAT_PREFIX, type ProcessListenerDependencies } from './types';
+import { groupChatEmitters } from '../ipc/handlers/groupChat';
/**
* Maximum buffer size per session (10MB).
@@ -41,6 +42,21 @@ export function setupDataListener(
REGEX_SYNOPSIS_SESSION,
} = patterns;
+ // Listen to raw stdout for live output streaming to group chat participant peek panels.
+ // The 'data' event for stream-json sessions only fires at turn completion (result ready),
+ // so we need raw-stdout to stream chunks in real time during agent work.
+ processManager.on('raw-stdout', (sessionId: string, chunk: string) => {
+ if (!sessionId.startsWith(GROUP_CHAT_PREFIX)) return;
+ const participantInfo = outputParser.parseParticipantSessionId(sessionId);
+ if (participantInfo) {
+ groupChatEmitters.emitParticipantLiveOutput?.(
+ participantInfo.groupChatId,
+ participantInfo.participantName,
+ chunk
+ );
+ }
+ });
+
processManager.on('data', (sessionId: string, data: string) => {
// Fast path: skip regex for non-group-chat sessions (performance optimization)
// Most sessions don't start with 'group-chat-', so this avoids expensive regex matching
@@ -91,6 +107,7 @@ export function setupDataListener(
`WARNING: Buffer size ${totalLength} exceeds ${MAX_BUFFER_SIZE} bytes for participant ${participantInfo.participantName}`
);
}
+ // Note: live output is streamed via raw-stdout listener above (fires per chunk during work).
return; // Don't send to regular process:data handler
}
diff --git a/src/main/process-listeners/exit-listener.ts b/src/main/process-listeners/exit-listener.ts
index b06b5a5963..a96d1a1500 100644
--- a/src/main/process-listeners/exit-listener.ts
+++ b/src/main/process-listeners/exit-listener.ts
@@ -5,6 +5,7 @@
*/
import type { ProcessManager } from '../process-manager';
+import { captureException } from '../utils/sentry';
import { GROUP_CHAT_PREFIX, type ProcessListenerDependencies } from './types';
/**
@@ -243,6 +244,17 @@ export function setupExitListener(
error: String(err),
groupChatId,
});
+ // Reset to idle so user is not stuck waiting indefinitely
+ groupChatEmitters.emitStateChange?.(groupChatId, 'idle');
+ groupChatEmitters.emitMessage?.(groupChatId, {
+ timestamp: new Date().toISOString(),
+ from: 'system',
+ content: `⚠️ Synthesis failed. You can send another message to continue.`,
+ });
+ captureException(err, {
+ operation: 'groupChat:spawnModeratorSynthesis',
+ groupChatId,
+ });
});
} else if (!isLastParticipant) {
// More participants pending
diff --git a/src/main/process-manager/spawners/ChildProcessSpawner.ts b/src/main/process-manager/spawners/ChildProcessSpawner.ts
index b66f9c7531..ddded18792 100644
--- a/src/main/process-manager/spawners/ChildProcessSpawner.ts
+++ b/src/main/process-manager/spawners/ChildProcessSpawner.ts
@@ -429,6 +429,16 @@ export class ChildProcessSpawner {
});
childProcess.stdout.on('data', (data: Buffer | string) => {
const output = data.toString();
+ // Emit raw stdout before processing for live-streaming consumers (e.g., group chat peek).
+ // Wrapped in try/catch so a failing listener cannot prevent stdoutHandler from running.
+ try {
+ this.emitter.emit('raw-stdout', sessionId, output);
+ } catch (err) {
+ logger.error('[ProcessManager] raw-stdout listener error', 'ProcessManager', {
+ sessionId,
+ error: String(err),
+ });
+ }
this.stdoutHandler.handleData(sessionId, output);
});
} else {
diff --git a/src/prompts/group-chat-moderator-system.md b/src/prompts/group-chat-moderator-system.md
index 9e6f3da2d8..1ae3b4c9c1 100644
--- a/src/prompts/group-chat-moderator-system.md
+++ b/src/prompts/group-chat-moderator-system.md
@@ -29,3 +29,23 @@ Your role is to:
- If you need multiple rounds of work, keep @mentioning agents until the task is complete
- Only return to the user when you have a complete, actionable answer
- When you're done and ready to hand back to the user, provide a summary WITHOUT any @mentions
+
+## Auto Run Execution:
+
+- Use `!autorun @AgentName:filename.md` to trigger execution of a **specific** Auto Run document the agent just created or updated
+- Use `!autorun @AgentName` (without filename) only when you want to run ALL documents in the agent's Auto Run folder
+- **Always prefer the specific filename form** after an agent confirms creating or updating a document — this guarantees the right file is executed
+- Multiple agents can be triggered in parallel:
+ !autorun @Agent1:frontend-plan.md
+ !autorun @Agent2:backend-plan.md
+- Use this AFTER agents have confirmed their implementation plans as Auto Run documents
+- Do NOT combine !autorun with a regular @mention for the same agent in the same message
+- **Important**: Ask the agent to confirm the exact filename of the document it created before issuing !autorun
+
+## Commit & Switch Branch:
+
+- When the user sends `!commit`, instruct ALL participating agents to:
+ 1. Commit all staged and unstaged changes on their current branch with a descriptive commit message
+- @mention each agent with clear, specific instructions
+- After all agents respond, provide a summary with each agent's branch name and commit status
+- If an agent reports conflicts or errors, relay them clearly to the user
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 667edde5c0..e2c29b9076 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -143,6 +143,7 @@ import { useModalActions, useModalStore } from './stores/modalStore';
import { GitStatusProvider } from './contexts/GitStatusContext';
import { InputProvider, useInputContext } from './contexts/InputContext';
import { useGroupChatStore } from './stores/groupChatStore';
+import { registerGroupChatAutoRun } from './utils/groupChatAutoRunRegistry';
import { useBatchStore } from './stores/batchStore';
// All session state is read directly from useSessionStore in MaestroConsoleInner.
import { useSessionStore, selectActiveSession } from './stores/sessionStore';
@@ -808,6 +809,7 @@ function MaestroConsoleInner() {
handleOpenModeratorSession,
handleJumpToGroupChatMessage,
handleGroupChatRightTabChange,
+ handleStopAll,
handleSendGroupChatMessage,
handleGroupChatDraftChange,
handleRemoveGroupChatQueueItem,
@@ -1219,6 +1221,99 @@ function MaestroConsoleInner() {
handleClearAgentError,
});
+ // --- GROUP CHAT AUTO RUN BRIDGE ---
+ // When the moderator issues !autorun @AgentName, the main process emits
+ // groupChat:autoRunTriggered. Here we intercept that, find the session,
+ // and start a proper batch run via useBatchProcessor for full UI feedback.
+ const startBatchRunRef = useRef(startBatchRun);
+ startBatchRunRef.current = startBatchRun;
+
+ useEffect(() => {
+ const unsub = window.maestro.groupChat.onAutoRunTriggered?.(
+ (groupChatId, participantName, targetFilename) => {
+ // Helper: report failure back to the group chat as a system message so the
+ // moderator and user can see what went wrong and take corrective action.
+ const reportFailure = (reason: string) => {
+ console.warn(`[GroupChat:AutoRun] ${reason}`);
+ window.maestro.groupChat
+ .reportAutoRunComplete(
+ groupChatId,
+ participantName,
+ `⚠️ Auto Run could not start for @${participantName}: ${reason}`
+ )
+ .catch((e) =>
+ console.error('[GroupChat:AutoRun] Failed to report failure to moderator:', e)
+ );
+ };
+
+ const sessions = useSessionStore.getState().sessions;
+ const session = sessions.find((s) => s.name === participantName);
+ if (!session) {
+ reportFailure(
+ `No Maestro agent named "${participantName}" found. Make sure the agent exists and is open.`
+ );
+ return;
+ }
+ if (!session.autoRunFolderPath) {
+ reportFailure(
+ `Agent "${participantName}" has no Auto Run folder configured. Open the agent, go to the Auto Run tab, and configure a folder first.`
+ );
+ return;
+ }
+
+ // Fetch the document list, then start the batch run
+ window.maestro.autorun
+ .listDocs(session.autoRunFolderPath, session.sshRemoteId || undefined)
+ .then((result) => {
+ const allFiles = result.files || [];
+ if (allFiles.length === 0) {
+ reportFailure(
+ `No Auto Run documents found in "${session.autoRunFolderPath}". Create a document in the Auto Run tab first.`
+ );
+ return;
+ }
+
+ // If a specific filename was given (e.g. !autorun @Agent:plan.md), run only that doc.
+ // Otherwise run all docs in the folder.
+ let files: string[];
+ if (targetFilename) {
+ if (allFiles.includes(targetFilename)) {
+ files = [targetFilename];
+ } else {
+ // Specified file not found — report failure so the moderator can react
+ reportFailure(
+ `Specified file "${targetFilename}" not found in "${session.autoRunFolderPath}" for "${participantName}". Available files: ${allFiles.join(', ')}`
+ );
+ return;
+ }
+ } else {
+ files = allFiles;
+ }
+
+ const documents = files.map((filename, i) => ({
+ id: `${session.id}-${i}`,
+ filename,
+ resetOnCompletion: false,
+ isDuplicate: false,
+ }));
+ const config = {
+ documents,
+ prompt: '',
+ loopEnabled: false,
+ maxLoops: null,
+ };
+ // Register AFTER validating docs exist so no stale entry on failure
+ registerGroupChatAutoRun(session.id, groupChatId, participantName);
+ startBatchRunRef.current(session.id, config, session.autoRunFolderPath!);
+ })
+ .catch((err) => {
+ reportFailure(`Failed to read Auto Run folder: ${String(err)}`);
+ });
+ }
+ );
+ return () => unsub?.();
+ }, []); // Stable — reads sessions and startBatchRun from refs/store at call time
+
// --- AGENT IPC LISTENERS ---
// Extracted hook for all window.maestro.process.onXxx listeners
// (onData, onExit, onSessionId, onSlashCommands, onStderr, onCommandExit,
@@ -3039,6 +3134,7 @@ function MaestroConsoleInner() {
return anyParticipantMissingCost || moderatorMissingCost;
})()}
onSendMessage={handleSendGroupChatMessage}
+ onStopAll={handleStopAll}
onRename={() =>
activeGroupChatId && handleOpenRenameGroupChatModal(activeGroupChatId)
}
diff --git a/src/renderer/components/GroupChatHeader.tsx b/src/renderer/components/GroupChatHeader.tsx
index 637a899b5c..436df04982 100644
--- a/src/renderer/components/GroupChatHeader.tsx
+++ b/src/renderer/components/GroupChatHeader.tsx
@@ -5,8 +5,8 @@
* and provides actions for rename and info.
*/
-import { Info, Edit2, Columns, DollarSign } from 'lucide-react';
-import type { Theme, Shortcut } from '../types';
+import { Info, Edit2, Columns, DollarSign, StopCircle } from 'lucide-react';
+import type { Theme, Shortcut, GroupChatState } from '../types';
import { formatShortcutKeys } from '../utils/shortcutFormatter';
interface GroupChatHeaderProps {
@@ -17,6 +17,8 @@ interface GroupChatHeaderProps {
totalCost?: number;
/** True if one or more participants don't have cost data (makes total incomplete) */
costIncomplete?: boolean;
+ state: GroupChatState;
+ onStopAll: () => void;
onRename: () => void;
onShowInfo: () => void;
rightPanelOpen: boolean;
@@ -30,6 +32,8 @@ export function GroupChatHeader({
participantCount,
totalCost,
costIncomplete,
+ state,
+ onStopAll,
onRename,
onShowInfo,
rightPanelOpen,
@@ -72,6 +76,22 @@ export function GroupChatHeader({
+ {/* Stop All button - only shown when active */}
+ {state !== 'idle' && (
+
+ )}
void;
+ onStopAll: () => void;
onRename: () => void;
onShowInfo: () => void;
rightPanelOpen: boolean;
@@ -80,6 +81,7 @@ export function GroupChatPanel({
totalCost,
costIncomplete,
onSendMessage,
+ onStopAll,
onRename,
onShowInfo,
rightPanelOpen,
@@ -117,6 +119,8 @@ export function GroupChatPanel({
participantCount={groupChat.participants.length}
totalCost={totalCost}
costIncomplete={costIncomplete}
+ state={state}
+ onStopAll={onStopAll}
onRename={onRename}
onShowInfo={onShowInfo}
rightPanelOpen={rightPanelOpen}
diff --git a/src/renderer/components/GroupChatParticipants.tsx b/src/renderer/components/GroupChatParticipants.tsx
index 362db0fda8..27e9d12610 100644
--- a/src/renderer/components/GroupChatParticipants.tsx
+++ b/src/renderer/components/GroupChatParticipants.tsx
@@ -13,6 +13,7 @@ import { ParticipantCard } from './ParticipantCard';
import { formatShortcutKeys } from '../utils/shortcutFormatter';
import { buildParticipantColorMap } from '../utils/participantColors';
import { useResizablePanel } from '../hooks';
+import { useGroupChatStore } from '../stores/groupChatStore';
interface GroupChatParticipantsProps {
theme: Theme;
@@ -62,6 +63,8 @@ export function GroupChatParticipants({
side: 'right',
});
+ const participantLiveOutput = useGroupChatStore((s) => s.participantLiveOutput);
+
// Generate consistent colors for all participants (including "Moderator" for the moderator card)
const participantColors = useMemo(() => {
return buildParticipantColorMap(['Moderator', ...participants.map((p) => p.name)], theme);
@@ -164,10 +167,11 @@ export function GroupChatParticipants({
key={participant.sessionId}
theme={theme}
participant={participant}
- state={participantStates.get(participant.sessionId) || 'idle'}
+ state={participantStates.get(participant.name) || 'idle'}
color={participantColors[participant.name]}
groupChatId={groupChatId}
onContextReset={handleContextReset}
+ liveOutput={participantLiveOutput.get(participant.name)}
/>
))
)}
diff --git a/src/renderer/components/GroupChatRightPanel.tsx b/src/renderer/components/GroupChatRightPanel.tsx
index b8a2ee41c3..cbdf0b8406 100644
--- a/src/renderer/components/GroupChatRightPanel.tsx
+++ b/src/renderer/components/GroupChatRightPanel.tsx
@@ -20,6 +20,7 @@ import {
type ParticipantColorInfo,
} from '../utils/participantColors';
import { useResizablePanel } from '../hooks';
+import { useGroupChatStore } from '../stores/groupChatStore';
export type GroupChatRightTab = 'participants' | 'history';
@@ -80,6 +81,8 @@ export function GroupChatRightPanel({
onJumpToMessage,
onColorsComputed,
}: GroupChatRightPanelProps): JSX.Element | null {
+ const participantLiveOutput = useGroupChatStore((s) => s.participantLiveOutput);
+
// Color preferences state
const [colorPreferences, setColorPreferences] = useState>({});
const { panelRef, onResizeStart, transitionClass } = useResizablePanel({
@@ -322,6 +325,7 @@ export function GroupChatRightPanel({
color={participantColors[participant.name]}
groupChatId={groupChatId}
onContextReset={handleContextReset}
+ liveOutput={participantLiveOutput.get(`${groupChatId}:${participant.name}`)}
/>
);
})
diff --git a/src/renderer/components/ParticipantCard.tsx b/src/renderer/components/ParticipantCard.tsx
index 2aec2b916c..c31e4f2de1 100644
--- a/src/renderer/components/ParticipantCard.tsx
+++ b/src/renderer/components/ParticipantCard.tsx
@@ -5,8 +5,17 @@
* session ID, context usage, stats, and cost.
*/
-import { MessageSquare, Copy, Check, DollarSign, RotateCcw, Server } from 'lucide-react';
-import { useState, useCallback } from 'react';
+import {
+ MessageSquare,
+ Copy,
+ Check,
+ DollarSign,
+ RotateCcw,
+ Server,
+ Eye,
+ EyeOff,
+} from 'lucide-react';
+import { useState, useCallback, useEffect, useRef } from 'react';
import type { Theme, GroupChatParticipant, SessionState } from '../types';
import { getStatusColor } from '../utils/theme';
import { formatCost } from '../utils/formatters';
@@ -19,6 +28,7 @@ interface ParticipantCardProps {
color?: string;
groupChatId?: string;
onContextReset?: (participantName: string) => void;
+ liveOutput?: string;
}
/**
@@ -39,9 +49,19 @@ export function ParticipantCard({
color,
groupChatId,
onContextReset,
+ liveOutput,
}: ParticipantCardProps): JSX.Element {
const [copied, setCopied] = useState(false);
const [isResetting, setIsResetting] = useState(false);
+ const [peekOpen, setPeekOpen] = useState(false);
+ const peekRef = useRef(null);
+
+ // Auto-scroll peek output to bottom
+ useEffect(() => {
+ if (peekOpen && peekRef.current) {
+ peekRef.current.scrollTop = peekRef.current.scrollHeight;
+ }
+ }, [peekOpen, liveOutput]);
// Use agent's session ID (clean GUID) when available, otherwise show pending
const agentSessionId = participant.agentSessionId;
@@ -236,7 +256,41 @@ export function ParticipantCard({
Resetting...
)}
+ {/* Peek button - always visible */}
+
+
+ {/* Live output peek panel */}
+ {peekOpen && (
+
+ {liveOutput
+ ? liveOutput.length > 4096
+ ? liveOutput.slice(-4096)
+ : liveOutput
+ : '(no live output yet)'}
+
+ )}
);
}
diff --git a/src/renderer/components/Settings/SettingsModal.tsx b/src/renderer/components/Settings/SettingsModal.tsx
index a1d665dc0a..2aaa2a9b50 100644
--- a/src/renderer/components/Settings/SettingsModal.tsx
+++ b/src/renderer/components/Settings/SettingsModal.tsx
@@ -10,6 +10,7 @@ import {
FlaskConical,
Server,
Monitor,
+ Users,
} from 'lucide-react';
import { useSettings } from '../../hooks';
import type { Theme, LLMProvider } from '../../types';
@@ -45,6 +46,7 @@ interface SettingsModalProps {
| 'theme'
| 'notifications'
| 'aicommands'
+ | 'groupchat'
| 'ssh'
| 'encore';
hasNoAgents?: boolean;
@@ -92,6 +94,9 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
setSshRemoteIgnorePatterns,
sshRemoteHonorGitignore,
setSshRemoteHonorGitignore,
+ // Group Chat settings
+ moderatorStandingInstructions,
+ setModeratorStandingInstructions,
} = useSettings();
const [activeTab, setActiveTab] = useState<
@@ -102,6 +107,7 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
| 'theme'
| 'notifications'
| 'aicommands'
+ | 'groupchat'
| 'ssh'
| 'encore'
>('general');
@@ -166,6 +172,7 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
| 'theme'
| 'notifications'
| 'aicommands'
+ | 'groupchat'
| 'ssh'
| 'encore'
> = FEATURE_FLAGS.LLM_SETTINGS
@@ -177,6 +184,7 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
'theme',
'notifications',
'aicommands',
+ 'groupchat',
'ssh',
'encore',
]
@@ -187,6 +195,7 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
'theme',
'notifications',
'aicommands',
+ 'groupchat',
'ssh',
'encore',
];
@@ -391,6 +400,14 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
{activeTab === 'aicommands' && AI Commands}
+