Merge pull request #308 from pedramamini/0.15.0-polish

0.15.0 polish
This commit is contained in:
Pedram Amini
2026-02-05 14:48:57 -06:00
committed by GitHub
25 changed files with 120 additions and 48 deletions

View File

@@ -20,6 +20,44 @@ Settings are organized into tabs:
| **AI Commands** | View and edit slash commands, [Spec-Kit](./speckit-commands), and [OpenSpec](./openspec-commands) prompts | | **AI Commands** | View and edit slash commands, [Spec-Kit](./speckit-commands), and [OpenSpec](./openspec-commands) prompts |
| **SSH Hosts** | Configure remote hosts for [SSH agent execution](./ssh-remote-execution) | | **SSH Hosts** | Configure remote hosts for [SSH agent execution](./ssh-remote-execution) |
## Conductor Profile
The **Conductor Profile** (Settings → General → **About Me**) is a short description of yourself that gets injected into every AI agent's system prompt. This helps agents understand your background, preferences, and communication style so they can tailor responses accordingly.
**To configure:**
1. Open **Settings** (`Cmd+,` / `Ctrl+,`) → **General** tab
2. Find the **About Me** text area at the top
3. Write a brief profile describing yourself
### What to Include
A good conductor profile is concise (a few sentences to a short paragraph) and covers:
- **Your role/background**: Developer, researcher, team lead, etc.
- **Technical context**: Languages you work with, tools you prefer, platforms you use
- **Communication preferences**: Direct vs. detailed, level of explanation needed
- **Work style**: Preferences for how agents should approach tasks
### Example Profile
```
Security researcher. macOS desktop. TypeScript and Python for tools.
Direct communication, no fluff. Action over process. Push back on bad ideas.
Generate markdown for Obsidian. CST timezone.
```
### How Agents Use It
When you start a session, Maestro includes your conductor profile in the system prompt sent to the AI agent. This means:
- Agents adapt their response style to match your preferences
- Technical context helps agents make appropriate assumptions
- Communication preferences reduce back-and-forth clarification
### Using in Custom Commands
You can reference your conductor profile in [slash commands](./slash-commands) using the `{{CONDUCTOR_PROFILE}}` template variable. This is useful for commands that need to remind agents of your preferences mid-conversation.
## Checking for Updates ## Checking for Updates
Maestro checks for updates automatically on startup (configurable in Settings → General → **Check for updates on startup**). Maestro checks for updates automatically on startup (configurable in Settings → General → **Check for updates on startup**).
@@ -112,7 +150,6 @@ In-app toast notifications appear in the corner when events occur. Configure how
Notifications are sent when: Notifications are sent when:
- An AI task completes (OS notification + optional custom notification) - An AI task completes (OS notification + optional custom notification)
- A long-running command finishes (OS notification) - A long-running command finishes (OS notification)
- The LLM analysis generates a feedback synopsis (custom notification only, if configured)
## Sleep Prevention ## Sleep Prevention

View File

@@ -119,6 +119,7 @@ vi.mock('../../renderer/components/TemplateAutocompleteDropdown', () => ({
vi.mock('../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys) => keys?.join('+') || ''), formatShortcutKeys: vi.fn((keys) => keys?.join('+') || ''),
isMacOS: vi.fn(() => false),
})); }));
// Create a mock theme for testing // Create a mock theme for testing

View File

@@ -126,6 +126,7 @@ vi.mock('../../renderer/components/TemplateAutocompleteDropdown', () => ({
vi.mock('../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys) => keys?.join('+') || ''), formatShortcutKeys: vi.fn((keys) => keys?.join('+') || ''),
isMacOS: vi.fn(() => false),
})); }));
vi.mock('../../renderer/hooks/useGitStatusPolling', () => ({ vi.mock('../../renderer/hooks/useGitStatusPolling', () => ({

View File

@@ -96,6 +96,7 @@ vi.mock('../../../renderer/components/AutoRun', () => ({
// Mock shortcut formatter // Mock shortcut formatter
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')), formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false),
})); }));
// Create a mock theme for testing // Create a mock theme for testing

View File

@@ -38,6 +38,7 @@ vi.mock('../../../renderer/contexts/LayerStackContext', async () => {
// Mock formatShortcutKeys to return predictable output // Mock formatShortcutKeys to return predictable output
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: (keys: string[]) => keys.join('+'), formatShortcutKeys: (keys: string[]) => keys.join('+'),
isMacOS: () => false,
})); }));
// Sample theme for testing // Sample theme for testing

View File

@@ -109,6 +109,7 @@ vi.mock('../../../renderer/utils/tokenCounter', () => ({
// Mock shortcut formatter // Mock shortcut formatter
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string) => keys), formatShortcutKeys: vi.fn((keys: string) => keys),
isMacOS: vi.fn(() => false),
})); }));
// Mock remarkFileLinks // Mock remarkFileLinks

View File

@@ -333,9 +333,10 @@ describe('WizardInputPanel', () => {
expect(screen.getByText('Enter')).toBeInTheDocument(); expect(screen.getByText('Enter')).toBeInTheDocument();
}); });
it('shows "⌘ + Enter" when enterToSend is false', () => { it('shows "⌘ + Enter" (or "Ctrl + Enter" on non-Mac) when enterToSend is false', () => {
render(<WizardInputPanel {...defaultProps} enterToSend={false} />); render(<WizardInputPanel {...defaultProps} enterToSend={false} />);
expect(screen.getByText('⌘ + Enter')).toBeInTheDocument(); // Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(screen.getByText(/⌘ \+ Enter|Ctrl \+ Enter/)).toBeInTheDocument();
}); });
it('calls setEnterToSend when clicked', () => { it('calls setEnterToSend when clicked', () => {

View File

@@ -235,17 +235,18 @@ describe('InputArea', () => {
const props = createDefaultProps(); const props = createDefaultProps();
render(<InputArea {...props} />); render(<InputArea {...props} />);
const button = screen.getByTitle('Switch to Meta+Enter to send'); const button = screen.getByTitle(/Switch to (Cmd|Ctrl)\+Enter to send/);
expect(button).toBeInTheDocument(); expect(button).toBeInTheDocument();
expect(button).toHaveTextContent('Enter'); expect(button).toHaveTextContent('Enter');
}); });
it('renders Cmd+Enter when enterToSend is false', () => { it('renders Cmd+Enter (or Ctrl+Enter on non-Mac) when enterToSend is false', () => {
const props = createDefaultProps({ enterToSend: false }); const props = createDefaultProps({ enterToSend: false });
render(<InputArea {...props} />); render(<InputArea {...props} />);
const button = screen.getByTitle('Switch to Enter to send'); const button = screen.getByTitle('Switch to Enter to send');
expect(button).toHaveTextContent('⌘ + Enter'); // Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(button).toHaveTextContent(/⌘ \+ Enter|Ctrl \+ Enter/);
}); });
}); });
@@ -1505,7 +1506,7 @@ describe('InputArea', () => {
const props = createDefaultProps({ enterToSend: true, setEnterToSend }); const props = createDefaultProps({ enterToSend: true, setEnterToSend });
render(<InputArea {...props} />); render(<InputArea {...props} />);
fireEvent.click(screen.getByTitle('Switch to Meta+Enter to send')); fireEvent.click(screen.getByTitle(/Switch to (Cmd|Ctrl)\+Enter to send/));
expect(setEnterToSend).toHaveBeenCalledWith(false); expect(setEnterToSend).toHaveBeenCalledWith(false);
}); });

View File

@@ -148,6 +148,7 @@ vi.mock('../../../renderer/utils/tabHelpers', () => ({
// Mock shortcut formatter // Mock shortcut formatter
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys?.join('+') || ''), formatShortcutKeys: vi.fn((keys: string[]) => keys?.join('+') || ''),
isMacOS: vi.fn(() => false),
})); }));
// Configurable git status data for tests - can be modified in individual tests // Configurable git status data for tests - can be modified in individual tests

View File

@@ -185,8 +185,9 @@ describe('PromptComposerModal', () => {
/> />
); );
// When enterToSend is false, it shows "⌘ + Enter" // When enterToSend is false, it shows "⌘ + Enter" on Mac or "Ctrl + Enter" on other platforms
expect(screen.getByText('⌘ + Enter')).toBeInTheDocument(); // Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(screen.getByText(/⌘ \+ Enter|Ctrl \+ Enter/)).toBeInTheDocument();
}); });
it('should render close button with X icon', () => { it('should render close button with X icon', () => {

View File

@@ -59,6 +59,7 @@ vi.mock('../../../renderer/services/git', () => ({
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')), formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false),
})); }));
// Mock lucide-react // Mock lucide-react

View File

@@ -21,6 +21,7 @@ vi.mock('../../../renderer/components/AutoRun', () => ({
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys) => keys.join('+')), formatShortcutKeys: vi.fn((keys) => keys.join('+')),
isMacOS: vi.fn(() => false),
})); }));
// Mock lucide-react // Mock lucide-react

View File

@@ -38,6 +38,7 @@ vi.mock('../../../renderer/contexts/LayerStackContext', () => ({
// Mock formatShortcutKeys // Mock formatShortcutKeys
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')), formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false), // Test environment is not Mac
})); }));
// Mock AICommandsPanel // Mock AICommandsPanel
@@ -808,14 +809,15 @@ describe('SettingsModal', () => {
expect(setEnterToSendTerminal).toHaveBeenCalledWith(false); expect(setEnterToSendTerminal).toHaveBeenCalledWith(false);
}); });
it('should display Cmd+Enter when enter-to-send is false', async () => { it('should display Cmd+Enter (or Ctrl+Enter on non-Mac) when enter-to-send is false', async () => {
render(<SettingsModal {...createDefaultProps({ enterToSendAI: false })} />); render(<SettingsModal {...createDefaultProps({ enterToSendAI: false })} />);
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(100); await vi.advanceTimersByTimeAsync(100);
}); });
expect(screen.getByText('⌘ + Enter')).toBeInTheDocument(); // Test environment doesn't have Mac user agent, so it shows Ctrl + Enter
expect(screen.getByText(/⌘ \+ Enter|Ctrl \+ Enter/)).toBeInTheDocument();
}); });
}); });

View File

@@ -14,6 +14,7 @@ import type { Theme, Shortcut } from '../../../renderer/types';
// Mock the shortcutFormatter module // Mock the shortcutFormatter module
vi.mock('../../../renderer/utils/shortcutFormatter', () => ({ vi.mock('../../../renderer/utils/shortcutFormatter', () => ({
formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')), formatShortcutKeys: vi.fn((keys: string[]) => keys.join('+')),
isMacOS: vi.fn(() => false),
})); }));
// Import after mock to get the mocked version // Import after mock to get the mocked version

View File

@@ -271,7 +271,7 @@ describe('ToastContext', () => {
taskDuration: 5000, taskDuration: 5000,
agentSessionId: 'test-session-id', agentSessionId: 'test-session-id',
tabName: 'TestTab', tabName: 'TestTab',
audioNotification: { enabled: false }, audioNotification: { enabled: false, reason: 'disabled' },
}); });
}); });

View File

@@ -364,7 +364,10 @@ export function registerNotificationsHandlers(): void {
async (_event, text: string, command?: string): Promise<NotificationCommandResponse> => { async (_event, text: string, command?: string): Promise<NotificationCommandResponse> => {
// Skip if there's no content to send // Skip if there's no content to send
if (!text || text.trim().length === 0) { if (!text || text.trim().length === 0) {
logger.debug('Notification skipped - no content to send', 'Notification'); logger.info('Notification skipped - empty or whitespace-only content', 'Notification', {
textLength: text?.length ?? 0,
hasText: !!text,
});
return { success: true }; // Return success since there's nothing to do return { success: true }; // Return success since there's nothing to do
} }

View File

@@ -4,6 +4,7 @@ Rules:
- 2-4 words max, shorter is better - 2-4 words max, shorter is better
- Title Case, no punctuation - Title Case, no punctuation
- Be specific (mention tech/file names if relevant) - Be specific (mention tech/file names if relevant)
- Include ID numbers (PR #, issue #, ticket ID) when mentioned
- Use hyphens for compound concepts - Use hyphens for compound concepts
- If too vague, use format: YYYY-MM-DD Topic - If too vague, use format: YYYY-MM-DD Topic
@@ -13,4 +14,7 @@ Examples:
"refactor DB queries for pooling" = DB Connection Pooling "refactor DB queries for pooling" = DB Connection Pooling
"write tests for checkout" = Checkout Tests "write tests for checkout" = Checkout Tests
"fix TS errors in parser.ts" = Parser TS Errors "fix TS errors in parser.ts" = Parser TS Errors
"review PR #256" = PR 256 Review
"fix issue #42 memory leak" = Issue 42 Memory Leak
"implement JIRA-1234 feature" = JIRA-1234 Feature
"help with my code" = 2024-01-15 Code Help "help with my code" = 2024-01-15 Code Help

View File

@@ -22,7 +22,7 @@ import type {
QueuedItem, QueuedItem,
Shortcut, Shortcut,
} from '../types'; } from '../types';
import { formatShortcutKeys } from '../utils/shortcutFormatter'; import { formatShortcutKeys, isMacOS } from '../utils/shortcutFormatter';
import { QueuedItemsList } from './QueuedItemsList'; import { QueuedItemsList } from './QueuedItemsList';
import { normalizeMentionName } from '../utils/participantColors'; import { normalizeMentionName } from '../utils/participantColors';
@@ -549,10 +549,10 @@ export const GroupChatInput = React.memo(function GroupChatInput({
<button <button
onClick={() => setEnterToSend(!enterToSend)} onClick={() => setEnterToSend(!enterToSend)}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5" className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'} title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
> >
<Keyboard className="w-3 h-3" /> <Keyboard className="w-3 h-3" />
{enterToSend ? 'Enter' : '⌘ + Enter'} {enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -26,6 +26,7 @@ import type { Session, Theme } from '../../types';
import { WizardPill } from './WizardPill'; import { WizardPill } from './WizardPill';
import { WizardConfidenceGauge } from './WizardConfidenceGauge'; import { WizardConfidenceGauge } from './WizardConfidenceGauge';
import { WizardExitConfirmDialog } from './WizardExitConfirmDialog'; import { WizardExitConfirmDialog } from './WizardExitConfirmDialog';
import { isMacOS } from '../../utils/shortcutFormatter';
interface WizardInputPanelProps { interface WizardInputPanelProps {
/** Current session with wizard state */ /** Current session with wizard state */
@@ -322,10 +323,10 @@ export const WizardInputPanel = React.memo(function WizardInputPanel({
<button <button
onClick={() => setEnterToSend(!enterToSend)} onClick={() => setEnterToSend(!enterToSend)}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5" className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'} title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
> >
<Keyboard className="w-3 h-3" /> <Keyboard className="w-3 h-3" />
{enterToSend ? 'Enter' : '⌘ + Enter'} {enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -18,7 +18,7 @@ import {
Pin, Pin,
} from 'lucide-react'; } from 'lucide-react';
import type { Session, Theme, BatchRunState, Shortcut, ThinkingMode } from '../types'; import type { Session, Theme, BatchRunState, Shortcut, ThinkingMode } from '../types';
import { formatShortcutKeys } from '../utils/shortcutFormatter'; import { formatShortcutKeys, isMacOS } from '../utils/shortcutFormatter';
import type { TabCompletionSuggestion, TabCompletionFilter } from '../hooks'; import type { TabCompletionSuggestion, TabCompletionFilter } from '../hooks';
import type { import type {
SummarizeProgress, SummarizeProgress,
@@ -1087,10 +1087,10 @@ export const InputArea = React.memo(function InputArea(props: InputAreaProps) {
<button <button
onClick={() => setEnterToSend(!enterToSend)} onClick={() => setEnterToSend(!enterToSend)}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5" className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'} title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
> >
<Keyboard className="w-3 h-3" /> <Keyboard className="w-3 h-3" />
{enterToSend ? 'Enter' : '⌘ + Enter'} {enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -306,10 +306,6 @@ export function NotificationsPanel({
<ul className="text-xs opacity-70 space-y-1" style={{ color: theme.colors.textDim }}> <ul className="text-xs opacity-70 space-y-1" style={{ color: theme.colors.textDim }}>
<li> When an AI task completes</li> <li> When an AI task completes</li>
<li> When a long-running command finishes</li> <li> When a long-running command finishes</li>
<li>
When the LLM analysis generates a feedback synopsis (custom notification only, if
configured)
</li>
</ul> </ul>
<div <div
className="text-xs opacity-60 mt-3 pt-3" className="text-xs opacity-60 mt-3 pt-3"

View File

@@ -4,6 +4,7 @@ import type { Theme, ThinkingMode } from '../types';
import { useLayerStack } from '../contexts/LayerStackContext'; import { useLayerStack } from '../contexts/LayerStackContext';
import { MODAL_PRIORITIES } from '../constants/modalPriorities'; import { MODAL_PRIORITIES } from '../constants/modalPriorities';
import { estimateTokenCount } from '../../shared/formatters'; import { estimateTokenCount } from '../../shared/formatters';
import { isMacOS } from '../utils/shortcutFormatter';
interface PromptComposerModalProps { interface PromptComposerModalProps {
isOpen: boolean; isOpen: boolean;
@@ -450,11 +451,11 @@ export function PromptComposerModal({
<button <button
onClick={onToggleEnterToSend} onClick={onToggleEnterToSend}
className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5" className="flex items-center gap-1 text-[10px] opacity-50 hover:opacity-100 px-2 py-1 rounded hover:bg-white/5"
title={enterToSend ? 'Switch to Meta+Enter to send' : 'Switch to Enter to send'} title={enterToSend ? `Switch to ${isMacOS() ? 'Cmd' : 'Ctrl'}+Enter to send` : 'Switch to Enter to send'}
> >
<Keyboard className="w-3 h-3" style={{ color: theme.colors.textDim }} /> <Keyboard className="w-3 h-3" style={{ color: theme.colors.textDim }} />
<span style={{ color: theme.colors.textDim }}> <span style={{ color: theme.colors.textDim }}>
{enterToSend ? 'Enter' : '⌘ + Enter'} {enterToSend ? 'Enter' : isMacOS() ? '⌘ + Enter' : 'Ctrl + Enter'}
</span> </span>
</button> </button>
)} )}

View File

@@ -49,7 +49,7 @@ import { MODAL_PRIORITIES } from '../constants/modalPriorities';
import { AICommandsPanel } from './AICommandsPanel'; import { AICommandsPanel } from './AICommandsPanel';
import { SpecKitCommandsPanel } from './SpecKitCommandsPanel'; import { SpecKitCommandsPanel } from './SpecKitCommandsPanel';
import { OpenSpecCommandsPanel } from './OpenSpecCommandsPanel'; import { OpenSpecCommandsPanel } from './OpenSpecCommandsPanel';
import { formatShortcutKeys } from '../utils/shortcutFormatter'; import { formatShortcutKeys, isMacOS } from '../utils/shortcutFormatter';
import { ToggleButtonGroup } from './ToggleButtonGroup'; import { ToggleButtonGroup } from './ToggleButtonGroup';
import { SettingCheckbox } from './SettingCheckbox'; import { SettingCheckbox } from './SettingCheckbox';
import { FontConfigurationPanel } from './FontConfigurationPanel'; import { FontConfigurationPanel } from './FontConfigurationPanel';
@@ -1302,8 +1302,8 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
Input Send Behavior Input Send Behavior
</label> </label>
<p className="text-xs opacity-50 mb-3"> <p className="text-xs opacity-50 mb-3">
Configure how to send messages in each mode. Choose between Enter or Command+Enter Configure how to send messages in each mode. Choose between Enter or{' '}
for each input type. {isMacOS() ? 'Command' : 'Ctrl'}+Enter for each input type.
</p> </p>
{/* AI Mode Setting */} {/* AI Mode Setting */}
@@ -1324,13 +1324,13 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
border: `1px solid ${theme.colors.border}`, border: `1px solid ${theme.colors.border}`,
}} }}
> >
{props.enterToSendAI ? 'Enter' : ' + Enter'} {props.enterToSendAI ? 'Enter' : isMacOS() ? ' + Enter' : 'Ctrl + Enter'}
</button> </button>
</div> </div>
<p className="text-xs opacity-50"> <p className="text-xs opacity-50">
{props.enterToSendAI {props.enterToSendAI
? 'Press Enter to send. Use Shift+Enter for new line.' ? 'Press Enter to send. Use Shift+Enter for new line.'
: 'Press Command+Enter to send. Enter creates new line.'} : `Press ${isMacOS() ? 'Command' : 'Ctrl'}+Enter to send. Enter creates new line.`}
</p> </p>
</div> </div>
@@ -1352,13 +1352,13 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
border: `1px solid ${theme.colors.border}`, border: `1px solid ${theme.colors.border}`,
}} }}
> >
{props.enterToSendTerminal ? 'Enter' : ' + Enter'} {props.enterToSendTerminal ? 'Enter' : isMacOS() ? ' + Enter' : 'Ctrl + Enter'}
</button> </button>
</div> </div>
<p className="text-xs opacity-50"> <p className="text-xs opacity-50">
{props.enterToSendTerminal {props.enterToSendTerminal
? 'Press Enter to send. Use Shift+Enter for new line.' ? 'Press Enter to send. Use Shift+Enter for new line.'
: 'Press Command+Enter to send. Enter creates new line.'} : `Press ${isMacOS() ? 'Command' : 'Ctrl'}+Enter to send. Enter creates new line.`}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -34,6 +34,7 @@ import type { WizardError } from '../services/wizardErrorDetection';
import { AUTO_RUN_FOLDER_NAME, wizardDebugLogger } from '../services/phaseGenerator'; import { AUTO_RUN_FOLDER_NAME, wizardDebugLogger } from '../services/phaseGenerator';
import { getNextFillerPhrase } from '../services/fillerPhrases'; import { getNextFillerPhrase } from '../services/fillerPhrases';
import { ScreenReaderAnnouncement } from '../ScreenReaderAnnouncement'; import { ScreenReaderAnnouncement } from '../ScreenReaderAnnouncement';
import { isMacOS } from '../../../utils/shortcutFormatter';
interface ConversationScreenProps { interface ConversationScreenProps {
theme: Theme; theme: Theme;
@@ -1613,7 +1614,7 @@ export function ConversationScreen({
className="px-1.5 py-0.5 rounded text-xs" className="px-1.5 py-0.5 rounded text-xs"
style={{ backgroundColor: theme.colors.border }} style={{ backgroundColor: theme.colors.border }}
> >
+Enter {isMacOS() ? '' : 'Ctrl'}+Enter
</kbd> </kbd>
Send Send
</span> </span>

View File

@@ -98,7 +98,16 @@ export function ToastProvider({
// Capture audio feedback state for logging // Capture audio feedback state for logging
const { enabled: audioEnabled, command: audioCommand } = audioFeedbackRef.current; const { enabled: audioEnabled, command: audioCommand } = audioFeedbackRef.current;
// Determine if we have content to send for custom notification
// Also skip if there's no content to send
const hasContent = toast.message && toast.message.trim().length > 0;
// Determine if custom notification will actually be triggered
const willTriggerCustomNotification =
audioEnabled && audioCommand && !toast.skipCustomNotification && hasContent;
// Log toast to system logs (include audio notification info) // Log toast to system logs (include audio notification info)
// Only log enabled: true when we will actually trigger the notification
window.maestro.logger.toast(toast.title, { window.maestro.logger.toast(toast.title, {
type: toast.type, type: toast.type,
message: toast.message, message: toast.message,
@@ -107,23 +116,29 @@ export function ToastProvider({
taskDuration: toast.taskDuration, taskDuration: toast.taskDuration,
agentSessionId: toast.agentSessionId, agentSessionId: toast.agentSessionId,
tabName: toast.tabName, tabName: toast.tabName,
// Audio/TTS notification info // Audio/TTS notification info - reflects whether notification WILL be triggered
audioNotification: audioNotification: willTriggerCustomNotification
audioEnabled && audioCommand
? { ? {
enabled: true, enabled: true,
command: audioCommand, command: audioCommand,
} }
: { : {
enabled: false, enabled: false,
reason: !audioEnabled
? 'disabled'
: !audioCommand
? 'no-command'
: toast.skipCustomNotification
? 'opted-out'
: !hasContent
? 'no-content'
: 'unknown',
}, },
}); });
// Run custom notification command if enabled and configured // Run custom notification command if enabled and configured
// Skip for toasts that explicitly opt out (e.g., synopsis messages) // Skip for toasts that explicitly opt out (e.g., synopsis messages)
// Also skip if there's no content to send if (willTriggerCustomNotification) {
const hasContent = toast.message && toast.message.trim().length > 0;
if (audioEnabled && audioCommand && !toast.skipCustomNotification && hasContent) {
console.log( console.log(
'[ToastContext] Running custom notification with message:', '[ToastContext] Running custom notification with message:',
toast.message, toast.message,