mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 00:21:21 +00:00
Merge pull request #304 from pedramamini/conductor-profile
Add Conductor Profile
This commit is contained in:
@@ -12,7 +12,7 @@ Settings are organized into tabs:
|
||||
|
||||
| Tab | Contents |
|
||||
|-----|----------|
|
||||
| **General** | Shell configuration, input send behavior, default toggles (history, thinking), automatic tab naming, power management, updates, privacy, usage stats, storage location |
|
||||
| **General** | About Me (conductor profile), shell configuration, input send behavior, default toggles (history, thinking), automatic tab naming, power management, updates, privacy, usage stats, storage location |
|
||||
| **Display** | Font family and size, terminal width, log level and buffer, max output lines per response, document graph settings, context window warnings |
|
||||
| **Shortcuts** | Customize keyboard shortcuts (see [Keyboard Shortcuts](./keyboard-shortcuts)) |
|
||||
| **Themes** | Dark, light, and vibe mode themes, custom theme builder with import/export |
|
||||
|
||||
@@ -44,6 +44,12 @@ Create your own slash commands in **Settings → AI Commands**. Each command has
|
||||
|
||||
Commands support **template variables** that are automatically substituted at runtime:
|
||||
|
||||
### Conductor Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `{{CONDUCTOR_PROFILE}}` | Your "About Me" profile from Settings → General. Tells agents about your background, preferences, and communication style. |
|
||||
|
||||
### Agent Variables
|
||||
|
||||
| Variable | Description |
|
||||
|
||||
@@ -68,6 +68,10 @@ vi.mock('../../../renderer/components/CustomThemeBuilder', () => ({
|
||||
// Mock useSettings hook (used for context management settings and SSH remote ignore settings)
|
||||
vi.mock('../../../renderer/hooks/settings/useSettings', () => ({
|
||||
useSettings: () => ({
|
||||
// Conductor profile settings
|
||||
conductorProfile: '',
|
||||
setConductorProfile: vi.fn(),
|
||||
// Context management settings
|
||||
contextManagementSettings: {
|
||||
autoGroomContexts: true,
|
||||
maxContextTokens: 100000,
|
||||
|
||||
@@ -154,6 +154,12 @@ const mockMaestro = {
|
||||
shell: {
|
||||
openExternal: vi.fn(),
|
||||
},
|
||||
sshRemote: {
|
||||
getConfigs: vi.fn().mockResolvedValue({ success: true, configs: [] }),
|
||||
},
|
||||
sessions: {
|
||||
getAll: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
};
|
||||
|
||||
// Mock theme
|
||||
|
||||
@@ -159,6 +159,12 @@ const mockMaestro = {
|
||||
fs: {
|
||||
readFile: vi.fn(),
|
||||
},
|
||||
sshRemote: {
|
||||
getConfigs: vi.fn().mockResolvedValue({ success: true, configs: [] }),
|
||||
},
|
||||
sessions: {
|
||||
getAll: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
};
|
||||
|
||||
// Helper to render with required providers
|
||||
|
||||
@@ -58,6 +58,11 @@ describe('TEMPLATE_VARIABLES constant', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should include conductor variables', () => {
|
||||
const variables = TEMPLATE_VARIABLES.map((v) => v.variable);
|
||||
expect(variables).toContain('{{CONDUCTOR_PROFILE}}');
|
||||
});
|
||||
|
||||
it('should include key agent variables', () => {
|
||||
const variables = TEMPLATE_VARIABLES.map((v) => v.variable);
|
||||
expect(variables).toContain('{{AGENT_NAME}}');
|
||||
@@ -138,6 +143,24 @@ describe('substituteTemplateVariables', () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe('Conductor Variables', () => {
|
||||
it('should replace {{CONDUCTOR_PROFILE}} with conductorProfile', () => {
|
||||
const context = createTestContext({
|
||||
conductorProfile: 'Senior developer specializing in TypeScript and React',
|
||||
});
|
||||
const result = substituteTemplateVariables('Profile: {{CONDUCTOR_PROFILE}}', context);
|
||||
expect(result).toBe('Profile: Senior developer specializing in TypeScript and React');
|
||||
});
|
||||
|
||||
it('should replace {{CONDUCTOR_PROFILE}} with empty string when conductorProfile is undefined', () => {
|
||||
const context = createTestContext({
|
||||
conductorProfile: undefined,
|
||||
});
|
||||
const result = substituteTemplateVariables('Profile: {{CONDUCTOR_PROFILE}}', context);
|
||||
expect(result).toBe('Profile: ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Agent Variables', () => {
|
||||
it('should replace {{AGENT_NAME}} with session.name', () => {
|
||||
const context = createTestContext({
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
You are a Group Chat Moderator in Maestro, a multi-agent orchestration tool. Your role is to:
|
||||
You are a Group Chat Moderator in Maestro, a multi-agent orchestration tool.
|
||||
|
||||
## Conductor Profile
|
||||
|
||||
{{CONDUCTOR_PROFILE}}
|
||||
|
||||
Your role is to:
|
||||
|
||||
1. **Assist the user directly** - You are a capable AI assistant. For simple questions or tasks, respond directly without delegating to other agents.
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
You are **{{AGENT_NAME}}**, powered by **{{TOOL_TYPE}}**, operating as a Maestro-managed AI coding agent.
|
||||
|
||||
## Conductor Profile
|
||||
|
||||
{{CONDUCTOR_PROFILE}}
|
||||
|
||||
## About Maestro
|
||||
|
||||
Maestro is an Electron desktop application for managing multiple AI coding assistants simultaneously with a keyboard-first interface. For more information:
|
||||
@@ -27,8 +31,11 @@ Your session history is stored at `{{AGENT_HISTORY_PATH}}`. When you need contex
|
||||
- `timestamp`: When the task was completed (Unix ms)
|
||||
- `type`: `AUTO` (automated) or `USER` (interactive)
|
||||
- `success`: Whether the task succeeded
|
||||
- `fullResponse`: Complete AI response text (for detailed context)
|
||||
- `elapsedTimeMs`: How long the task took
|
||||
- `contextUsage`: Context window usage percentage at completion
|
||||
|
||||
To recall recent work, read the file and scan the most recent entries by timestamp.
|
||||
To recall recent work, read the file and scan the most recent entries by timestamp. Use `summary` for quick scanning and `fullResponse` when you need detailed context about what was done.
|
||||
|
||||
## Auto-run Documents
|
||||
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
You are a planning assistant helping in an existing Maestro session for "{{PROJECT_NAME}}".
|
||||
|
||||
## Conductor Profile
|
||||
|
||||
{{CONDUCTOR_PROFILE}}
|
||||
|
||||
## Your Role
|
||||
|
||||
You are helping plan work in an active session. The user has an established project context and wants to create or extend a Playbook.
|
||||
|
||||
## Task Recall
|
||||
|
||||
Your session history is stored at `{{AGENT_HISTORY_PATH}}`. When you need context about previously completed work in this project, read this JSON file and parse the `entries` array. Each entry contains:
|
||||
- `summary`: Brief description of the task
|
||||
- `timestamp`: When the task was completed (Unix ms)
|
||||
- `type`: `AUTO` (automated) or `USER` (interactive)
|
||||
- `success`: Whether the task succeeded
|
||||
- `fullResponse`: Complete AI response text (for detailed context)
|
||||
- `elapsedTimeMs`: How long the task took
|
||||
|
||||
To recall recent work, read the file and scan the most recent entries by timestamp. Use `summary` for quick scanning and `fullResponse` when you need detailed context about what has already been accomplished.
|
||||
|
||||
## File Access Restrictions
|
||||
|
||||
**WRITE ACCESS (Limited):**
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
You are a friendly project discovery assistant helping to set up "{{PROJECT_NAME}}".
|
||||
|
||||
## Conductor Profile
|
||||
|
||||
{{CONDUCTOR_PROFILE}}
|
||||
|
||||
## Your Role
|
||||
|
||||
You are 🎼 Maestro's onboarding assistant, helping the user define their project so we can create an actionable plan.
|
||||
|
||||
## Task Recall
|
||||
|
||||
Your session history is stored at `{{AGENT_HISTORY_PATH}}`. When you need context about previously completed work in this project, read this JSON file and parse the `entries` array. Each entry contains:
|
||||
- `summary`: Brief description of the task
|
||||
- `timestamp`: When the task was completed (Unix ms)
|
||||
- `type`: `AUTO` (automated) or `USER` (interactive)
|
||||
- `success`: Whether the task succeeded
|
||||
- `fullResponse`: Complete AI response text (for detailed context)
|
||||
- `elapsedTimeMs`: How long the task took
|
||||
|
||||
To recall recent work, read the file and scan the most recent entries by timestamp. Use `summary` for quick scanning and `fullResponse` when you need detailed context about what has already been accomplished.
|
||||
|
||||
## File Access Restrictions
|
||||
|
||||
**WRITE ACCESS (Limited):**
|
||||
|
||||
@@ -453,6 +453,7 @@ function MaestroConsoleInner() {
|
||||
const settings = useSettings();
|
||||
const {
|
||||
settingsLoaded,
|
||||
conductorProfile,
|
||||
llmProvider,
|
||||
setLlmProvider,
|
||||
modelSlug,
|
||||
@@ -4551,6 +4552,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
const substitutedSystemPrompt = substituteTemplateVariables(maestroSystemPrompt, {
|
||||
session: targetSession,
|
||||
gitBranch,
|
||||
conductorProfile,
|
||||
});
|
||||
effectivePrompt = `${substitutedSystemPrompt}\n\n---\n\n# User Request\n\n${effectivePrompt}`;
|
||||
}
|
||||
@@ -7497,7 +7499,8 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
activeTab.id, // Tab ID for per-tab isolation
|
||||
activeSession.id, // Session ID for playbook creation
|
||||
activeSession.autoRunFolderPath, // User-configured Auto Run folder path (if set)
|
||||
activeSession.sessionSshRemoteConfig // SSH remote config for remote execution
|
||||
activeSession.sessionSshRemoteConfig, // SSH remote config for remote execution
|
||||
conductorProfile // Conductor profile (user's About Me from settings)
|
||||
);
|
||||
|
||||
// Rename the tab to "Wizard" immediately when wizard starts
|
||||
@@ -7526,7 +7529,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
};
|
||||
addLogToActiveTab(activeSession.id, wizardLog);
|
||||
},
|
||||
[activeSession, startInlineWizard, addLogToActiveTab]
|
||||
[activeSession, startInlineWizard, addLogToActiveTab, conductorProfile]
|
||||
);
|
||||
|
||||
// Launch wizard in a new tab - triggered from Auto Run panel button
|
||||
@@ -7580,7 +7583,8 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
newTab.id,
|
||||
activeSession.id,
|
||||
activeSession.autoRunFolderPath, // User-configured Auto Run folder path (if set)
|
||||
activeSession.sessionSshRemoteConfig // SSH remote config for remote execution
|
||||
activeSession.sessionSshRemoteConfig, // SSH remote config for remote execution
|
||||
conductorProfile // Conductor profile (user's About Me from settings)
|
||||
);
|
||||
|
||||
// Show a system log entry
|
||||
@@ -7597,6 +7601,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
defaultShowThinking,
|
||||
startInlineWizard,
|
||||
addLogToTab,
|
||||
conductorProfile,
|
||||
]);
|
||||
|
||||
// Determine if wizard is active for the current tab
|
||||
@@ -7634,6 +7639,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
isWizardActive: isWizardActiveForCurrentTab,
|
||||
onSkillsCommand: handleSkillsCommand,
|
||||
automaticTabNamingEnabled,
|
||||
conductorProfile,
|
||||
});
|
||||
|
||||
// Auto-send context when a tab with autoSendOnActivate becomes active
|
||||
@@ -10113,6 +10119,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
promptToSend = substituteTemplateVariables(matchingCommand.prompt, {
|
||||
session,
|
||||
gitBranch,
|
||||
conductorProfile,
|
||||
});
|
||||
commandMetadata = {
|
||||
command: matchingCommand.command,
|
||||
@@ -10411,6 +10418,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
const substitutedSystemPrompt = substituteTemplateVariables(maestroSystemPrompt, {
|
||||
session,
|
||||
gitBranch,
|
||||
conductorProfile,
|
||||
});
|
||||
|
||||
// Prepend system prompt to user's message
|
||||
@@ -10482,6 +10490,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
const substitutedPrompt = substituteTemplateVariables(promptWithArgs, {
|
||||
session,
|
||||
gitBranch,
|
||||
conductorProfile,
|
||||
});
|
||||
|
||||
// For NEW sessions (no agentSessionId), prepend Maestro system prompt
|
||||
@@ -10494,6 +10503,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
const substitutedSystemPrompt = substituteTemplateVariables(maestroSystemPrompt, {
|
||||
session,
|
||||
gitBranch,
|
||||
conductorProfile,
|
||||
});
|
||||
|
||||
// Prepend system prompt to command's prompt (for agent only)
|
||||
|
||||
@@ -977,7 +977,7 @@ export const MainPanel = React.memo(
|
||||
<GitBranch className="w-3 h-3 shrink-0" />
|
||||
{/* Hide branch name text at narrow widths, show on hover via title */}
|
||||
{!useIconOnlyGitBranch && (
|
||||
<span className="truncate max-w-[120px]">
|
||||
<span className="truncate">
|
||||
{gitInfo?.branch || 'GIT'}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
Monitor,
|
||||
PartyPopper,
|
||||
Tag,
|
||||
User,
|
||||
} from 'lucide-react';
|
||||
import { useSettings } from '../hooks';
|
||||
import type {
|
||||
@@ -283,6 +284,9 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
|
||||
// Context management settings from useSettings hook
|
||||
const {
|
||||
// Conductor Profile (About Me)
|
||||
conductorProfile,
|
||||
setConductorProfile,
|
||||
contextManagementSettings,
|
||||
updateContextManagementSettings,
|
||||
// Document Graph settings
|
||||
@@ -951,6 +955,44 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
<div className="flex-1 p-6 overflow-y-auto scrollbar-thin">
|
||||
{activeTab === 'general' && (
|
||||
<div className="space-y-5">
|
||||
{/* About Me (Conductor Profile) */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-1 flex items-center gap-2">
|
||||
<User className="w-3 h-3" />
|
||||
About Me
|
||||
</label>
|
||||
<p className="text-xs opacity-50 mb-2">
|
||||
Tell us a little about yourself so that agents created under Maestro know how to
|
||||
work and communicate with you. As the conductor, you orchestrate the symphony of AI
|
||||
agents. (Optional, max 1000 characters)
|
||||
</p>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
value={conductorProfile}
|
||||
onChange={(e) => setConductorProfile(e.target.value)}
|
||||
placeholder="e.g., I'm a senior developer working on a React/TypeScript project. I prefer concise explanations and clean code patterns..."
|
||||
className="w-full p-3 rounded border bg-transparent outline-none text-sm resize-none"
|
||||
style={{
|
||||
borderColor: theme.colors.border,
|
||||
color: theme.colors.textMain,
|
||||
minHeight: '100px',
|
||||
}}
|
||||
maxLength={1000}
|
||||
/>
|
||||
<div
|
||||
className="absolute bottom-2 right-2 text-xs"
|
||||
style={{
|
||||
color:
|
||||
conductorProfile.length > 900
|
||||
? theme.colors.warning
|
||||
: theme.colors.textDim,
|
||||
}}
|
||||
>
|
||||
{conductorProfile.length}/1000
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Default Shell */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold opacity-70 uppercase mb-1 flex items-center gap-2">
|
||||
|
||||
@@ -326,6 +326,9 @@ export function AgentSelectionScreen({ theme }: AgentSelectionScreenProps): JSX.
|
||||
const [loadingModels, setLoadingModels] = useState(false);
|
||||
const [refreshingAgent, setRefreshingAgent] = useState(false);
|
||||
|
||||
// Track if user has existing agents (to show/hide the note about in-tab wizard)
|
||||
const [hasExistingAgents, setHasExistingAgents] = useState(false);
|
||||
|
||||
// SSH Remote configuration state
|
||||
// Initialize from wizard context if already set (e.g., when SSH was configured before opening wizard)
|
||||
const [sshRemotes, setSshRemotes] = useState<SshRemoteConfig[]>([]);
|
||||
@@ -492,6 +495,27 @@ export function AgentSelectionScreen({ theme }: AgentSelectionScreenProps): JSX.
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Check if user has existing agents on mount
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
async function checkExistingAgents() {
|
||||
try {
|
||||
const sessions = await window.maestro.sessions.getAll();
|
||||
if (mounted) {
|
||||
setHasExistingAgents(sessions.length > 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check existing agents:', error);
|
||||
}
|
||||
}
|
||||
checkExistingAgents();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Focus on mount - currently focus name field since only Claude is supported
|
||||
// TODO: When multiple agents are supported, focus the tiles instead
|
||||
useEffect(() => {
|
||||
@@ -1113,34 +1137,36 @@ export function AgentSelectionScreen({ theme }: AgentSelectionScreenProps): JSX.
|
||||
|
||||
</div>
|
||||
|
||||
{/* Section 2: Note box - centered in available space */}
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className="flex items-start gap-2.5 px-4 py-3 rounded-lg max-w-lg text-xs"
|
||||
style={{
|
||||
backgroundColor: theme.colors.accent + '15',
|
||||
border: `1px solid ${theme.colors.accent}30`,
|
||||
}}
|
||||
>
|
||||
<Info className="w-4 h-4 flex-shrink-0 mt-0.5" style={{ color: theme.colors.accent }} />
|
||||
<span style={{ color: theme.colors.textDim }}>
|
||||
<strong style={{ color: theme.colors.textMain }}>Note:</strong> The new agent wizard captures
|
||||
application inputs until complete. For a lighter touch, create a new agent then run{' '}
|
||||
<code
|
||||
className="px-1 py-0.5 rounded text-[11px]"
|
||||
style={{ backgroundColor: theme.colors.border }}
|
||||
>
|
||||
/wizard
|
||||
</code>
|
||||
{' '}or click the{' '}
|
||||
<Wand2
|
||||
className="inline w-3.5 h-3.5 align-text-bottom"
|
||||
style={{ color: theme.colors.accent }}
|
||||
/>
|
||||
{' '}button in the Auto Run panel. The in-tab wizard runs alongside your other work.
|
||||
</span>
|
||||
{/* Section 2: Note box - only shown if user has existing agents */}
|
||||
{hasExistingAgents && (
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className="flex items-start gap-2.5 px-4 py-3 rounded-lg max-w-lg text-xs"
|
||||
style={{
|
||||
backgroundColor: theme.colors.accent + '15',
|
||||
border: `1px solid ${theme.colors.accent}30`,
|
||||
}}
|
||||
>
|
||||
<Info className="w-4 h-4 flex-shrink-0 mt-0.5" style={{ color: theme.colors.accent }} />
|
||||
<span style={{ color: theme.colors.textDim }}>
|
||||
<strong style={{ color: theme.colors.textMain }}>Note:</strong> The new agent wizard captures
|
||||
application inputs until complete. For a lighter touch, create a new agent then run{' '}
|
||||
<code
|
||||
className="px-1 py-0.5 rounded text-[11px]"
|
||||
style={{ backgroundColor: theme.colors.border }}
|
||||
>
|
||||
/wizard
|
||||
</code>
|
||||
{' '}or click the{' '}
|
||||
<Wand2
|
||||
className="inline w-3.5 h-3.5 align-text-bottom"
|
||||
style={{ color: theme.colors.accent }}
|
||||
/>
|
||||
{' '}button in the Auto Run panel. The in-tab wizard runs alongside your other work.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Section 3: Agent Grid or Connection Error */}
|
||||
{sshConnectionError ? (
|
||||
|
||||
@@ -60,6 +60,10 @@ export interface SystemPromptConfig {
|
||||
existingDocs?: ExistingDocument[];
|
||||
/** Auto Run folder path (defaults to agentPath/Auto Run Docs if not provided) */
|
||||
autoRunFolderPath?: string;
|
||||
/** History file path for task recall (optional, enables AI to recall recent work) */
|
||||
historyFilePath?: string;
|
||||
/** Conductor profile (user's About Me from settings) */
|
||||
conductorProfile?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +116,7 @@ export const READY_CONFIDENCE_THRESHOLD = 80;
|
||||
* @returns The complete system prompt for the agent
|
||||
*/
|
||||
export function generateSystemPrompt(config: SystemPromptConfig): string {
|
||||
const { agentName, agentPath, existingDocs, autoRunFolderPath } = config;
|
||||
const { agentName, agentPath, existingDocs, autoRunFolderPath, historyFilePath, conductorProfile } = config;
|
||||
const projectName = agentName || 'this project';
|
||||
|
||||
// Default Auto Run folder to standard location under working directory
|
||||
@@ -140,6 +144,7 @@ export function generateSystemPrompt(config: SystemPromptConfig): string {
|
||||
|
||||
// Build template context for remaining variables (date/time, etc.)
|
||||
// Include autoRunFolderPath so {{AUTORUN_FOLDER}} is properly substituted
|
||||
// Include historyFilePath for {{AGENT_HISTORY_PATH}} task recall
|
||||
const templateContext: TemplateContext = {
|
||||
session: {
|
||||
id: 'wizard',
|
||||
@@ -150,6 +155,8 @@ export function generateSystemPrompt(config: SystemPromptConfig): string {
|
||||
autoRunFolderPath: effectiveAutoRunFolder,
|
||||
},
|
||||
autoRunFolder: effectiveAutoRunFolder,
|
||||
historyFilePath: historyFilePath,
|
||||
conductorProfile: conductorProfile,
|
||||
};
|
||||
|
||||
// Substitute any remaining template variables using the central function
|
||||
|
||||
@@ -72,6 +72,8 @@ export interface UseInputProcessingDeps {
|
||||
onSkillsCommand?: () => Promise<void>;
|
||||
/** Whether automatic tab naming is enabled */
|
||||
automaticTabNamingEnabled?: boolean;
|
||||
/** Conductor profile (user's About Me from settings) */
|
||||
conductorProfile?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,6 +130,7 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
isWizardActive,
|
||||
onSkillsCommand,
|
||||
automaticTabNamingEnabled,
|
||||
conductorProfile,
|
||||
} = deps;
|
||||
|
||||
// Ref for the processInput function so external code can access the latest version
|
||||
@@ -239,6 +242,7 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
substituteTemplateVariables(matchingCustomCommand.prompt, {
|
||||
session: activeSession,
|
||||
gitBranch,
|
||||
conductorProfile,
|
||||
});
|
||||
|
||||
// ALWAYS queue slash commands - they execute in order like write messages
|
||||
@@ -903,6 +907,7 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
session: freshSession,
|
||||
gitBranch,
|
||||
historyFilePath,
|
||||
conductorProfile,
|
||||
});
|
||||
|
||||
// Prepend system prompt to user's message
|
||||
|
||||
@@ -126,6 +126,10 @@ export interface UseSettingsReturn {
|
||||
// Loading state
|
||||
settingsLoaded: boolean;
|
||||
|
||||
// Conductor Profile (About Me)
|
||||
conductorProfile: string;
|
||||
setConductorProfile: (value: string) => void;
|
||||
|
||||
// LLM settings
|
||||
llmProvider: LLMProvider;
|
||||
modelSlug: string;
|
||||
@@ -360,6 +364,9 @@ export function useSettings(): UseSettingsReturn {
|
||||
// Loading state
|
||||
const [settingsLoaded, setSettingsLoaded] = useState(false);
|
||||
|
||||
// Conductor Profile (About Me) - optional, up to 1000 characters
|
||||
const [conductorProfile, setConductorProfileState] = useState('');
|
||||
|
||||
// LLM Config
|
||||
const [llmProvider, setLlmProviderState] = useState<LLMProvider>('openrouter');
|
||||
const [modelSlug, setModelSlugState] = useState('anthropic/claude-3.5-sonnet');
|
||||
@@ -517,6 +524,15 @@ export function useSettings(): UseSettingsReturn {
|
||||
|
||||
// Wrapper functions that persist to electron-store
|
||||
// PERF: All wrapped in useCallback to prevent re-renders
|
||||
|
||||
// Conductor Profile (About Me)
|
||||
const setConductorProfile = useCallback((value: string) => {
|
||||
// Enforce 1000 character limit
|
||||
const trimmed = value.slice(0, 1000);
|
||||
setConductorProfileState(trimmed);
|
||||
window.maestro.settings.set('conductorProfile', trimmed);
|
||||
}, []);
|
||||
|
||||
const setLlmProvider = useCallback((value: LLMProvider) => {
|
||||
setLlmProviderState(value);
|
||||
window.maestro.settings.set('llmProvider', value);
|
||||
@@ -1344,6 +1360,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
const allSettings = (await window.maestro.settings.getAll()) as Record<string, unknown>;
|
||||
|
||||
// Extract settings from the batch response
|
||||
const savedConductorProfile = allSettings['conductorProfile'];
|
||||
const savedEnterToSendAI = allSettings['enterToSendAI'];
|
||||
const savedEnterToSendTerminal = allSettings['enterToSendTerminal'];
|
||||
const savedDefaultSaveToHistory = allSettings['defaultSaveToHistory'];
|
||||
@@ -1411,6 +1428,9 @@ export function useSettings(): UseSettingsReturn {
|
||||
const savedFileTabAutoRefreshEnabled = allSettings['fileTabAutoRefreshEnabled'];
|
||||
const savedSuppressWindowsWarning = allSettings['suppressWindowsWarning'];
|
||||
|
||||
// Conductor Profile (About Me)
|
||||
if (savedConductorProfile !== undefined) setConductorProfileState(savedConductorProfile as string);
|
||||
|
||||
if (savedEnterToSendAI !== undefined) setEnterToSendAIState(savedEnterToSendAI as boolean);
|
||||
if (savedEnterToSendTerminal !== undefined)
|
||||
setEnterToSendTerminalState(savedEnterToSendTerminal as boolean);
|
||||
@@ -1814,6 +1834,8 @@ export function useSettings(): UseSettingsReturn {
|
||||
return useMemo(
|
||||
() => ({
|
||||
settingsLoaded,
|
||||
conductorProfile,
|
||||
setConductorProfile,
|
||||
llmProvider,
|
||||
modelSlug,
|
||||
apiKey,
|
||||
@@ -1963,6 +1985,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
[
|
||||
// State values
|
||||
settingsLoaded,
|
||||
conductorProfile,
|
||||
llmProvider,
|
||||
modelSlug,
|
||||
apiKey,
|
||||
@@ -2008,6 +2031,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
firstAutoRunCompleted,
|
||||
onboardingStats,
|
||||
// Setter functions (stable via useCallback)
|
||||
setConductorProfile,
|
||||
setLlmProvider,
|
||||
setModelSlug,
|
||||
setApiKey,
|
||||
|
||||
@@ -149,6 +149,10 @@ export interface InlineWizardState {
|
||||
remoteId: string | null;
|
||||
workingDirOverride?: string;
|
||||
};
|
||||
/** Conductor profile (user's About Me from settings) */
|
||||
conductorProfile?: string;
|
||||
/** History file path for task recall (fetched once during startWizard) */
|
||||
historyFilePath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,6 +210,7 @@ export interface UseInlineWizardReturn {
|
||||
* @param sessionId - The session ID for playbook creation
|
||||
* @param autoRunFolderPath - User-configured Auto Run folder path (if set, overrides default projectPath/Auto Run Docs)
|
||||
* @param sessionSshRemoteConfig - SSH remote configuration (for remote execution)
|
||||
* @param conductorProfile - Conductor profile (user's About Me from settings)
|
||||
*/
|
||||
startWizard: (
|
||||
naturalLanguageInput?: string,
|
||||
@@ -220,7 +225,8 @@ export interface UseInlineWizardReturn {
|
||||
enabled: boolean;
|
||||
remoteId: string | null;
|
||||
workingDirOverride?: string;
|
||||
}
|
||||
},
|
||||
conductorProfile?: string
|
||||
) => Promise<void>;
|
||||
/** End the wizard and restore previous UI state */
|
||||
endWizard: () => Promise<PreviousUIState | null>;
|
||||
@@ -467,7 +473,8 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
enabled: boolean;
|
||||
remoteId: string | null;
|
||||
workingDirOverride?: string;
|
||||
}
|
||||
},
|
||||
conductorProfile?: string
|
||||
): Promise<void> => {
|
||||
// Tab ID is required for per-tab wizard management
|
||||
const effectiveTabId = tabId || 'default';
|
||||
@@ -524,9 +531,23 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
subfolderPath: null,
|
||||
autoRunFolderPath: effectiveAutoRunFolderPath,
|
||||
sessionSshRemoteConfig,
|
||||
conductorProfile,
|
||||
}));
|
||||
|
||||
try {
|
||||
// Step 0: Fetch history file path for task recall (if session ID is available)
|
||||
// This is done early so it's available for both conversation session and state
|
||||
let historyFilePath: string | undefined;
|
||||
if (sessionId) {
|
||||
try {
|
||||
const fetchedPath = await window.maestro.history.getFilePath(sessionId);
|
||||
historyFilePath = fetchedPath ?? undefined; // Convert null to undefined
|
||||
} catch {
|
||||
// History file path not available - continue without it
|
||||
logger.debug('Could not fetch history file path', '[InlineWizard]', { sessionId });
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Check for existing Auto Run documents in the configured folder
|
||||
// Use the effective Auto Run folder path (user-configured or default)
|
||||
let hasExistingDocs = false;
|
||||
@@ -591,6 +612,7 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
supportedWizardAgents.includes(agentType) &&
|
||||
effectiveAutoRunFolderPath
|
||||
) {
|
||||
// historyFilePath was fetched in Step 0 above
|
||||
const session = startInlineWizardConversation({
|
||||
mode,
|
||||
agentType,
|
||||
@@ -600,6 +622,8 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
existingDocs: docsWithContent.length > 0 ? docsWithContent : undefined,
|
||||
autoRunFolderPath: effectiveAutoRunFolderPath,
|
||||
sessionSshRemoteConfig,
|
||||
conductorProfile,
|
||||
historyFilePath,
|
||||
});
|
||||
|
||||
// Store conversation session per-tab
|
||||
@@ -629,12 +653,14 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
}
|
||||
|
||||
// Update state with parsed results
|
||||
// Store historyFilePath so it's available for setMode if user is in 'ask' mode
|
||||
setTabState(effectiveTabId, (prev) => ({
|
||||
...prev,
|
||||
isInitializing: false,
|
||||
mode,
|
||||
goal,
|
||||
existingDocuments: existingDocs,
|
||||
historyFilePath,
|
||||
}));
|
||||
} catch (error) {
|
||||
// Handle any errors during initialization
|
||||
@@ -747,6 +773,7 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
effectiveAutoRunFolderPath
|
||||
) {
|
||||
console.log('[useInlineWizard] Auto-creating session for direct message in ask mode');
|
||||
// Use historyFilePath from state (fetched during startWizard)
|
||||
session = startInlineWizardConversation({
|
||||
mode: 'new',
|
||||
agentType: currentState.agentType,
|
||||
@@ -756,6 +783,8 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
existingDocs: undefined,
|
||||
autoRunFolderPath: effectiveAutoRunFolderPath,
|
||||
sessionSshRemoteConfig: currentState.sessionSshRemoteConfig,
|
||||
conductorProfile: currentState.conductorProfile,
|
||||
historyFilePath: currentState.historyFilePath,
|
||||
});
|
||||
conversationSessionsMap.current.set(tabId, session);
|
||||
// Update mode to 'new' since we're proceeding with a new plan
|
||||
@@ -928,6 +957,7 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
(currentState.projectPath ? getAutoRunFolderPath(currentState.projectPath) : null);
|
||||
|
||||
if (currentState.agentType && effectiveAutoRunFolderPath) {
|
||||
// Use historyFilePath from state (fetched during startWizard)
|
||||
const session = startInlineWizardConversation({
|
||||
mode: newMode,
|
||||
agentType: currentState.agentType,
|
||||
@@ -937,6 +967,8 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
existingDocs: undefined, // Will be loaded separately if needed
|
||||
autoRunFolderPath: effectiveAutoRunFolderPath,
|
||||
sessionSshRemoteConfig: currentState.sessionSshRemoteConfig,
|
||||
conductorProfile: currentState.conductorProfile,
|
||||
historyFilePath: currentState.historyFilePath,
|
||||
});
|
||||
|
||||
conversationSessionsMap.current.set(tabId, session);
|
||||
@@ -1191,6 +1223,7 @@ export function useInlineWizard(): UseInlineWizardReturn {
|
||||
autoRunFolderPath: effectiveAutoRunFolderPath,
|
||||
sessionId: currentState.sessionId || undefined,
|
||||
sessionSshRemoteConfig: currentState.sessionSshRemoteConfig,
|
||||
conductorProfile: currentState.conductorProfile,
|
||||
callbacks: {
|
||||
onStart: () => {
|
||||
console.log('[useInlineWizard] Document generation started');
|
||||
|
||||
@@ -96,6 +96,10 @@ export interface InlineWizardConversationConfig {
|
||||
remoteId: string | null;
|
||||
workingDirOverride?: string;
|
||||
};
|
||||
/** Conductor profile (user's About Me from settings) */
|
||||
conductorProfile?: string;
|
||||
/** History file path for task recall (optional, enables AI to recall recent work) */
|
||||
historyFilePath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,6 +217,7 @@ export function generateInlineWizardPrompt(config: InlineWizardConversationConfi
|
||||
}
|
||||
|
||||
// Build template context for remaining variables
|
||||
// Include historyFilePath for {{AGENT_HISTORY_PATH}} task recall
|
||||
const templateContext: TemplateContext = {
|
||||
session: {
|
||||
id: 'inline-wizard',
|
||||
@@ -223,6 +228,8 @@ export function generateInlineWizardPrompt(config: InlineWizardConversationConfi
|
||||
autoRunFolderPath: autoRunFolderPath,
|
||||
},
|
||||
autoRunFolder: autoRunFolderPath,
|
||||
conductorProfile: config.conductorProfile,
|
||||
historyFilePath: config.historyFilePath,
|
||||
};
|
||||
|
||||
// Substitute any remaining template variables
|
||||
|
||||
@@ -134,6 +134,8 @@ export interface DocumentGenerationConfig {
|
||||
remoteId: string | null;
|
||||
workingDirOverride?: string;
|
||||
};
|
||||
/** Conductor profile (user's About Me from settings) */
|
||||
conductorProfile?: string;
|
||||
/** Optional callbacks */
|
||||
callbacks?: DocumentGenerationCallbacks;
|
||||
}
|
||||
@@ -358,6 +360,7 @@ export function generateDocumentPrompt(
|
||||
cwd: directoryPath,
|
||||
fullPath: directoryPath,
|
||||
},
|
||||
conductorProfile: config.conductorProfile,
|
||||
};
|
||||
|
||||
// Substitute any remaining template variables
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
*
|
||||
* Available variables (case-insensitive):
|
||||
*
|
||||
* Conductor Variables (the Maestro user):
|
||||
* {{CONDUCTOR_PROFILE}} - User's About Me profile (from Settings → General)
|
||||
*
|
||||
* Agent Variables:
|
||||
* {{AGENT_NAME}} - Agent name
|
||||
* {{AGENT_PATH}} - Agent home directory path (full path to project)
|
||||
@@ -68,12 +71,15 @@ export interface TemplateContext {
|
||||
documentPath?: string;
|
||||
// History file path for task recall
|
||||
historyFilePath?: string;
|
||||
// Conductor profile (user's About Me from settings)
|
||||
conductorProfile?: string;
|
||||
}
|
||||
|
||||
// List of all available template variables for documentation (alphabetically sorted)
|
||||
// Variables marked as autoRunOnly are only shown in Auto Run contexts, not in AI Commands settings
|
||||
export const TEMPLATE_VARIABLES = [
|
||||
{ variable: '{{AGENT_GROUP}}', description: 'Agent group name' },
|
||||
{ variable: '{{CONDUCTOR_PROFILE}}', description: 'Conductor\'s About Me profile' },
|
||||
{ variable: '{{AGENT_HISTORY_PATH}}', description: 'History file path (task recall)' },
|
||||
{ variable: '{{AGENT_NAME}}', description: 'Agent name' },
|
||||
{ variable: '{{AGENT_PATH}}', description: 'Agent home directory path' },
|
||||
@@ -120,11 +126,15 @@ export function substituteTemplateVariables(template: string, context: TemplateC
|
||||
documentName,
|
||||
documentPath,
|
||||
historyFilePath,
|
||||
conductorProfile,
|
||||
} = context;
|
||||
const now = new Date();
|
||||
|
||||
// Build replacements map
|
||||
const replacements: Record<string, string> = {
|
||||
// Conductor variables (the Maestro user)
|
||||
CONDUCTOR_PROFILE: conductorProfile || '',
|
||||
|
||||
// Agent variables
|
||||
AGENT_NAME: session.name,
|
||||
AGENT_PATH: session.fullPath || session.projectRoot || session.cwd,
|
||||
|
||||
Reference in New Issue
Block a user