diff --git a/src/app/api/sessions/route.ts b/src/app/api/sessions/route.ts index 08e73eb..5ce93b8 100644 --- a/src/app/api/sessions/route.ts +++ b/src/app/api/sessions/route.ts @@ -12,14 +12,26 @@ export async function GET(request: NextRequest) { try { const gatewaySessions = getAllGatewaySessions() - // If gateway sessions exist, return those + // If gateway sessions exist, deduplicate and return those if (gatewaySessions.length > 0) { - const sessions = gatewaySessions.map((s) => { + // Deduplicate by sessionId — OpenClaw tracks cron runs under the same + // session ID as the parent session, causing duplicate React keys (#80). + // Keep the most recently updated entry when duplicates exist. + const sessionMap = new Map() + for (const s of gatewaySessions) { + const id = s.sessionId || `${s.agent}:${s.key}` + const existing = sessionMap.get(id) + if (!existing || s.updatedAt > existing.updatedAt) { + sessionMap.set(id, s) + } + } + + const sessions = Array.from(sessionMap.values()).map((s) => { const total = s.totalTokens || 0 const context = s.contextTokens || 35000 const pct = context > 0 ? Math.round((total / context) * 100) : 0 return { - id: s.sessionId || s.key, + id: s.sessionId || `${s.agent}:${s.key}`, key: s.key, agent: s.agent, kind: s.chatType || 'unknown', diff --git a/src/components/panels/task-board-panel.tsx b/src/components/panels/task-board-panel.tsx index acf3e5f..e2b5418 100644 --- a/src/components/panels/task-board-panel.tsx +++ b/src/components/panels/task-board-panel.tsx @@ -70,6 +70,7 @@ export function TaskBoardPanel() { const [selectedTask, setSelectedTask] = useState(null) const [draggedTask, setDraggedTask] = useState(null) const [showCreateModal, setShowCreateModal] = useState(false) + const [editingTask, setEditingTask] = useState(null) const dragCounter = useRef(0) // Fetch tasks and agents @@ -416,12 +417,16 @@ export function TaskBoardPanel() { {/* Task Detail Modal */} - {selectedTask && ( + {selectedTask && !editingTask && ( setSelectedTask(null)} onUpdate={fetchData} + onEdit={(taskToEdit) => { + setEditingTask(taskToEdit) + setSelectedTask(null) + }} /> )} @@ -433,21 +438,33 @@ export function TaskBoardPanel() { onCreated={fetchData} /> )} + + {/* Edit Task Modal */} + {editingTask && ( + setEditingTask(null)} + onUpdated={() => { fetchData(); setEditingTask(null) }} + /> + )} ) } // Task Detail Modal Component (placeholder - would be implemented separately) -function TaskDetailModal({ - task, - agents, - onClose, - onUpdate -}: { +function TaskDetailModal({ + task, + agents, + onClose, + onUpdate, + onEdit +}: { task: Task agents: Agent[] onClose: () => void onUpdate: () => void + onEdit: (task: Task) => void }) { const [comments, setComments] = useState([]) const [loadingComments, setLoadingComments] = useState(false) @@ -588,12 +605,20 @@ function TaskDetailModal({

{task.title}

- +
+ + +

{task.description || 'No description'}

@@ -795,7 +820,9 @@ function CreateTaskModal({ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - + + if (!formData.title.trim()) return + try { const response = await fetch('/api/tasks', { method: 'POST', @@ -807,8 +834,12 @@ function CreateTaskModal({ }) }) - if (!response.ok) throw new Error('Failed to create task') - + if (!response.ok) { + const errorData = await response.json() + const errorMsg = errorData.details ? errorData.details.join(', ') : errorData.error + throw new Error(errorMsg) + } + onCreated() onClose() } catch (error) { @@ -908,3 +939,161 @@ function CreateTaskModal({
) } + +// Edit Task Modal Component +function EditTaskModal({ + task, + agents, + onClose, + onUpdated +}: { + task: Task + agents: Agent[] + onClose: () => void + onUpdated: () => void +}) { + const [formData, setFormData] = useState({ + title: task.title, + description: task.description || '', + priority: task.priority, + status: task.status, + assigned_to: task.assigned_to || '', + tags: task.tags ? task.tags.join(', ') : '', + }) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!formData.title.trim()) return + + try { + const response = await fetch(`/api/tasks/${task.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + ...formData, + tags: formData.tags ? formData.tags.split(',').map(t => t.trim()) : [], + assigned_to: formData.assigned_to || undefined + }) + }) + + if (!response.ok) { + const errorData = await response.json() + const errorMsg = errorData.details ? errorData.details.join(', ') : errorData.error + throw new Error(errorMsg) + } + + onUpdated() + } catch (error) { + console.error('Error updating task:', error) + } + } + + return ( +
+
+
+

Edit Task

+ +
+
+ + setFormData(prev => ({ ...prev, title: e.target.value }))} + className="w-full bg-surface-1 text-foreground border border-border rounded-md px-3 py-2 focus:outline-none focus:ring-1 focus:ring-primary/50" + required + /> +
+ +
+ +