From 2711ed4031f552d97922ccd07e1bc83cb655ed3a Mon Sep 17 00:00:00 2001 From: Nick Roth Date: Fri, 13 Mar 2026 07:43:51 -0500 Subject: [PATCH 1/6] fix: add SSH remote support for wizard file operations --- src/renderer/App.tsx | 29 +++++++++++++++++-- .../Wizard/screens/PreparingPlanScreen.tsx | 3 ++ .../Wizard/services/phaseGenerator.ts | 17 +++++++++-- .../inlineWizardDocumentGeneration.ts | 2 +- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 667edde5c0..b5473c642b 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -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'; @@ -337,7 +342,7 @@ function MaestroConsoleInner() { // --- WIZARD (onboarding wizard for new users) --- const { state: wizardState, - openWizard: openWizardModal, + openWizard: _baseOpenWizardModal, restoreState: restoreWizardState, loadResumeState: _loadResumeState, clearResumeState, @@ -346,6 +351,26 @@ 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'); + if ( + saved && + typeof saved === 'object' && + 'currentStep' in saved && + saved.currentStep !== 'agent-selection' + ) { + useModalStore + .getState() + .openModal('wizardResume', { state: saved as SerializableWizardState }); + return; + } + } catch (e) { + console.error('[App] Failed to check wizard resume state:', e); + } + _baseOpenWizardModal(); + }, [_baseOpenWizardModal]); // --- SETTINGS (from useSettings hook) --- const settings = useSettings(); const { diff --git a/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx b/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx index 5af28810b0..c6ab2dc34c 100644 --- a/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx +++ b/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx @@ -768,6 +768,9 @@ 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, diff --git a/src/renderer/components/Wizard/services/phaseGenerator.ts b/src/renderer/components/Wizard/services/phaseGenerator.ts index f7fac0d6c1..c0898d0f8f 100644 --- a/src/renderer/components/Wizard/services/phaseGenerator.ts +++ b/src/renderer/components/Wizard/services/phaseGenerator.ts @@ -976,9 +976,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); @@ -1225,7 +1230,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; @@ -1242,7 +1248,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}`; diff --git a/src/renderer/services/inlineWizardDocumentGeneration.ts b/src/renderer/services/inlineWizardDocumentGeneration.ts index 5f84ab8e52..cbe41450c1 100644 --- a/src/renderer/services/inlineWizardDocumentGeneration.ts +++ b/src/renderer/services/inlineWizardDocumentGeneration.ts @@ -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 || undefined) .then((watchResult) => { if (watchResult.success) { console.log('[InlineWizardDocGen] Started watching folder:', subfolderPath); From 529565fabffbb4d359b5deef4608eaba0ba9be46 Mon Sep 17 00:00:00 2001 From: Nick Roth Date: Fri, 13 Mar 2026 07:44:35 -0500 Subject: [PATCH 2/6] Fix: Pass sshRemoteId to saveDocuments() and use consistent null coercion Addresses Greptile review comments: - PreparingPlanScreen.tsx: Pass sshRemoteId as 5th parameter to phaseGenerator.saveDocuments() - inlineWizardDocumentGeneration.ts: Use ?? undefined at extraction time, remove || undefined at call sites --- .../components/Wizard/screens/PreparingPlanScreen.tsx | 3 ++- src/renderer/services/inlineWizardDocumentGeneration.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx b/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx index c6ab2dc34c..b6ed1dcdb0 100644 --- a/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx +++ b/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx @@ -778,7 +778,8 @@ export function PreparingPlanScreen({ theme }: PreparingPlanScreenProps): JSX.El // 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) { diff --git a/src/renderer/services/inlineWizardDocumentGeneration.ts b/src/renderer/services/inlineWizardDocumentGeneration.ts index cbe41450c1..3c0dfc3a28 100644 --- a/src/renderer/services/inlineWizardDocumentGeneration.ts +++ b/src/renderer/services/inlineWizardDocumentGeneration.ts @@ -678,7 +678,7 @@ async function saveDocument( autoRunFolderPath, filename, doc.content, - sshRemoteId || undefined + sshRemoteId ); if (!result.success) { @@ -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 @@ -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, sshRemoteId || undefined) + .watchFolder(subfolderPath, sshRemoteId) .then((watchResult) => { if (watchResult.success) { console.log('[InlineWizardDocGen] Started watching folder:', subfolderPath); @@ -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) { From 4b14cf90c6e41bef3206e96273f0f0c9f81bc54a Mon Sep 17 00:00:00 2001 From: Nick Roth Date: Fri, 13 Mar 2026 07:45:24 -0500 Subject: [PATCH 3/6] Address PR review comments - Add Sentry captureException to openWizardModal error handler (App.tsx) - Greptile fixes already committed: sshRemoteId parameter passing and ?? undefined pattern --- src/renderer/App.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index b5473c642b..482cc98673 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -47,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 @@ -367,6 +369,7 @@ function MaestroConsoleInner() { return; } } catch (e) { + captureException(e, { extra: { context: 'openWizardModal', setting: 'wizardResumeState' } }); console.error('[App] Failed to check wizard resume state:', e); } _baseOpenWizardModal(); From bc501e3df356d7dc854b226d609c47c92171bba4 Mon Sep 17 00:00:00 2001 From: Nick Roth Date: Fri, 13 Mar 2026 07:45:59 -0500 Subject: [PATCH 4/6] Fix: Strengthen runtime validation for wizard resume state - Add explicit validation that currentStep is a known resumable step - Check typeof saved.currentStep === 'string' before includes() check - Validates against known steps: directory-selection, conversation, preparing-plan, phase-review --- src/renderer/App.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 482cc98673..c13ee75b29 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -357,11 +357,19 @@ function MaestroConsoleInner() { 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 && - saved.currentStep !== 'agent-selection' + typeof saved.currentStep === 'string' && + resumableSteps.includes(saved.currentStep) ) { useModalStore .getState() From 88bfa495caa72362900cd13f5475eb6c06187598 Mon Sep 17 00:00:00 2001 From: Nick Roth Date: Fri, 13 Mar 2026 08:38:28 -0500 Subject: [PATCH 5/6] Fix: Address remaining CodeRabbit review comments - phaseGenerator.ts: Pass sshRemoteId to fs.readFile in watcher callback (line 1025) - PreparingPlanScreen.tsx: Add state.sessionSshRemoteConfig to useCallback dependency array --- src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx | 1 + src/renderer/components/Wizard/services/phaseGenerator.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx b/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx index b6ed1dcdb0..412f3a88da 100644 --- a/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx +++ b/src/renderer/components/Wizard/screens/PreparingPlanScreen.tsx @@ -832,6 +832,7 @@ export function PreparingPlanScreen({ theme }: PreparingPlanScreenProps): JSX.El state.directoryPath, state.agentName, state.conversationHistory, + state.sessionSshRemoteConfig, setGeneratingDocuments, setGeneratedDocuments, setGenerationError, diff --git a/src/renderer/components/Wizard/services/phaseGenerator.ts b/src/renderer/components/Wizard/services/phaseGenerator.ts index c0898d0f8f..16c39c49ea 100644 --- a/src/renderer/components/Wizard/services/phaseGenerator.ts +++ b/src/renderer/components/Wizard/services/phaseGenerator.ts @@ -1022,7 +1022,7 @@ class PhaseGenerator { const readWithRetry = async (retries = 3, delayMs = 200): Promise => { 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:', From ff0b07218023e797bac367e2d3e6700cd3634453 Mon Sep 17 00:00:00 2001 From: Nick Roth Date: Fri, 13 Mar 2026 11:26:14 -0500 Subject: [PATCH 6/6] Fix: Complete SSH remote support for disk fallback and watcher paths Addresses CodeRabbit review comments: - phaseGenerator.ts: Pass sshRemoteId through readDocumentsFromDisk, listDocs, and readDoc calls - inlineWizardDocumentGeneration.ts: Pass sshRemoteId through readFile, readDocumentsFromDisk, listDocs, and readDoc calls - Ensures SSH context is preserved in all file operations for remote sessions --- .../Wizard/services/phaseGenerator.ts | 18 ++++++++++++++---- .../services/inlineWizardDocumentGeneration.ts | 17 ++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/renderer/components/Wizard/services/phaseGenerator.ts b/src/renderer/components/Wizard/services/phaseGenerator.ts index 16c39c49ea..3b69372037 100644 --- a/src/renderer/components/Wizard/services/phaseGenerator.ts +++ b/src/renderer/components/Wizard/services/phaseGenerator.ts @@ -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', { @@ -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', { @@ -1157,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 { + private async readDocumentsFromDisk( + directoryPath: string, + sshRemoteId?: string + ): Promise { 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 []; } @@ -1174,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); diff --git a/src/renderer/services/inlineWizardDocumentGeneration.ts b/src/renderer/services/inlineWizardDocumentGeneration.ts index 3c0dfc3a28..5bb53b1d06 100644 --- a/src/renderer/services/inlineWizardDocumentGeneration.ts +++ b/src/renderer/services/inlineWizardDocumentGeneration.ts @@ -871,7 +871,7 @@ export async function generateInlineDocuments( const readWithRetry = async (retries = 3, delayMs = 200): Promise => { 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:', @@ -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; @@ -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 { +async function readDocumentsFromDisk( + autoRunFolderPath: string, + sshRemoteId?: string +): Promise { 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 []; } @@ -1254,7 +1257,11 @@ async function readDocumentsFromDisk(autoRunFolderPath: string): Promise