## CHANGES

- Introduced explicit read/write access rules: Auto Run writes only! 🔒
- Updated all wizard prompt templates with clearer safety constraints 🧭
- Refined tests to validate new “WRITE Limited / READ Unrestricted” wording 🧪
- Synced inline wizard context into `session.wizardState` for stable UI 🧷
- Scoped inline wizard to the tab it started on for isolation 🧩
- Auto-sent an initial wizard greeting to kickstart discovery instantly 🚀
- Added “Exit wizard” support (Escape/pill) via new `onExitWizard` callback 🚪
- Fixed wizard loading indicator to use `wizardState.isWaiting` accurately 
- Restricted wizard conversation tools to read-only operations for safety 📚
- Deduplicated parsing by reusing shared structured-output + confidence utilities 🧰
This commit is contained in:
Pedram Amini
2026-01-02 14:39:49 -06:00
parent 478759c41c
commit a8be0dc88a
13 changed files with 332 additions and 177 deletions

View File

@@ -528,14 +528,17 @@ describe('wizardPrompts', () => {
expect(prompt).toContain('Maestro');
});
it('should include directory restriction instructions', () => {
it('should include file access restriction instructions', () => {
const config: SystemPromptConfig = {
agentName: 'Test',
agentPath: '/specific/path',
};
const prompt = generateSystemPrompt(config);
expect(prompt).toContain('ONLY create or modify files within this directory');
// Check for the new file access restriction format
expect(prompt).toContain('WRITE ACCESS (Limited)');
expect(prompt).toContain('READ ACCESS (Unrestricted)');
expect(prompt).toContain('ONLY create or modify files in the Auto Run folder');
expect(prompt).toContain('/specific/path');
});

View File

@@ -4,10 +4,20 @@ You are an expert project planner creating actionable task documents for "{{PROJ
Based on the project discovery conversation below, create a series of Auto Run documents that will guide an AI coding assistant through building this project step by step.
## Working Directory
## File Access Restrictions
All files will be created in: {{DIRECTORY_PATH}}
The documents will be saved to: {{DIRECTORY_PATH}}/{{AUTO_RUN_FOLDER_NAME}}/
**WRITE ACCESS (Limited):**
You may ONLY create files in the Auto Run folder:
`{{DIRECTORY_PATH}}/{{AUTO_RUN_FOLDER_NAME}}/`
Do NOT write, create, or modify files anywhere else. The wizard outputs documents using special markers (---BEGIN DOCUMENT--- / ---END DOCUMENT---) which are then saved by the application to the Auto Run folder.
**READ ACCESS (Unrestricted):**
You may READ files from anywhere to inform your planning:
- Read any file in: `{{DIRECTORY_PATH}}`
- Examine project structure, code, and configuration
This restriction ensures the wizard can safely run in parallel with other AI operations.
## Critical Requirements for Phase 1

View File

@@ -4,10 +4,20 @@ You are an expert project planner creating actionable task documents for "{{PROJ
Based on the project discovery conversation below, create or update Auto Run documents. The user has existing documents and wants to extend or modify their plans.
## Working Directory
## File Access Restrictions
All files will be created or updated in: {{DIRECTORY_PATH}}
The documents folder: {{DIRECTORY_PATH}}/{{AUTO_RUN_FOLDER_NAME}}/
**WRITE ACCESS (Limited):**
You may ONLY create or update files in the Auto Run folder:
`{{DIRECTORY_PATH}}/{{AUTO_RUN_FOLDER_NAME}}/`
Do NOT write, create, or modify files anywhere else. The wizard outputs documents using special markers (---BEGIN DOCUMENT--- / ---END DOCUMENT---) which are then saved by the application to the Auto Run folder.
**READ ACCESS (Unrestricted):**
You may READ files from anywhere to inform your planning:
- Read any file in: `{{DIRECTORY_PATH}}`
- Examine project structure, code, and configuration
This restriction ensures the wizard can safely run in parallel with other AI operations.
## Existing Documents

View File

@@ -4,12 +4,24 @@ You are a planning assistant helping extend existing work in "{{PROJECT_NAME}}".
You are helping iterate on existing plans. The user has Auto Run documents and wants to extend or modify them.
## Working Directory
## File Access Restrictions
You will ONLY create or modify files within this directory:
{{AGENT_PATH}}
**WRITE ACCESS (Limited):**
You may ONLY create or modify files in the Auto Run folder:
`{{AUTORUN_FOLDER}}`
Do not reference, create, or modify files outside this path.
Do NOT write, create, or modify files anywhere else. This includes:
- No creating files in the working directory
- No modifying existing project files
- No creating temporary files outside the Auto Run folder
**READ ACCESS (Unrestricted):**
You may READ files from anywhere to understand the project:
- Read any file in the working directory: `{{AGENT_PATH}}`
- Read any file the user references
- Examine project structure, code, and configuration
This restriction ensures the wizard can safely run in parallel with other AI operations without file conflicts.
## Existing Documents

View File

@@ -4,12 +4,24 @@ You are a planning assistant starting fresh in an existing Maestro session for "
You are helping create a new action plan in an active session. The user has an established project but wants to start fresh with a new plan.
## Working Directory
## File Access Restrictions
You will ONLY create or modify files within this directory:
{{AGENT_PATH}}
**WRITE ACCESS (Limited):**
You may ONLY create or modify files in the Auto Run folder:
`{{AUTORUN_FOLDER}}`
Do not reference, create, or modify files outside this path.
Do NOT write, create, or modify files anywhere else. This includes:
- No creating files in the working directory
- No modifying existing project files
- No creating temporary files outside the Auto Run folder
**READ ACCESS (Unrestricted):**
You may READ files from anywhere to understand the project:
- Read any file in the working directory: `{{AGENT_PATH}}`
- Read any file the user references
- Examine project structure, code, and configuration
This restriction ensures the wizard can safely run in parallel with other AI operations without file conflicts.
## Auto-run Documents

View File

@@ -4,12 +4,24 @@ You are a planning assistant helping in an existing Maestro session for "{{PROJE
You are helping plan work in an active session. The user has an established project context and wants to create or extend an action plan.
## Working Directory
## File Access Restrictions
You will ONLY create or modify files within this directory:
{{AGENT_PATH}}
**WRITE ACCESS (Limited):**
You may ONLY create or modify files in the Auto Run folder:
`{{AUTORUN_FOLDER}}`
Do not reference, create, or modify files outside this path.
Do NOT write, create, or modify files anywhere else. This includes:
- No creating files in the working directory
- No modifying existing project files
- No creating temporary files outside the Auto Run folder
**READ ACCESS (Unrestricted):**
You may READ files from anywhere to understand the project:
- Read any file in the working directory: `{{AGENT_PATH}}`
- Read any file the user references
- Examine project structure, code, and configuration
This restriction ensures the wizard can safely run in parallel with other AI operations without file conflicts.
## Auto-run Documents

View File

@@ -4,12 +4,24 @@ You are a friendly project discovery assistant helping to set up "{{PROJECT_NAME
You are 🎼 Maestro's onboarding assistant, helping the user define their project so we can create an actionable plan.
## Working Directory
## File Access Restrictions
You will ONLY create or modify files within this directory:
{{AGENT_PATH}}
**WRITE ACCESS (Limited):**
You may ONLY create or modify files in the Auto Run folder:
`{{AUTORUN_FOLDER}}`
Do not reference, create, or modify files outside this path, **except** for the Auto Run folder which may be located elsewhere.
Do NOT write, create, or modify files anywhere else. This includes:
- No creating files in the working directory
- No modifying existing project files
- No creating temporary files outside the Auto Run folder
**READ ACCESS (Unrestricted):**
You may READ files from anywhere to understand the project:
- Read any file in the working directory: `{{AGENT_PATH}}`
- Read any file the user references
- Examine project structure, code, and configuration
This restriction ensures the wizard can safely run in parallel with other AI operations without file conflicts.
## Auto-run Documents

View File

@@ -34,7 +34,6 @@ import { GroupChatRightPanel, type GroupChatRightTab } from './components/GroupC
import {
// Batch processing
useBatchProcessor,
useInlineWizard,
type PreviousUIState,
// Settings
useSettings,
@@ -89,7 +88,7 @@ import { InputProvider, useInputContext } from './contexts/InputContext';
import { GroupChatProvider, useGroupChat } from './contexts/GroupChatContext';
import { AutoRunProvider, useAutoRun } from './contexts/AutoRunContext';
import { SessionProvider, useSession } from './contexts/SessionContext';
import { InlineWizardProvider } from './contexts/InlineWizardContext';
import { InlineWizardProvider, useInlineWizardContext } from './contexts/InlineWizardContext';
import { ToastContainer } from './components/Toast';
// Import services
@@ -4239,13 +4238,127 @@ You are taking over this conversation. Based on the context above, provide a bri
return activeSession ? getBatchState(activeSession.id) : getBatchState('');
}, [activeBatchSessionIds, activeSession, getBatchState]);
// Inline wizard hook for /wizard command
// Inline wizard context for /wizard command
// This manages the state for the inline wizard that creates/iterates on Auto Run documents
const {
startWizard: startInlineWizard,
endWizard: endInlineWizard,
clearError: clearInlineWizardError,
retryLastMessage: retryInlineWizardMessage,
} = useInlineWizard();
generateDocuments: generateInlineWizardDocuments,
// State for syncing to session.wizardState
isWizardActive: inlineWizardActive,
isWaiting: inlineWizardIsWaiting,
wizardMode: inlineWizardMode,
wizardGoal: inlineWizardGoal,
confidence: inlineWizardConfidence,
ready: inlineWizardReady,
conversationHistory: inlineWizardConversationHistory,
error: inlineWizardError,
isGeneratingDocs: inlineWizardIsGeneratingDocs,
generatedDocuments: inlineWizardGeneratedDocuments,
streamingContent: inlineWizardStreamingContent,
generationProgress: inlineWizardGenerationProgress,
state: inlineWizardState,
wizardTabId: inlineWizardTabId,
} = useInlineWizardContext();
// Sync inline wizard context state to session.wizardState
// This bridges the gap between the context-based state and session-based UI rendering
// The wizard is per-tab, so only sync when the active tab matches the wizard's tab
useEffect(() => {
if (!activeSession) return;
const activeTab = getActiveTab(activeSession);
const activeTabId = activeTab?.id;
// Only sync if the active tab is the wizard's tab
const isWizardTab = inlineWizardTabId && activeTabId === inlineWizardTabId;
const currentWizardState = activeSession.wizardState;
const shouldHaveWizardState = isWizardTab && (inlineWizardActive || inlineWizardIsGeneratingDocs);
if (!shouldHaveWizardState && !currentWizardState) {
// Neither active nor has state - nothing to do
return;
}
if (!shouldHaveWizardState && currentWizardState) {
// Wizard was deactivated or we switched to a different tab - clear the state
setSessions(prev => prev.map(s =>
s.id === activeSession.id
? { ...s, wizardState: undefined }
: s
));
return;
}
if (!isWizardTab) {
// Not the wizard's tab - don't sync
return;
}
// Sync the wizard state to the session
const newWizardState = {
isActive: inlineWizardActive,
isWaiting: inlineWizardIsWaiting,
mode: inlineWizardMode === 'ask' ? 'new' : inlineWizardMode, // Map 'ask' to 'new' for session state
goal: inlineWizardGoal ?? undefined,
confidence: inlineWizardConfidence,
ready: inlineWizardReady,
conversationHistory: inlineWizardConversationHistory.map(msg => ({
id: msg.id,
role: msg.role,
content: msg.content,
timestamp: msg.timestamp,
confidence: msg.confidence,
ready: msg.ready,
})),
previousUIState: inlineWizardState.previousUIState ?? {
readOnlyMode: false,
saveToHistory: true,
showThinking: false,
},
error: inlineWizardError,
isGeneratingDocs: inlineWizardIsGeneratingDocs,
generatedDocuments: inlineWizardGeneratedDocuments.map(doc => ({
filename: doc.filename,
content: doc.content,
taskCount: doc.taskCount,
savedPath: doc.savedPath,
})),
streamingContent: inlineWizardStreamingContent,
currentGeneratingIndex: inlineWizardGenerationProgress?.current,
totalDocuments: inlineWizardGenerationProgress?.total,
autoRunFolderPath: inlineWizardState.projectPath
? `${inlineWizardState.projectPath}/Auto Run Docs`
: undefined,
};
setSessions(prev => prev.map(s =>
s.id === activeSession.id
? { ...s, wizardState: newWizardState }
: s
));
}, [
activeSession?.id,
activeSession?.activeTabId,
inlineWizardTabId,
inlineWizardActive,
inlineWizardIsWaiting,
inlineWizardMode,
inlineWizardGoal,
inlineWizardConfidence,
inlineWizardReady,
inlineWizardConversationHistory,
inlineWizardError,
inlineWizardIsGeneratingDocs,
inlineWizardGeneratedDocuments,
inlineWizardStreamingContent,
inlineWizardGenerationProgress,
inlineWizardState.previousUIState,
inlineWizardState.projectPath,
setSessions,
]);
// Handler for the built-in /history command
// Requests a synopsis from the current agent session and saves to history
@@ -4424,7 +4537,14 @@ You are taking over this conversation. Based on the context above, provide a bri
// Start the inline wizard with the argument text (natural language input)
// The wizard will use the intent parser to determine mode (new/iterate/ask)
startInlineWizard(args || undefined, currentUIState);
startInlineWizard(
args || undefined,
currentUIState,
activeSession.projectRoot || activeSession.cwd, // Project path for Auto Run folder detection
activeSession.toolType, // Agent type for AI conversation
activeSession.name, // Session/project name
activeTab.id // Tab ID for per-tab isolation
);
// Show a system log entry indicating wizard started
const wizardLog: LogEntry = {
@@ -10048,9 +10168,12 @@ You are taking over this conversation. Based on the context above, provide a bri
// Refresh the Auto Run panel to show newly generated documents
handleAutoRunRefresh();
}}
// Inline wizard error handling callbacks
// Inline wizard callbacks
onWizardLetsGo={generateInlineWizardDocuments}
onWizardRetry={retryInlineWizardMessage}
onWizardClearError={clearInlineWizardError}
// Inline wizard exit handler (for WizardInputPanel)
onExitWizard={endInlineWizard}
/>
)}

View File

@@ -245,6 +245,8 @@ interface MainPanelProps {
onWizardRetry?: () => void;
/** Called when user dismisses an error in the wizard */
onWizardClearError?: () => void;
/** Called when user exits inline wizard mode (Escape or clicks pill) */
onExitWizard?: () => void;
}
// PERFORMANCE: Wrap with React.memo to prevent re-renders when parent (App.tsx) re-renders
@@ -296,6 +298,8 @@ export const MainPanel = React.memo(forwardRef<MainPanelHandle, MainPanelProps>(
mergeSourceName,
mergeTargetName,
onCancelMerge,
// Inline wizard exit handler
onExitWizard,
} = props;
// isCurrentSessionAutoMode: THIS session has active batch run (for all UI indicators)
@@ -1139,7 +1143,7 @@ export const MainPanel = React.memo(forwardRef<MainPanelHandle, MainPanelProps>(
key={`wizard-${activeSession.id}-${activeSession.activeTabId}`}
theme={theme}
conversationHistory={activeSession.wizardState.conversationHistory}
isLoading={activeSession.state === 'busy'}
isLoading={activeSession.wizardState.isWaiting ?? false}
agentName={activeSession.name}
confidence={activeSession.wizardState.confidence}
ready={activeSession.wizardState.ready}
@@ -1275,6 +1279,8 @@ export const MainPanel = React.memo(forwardRef<MainPanelHandle, MainPanelProps>(
mergeSourceName={mergeSourceName}
mergeTargetName={mergeTargetName}
onCancelMerge={onCancelMerge}
// Inline wizard mode
onExitWizard={onExitWizard}
/>
</div>
)}

View File

@@ -112,6 +112,7 @@ export function InlineWizardProvider({ children }: InlineWizardProviderProps) {
wizardState.generateDocuments,
wizardState.streamingContent,
wizardState.generationProgress,
wizardState.wizardTabId,
]);
return (

View File

@@ -9,7 +9,7 @@
* runs inline within the existing AI conversation interface.
*/
import { useState, useCallback, useRef } from 'react';
import { useState, useCallback, useRef, useEffect } from 'react';
import { parseWizardIntent } from '../services/wizardIntentParser';
import {
hasExistingAutoRunDocs,
@@ -123,6 +123,10 @@ export interface InlineWizardState {
agentType: ToolType | null;
/** Session name/project name */
sessionName: string | null;
/** Tab ID the wizard was started on (for per-tab isolation) */
tabId: string | null;
/** Whether the initial greeting has been sent to kick off the conversation */
initialGreetingSent: boolean;
/** Streaming content being generated (accumulates as AI outputs) */
streamingContent: string;
/** Progress tracking for document generation */
@@ -163,6 +167,8 @@ export interface UseInlineWizardReturn {
streamingContent: string;
/** Progress tracking for document generation (e.g., "Phase 1 of 3") */
generationProgress: GenerationProgress | null;
/** Tab ID the wizard was started on (for per-tab isolation) */
wizardTabId: string | null;
/** Full wizard state */
state: InlineWizardState;
/**
@@ -172,13 +178,15 @@ export interface UseInlineWizardReturn {
* @param projectPath - Project path to check for existing Auto Run documents
* @param agentType - The AI agent type to use for conversation
* @param sessionName - The session name (used as project name)
* @param tabId - The tab ID to associate the wizard with
*/
startWizard: (
naturalLanguageInput?: string,
currentUIState?: PreviousUIState,
projectPath?: string,
agentType?: ToolType,
sessionName?: string
sessionName?: string,
tabId?: string
) => Promise<void>;
/** End the wizard and restore previous UI state */
endWizard: () => Promise<PreviousUIState | null>;
@@ -256,6 +264,8 @@ const initialState: InlineWizardState = {
projectPath: null,
agentType: null,
sessionName: null,
tabId: null,
initialGreetingSent: false,
streamingContent: '',
generationProgress: null,
};
@@ -357,7 +367,8 @@ export function useInlineWizard(): UseInlineWizardReturn {
currentUIState?: PreviousUIState,
projectPath?: string,
agentType?: ToolType,
sessionName?: string
sessionName?: string,
tabId?: string
): Promise<void> => {
// Store current UI state for later restoration
if (currentUIState) {
@@ -383,6 +394,7 @@ export function useInlineWizard(): UseInlineWizardReturn {
projectPath: projectPath || null,
agentType: agentType || null,
sessionName: sessionName || null,
tabId: tabId || null,
}));
try {
@@ -918,6 +930,55 @@ export function useInlineWizard(): UseInlineWizardReturn {
// Compute readyToGenerate based on ready flag and confidence threshold
const readyToGenerate = state.ready && state.confidence >= READY_CONFIDENCE_THRESHOLD;
// Automatically send an initial greeting to start the conversation
// This triggers the agent to examine the project and ask opening questions
useEffect(() => {
// Only send if:
// - Wizard is active and not initializing
// - Mode is determined (new or iterate)
// - Haven't sent the initial greeting yet
// - No conversation history yet
// - Not currently waiting for a response
const shouldSendGreeting =
state.isActive &&
!state.isInitializing &&
(state.mode === 'new' || state.mode === 'iterate') &&
!state.initialGreetingSent &&
state.conversationHistory.length === 0 &&
!state.isWaiting &&
conversationSessionRef.current;
if (shouldSendGreeting) {
// Mark as sent immediately to prevent duplicate sends
setState((prev) => ({ ...prev, initialGreetingSent: true }));
// Build an appropriate initial message based on mode and goal
let initialMessage: string;
if (state.mode === 'iterate' && state.goal) {
initialMessage = `I want to ${state.goal}`;
} else if (state.mode === 'iterate') {
initialMessage = 'I want to iterate on my existing Auto Run documents.';
} else {
initialMessage = 'Hello! I want to create a new action plan.';
}
// Send the initial message to trigger the agent's greeting
// We use a short delay to ensure state is fully updated
setTimeout(() => {
sendMessage(initialMessage);
}, 100);
}
}, [
state.isActive,
state.isInitializing,
state.mode,
state.goal,
state.initialGreetingSent,
state.conversationHistory.length,
state.isWaiting,
sendMessage,
]);
return {
// Convenience accessors
isWizardActive: state.isActive,
@@ -935,6 +996,7 @@ export function useInlineWizard(): UseInlineWizardReturn {
error: state.error,
streamingContent: state.streamingContent,
generationProgress: state.generationProgress,
wizardTabId: state.tabId,
// Full state
state,

View File

@@ -16,6 +16,10 @@ import {
wizardInlineIteratePrompt,
wizardInlineNewPrompt,
} from '../../prompts';
import {
parseStructuredOutput,
getConfidenceColor,
} from '../components/Wizard/services/wizardPrompts';
/**
* Extended ExistingDocument interface that includes loaded content.
@@ -278,141 +282,35 @@ function buildPromptWithContext(
/**
* Parse a structured response from the agent.
*
* Attempts to extract JSON from the response with multiple fallback strategies.
* Delegates to the shared parseStructuredOutput from wizardPrompts.ts to avoid
* code duplication. The shared implementation handles multiple fallback strategies
* for extracting JSON from agent responses.
*
* @param response The raw response string from the agent
* @returns Parsed WizardResponse or null if parsing failed
*/
export function parseWizardResponse(response: string): WizardResponse | null {
const rawText = response.trim();
const result = parseStructuredOutput(response);
// Strategy 1: Direct JSON parse
try {
const parsed = JSON.parse(rawText);
if (isValidWizardResponse(parsed)) {
return normalizeResponse(parsed);
}
} catch {
// Continue to next strategy
if (result.parseSuccess && result.structured) {
// Apply our ready threshold check on top of the shared parsing
return {
confidence: result.structured.confidence,
ready: result.structured.ready && result.structured.confidence >= READY_CONFIDENCE_THRESHOLD,
message: result.structured.message,
};
}
// Strategy 2: Extract JSON from markdown code blocks
const codeBlockMatch = rawText.match(/```(?:json)?\s*([\s\S]*?)```/);
if (codeBlockMatch) {
try {
const parsed = JSON.parse(codeBlockMatch[1].trim());
if (isValidWizardResponse(parsed)) {
return normalizeResponse(parsed);
}
} catch {
// Continue to next strategy
}
// If parsing failed but we have a structured response from fallback, use it
if (result.structured) {
return {
confidence: result.structured.confidence,
ready: result.structured.ready && result.structured.confidence >= READY_CONFIDENCE_THRESHOLD,
message: result.structured.message,
};
}
// Strategy 3: Find JSON object pattern with required fields
const jsonMatch = rawText.match(/\{[\s\S]*"confidence"[\s\S]*"ready"[\s\S]*"message"[\s\S]*\}/);
if (jsonMatch) {
try {
const parsed = JSON.parse(jsonMatch[0]);
if (isValidWizardResponse(parsed)) {
return normalizeResponse(parsed);
}
} catch {
// Continue to next strategy
}
}
// Strategy 4: Find any JSON object pattern
const anyJsonMatch = rawText.match(/\{[^{}]*\}/);
if (anyJsonMatch) {
try {
const parsed = JSON.parse(anyJsonMatch[0]);
if (isValidWizardResponse(parsed)) {
return normalizeResponse(parsed);
}
} catch {
// Continue to fallback
}
}
// Fallback: Create a response from raw text with heuristics
return createFallbackResponse(rawText);
}
/**
* Check if an object matches the expected wizard response format.
*/
function isValidWizardResponse(obj: unknown): obj is WizardResponse {
if (typeof obj !== 'object' || obj === null) {
return false;
}
const response = obj as Record<string, unknown>;
return (
typeof response.confidence === 'number' &&
typeof response.ready === 'boolean' &&
typeof response.message === 'string'
);
}
/**
* Normalize a response to ensure valid ranges and types.
*/
function normalizeResponse(response: WizardResponse): WizardResponse {
return {
confidence: Math.max(0, Math.min(100, Math.round(response.confidence))),
ready: response.ready && response.confidence >= READY_CONFIDENCE_THRESHOLD,
message: response.message.trim(),
};
}
/**
* Create a fallback response when parsing fails.
* Uses heuristics to extract useful information from raw text.
*/
function createFallbackResponse(rawText: string): WizardResponse {
const DEFAULT_CONFIDENCE = 20;
// Try to extract confidence from text patterns
let confidence = DEFAULT_CONFIDENCE;
const confidenceMatch = rawText.match(/confidence[:\s]*(\d+)/i) ||
rawText.match(/(\d+)\s*%?\s*confiden/i);
if (confidenceMatch) {
const extractedConfidence = parseInt(confidenceMatch[1], 10);
if (extractedConfidence >= 0 && extractedConfidence <= 100) {
confidence = extractedConfidence;
}
}
// Try to detect ready status from text
const readyPatterns = /\b(ready to proceed|ready to create|let's proceed|shall we proceed|i'm ready)\b/i;
const notReadyPatterns = /\b(need more|clarif|question|tell me more|could you explain)\b/i;
let ready = false;
if (confidence >= READY_CONFIDENCE_THRESHOLD && readyPatterns.test(rawText)) {
ready = true;
}
if (notReadyPatterns.test(rawText)) {
ready = false;
}
// Clean up the message
let message = rawText
.replace(/```(?:json)?/g, '')
.replace(/```/g, '')
.replace(/^\s*\{[\s\S]*?\}\s*$/g, '')
.trim();
if (!message) {
message = rawText;
}
return {
confidence,
ready,
message,
};
return null;
}
/**
@@ -488,6 +386,8 @@ function extractResultFromStreamJson(output: string, agentType: ToolType): strin
/**
* Build CLI args for the agent based on its type and capabilities.
* For wizard conversations, we restrict tool usage to read-only operations
* to prevent the agent from making changes during the discovery phase.
*/
function buildArgsForAgent(agent: any): string[] {
const agentId = agent.id;
@@ -498,6 +398,12 @@ function buildArgsForAgent(agent: any): string[] {
if (!args.includes('--include-partial-messages')) {
args.push('--include-partial-messages');
}
// Restrict to read-only tools during wizard conversation
// The agent can read files to understand the project, but cannot write/edit
// This ensures the wizard conversation phase doesn't make code changes
if (!args.includes('--allowedTools')) {
args.push('--allowedTools', 'Read', 'Glob', 'Grep', 'LS');
}
return args;
}
@@ -703,21 +609,5 @@ export async function endInlineWizardConversation(
}
}
/**
* Get the color for the confidence meter based on the level.
*
* @param confidence The confidence level (0-100)
* @returns HSL color string transitioning from red to yellow to green
*/
export function getConfidenceColor(confidence: number): string {
const clampedConfidence = Math.max(0, Math.min(100, confidence));
let hue: number;
if (clampedConfidence <= 50) {
hue = (clampedConfidence / 50) * 60; // 0 to 60 (red to yellow)
} else {
hue = 60 + ((clampedConfidence - 50) / 50) * 60; // 60 to 120 (yellow to green)
}
return `hsl(${hue}, 80%, 45%)`;
}
// Re-export getConfidenceColor from the shared location for backwards compatibility
export { getConfidenceColor };

View File

@@ -98,6 +98,8 @@ export interface WizardGeneratedDocument {
export interface SessionWizardState {
/** Whether wizard is currently active */
isActive: boolean;
/** Whether waiting for AI response */
isWaiting?: boolean;
/** Current wizard mode: 'new' for creating documents, 'iterate' for modifying existing */
mode: WizardMode;
/** Goal for iterate mode (what the user wants to add/change) */