feat: add CLI session sharing functionality

- Implemented share token creation and revocation for CLI sessions.
- Added a new page for viewing shared CLI sessions with SSE support.
- Introduced hooks for fetching and managing CLI session shares.
- Enhanced the IssueTerminalTab component to handle share tokens and display active shares.
- Updated API routes to support fetching and revoking share tokens.
- Added unit tests for the CLI session share manager and rate limiter.
- Updated localization files to include new strings for sharing functionality.
This commit is contained in:
catlog22
2026-02-09 22:57:05 +08:00
parent 362f354f1c
commit d0cdee2e68
18 changed files with 748 additions and 23 deletions

View File

@@ -19,7 +19,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { QueueCard } from '@/components/issue/queue/QueueCard';
import { QueueBoard } from '@/components/issue/queue/QueueBoard';
import { SolutionDrawer } from '@/components/issue/queue/SolutionDrawer';
import { useIssueQueue, useQueueHistory, useQueueMutations } from '@/hooks';
import { useIssueQueue, useIssueQueueById, useQueueHistory, useQueueMutations } from '@/hooks';
import type { QueueItem } from '@/lib/api';
// ========== Loading Skeleton ==========
@@ -74,9 +74,11 @@ function QueueEmptyState() {
export function QueuePanel() {
const { formatMessage } = useIntl();
const [selectedItem, setSelectedItem] = useState<QueueItem | null>(null);
const [selectedQueueId, setSelectedQueueId] = useState<string>('');
const { data: queueData, isLoading, error } = useIssueQueue();
const activeQueueQuery = useIssueQueue();
const { data: historyIndex } = useQueueHistory();
const selectedQueueQuery = useIssueQueueById(selectedQueueId);
const {
activateQueue,
deactivateQueue,
@@ -90,8 +92,12 @@ export function QueuePanel() {
isSplitting,
} = useQueueMutations();
// Get queue data with proper type
const queue = queueData;
const queue = selectedQueueId && selectedQueueQuery.data ? selectedQueueQuery.data : activeQueueQuery.data;
const isLoading =
activeQueueQuery.isLoading ||
(selectedQueueId ? selectedQueueQuery.isLoading && !selectedQueueQuery.data : false);
const error = activeQueueQuery.error || selectedQueueQuery.error;
const taskCount = queue?.tasks?.length || 0;
const solutionCount = queue?.solutions?.length || 0;
const conflictCount = queue?.conflicts?.length || 0;
@@ -100,13 +106,13 @@ export function QueuePanel() {
const activeQueueId = historyIndex?.active_queue_id || null;
const activeQueueIds = historyIndex?.active_queue_ids || [];
const queueId = queue?.id;
const [selectedQueueId, setSelectedQueueId] = useState<string>('');
// Keep selector in sync with active queue id
useEffect(() => {
if (selectedQueueId) return;
if (activeQueueId) setSelectedQueueId(activeQueueId);
else if (queueId) setSelectedQueueId(queueId);
}, [activeQueueId, queueId]);
}, [activeQueueId, queueId, selectedQueueId]);
const handleActivate = async (queueId: string) => {
try {