Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import { DebugWizardModal } from './components/DebugWizardModal';
import { DebugPackageModal } from './components/DebugPackageModal';
import { WindowsWarningModal } from './components/WindowsWarningModal';
import { GistPublishModal } from './components/GistPublishModal';
import { MaestroWizard, useWizard, WizardResumeModal } from './components/Wizard';
import {
MaestroWizard,
useWizard,
WizardResumeModal,
type SerializableWizardState,
} from './components/Wizard';
import { TourOverlay } from './components/Wizard/tour';
// CONDUCTOR_BADGES moved to useAutoRunAchievements hook
import { EmptyStateView } from './components/EmptyStateView';
Expand All @@ -42,6 +47,8 @@ const DirectorNotesModal = lazy(() =>
import('./components/DirectorNotes').then((m) => ({ default: m.DirectorNotesModal }))
);

import { captureException } from './utils/sentry';

// SymphonyContributionData type moved to useSymphonyContribution hook

// Group Chat Components
Expand Down Expand Up @@ -337,7 +344,7 @@ function MaestroConsoleInner() {
// --- WIZARD (onboarding wizard for new users) ---
const {
state: wizardState,
openWizard: openWizardModal,
openWizard: _baseOpenWizardModal,
restoreState: restoreWizardState,
loadResumeState: _loadResumeState,
clearResumeState,
Expand All @@ -346,6 +353,35 @@ function MaestroConsoleInner() {
goToStep: wizardGoToStep,
} = useWizard();

// Wrapper for openWizard that checks for resume state
const openWizardModal = useCallback(async () => {
try {
const saved = await window.maestro.settings.get('wizardResumeState');
// Validate saved state has a resumable step before casting
const resumableSteps = [
'directory-selection',
'conversation',
'preparing-plan',
'phase-review',
];
if (
saved &&
typeof saved === 'object' &&
'currentStep' in saved &&
typeof saved.currentStep === 'string' &&
resumableSteps.includes(saved.currentStep)
) {
useModalStore
.getState()
.openModal('wizardResume', { state: saved as SerializableWizardState });
return;
}
} catch (e) {
captureException(e, { extra: { context: 'openWizardModal', setting: 'wizardResumeState' } });
console.error('[App] Failed to check wizard resume state:', e);
}
_baseOpenWizardModal();
}, [_baseOpenWizardModal]);
// --- SETTINGS (from useSettings hook) ---
const settings = useSettings();
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -768,14 +768,18 @@ export function PreparingPlanScreen({ theme }: PreparingPlanScreenProps): JSX.El

// Save documents to disk in "Initiation" subfolder
setProgressMessage('Saving documents...');
const sshRemoteId = state.sessionSshRemoteConfig?.enabled
? (state.sessionSshRemoteConfig.remoteId ?? undefined)
: undefined;
const saveResult = await phaseGenerator.saveDocuments(
state.directoryPath,
genResult.documents,
(file) => {
// Add file to the created files list as it's saved
addCreatedFile(file);
},
'Initiation' // Save in Initiation subfolder
'Initiation', // Save in Initiation subfolder
sshRemoteId
);

if (saveResult.success) {
Expand Down Expand Up @@ -828,6 +832,7 @@ export function PreparingPlanScreen({ theme }: PreparingPlanScreenProps): JSX.El
state.directoryPath,
state.agentName,
state.conversationHistory,
state.sessionSshRemoteConfig,
setGeneratingDocuments,
setGeneratedDocuments,
setGenerationError,
Expand Down
37 changes: 29 additions & 8 deletions src/renderer/components/Wizard/services/phaseGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,9 @@ class PhaseGenerator {
// For SSH remote sessions, skip the availability check since we're executing remotely
// The agent detector checks for binaries locally, but we need to execute on the remote host
const isRemoteSession = config.sshRemoteConfig?.enabled && config.sshRemoteConfig?.remoteId;
const sshRemoteId = config.sshRemoteConfig?.enabled
? (config.sshRemoteConfig.remoteId ?? undefined)
: undefined;

if (!agent) {
wizardDebugLogger.log('error', 'Agent configuration not found', {
Expand Down Expand Up @@ -706,7 +709,7 @@ class PhaseGenerator {
if (!hasValidParsedDocs) {
callbacks?.onProgress?.('Checking for documents on disk...');
wizardDebugLogger.log('info', 'Checking for documents on disk (parsed docs invalid)');
const diskDocs = await this.readDocumentsFromDisk(config.directoryPath);
const diskDocs = await this.readDocumentsFromDisk(config.directoryPath, sshRemoteId);
if (diskDocs.length > 0) {
console.log('[PhaseGenerator] Found documents on disk:', diskDocs.length);
wizardDebugLogger.log('info', 'Found documents on disk', {
Expand Down Expand Up @@ -976,9 +979,14 @@ class PhaseGenerator {
subfolder: config.subfolder,
});

// Extract sshRemoteId for remote sessions
const sshRemoteId = config.sshRemoteConfig?.enabled
? (config.sshRemoteConfig.remoteId ?? undefined)
: undefined;

// Start watching the folder for file changes
window.maestro.autorun
.watchFolder(autoRunPath)
.watchFolder(autoRunPath, sshRemoteId)
.then((result) => {
if (result.success) {
console.log('[PhaseGenerator] Started watching folder:', autoRunPath);
Expand Down Expand Up @@ -1017,7 +1025,7 @@ class PhaseGenerator {
const readWithRetry = async (retries = 3, delayMs = 200): Promise<void> => {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const content = await window.maestro.fs.readFile(fullPath);
const content = await window.maestro.fs.readFile(fullPath, sshRemoteId);
if (content && typeof content === 'string' && content.length > 0) {
console.log(
'[PhaseGenerator] File read successful:',
Expand Down Expand Up @@ -1152,13 +1160,16 @@ class PhaseGenerator {
* This is a fallback for when the agent writes files directly
* instead of outputting them with markers.
*/
private async readDocumentsFromDisk(directoryPath: string): Promise<ParsedDocument[]> {
private async readDocumentsFromDisk(
directoryPath: string,
sshRemoteId?: string
): Promise<ParsedDocument[]> {
const autoRunPath = `${directoryPath}/${AUTO_RUN_FOLDER_NAME}`;
const documents: ParsedDocument[] = [];

try {
// List files in the Auto Run folder
const listResult = await window.maestro.autorun.listDocs(autoRunPath);
const listResult = await window.maestro.autorun.listDocs(autoRunPath, sshRemoteId);
if (!listResult.success || !listResult.files) {
return [];
}
Expand All @@ -1169,7 +1180,11 @@ class PhaseGenerator {
for (const fileBaseName of listResult.files) {
const filename = fileBaseName.endsWith('.md') ? fileBaseName : `${fileBaseName}.md`;

const readResult = await window.maestro.autorun.readDoc(autoRunPath, fileBaseName);
const readResult = await window.maestro.autorun.readDoc(
autoRunPath,
fileBaseName,
sshRemoteId
);
if (readResult.success && readResult.content) {
// Extract phase number from filename
const phaseMatch = filename.match(/Phase-(\d+)/i);
Expand Down Expand Up @@ -1225,7 +1240,8 @@ class PhaseGenerator {
directoryPath: string,
documents: GeneratedDocument[],
onFileCreated?: (file: CreatedFileInfo) => void,
subfolder?: string
subfolder?: string,
sshRemoteId?: string
): Promise<{ success: boolean; savedPaths: string[]; error?: string; subfolderPath?: string }> {
const baseAutoRunPath = `${directoryPath}/${AUTO_RUN_FOLDER_NAME}`;
const autoRunPath = subfolder ? `${baseAutoRunPath}/${subfolder}` : baseAutoRunPath;
Expand All @@ -1242,7 +1258,12 @@ class PhaseGenerator {
console.log('[PhaseGenerator] Saving document:', filename);

// Write the document (autorun:writeDoc creates the folder if needed)
const result = await window.maestro.autorun.writeDoc(autoRunPath, filename, doc.content);
const result = await window.maestro.autorun.writeDoc(
autoRunPath,
filename,
doc.content,
sshRemoteId
);

if (result.success) {
const fullPath = `${autoRunPath}/${filename}`;
Expand Down
25 changes: 16 additions & 9 deletions src/renderer/services/inlineWizardDocumentGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ async function saveDocument(
autoRunFolderPath,
filename,
doc.content,
sshRemoteId || undefined
sshRemoteId
);

if (!result.success) {
Expand Down Expand Up @@ -721,7 +721,7 @@ export async function generateInlineDocuments(
// Create a date-prefixed subfolder name: "YYYY-MM-DD-Feature-Name" (with -2, -3, etc. if needed)
const baseFolderName = generateWizardFolderBaseName(projectName);
const sshRemoteId = config.sessionSshRemoteConfig?.enabled
? config.sessionSshRemoteConfig.remoteId
? (config.sessionSshRemoteConfig.remoteId ?? undefined)
: undefined;

// Only attempt to check existing folders if we're local OR if listDocs supports remote
Expand Down Expand Up @@ -843,7 +843,7 @@ export async function generateInlineDocuments(
// Set up file watcher for real-time document streaming
// The agent writes files directly, and we detect them here
window.maestro.autorun
.watchFolder(subfolderPath)
.watchFolder(subfolderPath, sshRemoteId)
.then((watchResult) => {
if (watchResult.success) {
console.log('[InlineWizardDocGen] Started watching folder:', subfolderPath);
Expand Down Expand Up @@ -871,7 +871,7 @@ export async function generateInlineDocuments(
const readWithRetry = async (retries = 3, delayMs = 200): Promise<void> => {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const content = await window.maestro.fs.readFile(fullPath);
const content = await window.maestro.fs.readFile(fullPath, sshRemoteId);
if (content && typeof content === 'string' && content.length > 0) {
console.log(
'[InlineWizardDocGen] File read successful:',
Expand Down Expand Up @@ -1096,7 +1096,7 @@ export async function generateInlineDocuments(
if (documents.length === 0 || totalTasks === 0) {
// Check for files on disk (agent may have written directly)
callbacks?.onProgress?.('Checking for documents on disk...');
const diskDocs = await readDocumentsFromDisk(subfolderPath);
const diskDocs = await readDocumentsFromDisk(subfolderPath, sshRemoteId);
if (diskDocs.length > 0) {
console.log('[InlineWizardDocGen] Found documents on disk:', diskDocs.length);
documents = diskDocs;
Expand All @@ -1113,7 +1113,7 @@ export async function generateInlineDocuments(
const savedDocuments: InlineGeneratedDocument[] = [];
for (const doc of documents) {
try {
const savedDoc = await saveDocument(subfolderPath, doc, sshRemoteId || undefined);
const savedDoc = await saveDocument(subfolderPath, doc, sshRemoteId);
savedDocuments.push(savedDoc);
callbacks?.onDocumentComplete?.(savedDoc);
} catch (error) {
Expand Down Expand Up @@ -1239,12 +1239,15 @@ async function createPlaybookForDocuments(
* Note: Documents read from disk are treated as new (isUpdate: false)
* since they were written directly by the agent.
*/
async function readDocumentsFromDisk(autoRunFolderPath: string): Promise<ParsedDocument[]> {
async function readDocumentsFromDisk(
autoRunFolderPath: string,
sshRemoteId?: string
): Promise<ParsedDocument[]> {
const documents: ParsedDocument[] = [];

try {
// List files in the Auto Run folder
const listResult = await window.maestro.autorun.listDocs(autoRunFolderPath);
const listResult = await window.maestro.autorun.listDocs(autoRunFolderPath, sshRemoteId);
if (!listResult.success || !listResult.files) {
return [];
}
Expand All @@ -1254,7 +1257,11 @@ async function readDocumentsFromDisk(autoRunFolderPath: string): Promise<ParsedD
for (const fileBaseName of listResult.files) {
const filename = fileBaseName.endsWith('.md') ? fileBaseName : `${fileBaseName}.md`;

const readResult = await window.maestro.autorun.readDoc(autoRunFolderPath, fileBaseName);
const readResult = await window.maestro.autorun.readDoc(
autoRunFolderPath,
fileBaseName,
sshRemoteId
);
if (readResult.success && readResult.content) {
// Extract phase number from filename
const phaseMatch = filename.match(/Phase-(\d+)/i);
Expand Down