mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-26 00:53:08 +00:00
Feature: Add PR review comments and resolution, improve AI prompt handling (#790)
* feat: Add PR review comments and resolution endpoints, improve prompt handling * Feature: File Editor (#789) * feat: Add file management feature * feat: Add auto-save functionality to file editor * fix: Replace HardDriveDownload icon with Save icon for consistency * fix: Prevent recursive copy/move and improve shell injection prevention * refactor: Extract editor settings form into separate component * ``` fix: Improve error handling and stabilize async operations - Add error event handlers to GraphQL process spawns to prevent unhandled rejections - Replace execAsync with execFile for safer command execution and better control - Fix timeout cleanup in withTimeout generator to prevent memory leaks - Improve outdated comment detection logic by removing redundant condition - Use resolveModelString for consistent model string handling - Replace || with ?? for proper falsy value handling in dialog initialization - Add comments clarifying branch name resolution logic for local branches with slashes - Add catch handler for project selection to handle async errors gracefully ``` * refactor: Extract PR review comments logic to dedicated service * fix: Improve robustness and UX for PR review and file operations * fix: Consolidate exec utilities and improve type safety * refactor: Replace ScrollArea with div and improve file tree layout
This commit is contained in:
@@ -240,9 +240,17 @@ interface TerminalViewProps {
|
||||
initialMode?: 'tab' | 'split';
|
||||
/** Unique nonce to allow opening the same worktree multiple times */
|
||||
nonce?: number;
|
||||
/** Command to run automatically when the terminal is created (e.g., from scripts submenu) */
|
||||
initialCommand?: string;
|
||||
}
|
||||
|
||||
export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }: TerminalViewProps) {
|
||||
export function TerminalView({
|
||||
initialCwd,
|
||||
initialBranch,
|
||||
initialMode,
|
||||
nonce,
|
||||
initialCommand,
|
||||
}: TerminalViewProps) {
|
||||
const {
|
||||
terminalState,
|
||||
setTerminalUnlocked,
|
||||
@@ -288,6 +296,10 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
const isCreatingRef = useRef<boolean>(false);
|
||||
const restoringProjectPathRef = useRef<string | null>(null);
|
||||
const [newSessionIds, setNewSessionIds] = useState<Set<string>>(new Set());
|
||||
// Per-session command overrides (e.g., from scripts submenu), takes priority over defaultRunScript
|
||||
const [sessionCommandOverrides, setSessionCommandOverrides] = useState<Map<string, string>>(
|
||||
new Map()
|
||||
);
|
||||
const [serverSessionInfo, setServerSessionInfo] = useState<{
|
||||
current: number;
|
||||
max: number;
|
||||
@@ -576,7 +588,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
|
||||
// Skip if we've already handled this exact request (prevents duplicate terminals)
|
||||
// Include mode and nonce in the key to allow opening same cwd multiple times
|
||||
const cwdKey = `${initialCwd}:${initialMode || 'default'}:${nonce || 0}`;
|
||||
const cwdKey = `${initialCwd}:${initialMode || 'default'}:${nonce || 0}:${initialCommand || ''}`;
|
||||
if (initialCwdHandledRef.current === cwdKey) return;
|
||||
|
||||
// Skip if terminal is not enabled or not unlocked
|
||||
@@ -618,8 +630,12 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
}
|
||||
|
||||
// Mark this session as new for running initial command
|
||||
if (defaultRunScript) {
|
||||
if (initialCommand || defaultRunScript) {
|
||||
setNewSessionIds((prev) => new Set(prev).add(data.data.id));
|
||||
// Store per-session command override if an explicit command was provided
|
||||
if (initialCommand) {
|
||||
setSessionCommandOverrides((prev) => new Map(prev).set(data.data.id, initialCommand));
|
||||
}
|
||||
}
|
||||
|
||||
// Show success toast with branch name if provided
|
||||
@@ -654,6 +670,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
initialCwd,
|
||||
initialBranch,
|
||||
initialMode,
|
||||
initialCommand,
|
||||
nonce,
|
||||
status?.enabled,
|
||||
status?.passwordRequired,
|
||||
@@ -1059,7 +1076,12 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
// Create terminal in new tab
|
||||
// customCwd: optional working directory (e.g., a specific worktree path)
|
||||
// branchName: optional branch name to display in the terminal panel header
|
||||
const createTerminalInNewTab = async (customCwd?: string, branchName?: string) => {
|
||||
// command: optional command to run when the terminal connects (e.g., from scripts menu)
|
||||
const createTerminalInNewTab = async (
|
||||
customCwd?: string,
|
||||
branchName?: string,
|
||||
command?: string
|
||||
) => {
|
||||
if (!canCreateTerminal('[Terminal] Debounced terminal tab creation')) {
|
||||
return;
|
||||
}
|
||||
@@ -1087,8 +1109,12 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
const { addTerminalToTab } = useAppStore.getState();
|
||||
addTerminalToTab(data.data.id, tabId, undefined, worktreeBranch);
|
||||
// Mark this session as new for running initial command
|
||||
if (defaultRunScript) {
|
||||
if (command || defaultRunScript) {
|
||||
setNewSessionIds((prev) => new Set(prev).add(data.data.id));
|
||||
// Store per-session command override if an explicit command was provided
|
||||
if (command) {
|
||||
setSessionCommandOverrides((prev) => new Map(prev).set(data.data.id, command));
|
||||
}
|
||||
}
|
||||
// Refresh session count
|
||||
fetchServerSettings();
|
||||
@@ -1136,6 +1162,18 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
// Always remove from UI - even if server says 404 (session may have already exited)
|
||||
removeTerminalFromLayout(sessionId);
|
||||
|
||||
// Clean up stale entries for killed sessions
|
||||
setSessionCommandOverrides((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(sessionId);
|
||||
return next;
|
||||
});
|
||||
setNewSessionIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(sessionId);
|
||||
return next;
|
||||
});
|
||||
|
||||
if (!response.ok && response.status !== 404) {
|
||||
// Log non-404 errors but still proceed with UI cleanup
|
||||
const data = await response.json().catch(() => ({}));
|
||||
@@ -1148,6 +1186,17 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
logger.error('Kill session error:', err);
|
||||
// Still remove from UI on network error - better UX than leaving broken terminal
|
||||
removeTerminalFromLayout(sessionId);
|
||||
// Clean up stale entries for killed sessions (same cleanup as try block)
|
||||
setSessionCommandOverrides((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(sessionId);
|
||||
return next;
|
||||
});
|
||||
setNewSessionIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(sessionId);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1182,6 +1231,22 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
})
|
||||
);
|
||||
|
||||
// Clean up stale entries for all killed sessions in this tab
|
||||
setSessionCommandOverrides((prev) => {
|
||||
const next = new Map(prev);
|
||||
for (const sessionId of sessionIds) {
|
||||
next.delete(sessionId);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setNewSessionIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
for (const sessionId of sessionIds) {
|
||||
next.delete(sessionId);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
|
||||
// Now remove the tab from state
|
||||
removeTerminalTab(tabId);
|
||||
// Refresh session count
|
||||
@@ -1255,6 +1320,12 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
next.delete(sessionId);
|
||||
return next;
|
||||
});
|
||||
// Clean up any per-session command override
|
||||
setSessionCommandOverrides((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(sessionId);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Navigate between terminal panes with directional awareness
|
||||
@@ -1387,6 +1458,9 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
const terminalFontSize = content.fontSize ?? terminalState.defaultFontSize;
|
||||
// Only run command on new sessions (not restored ones)
|
||||
const isNewSession = newSessionIds.has(content.sessionId);
|
||||
// Per-session command override takes priority over defaultRunScript
|
||||
const sessionCommand = sessionCommandOverrides.get(content.sessionId);
|
||||
const runCommand = isNewSession ? sessionCommand || defaultRunScript : undefined;
|
||||
return (
|
||||
<TerminalErrorBoundary
|
||||
key={`boundary-${content.sessionId}`}
|
||||
@@ -1413,6 +1487,10 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
createTerminal('vertical', content.sessionId, cwd, branchName);
|
||||
}}
|
||||
onNewTab={createTerminalInNewTab}
|
||||
onRunCommandInNewTab={(command: string) => {
|
||||
const { cwd, branchName: branch } = getActiveSessionWorktreeInfo();
|
||||
createTerminalInNewTab(cwd, branch, command);
|
||||
}}
|
||||
onNavigateUp={() => navigateToTerminal('up')}
|
||||
onNavigateDown={() => navigateToTerminal('down')}
|
||||
onNavigateLeft={() => navigateToTerminal('left')}
|
||||
@@ -1427,7 +1505,7 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
isDropTarget={activeDragId !== null && activeDragId !== content.sessionId}
|
||||
fontSize={terminalFontSize}
|
||||
onFontSizeChange={(size) => setTerminalPanelFontSize(content.sessionId, size)}
|
||||
runCommandOnConnect={isNewSession ? defaultRunScript : undefined}
|
||||
runCommandOnConnect={runCommand}
|
||||
onCommandRan={() => handleCommandRan(content.sessionId)}
|
||||
isMaximized={terminalState.maximizedSessionId === content.sessionId}
|
||||
onToggleMaximize={() => toggleTerminalMaximized(content.sessionId)}
|
||||
@@ -1971,6 +2049,10 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
createTerminal('vertical', terminalState.maximizedSessionId!, cwd, branchName);
|
||||
}}
|
||||
onNewTab={createTerminalInNewTab}
|
||||
onRunCommandInNewTab={(command: string) => {
|
||||
const { cwd, branchName: branch } = getActiveSessionWorktreeInfo();
|
||||
createTerminalInNewTab(cwd, branch, command);
|
||||
}}
|
||||
onSessionInvalid={() => {
|
||||
const sessionId = terminalState.maximizedSessionId!;
|
||||
logger.info(`Maximized session ${sessionId} is invalid, removing from layout`);
|
||||
@@ -1982,6 +2064,13 @@ export function TerminalView({ initialCwd, initialBranch, initialMode, nonce }:
|
||||
onFontSizeChange={(size) =>
|
||||
setTerminalPanelFontSize(terminalState.maximizedSessionId!, size)
|
||||
}
|
||||
runCommandOnConnect={
|
||||
newSessionIds.has(terminalState.maximizedSessionId)
|
||||
? sessionCommandOverrides.get(terminalState.maximizedSessionId) ||
|
||||
defaultRunScript
|
||||
: undefined
|
||||
}
|
||||
onCommandRan={() => handleCommandRan(terminalState.maximizedSessionId!)}
|
||||
isMaximized={true}
|
||||
onToggleMaximize={() => toggleTerminalMaximized(terminalState.maximizedSessionId!)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user