refactor: Enhance session management and error handling in AgentService and related components

- Improved session handling by implementing ensureSession to load sessions from disk if not in memory, reducing "session not found" errors.
- Enhanced error messages for non-existent sessions, providing clearer diagnostics.
- Updated CodexProvider and OpencodeProvider to improve error handling and messaging.
- Refactored various routes to use async/await for better readability and error handling.
- Added event emission for merge and stash operations in the MergeService and StashService.
- Cleaned up error messages in AgentExecutor to remove redundant prefixes and ANSI codes for better clarity.
This commit is contained in:
gsxdsm
2026-02-18 17:30:12 -08:00
parent 6903d3c508
commit df9a6314da
22 changed files with 827 additions and 148 deletions

View File

@@ -33,8 +33,8 @@ export function TruncatedFilePath({ path, className }: TruncatedFilePathProps) {
return (
<span className={cn('flex min-w-0', className)} title={path}>
<span className="truncate flex-shrink">{dirPart}</span>
<span className="flex-shrink-0 whitespace-nowrap">{filePart}</span>
<span className="truncate shrink">{dirPart}</span>
<span className="shrink-0 whitespace-nowrap">{filePart}</span>
</span>
);
}

View File

@@ -178,6 +178,10 @@ function parseDiff(diffText: string): ParsedFileDiff[] {
}
if (currentHunk) {
// Skip trailing empty line produced by split('\n') to avoid phantom context line
if (line === '' && i === lines.length - 1) {
continue;
}
if (line.startsWith('+')) {
currentHunk.lines.push({
type: 'addition',

View File

@@ -41,10 +41,10 @@ type PullPhase =
| 'error'; // Something went wrong
interface PullResult {
branch: string;
branch?: string;
remote?: string;
pulled: boolean;
message: string;
pulled?: boolean;
message?: string;
hasLocalChanges?: boolean;
localChangedFiles?: string[];
hasConflicts?: boolean;
@@ -52,6 +52,7 @@ interface PullResult {
conflictFiles?: string[];
stashed?: boolean;
stashRestored?: boolean;
stashRecoveryFailed?: boolean;
}
interface GitPullDialogProps {
@@ -167,9 +168,10 @@ export function GitPullDialog({
if (!worktree || !pullResult || !onCreateConflictResolutionFeature) return;
const effectiveRemote = pullResult.remote || remote;
const branch = pullResult.branch ?? worktree.branch;
const conflictInfo: MergeConflictInfo = {
sourceBranch: effectiveRemote ? `${effectiveRemote}/${pullResult.branch}` : pullResult.branch,
targetBranch: pullResult.branch,
sourceBranch: `${effectiveRemote || 'origin'}/${branch}`,
targetBranch: branch,
targetWorktreePath: worktree.path,
conflictFiles: pullResult.conflictFiles || [],
operationType: 'merge',
@@ -307,14 +309,16 @@ export function GitPullDialog({
</div>
)}
{pullResult?.stashed && !pullResult?.stashRestored && (
<div className="flex items-start gap-2 p-3 rounded-md bg-amber-500/10 border border-amber-500/20">
<AlertTriangle className="w-4 h-4 text-amber-500 mt-0.5 flex-shrink-0" />
<span className="text-amber-600 dark:text-amber-400 text-sm">
{pullResult.message}
</span>
</div>
)}
{pullResult?.stashed &&
(!pullResult?.stashRestored || pullResult?.stashRecoveryFailed) && (
<div className="flex items-start gap-2 p-3 rounded-md bg-amber-500/10 border border-amber-500/20">
<AlertTriangle className="w-4 h-4 text-amber-500 mt-0.5 flex-shrink-0" />
<span className="text-amber-600 dark:text-amber-400 text-sm">
{pullResult?.message ??
'Stash could not be restored. Your changes remain in the stash.'}
</span>
</div>
)}
</div>
</DialogDescription>
</DialogHeader>

View File

@@ -284,6 +284,7 @@ export function StashChangesDialog({
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
const [expandedFile, setExpandedFile] = useState<string | null>(null);
const [isLoadingDiffs, setIsLoadingDiffs] = useState(false);
const [loadDiffsError, setLoadDiffsError] = useState<string | null>(null);
// Parse diffs
const parsedDiffs = useMemo(() => parseDiff(diffContent), [diffContent]);
@@ -297,42 +298,47 @@ export function StashChangesDialog({
return map;
}, [parsedDiffs]);
// Load diffs when dialog opens
useEffect(() => {
if (open && worktree) {
const loadDiffs = useCallback(
async (cancelled: { current: boolean }) => {
setIsLoadingDiffs(true);
setLoadDiffsError(null);
setFiles([]);
setDiffContent('');
setSelectedFiles(new Set());
setExpandedFile(null);
let cancelled = false;
const loadDiffs = async () => {
try {
const api = getHttpApiClient();
const result = await api.git.getDiffs(worktree.path);
if (result.success) {
const fileList = result.files ?? [];
if (!cancelled) setFiles(fileList);
if (!cancelled) setDiffContent(result.diff ?? '');
// Select all files by default
if (!cancelled) setSelectedFiles(new Set(fileList.map((f: FileStatus) => f.path)));
}
} catch (err) {
console.warn('Failed to load diffs for stash dialog:', err);
} finally {
if (!cancelled) setIsLoadingDiffs(false);
try {
const api = getHttpApiClient();
const result = await api.git.getDiffs(worktree!.path);
if (result.success) {
const fileList = result.files ?? [];
if (!cancelled.current) setFiles(fileList);
if (!cancelled.current) setDiffContent(result.diff ?? '');
// Select all files by default
if (!cancelled.current)
setSelectedFiles(new Set(fileList.map((f: FileStatus) => f.path)));
}
};
loadDiffs();
} catch (err) {
console.warn('Failed to load diffs for stash dialog:', err);
if (!cancelled.current) {
setLoadDiffsError(err instanceof Error ? err.message : 'Failed to load changes');
}
} finally {
if (!cancelled.current) setIsLoadingDiffs(false);
}
},
[worktree]
);
// Load diffs when dialog opens
useEffect(() => {
if (open && worktree) {
const cancelled = { current: false };
loadDiffs(cancelled);
return () => {
cancelled = true;
cancelled.current = true;
};
}
}, [open, worktree]);
}, [open, worktree, loadDiffs]);
const handleToggleFile = useCallback((filePath: string) => {
setSelectedFiles((prev) => {
@@ -466,6 +472,20 @@ export function StashChangesDialog({
<Spinner size="sm" className="mr-2" />
<span className="text-sm">Loading changes...</span>
</div>
) : loadDiffsError ? (
<div className="flex flex-col items-center justify-center py-6 gap-2 border border-border rounded-lg">
<span className="text-sm text-destructive">Failed to load changes</span>
<Button
variant="outline"
size="sm"
onClick={() => {
const cancelled = { current: false };
loadDiffs(cancelled);
}}
>
Retry
</Button>
</div>
) : files.length === 0 ? (
<div className="flex items-center justify-center py-6 text-muted-foreground border border-border rounded-lg">
<span className="text-sm">No changes detected</span>

View File

@@ -1151,6 +1151,9 @@ export function useBoardActions({
if (result.success) {
// Refresh features from server to sync React Query cache
loadFeatures();
toast.success('All verified features archived', {
description: `Archived ${verifiedFeatures.length} feature(s).`,
});
} else {
logger.error('Bulk archive failed:', result);
// Reload features to sync state with server
@@ -1162,10 +1165,6 @@ export function useBoardActions({
// Reload features to sync state with server on error
loadFeatures();
}
toast.success('All verified features archived', {
description: `Archived ${verifiedFeatures.length} feature(s).`,
});
}, [features, runningAutoTasks, autoMode, updateFeature, currentProject, loadFeatures]);
const handleDuplicateFeature = useCallback(