feat: harden local shell, SFTP, and desktop session reliability

Finder-launched terminals now inherit a login PATH so tools like ollama resolve, destructive SFTP/editor actions require confirmation, and terminal search plus window dragging work again. SSH forwards and RDP sessions keep listeners and survive reconnect without FastPath crashes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-16 16:08:11 +08:00
1 parent 188f3556ed
commit 08696896f9
223 files changed
+13493 -3423

No files matched your search

+41 -8
View File
@@ -1,9 +1,10 @@
/**
* TransferPanel - 传输队列面板
*
* 显示文件上传/下载的传输队列
*
* 显示文件上传/下载的传输队列;进行中任务置顶。
*/
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ChevronDown, ChevronUp } from 'lucide-react'
import { TransferItem } from './TransferItem'
@@ -15,16 +16,47 @@ interface TransferPanelProps {
onToggle: () => void
onCancel?: (id: string) => void
onRemove: (id: string) => void
onRetry?: (id: string) => void
}
export function TransferPanel({ tasks, isOpen, onToggle, onCancel, onRemove }: TransferPanelProps) {
/** transferring → pending → failed/canceled → completed(组内保持原相对顺序) */
// eslint-disable-next-line react-refresh/only-export-components -- pure helper used by tests and panel
export function sortTransferTasks(tasks: TransferTask[]): TransferTask[] {
const rank = (status: TransferTask['status']): number => {
switch (status) {
case 'transferring':
return 0
case 'pending':
return 1
case 'failed':
case 'canceled':
return 2
case 'completed':
return 3
default:
return 4
}
}
return tasks
.map((task, index) => ({ task, index }))
.sort((a, b) => {
const byStatus = rank(a.task.status) - rank(b.task.status)
return byStatus !== 0 ? byStatus : a.index - b.index
})
.map(({ task }) => task)
}
export function TransferPanel({ tasks, isOpen, onToggle, onCancel, onRemove, onRetry }: TransferPanelProps) {
const { t } = useTranslation('sftp')
const activeCount = tasks.filter(t => t.status === 'pending' || t.status === 'transferring').length
const completedCount = tasks.filter(t => t.status === 'completed').length
const orderedTasks = useMemo(() => sortTransferTasks(tasks), [tasks])
const activeCount = tasks.filter((task) => task.status === 'pending' || task.status === 'transferring').length
const completedCount = tasks.filter((task) => task.status === 'completed').length
return (
<div className="bg-dark-800 border-t border-dark-700/30">
<button
type="button"
onClick={onToggle}
className="w-full flex items-center justify-between px-4 py-2 hover:bg-dark-700 transition-colors"
>
@@ -36,7 +68,7 @@ export function TransferPanel({ tasks, isOpen, onToggle, onCancel, onRemove }: T
</span>
)}
{completedCount > 0 && (
<span className="px-2 py-0.5 bg-green-500/20 text-green-400 text-xs rounded-full">
<span className="px-2 py-0.5 bg-feedback-success/15 text-feedback-success text-xs rounded-full">
{t('transfer.completed', { count: completedCount })}
</span>
)}
@@ -47,14 +79,15 @@ export function TransferPanel({ tasks, isOpen, onToggle, onCancel, onRemove }: T
</div>
</button>
{isOpen && tasks.length > 0 && (
{isOpen && orderedTasks.length > 0 && (
<div className="max-h-48 overflow-y-auto border-t border-dark-700/30">
{tasks.map((task) => (
{orderedTasks.map((task) => (
<TransferItem
key={task.id}
task={task}
onCancel={onCancel ? () => onCancel(task.id) : undefined}
onRemove={() => onRemove(task.id)}
onRetry={onRetry ? () => onRetry(task.id) : undefined}
/>
))}
</div>