Skip to content

Commit bc156f1

Browse files
joewinkeclaude
andcommitted
task(jat-2rd47): Add Resume option to TasksOpen right-click context menu
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1e987ab commit bc156f1

File tree

4 files changed

+48
-9
lines changed

4 files changed

+48
-9
lines changed

ide/src/lib/components/sessions/TasksOpen.svelte

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@
8585
onTaskClick = () => {},
8686
onAddTask = null,
8787
onFilterCountsChange = (_counts: Record<string, number>) => {},
88-
mobile = false
88+
mobile = false,
89+
resumableTasks = new Map<string, string>()
8990
}: {
9091
tasks: Task[];
9192
loading: boolean;
@@ -104,6 +105,8 @@
104105
onAddTask?: (() => void) | null;
105106
onFilterCountsChange?: (counts: Record<string, number>) => void;
106107
mobile?: boolean;
108+
/** Map of taskId → agentName for tasks with resumable sessions */
109+
resumableTasks?: Map<string, string>;
107110
} = $props();
108111
109112
// Alt key tracking for agent picker
@@ -1234,11 +1237,12 @@
12341237
let resumingTaskId = $state<string | null>(null);
12351238
12361239
async function handleResumeTask(task: Task) {
1237-
if (!task.assignee) return;
1240+
const agentName = task.assignee || resumableTasks.get(task.id);
1241+
if (!agentName) return;
12381242
closeContextMenu();
12391243
resumingTaskId = task.id;
12401244
try {
1241-
const response = await fetch(`/api/sessions/${task.assignee}/resume`, {
1245+
const response = await fetch(`/api/sessions/${agentName}/resume`, {
12421246
method: 'POST',
12431247
headers: { 'Content-Type': 'application/json' },
12441248
});
@@ -1250,7 +1254,7 @@
12501254
addToast({ message: 'Resume failed', type: 'error', details: data.message || data.error || 'Could not resume session' });
12511255
}
12521256
} else {
1253-
addToast({ message: 'Session resumed', type: 'success', details: `Resuming ${task.assignee}'s session` });
1257+
addToast({ message: 'Session resumed', type: 'success', details: `Resuming ${agentName}'s session` });
12541258
broadcastTaskEvent('session-resumed', task.id);
12551259
}
12561260
} catch (err) {
@@ -2002,8 +2006,8 @@
20022006
<span>Launch</span>
20032007
</button>
20042008

2005-
<!-- Resume (only for tasks with a previous assignee) -->
2006-
{#if ctxTask.assignee}
2009+
<!-- Resume (only for tasks with a resumable session) -->
2010+
{#if ctxTask.assignee || resumableTasks.has(ctxTask.id)}
20072011
<button class="task-context-menu-item" onmouseenter={() => { statusSubmenuOpen = false; prioritySubmenuOpen = false; epicSubmenuOpen = false; }} onclick={() => { const t = ctxTask!; handleResumeTask(t); ctxTask = null; }} disabled={resumingTaskId === ctxTask.id}>
20082012
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
20092013
<path d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z" />

ide/src/routes/api/tasks/sessions/+server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,8 @@ export const POST: RequestHandler = async ({ request }) => {
243243
return json({ sessions: {} });
244244
}
245245

246-
// Limit to prevent abuse
247-
const limitedTaskIds = taskIds.slice(0, 100);
246+
// Limit to prevent abuse (scanning /tmp is O(files) not O(taskIds), so this is generous)
247+
const limitedTaskIds = taskIds.slice(0, 500);
248248
const taskIdSet = new Set(limitedTaskIds);
249249

250250
const projectPath = getProjectPath();

ide/src/routes/tasks/+page.svelte

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,37 @@
10691069
);
10701070
}
10711071
1072+
// Map of taskId → agentName for tasks with previous sessions (passed to TasksOpen context menu)
1073+
let resumableTasks = $state(new Map<string, string>());
1074+
1075+
async function fetchResumableTasks() {
1076+
try {
1077+
const taskIds = openTasks
1078+
.filter(t => t.status === 'open' && t.issue_type !== 'epic')
1079+
.map(t => t.id);
1080+
if (taskIds.length === 0) return;
1081+
const response = await fetch('/api/tasks/sessions', {
1082+
method: 'POST',
1083+
headers: { 'Content-Type': 'application/json' },
1084+
body: JSON.stringify({ taskIds })
1085+
});
1086+
if (!response.ok) return;
1087+
const data = await response.json();
1088+
const map = new Map<string, string>();
1089+
for (const [taskId, sessions] of Object.entries(data.sessions || {})) {
1090+
const arr = sessions as Array<{ agentName: string; sessionId: string | null; isOnline: boolean }>;
1091+
// Find the first offline session (resume API will locate the session ID)
1092+
const resumable = arr.find(s => !s.isOnline);
1093+
if (resumable) {
1094+
map.set(taskId, resumable.agentName);
1095+
}
1096+
}
1097+
resumableTasks = map;
1098+
} catch {
1099+
// Silent fail
1100+
}
1101+
}
1102+
10721103
// Critical data for initial render (tasks + sessions clear the loading skeleton)
10731104
async function fetchCriticalData() {
10741105
await Promise.all([
@@ -1088,6 +1119,7 @@
10881119
fetchCompletedMemory(),
10891120
fetchBrowserSessions(),
10901121
fetchTaskImages(),
1122+
fetchResumableTasks(),
10911123
]);
10921124
}
10931125
@@ -2211,6 +2243,7 @@
22112243
{taskIntegrations}
22122244
{taskImages}
22132245
{epicsReadyForVerification}
2246+
{resumableTasks}
22142247
highlightedTaskIds={isSwarmHovered || isSwarmSpawning ? launchableIds : new Set()}
22152248
onSpawnTask={spawnTask as any}
22162249
onRetry={fetchTasks}
@@ -2279,6 +2312,7 @@
22792312
{projectColors}
22802313
{taskImages}
22812314
{epicsReadyForVerification}
2315+
{resumableTasks}
22822316
onSpawnTask={spawnTask as any}
22832317
onRetry={fetchTasks}
22842318
onTaskClick={(taskId) =>

tools/scripts/jat-complete-bundle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,9 +429,10 @@ fi
429429
# Ensure required fields are present
430430
BUNDLE_JSON=$(echo "$BUNDLE_JSON" | jq \
431431
--arg taskId "$TASK_ID" \
432+
--arg taskTitle "$TASK_TITLE" \
432433
--arg agentName "$AGENT_NAME" \
433434
--arg mode "$COMPLETION_MODE" \
434-
'. + {taskId: $taskId, agentName: $agentName, completionMode: $mode}')
435+
'. + {taskId: $taskId, taskTitle: $taskTitle, agentName: $agentName, completionMode: $mode}')
435436

436437
# Add nextTaskId if provided
437438
if [[ -n "$NEXT_TASK_ID" ]]; then

0 commit comments

Comments
 (0)