From fb97d4cb72d0a90dde102b7001603716ee6e4c3b Mon Sep 17 00:00:00 2001
From: Adam Malczewski <github@tradam.dev>
Date: Fri, 22 May 2026 00:19:14 +0900
Subject: feat: agent summoning system, todo improvements, security fixes,
 double-execution bug fix

- Add summon/retrieve tools for spawning child agents in new tabs
  - summon: non-blocking, returns agent_id immediately
  - retrieve: blocking, waits for child to finish, returns result
  - Child tools are intersected with parent permissions (no privilege escalation)
  - Working directory validated to stay within workspace
  - Abort controller stops orphaned agents on tab close
- Rename task_list tool to todo with comprehensive usage guidance in system prompt
- Rename PermissionLog.svelte to ToolPermissions.svelte
- Add 'Summon agents' toggle to tool permissions UI
- Redesign TaskListPanel with DaisyUI checkboxes (indeterminate for in-progress)
- Remove 'blocked' status from task system
- Add tab-created WebSocket event for child agent tab visibility
- Add HMR cleanup for WebSocket connections (close stale connections on hot reload)
- Fix ensureAssistantMessage to not throw on closed tabs
- Fix double tool execution: remove execute from AI SDK tool() in registry.ts
  (agent.ts already executes tools manually via executeToolWithStreaming)
- Fix all pre-existing test failures (missing mocks, stale API signatures)
- Add debug info to copy button (tab ID, injected skills, all tab IDs)
- Add tab ID and tools to conversation copy output
---
 .../src/lib/components/TaskListPanel.svelte        | 103 ++++++++++++---------
 1 file changed, 57 insertions(+), 46 deletions(-)

(limited to 'packages/frontend/src/lib/components/TaskListPanel.svelte')

diff --git a/packages/frontend/src/lib/components/TaskListPanel.svelte b/packages/frontend/src/lib/components/TaskListPanel.svelte
index 5f2ffe2..d70373f 100644
--- a/packages/frontend/src/lib/components/TaskListPanel.svelte
+++ b/packages/frontend/src/lib/components/TaskListPanel.svelte
@@ -1,41 +1,45 @@
 <script lang="ts">
-	interface TaskItem {
-		id: string;
-		title: string;
-		description: string;
-		status: "pending" | "in_progress" | "done" | "blocked";
-	}
+interface TaskItem {
+	id: string;
+	title: string;
+	description: string;
+	status: "pending" | "in_progress" | "done";
+}
 
-	const { tasks }: { tasks: TaskItem[] } = $props();
+const { tasks }: { tasks: TaskItem[] } = $props();
 
-	const doneCount = $derived(tasks.filter((t) => t.status === "done").length);
-	const inProgressCount = $derived(tasks.filter((t) => t.status === "in_progress").length);
+const doneCount = $derived(tasks.filter((t) => t.status === "done").length);
+const inProgressCount = $derived(tasks.filter((t) => t.status === "in_progress").length);
 
-	function badgeClass(status: TaskItem["status"]): string {
-		switch (status) {
-			case "pending":
-				return "badge badge-ghost badge-xs";
-			case "in_progress":
-				return "badge badge-info badge-xs";
-			case "done":
-				return "badge badge-success badge-xs";
-			case "blocked":
-				return "badge badge-warning badge-xs";
-		}
+function checkboxClass(status: TaskItem["status"]): string {
+	switch (status) {
+		case "pending":
+			return "checkbox checkbox-sm rounded-sm checkbox-secondary";
+		case "in_progress":
+			return "checkbox checkbox-sm rounded-sm checkbox-info";
+		case "done":
+			return "checkbox checkbox-sm rounded-sm checkbox-success";
 	}
+}
+
+function isChecked(status: TaskItem["status"]): boolean {
+	return status === "done";
+}
 
-	function statusIcon(status: TaskItem["status"]): string {
-		switch (status) {
-			case "pending":
-				return "⏳";
-			case "in_progress":
-				return "▶";
-			case "done":
-				return "✓";
-			case "blocked":
-				return "⚠";
-		}
+function isIndeterminate(status: TaskItem["status"]): boolean {
+	return status === "in_progress";
+}
+
+function statusLabel(status: TaskItem["status"]): string {
+	switch (status) {
+		case "pending":
+			return "Pending";
+		case "in_progress":
+			return "In progress";
+		case "done":
+			return "Done";
 	}
+}
 </script>
 
 <div class="flex flex-col gap-2">
@@ -43,28 +47,35 @@
 		<p class="text-xs text-base-content/50">No tasks yet.</p>
 	{:else}
 		<p class="text-xs text-base-content/60">
-			{tasks.length} task{tasks.length !== 1 ? "s" : ""}
-			({doneCount} done, {inProgressCount} in progress)
+			{doneCount}/{tasks.length} done{#if inProgressCount > 0}, {inProgressCount} in progress{/if}
 		</p>
-		<ul class="flex flex-col gap-1">
+		<ul class="flex flex-col gap-0.5">
 			{#each tasks as task (task.id)}
-				<li class="flex flex-col gap-0.5 rounded p-1.5 hover:bg-base-200 transition-colors">
-					<div class="flex items-center gap-1.5">
-						<span class={badgeClass(task.status)}>
-							{statusIcon(task.status)}
-						</span>
+				<li
+					class="flex items-start gap-2 rounded p-1.5 transition-colors {task.status === 'done' ? 'opacity-60' : ''}"
+				>
+					<input
+						type="checkbox"
+						class={checkboxClass(task.status)}
+						checked={isChecked(task.status)}
+						indeterminate={isIndeterminate(task.status)}
+						disabled
+						tabindex="-1"
+					/>
+					<div class="flex flex-col gap-0.5 min-w-0">
 						<span
-							class="text-sm leading-tight {task.status === 'in_progress'
-								? 'font-bold'
-								: 'font-medium'}"
+							class="text-xs leading-tight {task.status === 'done'
+								? 'line-through text-base-content/50'
+								: task.status === 'in_progress'
+									? 'font-semibold'
+									: ''}"
 						>
 							{task.title}
 						</span>
+						{#if task.description}
+							<p class="text-xs text-base-content/50 line-clamp-2">{task.description}</p>
+						{/if}
 					</div>
-					{#if task.description}
-						<p class="text-xs text-base-content/60 line-clamp-2 pl-5">{task.description}</p>
-					{/if}
-					<p class="text-xs text-base-content/30 pl-5 font-mono">{task.id}</p>
 				</li>
 			{/each}
 		</ul>
-- 
cgit v1.2.3

