mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
feat(agent-runner, session-management): enhance markdown formatting and session hotkey functionality
- Added a React library for proper markdown formatting in the Agent Runner, improving the display of session messages. - Changed the hotkey for creating a new session from "W" to "N" to ensure consistency across new feature buttons. - Updated the feature list to include new entries for these enhancements, along with a detailed implementation roadmap in the project specification. These changes improve user experience by enhancing message readability and streamlining session management.
This commit is contained in:
@@ -1 +1,24 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"id": "feature-1765376066671-lmchhkgez",
|
||||
"category": "Agent Runner",
|
||||
"description": "On the Agent Runner, the markdown is not being formatted properly. Can you please bring in a React library to format markdown so that on a session window of the agent, it'll properly format the markdown?",
|
||||
"steps": [],
|
||||
"status": "waiting_approval",
|
||||
"startedAt": "2025-12-10T14:15:24.929Z",
|
||||
"imagePaths": [],
|
||||
"skipTests": true,
|
||||
"summary": "Added markdown formatting to Agent Runner session messages. Modified: agent-view.tsx. Used existing Markdown component with react-markdown library to properly render headings, code blocks, lists, links, and other markdown elements in assistant messages."
|
||||
},
|
||||
{
|
||||
"id": "feature-1765376191866-n4r0s1l6q",
|
||||
"category": "Agent Runner",
|
||||
"description": "On the Agent Runner page, change the hotkey for making a session from W to New. I want all new feature buttons to be the hotkey New.",
|
||||
"steps": [],
|
||||
"status": "waiting_approval",
|
||||
"startedAt": "2025-12-10T14:16:34.207Z",
|
||||
"imagePaths": [],
|
||||
"skipTests": true,
|
||||
"summary": "Changed the new session hotkey from \"W\" to \"N\" on the Agent Runner page. Modified: use-keyboard-shortcuts.ts (line 125). This aligns with the user's request to have all \"new\" feature buttons use the \"N\" hotkey consistently."
|
||||
}
|
||||
]
|
||||
@@ -27,7 +27,7 @@ class ProjectAnalyzer {
|
||||
systemPrompt: promptBuilder.getProjectAnalysisSystemPrompt(),
|
||||
maxTurns: 50,
|
||||
cwd: projectPath,
|
||||
allowedTools: ["Read", "Glob", "Grep", "Bash"],
|
||||
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
|
||||
permissionMode: "acceptEdits",
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
|
||||
@@ -359,6 +359,7 @@ Analyze this project's codebase and update the .automaker/app_spec.txt file with
|
||||
3. **Technology Stack** - Languages, frameworks, libraries detected
|
||||
4. **Core Capabilities** - Main features and functionality
|
||||
5. **Implemented Features** - What features are already built
|
||||
6. **Implementation Roadmap** - Break down remaining work into phases with individual features
|
||||
|
||||
**Steps to Follow:**
|
||||
|
||||
@@ -406,10 +407,42 @@ Analyze this project's codebase and update the .automaker/app_spec.txt file with
|
||||
<implemented_features>
|
||||
<!-- List specific features that appear to be implemented -->
|
||||
</implemented_features>
|
||||
|
||||
<implementation_roadmap>
|
||||
<phase_1_foundation>
|
||||
<!-- List foundational features to build first -->
|
||||
</phase_1_foundation>
|
||||
<phase_2_core_logic>
|
||||
<!-- List core logic features -->
|
||||
</phase_2_core_logic>
|
||||
<phase_3_polish>
|
||||
<!-- List polish and enhancement features -->
|
||||
</phase_3_polish>
|
||||
</implementation_roadmap>
|
||||
</project_specification>
|
||||
\`\`\`
|
||||
|
||||
4. Ensure .automaker/feature_list.json exists (create as empty array [] if not)
|
||||
4. **IMPORTANT - Generate Feature List:**
|
||||
After writing the app_spec.txt, you MUST update .automaker/feature_list.json with features from the implementation_roadmap section:
|
||||
- Read the app_spec.txt you just created
|
||||
- For EVERY feature in each phase of the implementation_roadmap, create an entry
|
||||
- Write ALL features to .automaker/feature_list.json
|
||||
|
||||
The feature_list.json format should be:
|
||||
\`\`\`json
|
||||
[
|
||||
{
|
||||
"id": "feature-<timestamp>-<index>",
|
||||
"category": "<phase name, e.g., 'Phase 1: Foundation'>",
|
||||
"description": "<feature description>",
|
||||
"status": "backlog",
|
||||
"steps": ["Step 1", "Step 2", "..."],
|
||||
"skipTests": true
|
||||
}
|
||||
]
|
||||
\`\`\`
|
||||
|
||||
Generate unique IDs using the current timestamp and index (e.g., "feature-1234567890-0", "feature-1234567890-1", etc.)
|
||||
|
||||
5. Ensure .automaker/context/ directory exists
|
||||
|
||||
@@ -420,6 +453,7 @@ Analyze this project's codebase and update the .automaker/app_spec.txt file with
|
||||
- Only include information you can verify from the codebase
|
||||
- If unsure about something, note it as "to be determined"
|
||||
- Don't make up features that don't exist
|
||||
- Include EVERY feature from the roadmap in feature_list.json - do not skip any
|
||||
|
||||
Begin by exploring the project structure.`;
|
||||
}
|
||||
@@ -618,6 +652,7 @@ Your goal is to:
|
||||
- Identify programming languages, frameworks, and libraries
|
||||
- Detect existing features and capabilities
|
||||
- Update the .automaker/app_spec.txt with accurate information
|
||||
- Generate a feature list in .automaker/feature_list.json based on the implementation roadmap
|
||||
- Ensure all required .automaker files and directories exist
|
||||
|
||||
Be efficient - don't read every file, focus on:
|
||||
@@ -626,7 +661,13 @@ Be efficient - don't read every file, focus on:
|
||||
- Directory structure
|
||||
- README and documentation
|
||||
|
||||
You have read access to files and can run basic bash commands to explore the structure.`;
|
||||
**CRITICAL - Feature List Generation:**
|
||||
After creating/updating the app_spec.txt, you MUST also update .automaker/feature_list.json:
|
||||
1. Read the app_spec.txt you just wrote
|
||||
2. Extract all features from the implementation_roadmap section
|
||||
3. Write them to .automaker/feature_list.json in the correct format
|
||||
|
||||
You have access to Read, Write, Edit, Glob, Grep, and Bash tools. Use them to explore the structure and write the necessary files.`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,29 @@ export function SessionManager({
|
||||
const [editingName, setEditingName] = useState("");
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [newSessionName, setNewSessionName] = useState("");
|
||||
const [runningSessions, setRunningSessions] = useState<Set<string>>(new Set());
|
||||
|
||||
// Check running state for all sessions
|
||||
const checkRunningSessions = async (sessionList: SessionListItem[]) => {
|
||||
if (!window.electronAPI?.agent) return;
|
||||
|
||||
const runningIds = new Set<string>();
|
||||
|
||||
// Check each session's running state
|
||||
for (const session of sessionList) {
|
||||
try {
|
||||
const result = await window.electronAPI.agent.getHistory(session.id);
|
||||
if (result.success && result.isRunning) {
|
||||
runningIds.add(session.id);
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore errors for individual session checks
|
||||
console.warn(`[SessionManager] Failed to check running state for ${session.id}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
setRunningSessions(runningIds);
|
||||
};
|
||||
|
||||
// Load sessions
|
||||
const loadSessions = async () => {
|
||||
@@ -76,6 +99,8 @@ export function SessionManager({
|
||||
const result = await window.electronAPI.sessions.list(true);
|
||||
if (result.success && result.sessions) {
|
||||
setSessions(result.sessions);
|
||||
// Check running state for all sessions
|
||||
await checkRunningSessions(result.sessions);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -83,6 +108,20 @@ export function SessionManager({
|
||||
loadSessions();
|
||||
}, []);
|
||||
|
||||
// Periodically check running state for sessions (useful for detecting when agents finish)
|
||||
useEffect(() => {
|
||||
// Only poll if there are running sessions
|
||||
if (runningSessions.size === 0 && !isCurrentSessionThinking) return;
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
if (sessions.length > 0) {
|
||||
await checkRunningSessions(sessions);
|
||||
}
|
||||
}, 3000); // Check every 3 seconds
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [sessions, runningSessions.size, isCurrentSessionThinking]);
|
||||
|
||||
// Create new session with random name
|
||||
const handleCreateSession = async () => {
|
||||
if (!window.electronAPI?.sessions) return;
|
||||
@@ -328,13 +367,14 @@ export function SessionManager({
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{currentSessionId === session.id && isCurrentSessionThinking ? (
|
||||
{/* Show loading indicator if this session is running (either current session thinking or any session in runningSessions) */}
|
||||
{((currentSessionId === session.id && isCurrentSessionThinking) || runningSessions.has(session.id)) ? (
|
||||
<Loader2 className="w-4 h-4 text-primary animate-spin shrink-0" />
|
||||
) : (
|
||||
<MessageSquare className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||
)}
|
||||
<h3 className="font-medium truncate">{session.name}</h3>
|
||||
{currentSessionId === session.id && isCurrentSessionThinking && (
|
||||
{((currentSessionId === session.id && isCurrentSessionThinking) || runningSessions.has(session.id)) && (
|
||||
<span className="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded-full">
|
||||
thinking...
|
||||
</span>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useElectronAgent } from "@/hooks/use-electron-agent";
|
||||
import { SessionManager } from "@/components/session-manager";
|
||||
import { Markdown } from "@/components/ui/markdown";
|
||||
import type { ImageAttachment } from "@/store/app-store";
|
||||
import {
|
||||
useKeyboardShortcuts,
|
||||
@@ -30,7 +31,7 @@ import {
|
||||
} from "@/hooks/use-keyboard-shortcuts";
|
||||
|
||||
export function AgentView() {
|
||||
const { currentProject } = useAppStore();
|
||||
const { currentProject, setLastSelectedSession, getLastSelectedSession } = useAppStore();
|
||||
const [input, setInput] = useState("");
|
||||
const [selectedImages, setSelectedImages] = useState<ImageAttachment[]>([]);
|
||||
const [showImageDropZone, setShowImageDropZone] = useState(false);
|
||||
@@ -39,6 +40,9 @@ export function AgentView() {
|
||||
const [showSessionManager, setShowSessionManager] = useState(true);
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
|
||||
// Track if initial session has been loaded
|
||||
const initialSessionLoadedRef = useRef(false);
|
||||
|
||||
// Scroll management for auto-scroll
|
||||
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [isUserAtBottom, setIsUserAtBottom] = useState(true);
|
||||
@@ -66,6 +70,40 @@ export function AgentView() {
|
||||
},
|
||||
});
|
||||
|
||||
// Handle session selection with persistence
|
||||
const handleSelectSession = useCallback((sessionId: string | null) => {
|
||||
setCurrentSessionId(sessionId);
|
||||
// Persist the selection for this project
|
||||
if (currentProject?.path) {
|
||||
setLastSelectedSession(currentProject.path, sessionId);
|
||||
}
|
||||
}, [currentProject?.path, setLastSelectedSession]);
|
||||
|
||||
// Restore last selected session when switching to Agent view or when project changes
|
||||
useEffect(() => {
|
||||
if (!currentProject?.path) {
|
||||
// No project, reset
|
||||
setCurrentSessionId(null);
|
||||
initialSessionLoadedRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only restore once per project
|
||||
if (initialSessionLoadedRef.current) return;
|
||||
initialSessionLoadedRef.current = true;
|
||||
|
||||
const lastSessionId = getLastSelectedSession(currentProject.path);
|
||||
if (lastSessionId) {
|
||||
console.log("[AgentView] Restoring last selected session:", lastSessionId);
|
||||
setCurrentSessionId(lastSessionId);
|
||||
}
|
||||
}, [currentProject?.path, getLastSelectedSession]);
|
||||
|
||||
// Reset initialSessionLoadedRef when project changes
|
||||
useEffect(() => {
|
||||
initialSessionLoadedRef.current = false;
|
||||
}, [currentProject?.path]);
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
if ((!input.trim() && selectedImages.length === 0) || isProcessing) return;
|
||||
|
||||
@@ -441,7 +479,7 @@ export function AgentView() {
|
||||
<div className="w-80 border-r flex-shrink-0">
|
||||
<SessionManager
|
||||
currentSessionId={currentSessionId}
|
||||
onSelectSession={setCurrentSessionId}
|
||||
onSelectSession={handleSelectSession}
|
||||
projectPath={currentProject.path}
|
||||
isCurrentSessionThinking={isProcessing}
|
||||
onQuickCreateRef={quickCreateSessionRef}
|
||||
@@ -559,9 +597,13 @@ export function AgentView() {
|
||||
)}
|
||||
>
|
||||
<CardContent className="p-3">
|
||||
<p className="text-sm whitespace-pre-wrap">
|
||||
{message.content}
|
||||
</p>
|
||||
{message.role === "assistant" ? (
|
||||
<Markdown className="text-sm">{message.content}</Markdown>
|
||||
) : (
|
||||
<p className="text-sm whitespace-pre-wrap">
|
||||
{message.content}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
className={cn(
|
||||
"text-xs mt-2",
|
||||
|
||||
@@ -127,6 +127,10 @@ export function BoardView() {
|
||||
>([]);
|
||||
const [showSuggestionsDialog, setShowSuggestionsDialog] = useState(false);
|
||||
const [suggestionsCount, setSuggestionsCount] = useState(0);
|
||||
const [featureSuggestions, setFeatureSuggestions] = useState<
|
||||
import("@/lib/electron").FeatureSuggestion[]
|
||||
>([]);
|
||||
const [isGeneratingSuggestions, setIsGeneratingSuggestions] = useState(false);
|
||||
|
||||
// Make current project available globally for modal
|
||||
useEffect(() => {
|
||||
@@ -138,7 +142,7 @@ export function BoardView() {
|
||||
};
|
||||
}, [currentProject]);
|
||||
|
||||
// Listen for suggestions events to update count
|
||||
// Listen for suggestions events to update count (persists even when dialog is closed)
|
||||
useEffect(() => {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.suggestions) return;
|
||||
@@ -146,6 +150,10 @@ export function BoardView() {
|
||||
const unsubscribe = api.suggestions.onEvent((event) => {
|
||||
if (event.type === "suggestions_complete" && event.suggestions) {
|
||||
setSuggestionsCount(event.suggestions.length);
|
||||
setFeatureSuggestions(event.suggestions);
|
||||
setIsGeneratingSuggestions(false);
|
||||
} else if (event.type === "suggestions_error") {
|
||||
setIsGeneratingSuggestions(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1809,9 +1817,15 @@ export function BoardView() {
|
||||
open={showSuggestionsDialog}
|
||||
onClose={() => {
|
||||
setShowSuggestionsDialog(false);
|
||||
// Clear the count when dialog is closed (suggestions were either imported or dismissed)
|
||||
}}
|
||||
projectPath={currentProject.path}
|
||||
suggestions={featureSuggestions}
|
||||
setSuggestions={(suggestions) => {
|
||||
setFeatureSuggestions(suggestions);
|
||||
setSuggestionsCount(suggestions.length);
|
||||
}}
|
||||
isGenerating={isGeneratingSuggestions}
|
||||
setIsGenerating={setIsGeneratingSuggestions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,16 +28,23 @@ interface FeatureSuggestionsDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
projectPath: string;
|
||||
// Props to persist state across dialog open/close
|
||||
suggestions: FeatureSuggestion[];
|
||||
setSuggestions: (suggestions: FeatureSuggestion[]) => void;
|
||||
isGenerating: boolean;
|
||||
setIsGenerating: (generating: boolean) => void;
|
||||
}
|
||||
|
||||
export function FeatureSuggestionsDialog({
|
||||
open,
|
||||
onClose,
|
||||
projectPath,
|
||||
suggestions,
|
||||
setSuggestions,
|
||||
isGenerating,
|
||||
setIsGenerating,
|
||||
}: FeatureSuggestionsDialogProps) {
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [progress, setProgress] = useState<string[]>([]);
|
||||
const [suggestions, setSuggestions] = useState<FeatureSuggestion[]>([]);
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
@@ -46,6 +53,13 @@ export function FeatureSuggestionsDialog({
|
||||
|
||||
const { features, setFeatures } = useAppStore();
|
||||
|
||||
// Initialize selectedIds when suggestions change
|
||||
useEffect(() => {
|
||||
if (suggestions.length > 0 && selectedIds.size === 0) {
|
||||
setSelectedIds(new Set(suggestions.map((s) => s.id)));
|
||||
}
|
||||
}, [suggestions, selectedIds.size]);
|
||||
|
||||
// Auto-scroll progress when new content arrives
|
||||
useEffect(() => {
|
||||
if (autoScrollRef.current && scrollRef.current && isGenerating) {
|
||||
@@ -53,7 +67,7 @@ export function FeatureSuggestionsDialog({
|
||||
}
|
||||
}, [progress, isGenerating]);
|
||||
|
||||
// Listen for suggestion events
|
||||
// Listen for suggestion events when dialog is open
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
@@ -85,7 +99,7 @@ export function FeatureSuggestionsDialog({
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [open]);
|
||||
}, [open, setSuggestions, setIsGenerating]);
|
||||
|
||||
// Start generating suggestions
|
||||
const handleGenerate = useCallback(async () => {
|
||||
@@ -111,7 +125,7 @@ export function FeatureSuggestionsDialog({
|
||||
toast.error("Failed to start generation");
|
||||
setIsGenerating(false);
|
||||
}
|
||||
}, [projectPath]);
|
||||
}, [projectPath, setIsGenerating, setSuggestions]);
|
||||
|
||||
// Stop generating
|
||||
const handleStop = useCallback(async () => {
|
||||
@@ -125,7 +139,7 @@ export function FeatureSuggestionsDialog({
|
||||
} catch (error) {
|
||||
console.error("Failed to stop generation:", error);
|
||||
}
|
||||
}, []);
|
||||
}, [setIsGenerating]);
|
||||
|
||||
// Toggle suggestion selection
|
||||
const toggleSelection = useCallback((id: string) => {
|
||||
@@ -198,6 +212,12 @@ export function FeatureSuggestionsDialog({
|
||||
setFeatures(updatedFeatures);
|
||||
|
||||
toast.success(`Imported ${newFeatures.length} features to backlog!`);
|
||||
|
||||
// Clear suggestions after importing
|
||||
setSuggestions([]);
|
||||
setSelectedIds(new Set());
|
||||
setProgress([]);
|
||||
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Failed to import features:", error);
|
||||
@@ -205,7 +225,7 @@ export function FeatureSuggestionsDialog({
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
}
|
||||
}, [selectedIds, suggestions, features, setFeatures, projectPath, onClose]);
|
||||
}, [selectedIds, suggestions, features, setFeatures, setSuggestions, projectPath, onClose]);
|
||||
|
||||
// Handle scroll to detect if user scrolled up
|
||||
const handleScroll = () => {
|
||||
|
||||
@@ -137,8 +137,7 @@ export function useElectronAgent({
|
||||
let mounted = true;
|
||||
|
||||
const initialize = async () => {
|
||||
// Reset state when switching sessions
|
||||
setIsProcessing(false);
|
||||
// Reset error state when switching sessions
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
@@ -154,13 +153,23 @@ export function useElectronAgent({
|
||||
console.log("[useElectronAgent] Loaded", result.messages.length, "messages");
|
||||
setMessages(result.messages);
|
||||
setIsConnected(true);
|
||||
|
||||
// Check if the agent is currently running for this session
|
||||
const historyResult = await window.electronAPI.agent.getHistory(sessionId);
|
||||
if (mounted && historyResult.success) {
|
||||
const isRunning = historyResult.isRunning || false;
|
||||
console.log("[useElectronAgent] Session running state:", isRunning);
|
||||
setIsProcessing(isRunning);
|
||||
}
|
||||
} else {
|
||||
setError(result.error || "Failed to start session");
|
||||
setIsProcessing(false);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
console.error("[useElectronAgent] Failed to initialize:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to initialize");
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export const ACTION_SHORTCUTS: Record<string, string> = {
|
||||
addFeature: "N", // N for New feature
|
||||
addContextFile: "F", // F for File (add context file)
|
||||
startNext: "G", // G for Grab (start next features from backlog)
|
||||
newSession: "W", // W for new session (in agent view)
|
||||
newSession: "N", // N for New session (in agent view)
|
||||
openProject: "O", // O for Open project (navigate to welcome view)
|
||||
projectPicker: "P", // P for Project picker
|
||||
cyclePrevProject: "Q", // Q for previous project (cycle back through MRU)
|
||||
|
||||
@@ -101,6 +101,9 @@ export interface AppState {
|
||||
currentView: ViewMode;
|
||||
sidebarOpen: boolean;
|
||||
|
||||
// Agent Session state (per-project, keyed by project path)
|
||||
lastSelectedSessionByProject: Record<string, string>; // projectPath -> sessionId
|
||||
|
||||
// Theme
|
||||
theme: ThemeMode;
|
||||
|
||||
@@ -223,6 +226,10 @@ export interface AppActions {
|
||||
// Feature Default Settings actions
|
||||
setDefaultSkipTests: (skip: boolean) => void;
|
||||
|
||||
// Agent Session actions
|
||||
setLastSelectedSession: (projectPath: string, sessionId: string | null) => void;
|
||||
getLastSelectedSession: (projectPath: string) => string | null;
|
||||
|
||||
// Reset
|
||||
reset: () => void;
|
||||
}
|
||||
@@ -235,6 +242,7 @@ const initialState: AppState = {
|
||||
projectHistoryIndex: -1,
|
||||
currentView: "welcome",
|
||||
sidebarOpen: true,
|
||||
lastSelectedSessionByProject: {},
|
||||
theme: "dark",
|
||||
features: [],
|
||||
appSpec: "",
|
||||
@@ -682,6 +690,27 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
// Feature Default Settings actions
|
||||
setDefaultSkipTests: (skip) => set({ defaultSkipTests: skip }),
|
||||
|
||||
// Agent Session actions
|
||||
setLastSelectedSession: (projectPath, sessionId) => {
|
||||
const current = get().lastSelectedSessionByProject;
|
||||
if (sessionId === null) {
|
||||
// Remove the entry for this project
|
||||
const { [projectPath]: _, ...rest } = current;
|
||||
set({ lastSelectedSessionByProject: rest });
|
||||
} else {
|
||||
set({
|
||||
lastSelectedSessionByProject: {
|
||||
...current,
|
||||
[projectPath]: sessionId,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getLastSelectedSession: (projectPath) => {
|
||||
return get().lastSelectedSessionByProject[projectPath] || null;
|
||||
},
|
||||
|
||||
// Reset
|
||||
reset: () => set(initialState),
|
||||
}),
|
||||
@@ -702,6 +731,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
maxConcurrency: state.maxConcurrency,
|
||||
kanbanCardDetailLevel: state.kanbanCardDetailLevel,
|
||||
defaultSkipTests: state.defaultSkipTests,
|
||||
lastSelectedSessionByProject: state.lastSelectedSessionByProject,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
18
app/src/types/electron.d.ts
vendored
18
app/src/types/electron.d.ts
vendored
@@ -291,6 +291,24 @@ export interface AutoModeAPI {
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
resumeFeature: (projectPath: string, featureId: string) => Promise<{
|
||||
success: boolean;
|
||||
passes?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
contextExists: (projectPath: string, featureId: string) => Promise<{
|
||||
success: boolean;
|
||||
exists?: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
analyzeProject: (projectPath: string) => Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
followUpFeature: (projectPath: string, featureId: string, prompt: string, imagePaths?: string[]) => Promise<{
|
||||
success: boolean;
|
||||
passes?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user