@parhelia/core 0.1.12272 → 0.1.12285

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/agents-view/AgentCard.js +26 -2
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/components/MarkdownDisplay.d.ts +1 -1
  4. package/dist/components/MarkdownDisplay.js +6 -0
  5. package/dist/components/MarkdownDisplay.js.map +1 -1
  6. package/dist/config/notificationRoutes.js +19 -0
  7. package/dist/config/notificationRoutes.js.map +1 -1
  8. package/dist/editor/ConcurrentUserLimitDialog.d.ts +1 -1
  9. package/dist/editor/ConcurrentUserLimitDialog.js +46 -40
  10. package/dist/editor/ConcurrentUserLimitDialog.js.map +1 -1
  11. package/dist/editor/ai/AgentTerminal.d.ts +3 -1
  12. package/dist/editor/ai/AgentTerminal.js +92 -26
  13. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  14. package/dist/editor/ai/dialogs/AgentDialogHandler.js +32 -22
  15. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  16. package/dist/editor/ai/dialogs/QuestionnaireInline.js +4 -2
  17. package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
  18. package/dist/editor/client/EditorShell.js +6 -1
  19. package/dist/editor/client/EditorShell.js.map +1 -1
  20. package/dist/editor/notifications/WatchButton.js +3 -3
  21. package/dist/editor/notifications/WatchButton.js.map +1 -1
  22. package/dist/editor/notifications/useNotifications.js +19 -1
  23. package/dist/editor/notifications/useNotifications.js.map +1 -1
  24. package/dist/editor/services/agentService.d.ts +1 -0
  25. package/dist/editor/services/agentService.js.map +1 -1
  26. package/dist/editor/settings/About.js +23 -8
  27. package/dist/editor/settings/About.js.map +1 -1
  28. package/dist/licensing/LicenseOverlay.js +1 -3
  29. package/dist/licensing/LicenseOverlay.js.map +1 -1
  30. package/dist/licensing/types.d.ts +2 -0
  31. package/dist/licensing/types.js.map +1 -1
  32. package/dist/revision.d.ts +2 -2
  33. package/dist/revision.js +2 -2
  34. package/dist/task-board/TaskBoardWorkspace.js +390 -443
  35. package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
  36. package/dist/task-board/components/CreateProjectDialog.js +37 -17
  37. package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
  38. package/dist/task-board/components/ProjectSelector.js +1 -1
  39. package/dist/task-board/components/ProjectSelector.js.map +1 -1
  40. package/dist/task-board/components/TaskAgentPanel.js +2 -6
  41. package/dist/task-board/components/TaskAgentPanel.js.map +1 -1
  42. package/dist/task-board/components/TaskBoardMyTasksSidebar.js +1 -1
  43. package/dist/task-board/components/TaskBoardMyTasksSidebar.js.map +1 -1
  44. package/dist/task-board/components/TaskCard.d.ts +1 -2
  45. package/dist/task-board/components/TaskCard.js +7 -3
  46. package/dist/task-board/components/TaskCard.js.map +1 -1
  47. package/dist/task-board/components/TaskDetailPanel.d.ts +0 -2
  48. package/dist/task-board/components/TaskDetailPanel.js +5 -12
  49. package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
  50. package/dist/task-board/components/TaskReviewActions.d.ts +3 -0
  51. package/dist/task-board/components/TaskReviewActions.js +8 -7
  52. package/dist/task-board/components/TaskReviewActions.js.map +1 -1
  53. package/dist/task-board/components/TaskRow.d.ts +0 -2
  54. package/dist/task-board/components/TaskRow.js +7 -4
  55. package/dist/task-board/components/TaskRow.js.map +1 -1
  56. package/dist/task-board/components/WizardCommunicationCenter.d.ts +3 -0
  57. package/dist/task-board/components/WizardCommunicationCenter.js +181 -22
  58. package/dist/task-board/components/WizardCommunicationCenter.js.map +1 -1
  59. package/dist/task-board/index.d.ts +1 -1
  60. package/dist/task-board/services/taskService.d.ts +4 -1
  61. package/dist/task-board/services/taskService.js +3 -0
  62. package/dist/task-board/services/taskService.js.map +1 -1
  63. package/dist/task-board/taskAgentLink.js +5 -16
  64. package/dist/task-board/taskAgentLink.js.map +1 -1
  65. package/dist/task-board/taskExecutionRecords.d.ts +5 -0
  66. package/dist/task-board/taskExecutionRecords.js +49 -0
  67. package/dist/task-board/taskExecutionRecords.js.map +1 -0
  68. package/dist/task-board/taskExecutionStatus.d.ts +7 -3
  69. package/dist/task-board/taskExecutionStatus.js +75 -113
  70. package/dist/task-board/taskExecutionStatus.js.map +1 -1
  71. package/dist/task-board/taskStatus.d.ts +9 -2
  72. package/dist/task-board/taskStatus.js +53 -8
  73. package/dist/task-board/taskStatus.js.map +1 -1
  74. package/dist/task-board/types.d.ts +30 -5
  75. package/dist/task-board/views/KanbanView.d.ts +0 -2
  76. package/dist/task-board/views/KanbanView.js +14 -11
  77. package/dist/task-board/views/KanbanView.js.map +1 -1
  78. package/dist/task-board/views/ListView.d.ts +0 -2
  79. package/dist/task-board/views/ListView.js +3 -13
  80. package/dist/task-board/views/ListView.js.map +1 -1
  81. package/dist/task-board/views/WizardView.d.ts +0 -2
  82. package/dist/task-board/views/WizardView.js +54 -61
  83. package/dist/task-board/views/WizardView.js.map +1 -1
  84. package/package.json +1 -1
  85. package/styles.css +5 -0
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { toast } from "sonner";
4
4
  import { Splitter } from "../editor/ui/Splitter";
5
- import { deleteProject, getProjects, getTasks, getDependencies, runOrchestrator, triggerPlanning, updateProject, updateTask, } from "./services/taskService";
6
- import { getActiveAgents, getAgent, } from "../editor/services/agentService";
5
+ import { deleteProject, getExecutionState, getProjects, getTasks, getDependencies, runOrchestrator, triggerPlanning, updateProject, updateTask, } from "./services/taskService";
6
+ import { getAgent, } from "../editor/services/agentService";
7
7
  import { loadAiProfiles } from "../editor/services/aiService";
8
8
  import { useEditContext } from "../editor/client/editContext";
9
9
  import { KanbanView } from "./views/KanbanView";
@@ -19,14 +19,27 @@ import { CreateProjectDialog } from "./components/CreateProjectDialog";
19
19
  import { ProjectSettingsDialog } from "./components/ProjectSettingsDialog";
20
20
  import { EditorSlotContextProvider } from "../editor/views/editorSlotContext";
21
21
  import { SingleEditView } from "../editor/views/SingleEditView";
22
+ import { useEditorSlotContext } from "../editor/views/editorSlotContext";
22
23
  import { Select } from "../components/ui/select";
23
24
  import { getLinkedAgentId } from "./taskAgentLink";
24
25
  import { setTaskBoardNavState, resetTaskBoardNavState, } from "./taskBoardNavStore";
25
26
  import { flattenProjectsHierarchy } from "./utils/projectHierarchy";
26
- import { normalizeTaskStatus } from "./taskStatus";
27
+ import { normalizeTaskStatus, } from "./taskStatus";
27
28
  import { getTaskExecutionDisplay, } from "./taskExecutionStatus";
29
+ import { indexTaskExecutionRecords, overlayTaskExecutionList, } from "./taskExecutionRecords";
28
30
  import { WizardCommunicationCenter } from "./components/WizardCommunicationCenter";
29
31
  import { Loader2 } from "lucide-react";
32
+ import { MainContentTree } from "../editor/sidebar/MainContentTree";
33
+ function TaskboardPreviewEditor({ itemDescriptor, }) {
34
+ const slotContext = useEditorSlotContext({
35
+ slotId: "taskboard-preview-slot",
36
+ itemDescriptor,
37
+ });
38
+ if (!slotContext) {
39
+ return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm font-medium", children: "Editor preview unavailable" }), _jsx("div", { className: "text-muted-foreground text-xs", children: "Open the editor context item to preview and edit it here." })] }));
40
+ }
41
+ return (_jsx(EditorSlotContextProvider, { value: slotContext, children: _jsx(SingleEditView, { compareView: false, name: "taskboard-workspace-preview", view: "primary" }) }));
42
+ }
30
43
  const TASKBOARD_PROJECT_QUERY_KEY = "tbProjectId";
31
44
  const TASKBOARD_TASK_QUERY_KEY = "tbTaskId";
32
45
  const TASKBOARD_VIEW_QUERY_KEY = "tbView";
@@ -54,84 +67,10 @@ function getTaskBoardStateFromUrl() {
54
67
  isWizardMode: getInitialWizardMode(),
55
68
  };
56
69
  }
57
- function normalizeAgentStatus(status) {
58
- if (status === undefined || status === null)
59
- return undefined;
60
- if (typeof status === "number") {
61
- return status;
62
- }
63
- const normalized = String(status)
64
- .trim()
65
- .replace(/[^a-z0-9]/gi, "")
66
- .toLowerCase();
67
- switch (normalized) {
68
- case "running":
69
- return "running";
70
- case "waitingforapproval":
71
- return "waitingForApproval";
72
- case "waitingforinput":
73
- return "waitingForInput";
74
- case "costlimitreached":
75
- return "costLimitReached";
76
- case "error":
77
- return "error";
78
- case "idle":
79
- return "idle";
80
- case "completed":
81
- return "completed";
82
- case "closed":
83
- return "closed";
84
- case "cancelled":
85
- return "cancelled";
86
- case "new":
87
- return "new";
88
- }
89
- switch (status) {
90
- case "Running":
91
- return "running";
92
- case "WaitingForApproval":
93
- return "waitingForApproval";
94
- case "WaitingForInput":
95
- return "waitingForInput";
96
- case "CostLimitReached":
97
- return "costLimitReached";
98
- case "Error":
99
- return "error";
100
- case "Idle":
101
- return "idle";
102
- case "Completed":
103
- return "completed";
104
- case "Closed":
105
- return "closed";
106
- case "Cancelled":
107
- return "cancelled";
108
- case "New":
109
- return "new";
110
- default:
111
- return status;
112
- }
113
- }
114
- function isClosedAgentStatus(status) {
115
- if (status === undefined || status === null)
116
- return false;
117
- if (typeof status === "number")
118
- return status === 5;
119
- return (String(status)
120
- .replace(/[^a-z0-9]/gi, "")
121
- .toLowerCase() === "closed");
122
- }
123
- function getAgentStatusById(agentStatusesById, agentId) {
124
- if (!agentId)
70
+ function getTaskExecutionRecordById(recordsByTaskId, taskId) {
71
+ if (!taskId)
125
72
  return undefined;
126
- return agentStatusesById[agentId] ?? agentStatusesById[agentId.toLowerCase()];
127
- }
128
- function getTaskAgentStatus(task, agentStatusesById) {
129
- const linkedAgentId = getLinkedAgentId(task ?? null);
130
- const liveStatus = getAgentStatusById(agentStatusesById, linkedAgentId);
131
- if (liveStatus !== undefined) {
132
- return liveStatus;
133
- }
134
- return normalizeAgentStatus(task?.agentStatus);
73
+ return recordsByTaskId[taskId];
135
74
  }
136
75
  function addAgentDisplayNames(taskList, agentProfileTitlesById) {
137
76
  return taskList.map((task) => {
@@ -158,7 +97,7 @@ function buildTaskCounts(taskList) {
158
97
  done: 0,
159
98
  };
160
99
  for (const task of taskList) {
161
- const normalizedStatus = normalizeTaskStatus(task.status, task.executionStatus);
100
+ const normalizedStatus = normalizeTaskStatus(task.status, task.executionState);
162
101
  if (normalizedStatus === "Todo")
163
102
  counts.todo += 1;
164
103
  else if (normalizedStatus === "InProgress")
@@ -188,7 +127,9 @@ export function TaskBoardWorkspace() {
188
127
  const [tasks, setTasks] = useState([]);
189
128
  const [isTasksLoading, setIsTasksLoading] = useState(false);
190
129
  const [dependencies, setDependencies] = useState([]);
130
+ const [executionRecordsByTaskId, setExecutionRecordsByTaskId] = useState({});
191
131
  const [selectedTaskId, setSelectedTaskId] = useState(() => getInitialQueryValue(TASKBOARD_TASK_QUERY_KEY));
132
+ const [selectedTaskSource, setSelectedTaskSource] = useState(() => getInitialQueryValue(TASKBOARD_TASK_QUERY_KEY) ? "system" : null);
192
133
  const [activeTab, setActiveTab] = useState(() => getInitialViewTabIndex());
193
134
  const [isWizardMode, setIsWizardMode] = useState(() => getInitialWizardMode());
194
135
  const [createDialogOpen, setCreateDialogOpen] = useState(false);
@@ -200,10 +141,10 @@ export function TaskBoardWorkspace() {
200
141
  const [showAllProjects, setShowAllProjects] = useState(false);
201
142
  const [isProjectsLoading, setIsProjectsLoading] = useState(false);
202
143
  const [projectViewLoadingId, setProjectViewLoadingId] = useState(null);
203
- const [agentStatusesById, setAgentStatusesById] = useState({});
204
144
  const [agentProfileTitlesById, setAgentProfileTitlesById] = useState({});
205
145
  const [subprojectTaskCounts, setSubprojectTaskCounts] = useState({});
206
146
  const [subprojectTasksByProjectId, setSubprojectTasksByProjectId] = useState({});
147
+ const [subprojectExecutionRecordsByProjectId, setSubprojectExecutionRecordsByProjectId] = useState({});
207
148
  const [subprojectCountsLoading, setSubprojectCountsLoading] = useState(false);
208
149
  const [previewItemName, setPreviewItemName] = useState("");
209
150
  const [previewItemPath, setPreviewItemPath] = useState("");
@@ -212,9 +153,15 @@ export function TaskBoardWorkspace() {
212
153
  const [contextItemPathsByKey, setContextItemPathsByKey] = useState({});
213
154
  const [wizardPinnedTaskId, setWizardPinnedTaskId] = useState(null);
214
155
  const [wizardForceOverview, setWizardForceOverview] = useState(false);
156
+ const setTaskSelection = useCallback((taskId, options) => {
157
+ setSelectedTaskId(taskId);
158
+ setSelectedTaskSource(taskId ? (options?.source ?? "system") : null);
159
+ if (options?.pinInWizard !== undefined) {
160
+ setWizardPinnedTaskId(options.pinInWizard ? taskId : null);
161
+ }
162
+ }, []);
215
163
  const wsRefreshTimeoutRef = useRef(null);
216
164
  const wsSubprojectRefreshTimeoutRef = useRef(null);
217
- const wsProjectRefreshTimeoutRef = useRef(null);
218
165
  const wizardCloseTransitionTimeoutRef = useRef(null);
219
166
  const projectsRequestCountRef = useRef(0);
220
167
  const tasksRequestCountRef = useRef(0);
@@ -223,23 +170,23 @@ export function TaskBoardWorkspace() {
223
170
  const latestSubprojectCountsProjectIdRef = useRef(null);
224
171
  const refreshProjectsRef = useRef(null);
225
172
  const refreshTasksRef = useRef(null);
173
+ const refreshExecutionStateRef = useRef(null);
226
174
  const refreshDependenciesRef = useRef(null);
227
175
  const refreshSubprojectTaskCountsRef = useRef(null);
228
176
  const selectedProjectIdRef = useRef(null);
229
177
  const directSubprojectIdsRef = useRef(new Set());
230
178
  const tasksRef = useRef([]);
231
179
  const previousTaskStatusesRef = useRef({});
180
+ const previousWizardAttentionSignatureRef = useRef("");
232
181
  const autoSelectedProjectIdsRef = useRef(new Set());
233
- const taskBoardSubscribedAgentIdsRef = useRef(new Set());
234
- const taskBoardSocketVersionRef = useRef(null);
235
182
  const syncTaskBoardStateFromUrl = useCallback(() => {
236
183
  const nextState = getTaskBoardStateFromUrl();
237
184
  setSelectedProjectId(nextState.projectId);
238
- setSelectedTaskId(nextState.taskId);
185
+ setTaskSelection(nextState.taskId, { source: "system" });
239
186
  setActiveTab(nextState.activeTab);
240
187
  setIsWizardMode(nextState.isWizardMode);
241
188
  setWizardForceOverview(false);
242
- }, []);
189
+ }, [setTaskSelection]);
243
190
  useEffect(() => {
244
191
  let cancelled = false;
245
192
  loadAiProfiles()
@@ -262,10 +209,10 @@ export function TaskBoardWorkspace() {
262
209
  cancelled = true;
263
210
  };
264
211
  }, []);
265
- const tasksWithDisplayAssignees = useMemo(() => addAgentDisplayNames(tasks, agentProfileTitlesById), [tasks, agentProfileTitlesById]);
212
+ const tasksWithDisplayAssignees = useMemo(() => addAgentDisplayNames(overlayTaskExecutionList(tasks, executionRecordsByTaskId), agentProfileTitlesById), [tasks, executionRecordsByTaskId, agentProfileTitlesById]);
266
213
  const selectedProject = useMemo(() => projects.find((p) => p.project.projectId === selectedProjectId) ?? null, [projects, selectedProjectId]);
267
214
  const settingsProject = useMemo(() => projects.find((p) => p.project.projectId === settingsProjectId) ?? null, [projects, settingsProjectId]);
268
- const selectedProjectTaskCounts = useMemo(() => buildTaskCounts(tasks), [tasks]);
215
+ const selectedProjectTaskCounts = useMemo(() => buildTaskCounts(tasksWithDisplayAssignees), [tasksWithDisplayAssignees]);
269
216
  const directSubprojects = useMemo(() => selectedProjectId
270
217
  ? projects
271
218
  .filter((project) => project.project.parentProjectId === selectedProjectId)
@@ -274,10 +221,14 @@ export function TaskBoardWorkspace() {
274
221
  const subprojectTasksWithDisplayAssignees = useMemo(() => {
275
222
  const nextTasks = {};
276
223
  for (const [projectId, taskList] of Object.entries(subprojectTasksByProjectId)) {
277
- nextTasks[projectId] = addAgentDisplayNames(taskList, agentProfileTitlesById);
224
+ nextTasks[projectId] = addAgentDisplayNames(overlayTaskExecutionList(taskList, subprojectExecutionRecordsByProjectId[projectId] || {}), agentProfileTitlesById);
278
225
  }
279
226
  return nextTasks;
280
- }, [subprojectTasksByProjectId, agentProfileTitlesById]);
227
+ }, [
228
+ subprojectTasksByProjectId,
229
+ subprojectExecutionRecordsByProjectId,
230
+ agentProfileTitlesById,
231
+ ]);
281
232
  const wizardSubprojectTaskLists = useMemo(() => directSubprojects.map((subproject) => ({
282
233
  projectId: subproject.project.projectId,
283
234
  title: subproject.project.title,
@@ -288,6 +239,15 @@ export function TaskBoardWorkspace() {
288
239
  ...tasksWithDisplayAssignees,
289
240
  ...wizardSubprojectTaskLists.flatMap((subproject) => subproject.tasks),
290
241
  ], [tasksWithDisplayAssignees, wizardSubprojectTaskLists]);
242
+ const wizardExecutionRecordsByTaskId = useMemo(() => {
243
+ const merged = {
244
+ ...executionRecordsByTaskId,
245
+ };
246
+ for (const records of Object.values(subprojectExecutionRecordsByProjectId)) {
247
+ Object.assign(merged, records);
248
+ }
249
+ return merged;
250
+ }, [executionRecordsByTaskId, subprojectExecutionRecordsByProjectId]);
291
251
  const taskSelectionUniverse = useMemo(() => isWizardMode
292
252
  ? wizardTasksWithDisplayAssignees
293
253
  : tasksWithDisplayAssignees, [isWizardMode, wizardTasksWithDisplayAssignees, tasksWithDisplayAssignees]);
@@ -377,18 +337,19 @@ export function TaskBoardWorkspace() {
377
337
  const handleSelectProject = useCallback((projectId) => {
378
338
  setSelectedProjectId((previousProjectId) => {
379
339
  if (previousProjectId !== projectId) {
380
- setSelectedTaskId(null);
340
+ setTaskSelection(null, { pinInWizard: false });
381
341
  setWizardForceOverview(false);
382
342
  }
383
343
  return projectId;
384
344
  });
385
- }, []);
345
+ }, [setTaskSelection]);
386
346
  const clearSelectedProjectData = useCallback(() => {
387
347
  setTasks([]);
388
348
  setDependencies([]);
349
+ setExecutionRecordsByTaskId({});
389
350
  setSubprojectTaskCounts({});
390
351
  setSubprojectTasksByProjectId({});
391
- setAgentStatusesById({});
352
+ setSubprojectExecutionRecordsByProjectId({});
392
353
  }, []);
393
354
  const isPlanning = selectedProject?.project.status === "Planning";
394
355
  const selectedTask = useMemo(() => taskSelectionUniverse.find((t) => t.taskId === selectedTaskId) ?? null, [taskSelectionUniverse, selectedTaskId]);
@@ -421,7 +382,8 @@ export function TaskBoardWorkspace() {
421
382
  const taskById = new Map(tasksWithDisplayAssignees.map((task) => [task.taskId, task]));
422
383
  return blockerIds.some((blockerId) => {
423
384
  const blockerTask = taskById.get(blockerId);
424
- return !blockerTask || blockerTask.status !== "Done";
385
+ return (!blockerTask ||
386
+ normalizeTaskStatus(blockerTask.status, blockerTask.executionState) !== "Done");
425
387
  });
426
388
  }, [
427
389
  selectedTask,
@@ -450,7 +412,7 @@ export function TaskBoardWorkspace() {
450
412
  const selectedTaskStatus = useMemo(() => {
451
413
  if (!selectedTask)
452
414
  return null;
453
- return normalizeTaskStatus(selectedTask.status, selectedTask.executionStatus);
415
+ return normalizeTaskStatus(selectedTask.status, selectedTask.executionState);
454
416
  }, [selectedTask]);
455
417
  const selectedTaskHasResultData = useMemo(() => {
456
418
  return !!selectedTask?.resultData?.trim();
@@ -466,18 +428,9 @@ export function TaskBoardWorkspace() {
466
428
  selectedTaskStatus,
467
429
  selectedTaskHasResultData,
468
430
  ]);
469
- const currentAgentStatus = useMemo(() => {
470
- return getTaskAgentStatus(selectedTask, agentStatusesById);
471
- }, [selectedTask, agentStatusesById]);
472
431
  const displayAgentId = useMemo(() => {
473
- if (!currentAgentId)
474
- return null;
475
- if (isClosedAgentStatus(currentAgentStatus) &&
476
- selectedTaskStatus !== "Done") {
477
- return null;
478
- }
479
432
  return currentAgentId;
480
- }, [currentAgentId, currentAgentStatus, selectedTaskStatus]);
433
+ }, [currentAgentId]);
481
434
  useEffect(() => {
482
435
  if (isMobile && selectedTaskId) {
483
436
  setMobileActiveTab(1); // Task tab
@@ -520,55 +473,34 @@ export function TaskBoardWorkspace() {
520
473
  return a.sortOrder - b.sortOrder;
521
474
  return a.createdAt.localeCompare(b.createdAt);
522
475
  });
523
- const taskById = new Map(sortedTasks.map((task) => [task.taskId, task]));
524
- const blockersByTaskId = new Map();
525
- for (const dependency of dependencies) {
526
- if (dependency.dependencyType !== "BlockedBy")
527
- continue;
528
- const blockerIds = blockersByTaskId.get(dependency.taskId) || [];
529
- blockerIds.push(dependency.dependsOnTaskId);
530
- blockersByTaskId.set(dependency.taskId, blockerIds);
531
- }
532
- const openTasks = sortedTasks.filter((task) => normalizeTaskStatus(task.status, task.executionStatus) !== "Done");
533
- const blockedTaskIds = new Set();
534
- for (const task of openTasks) {
535
- const blockerIds = blockersByTaskId.get(task.taskId) || [];
536
- const isBlocked = blockerIds.some((blockerId) => {
537
- const blockerTask = taskById.get(blockerId);
538
- if (!blockerTask)
539
- return true;
540
- return (normalizeTaskStatus(blockerTask.status, blockerTask.executionStatus) !== "Done");
541
- });
542
- if (isBlocked)
543
- blockedTaskIds.add(task.taskId);
544
- }
476
+ const openTasks = sortedTasks.filter((task) => normalizeTaskStatus(task.status, task.executionState) !== "Done");
545
477
  let runningCount = 0;
546
478
  let approvalCount = 0;
479
+ let blockedCount = 0;
547
480
  let attentionTask = null;
548
481
  let attentionAgentId = null;
549
- let attentionAgentStatus;
550
482
  let attentionExecutionDisplay = null;
551
483
  for (const task of openTasks) {
552
- const normalizedTaskStatus = normalizeTaskStatus(task.status, task.executionStatus);
484
+ const record = getTaskExecutionRecordById(wizardExecutionRecordsByTaskId, task.taskId);
485
+ const executionStateFromBackend = record?.executionState ?? task.executionState;
486
+ const normalizedTaskStatus = normalizeTaskStatus(task.status, executionStateFromBackend);
553
487
  const agentId = getLinkedAgentId(task);
554
- const agentStatus = getTaskAgentStatus(task, agentStatusesById);
555
- const executionDisplay = getTaskExecutionDisplay(task.executionStatus, agentStatus, task.status);
556
- const isBlocked = blockedTaskIds.has(task.taskId);
557
- const normalizedAgentStatus = normalizeAgentStatus(agentStatus);
558
- const isRunning = !isBlocked &&
559
- (task.executionStatus === "Running" ||
560
- task.executionStatus === "Queued" ||
561
- normalizedAgentStatus === "running");
488
+ const executionDisplay = getTaskExecutionDisplay(record);
489
+ const isBlocked = executionStateFromBackend === "WaitingForDependency";
490
+ const isRunning = executionStateFromBackend === "Working";
491
+ if (isBlocked) {
492
+ blockedCount += 1;
493
+ }
562
494
  if (isRunning) {
563
495
  runningCount += 1;
564
496
  }
565
497
  if (normalizedTaskStatus === "Review") {
566
498
  approvalCount += 1;
567
499
  }
568
- if (!attentionTask && !isBlocked && executionDisplay?.needsAttention) {
500
+ const needsAttention = !!executionDisplay?.needsAttention || normalizedTaskStatus === "Review";
501
+ if (!attentionTask && !isBlocked && needsAttention) {
569
502
  attentionTask = task;
570
503
  attentionAgentId = agentId;
571
- attentionAgentStatus = agentStatus;
572
504
  attentionExecutionDisplay = executionDisplay;
573
505
  }
574
506
  }
@@ -579,34 +511,77 @@ export function TaskBoardWorkspace() {
579
511
  else if (runningCount > 0) {
580
512
  summaryState = "working";
581
513
  }
582
- else if (blockedTaskIds.size === openTasks.length) {
514
+ else if (blockedCount === openTasks.length) {
583
515
  summaryState = "blocked";
584
516
  }
585
517
  return {
586
518
  task: attentionTask,
587
519
  agentId: attentionAgentId,
588
- agentStatus: attentionAgentStatus,
589
520
  executionDisplay: attentionExecutionDisplay,
590
521
  summaryState,
591
522
  stats: {
592
523
  totalCount: sortedTasks.length,
593
524
  completedCount: sortedTasks.length - openTasks.length,
594
525
  approvalCount,
595
- blockedCount: blockedTaskIds.size,
526
+ blockedCount,
596
527
  runningCount,
597
528
  },
598
529
  };
599
- }, [wizardTasksWithDisplayAssignees, dependencies, agentStatusesById]);
530
+ }, [wizardTasksWithDisplayAssignees, wizardExecutionRecordsByTaskId]);
600
531
  const wizardDisplayedTask = wizardForceOverview
601
532
  ? null
602
533
  : selectedTask ?? wizardPinnedTask ?? wizardAttentionState.task;
603
534
  const wizardDisplayedAgentId = wizardDisplayedTask
604
535
  ? getLinkedAgentId(wizardDisplayedTask)
605
536
  : null;
606
- const wizardDisplayedAgentStatus = getAgentStatusById(agentStatusesById, wizardDisplayedAgentId);
607
537
  const wizardDisplayedExecutionDisplay = wizardDisplayedTask
608
- ? getTaskExecutionDisplay(wizardDisplayedTask.executionStatus, wizardDisplayedAgentStatus, wizardDisplayedTask.status)
538
+ ? getTaskExecutionDisplay(getTaskExecutionRecordById(wizardExecutionRecordsByTaskId, wizardDisplayedTask.taskId))
609
539
  : null;
540
+ useEffect(() => {
541
+ const attentionSnapshot = JSON.stringify({
542
+ selectedTaskId,
543
+ selectedTaskSource,
544
+ wizardPinnedTaskId,
545
+ wizardForceOverview,
546
+ attentionTaskId: wizardAttentionState.task?.taskId ?? null,
547
+ attentionAgentId: wizardAttentionState.agentId ?? null,
548
+ summaryState: wizardAttentionState.summaryState,
549
+ totalCount: wizardAttentionState.stats.totalCount,
550
+ completedCount: wizardAttentionState.stats.completedCount,
551
+ approvalCount: wizardAttentionState.stats.approvalCount,
552
+ blockedCount: wizardAttentionState.stats.blockedCount,
553
+ runningCount: wizardAttentionState.stats.runningCount,
554
+ displayedTaskId: wizardDisplayedTask?.taskId ?? null,
555
+ displayedExecutionLabel: wizardDisplayedExecutionDisplay?.label ?? null,
556
+ displayedNeedsAttention: wizardDisplayedExecutionDisplay?.needsAttention ?? null,
557
+ });
558
+ if (previousWizardAttentionSignatureRef.current === attentionSnapshot) {
559
+ return;
560
+ }
561
+ previousWizardAttentionSignatureRef.current = attentionSnapshot;
562
+ }, [
563
+ selectedTaskId,
564
+ selectedTaskSource,
565
+ wizardAttentionState,
566
+ wizardDisplayedExecutionDisplay?.label,
567
+ wizardDisplayedExecutionDisplay?.needsAttention,
568
+ wizardDisplayedTask?.taskId,
569
+ wizardForceOverview,
570
+ wizardPinnedTaskId,
571
+ ]);
572
+ useEffect(() => {
573
+ if (!wizardDisplayedTask ||
574
+ (wizardDisplayedExecutionDisplay?.label !== "Questions" &&
575
+ wizardDisplayedTask.executionState !== "WaitingForInput" &&
576
+ wizardDisplayedTask.executionState !== "WaitingForUser")) {
577
+ return;
578
+ }
579
+ }, [
580
+ wizardDisplayedTask,
581
+ wizardDisplayedAgentId,
582
+ wizardDisplayedExecutionDisplay?.label,
583
+ selectedProjectId,
584
+ ]);
610
585
  const clearWizardCloseTransitionTimeout = useCallback(() => {
611
586
  if (wizardCloseTransitionTimeoutRef.current !== null) {
612
587
  window.clearTimeout(wizardCloseTransitionTimeoutRef.current);
@@ -627,48 +602,25 @@ export function TaskBoardWorkspace() {
627
602
  return a.sortOrder - b.sortOrder;
628
603
  return a.createdAt.localeCompare(b.createdAt);
629
604
  });
630
- const taskById = new Map(sortedTasks.map((task) => [task.taskId, task]));
631
- const blockersByTaskId = new Map();
632
- for (const dependency of dependencies) {
633
- if (dependency.dependencyType !== "BlockedBy")
634
- continue;
635
- const blockerIds = blockersByTaskId.get(dependency.taskId) || [];
636
- blockerIds.push(dependency.dependsOnTaskId);
637
- blockersByTaskId.set(dependency.taskId, blockerIds);
638
- }
639
605
  const openTasks = sortedTasks.filter((task) => {
640
606
  if (excludedTaskId && task.taskId === excludedTaskId)
641
607
  return false;
642
- return (normalizeTaskStatus(task.status, task.executionStatus) !== "Done");
608
+ return normalizeTaskStatus(task.status, task.executionState) !== "Done";
643
609
  });
644
- const blockedTaskIds = new Set();
645
610
  for (const task of openTasks) {
646
- const blockerIds = blockersByTaskId.get(task.taskId) || [];
647
- const isBlocked = blockerIds.some((blockerId) => {
648
- const blockerTask = taskById.get(blockerId);
649
- if (!blockerTask)
650
- return true;
651
- return (normalizeTaskStatus(blockerTask.status, blockerTask.executionStatus) !== "Done");
652
- });
653
- if (isBlocked)
654
- blockedTaskIds.add(task.taskId);
655
- }
656
- for (const task of openTasks) {
657
- const isBlocked = blockedTaskIds.has(task.taskId);
658
- const agentId = getLinkedAgentId(task);
659
- const agentStatus = getTaskAgentStatus(task, agentStatusesById);
660
- const executionDisplay = getTaskExecutionDisplay(task.executionStatus, agentStatus, task.status);
661
- const normalizedAgentStatus = normalizeAgentStatus(agentStatus);
662
- const isRunning = !isBlocked &&
663
- (task.executionStatus === "Running" ||
664
- task.executionStatus === "Queued" ||
665
- normalizedAgentStatus === "running");
666
- if (!isBlocked && executionDisplay?.needsAttention) {
611
+ const record = getTaskExecutionRecordById(wizardExecutionRecordsByTaskId, task.taskId);
612
+ const executionStateFromBackend = record?.executionState ?? task.executionState;
613
+ const isBlocked = executionStateFromBackend === "WaitingForDependency";
614
+ const executionDisplay = getTaskExecutionDisplay(record);
615
+ const normalizedTaskStatus = normalizeTaskStatus(task.status, executionStateFromBackend);
616
+ const needsAttention = !!executionDisplay?.needsAttention ||
617
+ normalizedTaskStatus === "Review";
618
+ if (!isBlocked && needsAttention) {
667
619
  return task.taskId;
668
620
  }
669
621
  }
670
622
  return null;
671
- }, [wizardTasksWithDisplayAssignees, dependencies, agentStatusesById]);
623
+ }, [wizardExecutionRecordsByTaskId, wizardTasksWithDisplayAssignees]);
672
624
  // ── data fetching ──
673
625
  const refreshProjects = useCallback(async (options) => {
674
626
  projectsRequestCountRef.current += 1;
@@ -767,6 +719,14 @@ export function TaskBoardWorkspace() {
767
719
  }
768
720
  }
769
721
  }, []);
722
+ const refreshExecutionState = useCallback(async (projectId) => {
723
+ const result = await getExecutionState({ projectId });
724
+ if (result.type !== "success") {
725
+ toast.error(result.summary || "Failed to load execution state");
726
+ return;
727
+ }
728
+ setExecutionRecordsByTaskId(indexTaskExecutionRecords(result.data?.records));
729
+ }, []);
770
730
  const refreshSubprojectTaskCounts = useCallback(async () => {
771
731
  const requestProjectId = selectedProjectId;
772
732
  latestSubprojectCountsProjectIdRef.current = requestProjectId;
@@ -774,6 +734,7 @@ export function TaskBoardWorkspace() {
774
734
  if (latestSubprojectCountsProjectIdRef.current === requestProjectId) {
775
735
  setSubprojectTaskCounts({});
776
736
  setSubprojectTasksByProjectId({});
737
+ setSubprojectExecutionRecordsByProjectId({});
777
738
  }
778
739
  return;
779
740
  }
@@ -781,15 +742,23 @@ export function TaskBoardWorkspace() {
781
742
  try {
782
743
  const results = await Promise.all(directSubprojects.map(async (subproject) => {
783
744
  const projectId = subproject.project.projectId;
784
- const response = await getTasks(projectId);
785
- if (response.type !== "success") {
786
- return [projectId, null, null];
745
+ const [tasksResponse, executionResponse] = await Promise.all([
746
+ getTasks(projectId),
747
+ getExecutionState({ projectId }),
748
+ ]);
749
+ if (tasksResponse.type !== "success") {
750
+ return [projectId, null, null, null];
787
751
  }
788
- const tasksForProject = response.data || [];
752
+ const tasksForProject = tasksResponse.data || [];
753
+ const recordsByTaskId = executionResponse.type === "success"
754
+ ? indexTaskExecutionRecords(executionResponse.data?.records)
755
+ : {};
756
+ const tasksWithExecution = overlayTaskExecutionList(tasksForProject, recordsByTaskId);
789
757
  return [
790
758
  projectId,
791
- buildTaskCounts(tasksForProject),
759
+ buildTaskCounts(tasksWithExecution),
792
760
  tasksForProject,
761
+ recordsByTaskId,
793
762
  ];
794
763
  }));
795
764
  if (latestSubprojectCountsProjectIdRef.current !== requestProjectId) {
@@ -797,14 +766,18 @@ export function TaskBoardWorkspace() {
797
766
  }
798
767
  const nextCounts = {};
799
768
  const nextTasksByProjectId = {};
800
- for (const [projectId, counts, taskList] of results) {
769
+ const nextExecutionByProjectId = {};
770
+ for (const [projectId, counts, taskList, executionByTaskId] of results) {
801
771
  if (counts)
802
772
  nextCounts[projectId] = counts;
803
773
  if (taskList)
804
774
  nextTasksByProjectId[projectId] = taskList;
775
+ if (executionByTaskId)
776
+ nextExecutionByProjectId[projectId] = executionByTaskId;
805
777
  }
806
778
  setSubprojectTaskCounts(nextCounts);
807
779
  setSubprojectTasksByProjectId(isWizardMode ? nextTasksByProjectId : {});
780
+ setSubprojectExecutionRecordsByProjectId(isWizardMode ? nextExecutionByProjectId : {});
808
781
  }
809
782
  finally {
810
783
  setSubprojectCountsLoading(false);
@@ -825,8 +798,8 @@ export function TaskBoardWorkspace() {
825
798
  if (!firstTask)
826
799
  return;
827
800
  autoSelectedProjectIdsRef.current.add(selectedProjectId);
828
- setSelectedTaskId(firstTask.taskId);
829
- }, [selectedProjectId, selectedTaskId, tasks]);
801
+ setTaskSelection(firstTask.taskId, { source: "system" });
802
+ }, [selectedProjectId, selectedTaskId, setTaskSelection, tasks]);
830
803
  useEffect(() => {
831
804
  if (!wizardPinnedTaskId)
832
805
  return;
@@ -835,24 +808,95 @@ export function TaskBoardWorkspace() {
835
808
  }
836
809
  setWizardPinnedTaskId(null);
837
810
  }, [taskSelectionUniverse, wizardPinnedTaskId]);
811
+ // Pin to a task as soon as it shows Questions/WaitingForInput/WaitingForUser, so we don't
812
+ // switch away when another subproject also gets Questions (wizardAttentionState
813
+ // would pick a different task and we'd lose the questionnaire to agent mismatch).
814
+ useEffect(() => {
815
+ if (!isWizardMode)
816
+ return;
817
+ if (wizardForceOverview)
818
+ return;
819
+ if (selectedTaskId)
820
+ return;
821
+ if (wizardPinnedTaskId)
822
+ return;
823
+ if (!wizardDisplayedTask?.taskId)
824
+ return;
825
+ const needsInput = wizardDisplayedExecutionDisplay?.label === "Questions" ||
826
+ wizardDisplayedExecutionDisplay?.label === "Waiting for input" ||
827
+ wizardDisplayedExecutionDisplay?.label === "Waiting for user";
828
+ if (!needsInput)
829
+ return;
830
+ setWizardPinnedTaskId(wizardDisplayedTask.taskId);
831
+ }, [
832
+ isWizardMode,
833
+ wizardForceOverview,
834
+ selectedTaskId,
835
+ wizardPinnedTaskId,
836
+ wizardDisplayedTask?.taskId,
837
+ wizardDisplayedExecutionDisplay?.label,
838
+ ]);
838
839
  useEffect(() => {
839
840
  if (!isWizardMode)
840
841
  return;
841
842
  if (wizardForceOverview)
842
843
  return;
843
- if (selectedTaskId || wizardPinnedTaskId)
844
+ if (selectedTaskId)
844
845
  return;
845
846
  const attentionTaskId = wizardAttentionState.task?.taskId;
846
- if (!attentionTaskId)
847
+ if (!attentionTaskId) {
848
+ return;
849
+ }
850
+ if (wizardPinnedTaskId === attentionTaskId)
851
+ return;
852
+ // Don't auto-pin away from a task that has Questions, Waiting for input, or Waiting for user—
853
+ // switching would unmount its AgentTerminal and cause the questionnaire to be
854
+ // dispatched to the wrong terminal (agent mismatch). Keep user on current task.
855
+ const displayedNeedsInput = wizardDisplayedExecutionDisplay?.label === "Questions" ||
856
+ wizardDisplayedExecutionDisplay?.label === "Waiting for input" ||
857
+ wizardDisplayedExecutionDisplay?.label === "Waiting for user";
858
+ if (displayedNeedsInput && wizardDisplayedTask?.taskId) {
847
859
  return;
860
+ }
848
861
  setWizardPinnedTaskId(attentionTaskId);
849
862
  }, [
850
863
  isWizardMode,
851
864
  selectedTaskId,
865
+ selectedTaskSource,
866
+ wizardDisplayedExecutionDisplay?.label,
867
+ wizardDisplayedTask?.taskId,
852
868
  wizardPinnedTaskId,
853
869
  wizardForceOverview,
870
+ wizardAttentionState.summaryState,
854
871
  wizardAttentionState.task?.taskId,
855
872
  ]);
873
+ useEffect(() => {
874
+ if (!isWizardMode)
875
+ return;
876
+ if (selectedTaskSource === "user")
877
+ return;
878
+ if (!selectedTask)
879
+ return;
880
+ if (normalizeTaskStatus(selectedTask.status, selectedTask.executionState) !== "Done") {
881
+ return;
882
+ }
883
+ const nextAttentionTaskId = findNextAttentionTaskId(selectedTask.taskId);
884
+ if (nextAttentionTaskId) {
885
+ setWizardForceOverview(false);
886
+ setTaskSelection(nextAttentionTaskId, {
887
+ source: "system",
888
+ pinInWizard: true,
889
+ });
890
+ return;
891
+ }
892
+ setTaskSelection(null, { pinInWizard: false });
893
+ }, [
894
+ findNextAttentionTaskId,
895
+ isWizardMode,
896
+ selectedTask,
897
+ selectedTaskSource,
898
+ setTaskSelection,
899
+ ]);
856
900
  useEffect(() => {
857
901
  if (isWizardMode)
858
902
  return;
@@ -902,70 +946,21 @@ export function TaskBoardWorkspace() {
902
946
  if (!selectedProjectId)
903
947
  return;
904
948
  await refreshTasks(selectedProjectId);
949
+ await refreshExecutionState(selectedProjectId);
905
950
  await refreshDependencies(selectedProjectId);
906
951
  await refreshSubprojectTaskCounts();
907
952
  }, [
908
953
  selectedProjectId,
909
954
  refreshTasks,
955
+ refreshExecutionState,
910
956
  refreshDependencies,
911
957
  refreshSubprojectTaskCounts,
912
958
  ]);
913
- const refreshAgentStatuses = useCallback(async (taskList) => {
914
- const agentIds = Array.from(new Set(taskList
915
- .map((task) => getLinkedAgentId(task))
916
- .filter((id) => typeof id === "string" && id.length > 0)));
917
- if (agentIds.length === 0) {
918
- setAgentStatusesById({});
919
- return;
920
- }
921
- try {
922
- const response = await getActiveAgents({
923
- limit: 1000,
924
- includeOwned: true,
925
- includeShared: true,
926
- excludeClosed: false,
927
- });
928
- const wanted = new Set(agentIds.map((id) => id.toLowerCase()));
929
- const apiStatuses = {};
930
- for (const agent of response.agents || []) {
931
- const normalizedAgentId = agent.id.toLowerCase();
932
- if (wanted.has(normalizedAgentId)) {
933
- apiStatuses[normalizedAgentId] =
934
- normalizeAgentStatus(agent.status) ?? agent.status;
935
- }
936
- }
937
- setAgentStatusesById((prev) => {
938
- const merged = { ...prev };
939
- for (const [id, apiStatus] of Object.entries(apiStatuses)) {
940
- const existing = merged[id];
941
- const existingNormalized = existing !== undefined ? normalizeAgentStatus(existing) : undefined;
942
- if (existingNormalized === "waitingForInput" ||
943
- existingNormalized === "waitingForApproval" ||
944
- existingNormalized === "error" ||
945
- existingNormalized === "costLimitReached") {
946
- const apiNormalized = normalizeAgentStatus(apiStatus);
947
- if (apiNormalized !== "waitingForInput" &&
948
- apiNormalized !== "waitingForApproval" &&
949
- apiNormalized !== "error" &&
950
- apiNormalized !== "costLimitReached" &&
951
- apiNormalized !== "completed" &&
952
- apiNormalized !== "closed") {
953
- continue;
954
- }
955
- }
956
- merged[id] = apiStatus;
957
- }
958
- return merged;
959
- });
960
- }
961
- catch {
962
- // best-effort only
963
- }
964
- }, []);
965
959
  // Sync refs with latest function/state values for use in WebSocket timeout callback
966
960
  useEffect(() => {
967
961
  refreshProjectsRef.current = refreshProjects;
968
962
  refreshTasksRef.current = refreshTasks;
963
+ refreshExecutionStateRef.current = refreshExecutionState;
969
964
  refreshDependenciesRef.current = refreshDependencies;
970
965
  refreshSubprojectTaskCountsRef.current = refreshSubprojectTaskCounts;
971
966
  selectedProjectIdRef.current = selectedProjectId;
@@ -974,51 +969,13 @@ export function TaskBoardWorkspace() {
974
969
  }, [
975
970
  refreshProjects,
976
971
  refreshTasks,
972
+ refreshExecutionState,
977
973
  refreshDependencies,
978
974
  refreshSubprojectTaskCounts,
979
975
  selectedProjectId,
980
976
  directSubprojects,
981
977
  taskSelectionUniverse,
982
978
  ]);
983
- // Ensure task-board receives live agent status updates (including WaitingForInput)
984
- // without requiring the terminal panel to be opened. Re-run on socket reconnect
985
- // so subscriptions are restored after page reload / reconnect.
986
- useEffect(() => {
987
- const socketVersion = editContext?.socketConnectionVersion ?? null;
988
- if (taskBoardSocketVersionRef.current !== socketVersion) {
989
- taskBoardSocketVersionRef.current = socketVersion;
990
- taskBoardSubscribedAgentIdsRef.current.clear();
991
- }
992
- if (!selectedProjectId)
993
- return;
994
- const desiredAgentIds = new Set(taskSelectionUniverse
995
- .map((task) => getLinkedAgentId(task))
996
- .filter((id) => typeof id === "string" && id.length > 0)
997
- .map((id) => id.toLowerCase()));
998
- // Delay one tick so socket listeners are attached before pending dialog replay
999
- // is emitted by the server for subscribed agents.
1000
- const subscribeTimeout = window.setTimeout(() => {
1001
- const socket = globalThis.editorSocket;
1002
- if (!socket || socket.readyState !== WebSocket.OPEN)
1003
- return;
1004
- for (const agentId of desiredAgentIds) {
1005
- if (taskBoardSubscribedAgentIdsRef.current.has(agentId))
1006
- continue;
1007
- socket.send(JSON.stringify({
1008
- type: "agent:subscribe",
1009
- agentId,
1010
- }));
1011
- taskBoardSubscribedAgentIdsRef.current.add(agentId);
1012
- }
1013
- }, 0);
1014
- return () => {
1015
- window.clearTimeout(subscribeTimeout);
1016
- };
1017
- }, [
1018
- selectedProjectId,
1019
- taskSelectionUniverse,
1020
- editContext?.socketConnectionVersion,
1021
- ]);
1022
979
  const handleRunOrchestrator = useCallback(async (options) => {
1023
980
  if (!selectedProjectId)
1024
981
  return;
@@ -1070,9 +1027,6 @@ export function TaskBoardWorkspace() {
1070
1027
  return;
1071
1028
  setRunningOrchestrator(true);
1072
1029
  try {
1073
- if (task.executionStatus === "Idle") {
1074
- await updateTask({ taskId: task.taskId, executionStatus: "Queued" });
1075
- }
1076
1030
  const result = await runOrchestrator(taskProjectId);
1077
1031
  if (result.type !== "success") {
1078
1032
  toast.error(result.summary || "Failed to launch agent");
@@ -1104,7 +1058,6 @@ export function TaskBoardWorkspace() {
1104
1058
  const reopenResult = await updateTask({
1105
1059
  taskId: selectedTask.taskId,
1106
1060
  status: "Todo",
1107
- executionStatus: "Idle",
1108
1061
  });
1109
1062
  if (reopenResult.type !== "success") {
1110
1063
  toast.error(reopenResult.summary || "Failed to reopen task");
@@ -1124,15 +1077,6 @@ export function TaskBoardWorkspace() {
1124
1077
  }
1125
1078
  setRunningOrchestrator(true);
1126
1079
  try {
1127
- const queueResult = await updateTask({
1128
- taskId: selectedTask.taskId,
1129
- executionStatus: "Queued",
1130
- });
1131
- if (queueResult.type !== "success") {
1132
- toast.error(queueResult.summary || "Failed to queue agent");
1133
- await refreshVisibleTaskData();
1134
- return;
1135
- }
1136
1080
  const result = await runOrchestrator(taskProjectId);
1137
1081
  if (result.type !== "success") {
1138
1082
  toast.error(result.summary || "Failed to launch agent");
@@ -1176,17 +1120,25 @@ export function TaskBoardWorkspace() {
1176
1120
  }
1177
1121
  toast.success("Planning agent started");
1178
1122
  await refreshTasks(selectedProjectId);
1123
+ await refreshExecutionState(selectedProjectId);
1179
1124
  await refreshDependencies(selectedProjectId);
1180
1125
  const plan = tasks.find((t) => String(t.taskType ?? "").toLowerCase() === "plan") ??
1181
1126
  tasks.find((t) => t.title === "Project Plan") ??
1182
1127
  null;
1183
1128
  if (plan)
1184
- setSelectedTaskId(plan.taskId);
1129
+ setTaskSelection(plan.taskId, { source: "system" });
1185
1130
  }
1186
1131
  catch {
1187
1132
  toast.error("Failed to start planning");
1188
1133
  }
1189
- }, [selectedProjectId, refreshTasks, refreshDependencies, tasks]);
1134
+ }, [
1135
+ selectedProjectId,
1136
+ refreshTasks,
1137
+ refreshExecutionState,
1138
+ refreshDependencies,
1139
+ setTaskSelection,
1140
+ tasks,
1141
+ ]);
1190
1142
  const handleProjectStatusChange = useCallback(async (status) => {
1191
1143
  if (!selectedProject)
1192
1144
  return;
@@ -1215,8 +1167,8 @@ export function TaskBoardWorkspace() {
1215
1167
  }, [selectedProject, refreshProjects]);
1216
1168
  const handleSelectMyTask = useCallback((task) => {
1217
1169
  handleSelectProject(task.projectId);
1218
- setSelectedTaskId(task.taskId);
1219
- }, [handleSelectProject]);
1170
+ setTaskSelection(task.taskId, { source: "user" });
1171
+ }, [handleSelectProject, setTaskSelection]);
1220
1172
  useEffect(() => {
1221
1173
  setTaskBoardNavState({
1222
1174
  projectTitle: selectedProject?.project.title ?? "Tasks",
@@ -1326,9 +1278,11 @@ export function TaskBoardWorkspace() {
1326
1278
  setProjectViewLoadingId(selectedProjectId);
1327
1279
  let cancelled = false;
1328
1280
  const refreshTasksFn = refreshTasksRef.current;
1281
+ const refreshExecutionStateFn = refreshExecutionStateRef.current;
1329
1282
  const refreshDependenciesFn = refreshDependenciesRef.current;
1330
1283
  void Promise.all([
1331
1284
  refreshTasksFn?.(selectedProjectId),
1285
+ refreshExecutionStateFn?.(selectedProjectId),
1332
1286
  refreshDependenciesFn?.(selectedProjectId),
1333
1287
  ]).finally(() => {
1334
1288
  if (cancelled)
@@ -1347,24 +1301,19 @@ export function TaskBoardWorkspace() {
1347
1301
  useEffect(() => {
1348
1302
  void refreshSubprojectTaskCounts();
1349
1303
  }, [refreshSubprojectTaskCounts]);
1350
- useEffect(() => {
1351
- if (!selectedProjectId) {
1352
- setAgentStatusesById({});
1353
- return;
1354
- }
1355
- void refreshAgentStatuses(taskSelectionUniverse);
1356
- }, [selectedProjectId, taskSelectionUniverse, refreshAgentStatuses]);
1357
1304
  useEffect(() => {
1358
1305
  previousTaskStatusesRef.current = {};
1306
+ previousWizardAttentionSignatureRef.current = "";
1359
1307
  }, [selectedProjectId]);
1360
1308
  useEffect(() => {
1361
1309
  if (!selectedProjectId)
1362
1310
  return;
1363
1311
  const previousStatuses = previousTaskStatusesRef.current;
1364
1312
  const currentStatuses = {};
1313
+ const transitions = [];
1365
1314
  let movedToDone = false;
1366
- for (const task of tasks) {
1367
- const currentStatus = normalizeTaskStatus(task.status, task.executionStatus);
1315
+ for (const task of tasksWithDisplayAssignees) {
1316
+ const currentStatus = normalizeTaskStatus(task.status, task.executionState);
1368
1317
  currentStatuses[task.taskId] = currentStatus;
1369
1318
  const previousStatus = previousStatuses[task.taskId];
1370
1319
  if (previousStatus &&
@@ -1372,14 +1321,27 @@ export function TaskBoardWorkspace() {
1372
1321
  currentStatus === "Done") {
1373
1322
  movedToDone = true;
1374
1323
  }
1324
+ if (!previousStatus || previousStatus !== currentStatus) {
1325
+ transitions.push({
1326
+ taskId: task.taskId,
1327
+ title: task.title,
1328
+ previousStatus: previousStatus ?? null,
1329
+ currentStatus,
1330
+ rawStatus: task.status ?? null,
1331
+ executionState: task.executionState ?? null,
1332
+ });
1333
+ }
1375
1334
  }
1376
1335
  previousTaskStatusesRef.current = currentStatuses;
1336
+ if (transitions.length > 0) {
1337
+ // transitions tracked for potential future use
1338
+ }
1377
1339
  if (movedToDone && canCreate && !isPlanning && !runningOrchestrator) {
1378
1340
  void handleRunOrchestrator({ silentWhenNothingToLaunch: true });
1379
1341
  }
1380
1342
  }, [
1381
1343
  selectedProjectId,
1382
- tasks,
1344
+ tasksWithDisplayAssignees,
1383
1345
  canCreate,
1384
1346
  isPlanning,
1385
1347
  runningOrchestrator,
@@ -1394,6 +1356,64 @@ export function TaskBoardWorkspace() {
1394
1356
  return;
1395
1357
  const unsubscribe = addListener((message) => {
1396
1358
  const messageType = message?.type;
1359
+ if (messageType === "task:state-changed") {
1360
+ const payload = message?.payload;
1361
+ const projectId = payload?.projectId ?? payload?.ProjectId ?? payload?.record?.projectId;
1362
+ const record = payload?.record;
1363
+ const currentProjectId = selectedProjectIdRef.current;
1364
+ if (!projectId || !currentProjectId || !record?.taskId)
1365
+ return;
1366
+ if (projectId === currentProjectId) {
1367
+ setExecutionRecordsByTaskId((prev) => {
1368
+ const current = prev[record.taskId];
1369
+ if (current && current.version > record.version) {
1370
+ return prev;
1371
+ }
1372
+ if (current &&
1373
+ current.version === record.version &&
1374
+ current.taskStatus === record.taskStatus &&
1375
+ current.executionState === record.executionState &&
1376
+ current.attentionReason === record.attentionReason &&
1377
+ current.waitingOnDialogId === record.waitingOnDialogId &&
1378
+ current.waitingOnToolCallId === record.waitingOnToolCallId &&
1379
+ current.agentId === record.agentId) {
1380
+ return prev;
1381
+ }
1382
+ return {
1383
+ ...prev,
1384
+ [record.taskId]: record,
1385
+ };
1386
+ });
1387
+ return;
1388
+ }
1389
+ if (directSubprojectIdsRef.current.has(projectId)) {
1390
+ setSubprojectExecutionRecordsByProjectId((prev) => {
1391
+ const projectRecords = prev[projectId] ?? {};
1392
+ const current = projectRecords[record.taskId];
1393
+ if (current && (current.version ?? 0) > (record.version ?? 0)) {
1394
+ return prev;
1395
+ }
1396
+ return {
1397
+ ...prev,
1398
+ [projectId]: {
1399
+ ...projectRecords,
1400
+ [record.taskId]: record,
1401
+ },
1402
+ };
1403
+ });
1404
+ if (wsSubprojectRefreshTimeoutRef.current !== null) {
1405
+ window.clearTimeout(wsSubprojectRefreshTimeoutRef.current);
1406
+ wsSubprojectRefreshTimeoutRef.current = null;
1407
+ }
1408
+ wsSubprojectRefreshTimeoutRef.current = window.setTimeout(() => {
1409
+ wsSubprojectRefreshTimeoutRef.current = null;
1410
+ const refreshSubprojectsFn = refreshSubprojectTaskCountsRef.current;
1411
+ if (refreshSubprojectsFn)
1412
+ void refreshSubprojectsFn();
1413
+ }, 250);
1414
+ }
1415
+ return;
1416
+ }
1397
1417
  if (messageType !== "task:updated" &&
1398
1418
  messageType !== "task:created" &&
1399
1419
  messageType !== "task:deleted") {
@@ -1432,10 +1452,12 @@ export function TaskBoardWorkspace() {
1432
1452
  const pid = selectedProjectIdRef.current;
1433
1453
  const refreshProjectsFn = refreshProjectsRef.current;
1434
1454
  const refreshTasksFn = refreshTasksRef.current;
1455
+ const refreshExecutionStateFn = refreshExecutionStateRef.current;
1435
1456
  const refreshDepsFn = refreshDependenciesRef.current;
1436
- if (!pid || !refreshTasksFn || !refreshDepsFn)
1457
+ if (!pid || !refreshTasksFn || !refreshDepsFn || !refreshExecutionStateFn)
1437
1458
  return;
1438
1459
  void refreshTasksFn(pid);
1460
+ void refreshExecutionStateFn(pid);
1439
1461
  void refreshDepsFn(pid);
1440
1462
  if (refreshProjectsFn)
1441
1463
  void refreshProjectsFn();
@@ -1461,96 +1483,6 @@ export function TaskBoardWorkspace() {
1461
1483
  });
1462
1484
  return () => unsubscribe();
1463
1485
  }, [editContext, refreshProjects]);
1464
- useEffect(() => {
1465
- const addListener = editContext?.addSocketMessageListener;
1466
- if (!addListener)
1467
- return;
1468
- const unsubscribe = addListener((message) => {
1469
- const type = message?.type;
1470
- if (!type)
1471
- return;
1472
- const payload = message?.payload ?? {};
1473
- const agentId = payload?.agentId;
1474
- if (!agentId || typeof agentId !== "string")
1475
- return;
1476
- const trackedAgentIds = new Set(tasksRef.current
1477
- .map((task) => getLinkedAgentId(task))
1478
- .filter((id) => typeof id === "string" && id.length > 0)
1479
- .map((id) => id.toLowerCase()));
1480
- if (!trackedAgentIds.has(agentId.toLowerCase()))
1481
- return;
1482
- const queueProjectsRefresh = () => {
1483
- if (wsProjectRefreshTimeoutRef.current !== null) {
1484
- window.clearTimeout(wsProjectRefreshTimeoutRef.current);
1485
- wsProjectRefreshTimeoutRef.current = null;
1486
- }
1487
- wsProjectRefreshTimeoutRef.current = window.setTimeout(() => {
1488
- wsProjectRefreshTimeoutRef.current = null;
1489
- const refreshProjectsFn = refreshProjectsRef.current;
1490
- if (!refreshProjectsFn)
1491
- return;
1492
- void refreshProjectsFn();
1493
- }, 500);
1494
- };
1495
- if (type === "agent:run:status") {
1496
- const status = normalizeAgentStatus(payload?.data?.state ?? payload?.state);
1497
- if (status === undefined)
1498
- return;
1499
- const normalizedAgentId = agentId.toLowerCase();
1500
- setAgentStatusesById((prev) => ({
1501
- ...prev,
1502
- [normalizedAgentId]: status,
1503
- }));
1504
- queueProjectsRefresh();
1505
- return;
1506
- }
1507
- if (type === "agent-dialog-request") {
1508
- const normalizedAgentId = agentId.toLowerCase();
1509
- setAgentStatusesById((prev) => ({
1510
- ...prev,
1511
- [normalizedAgentId]: "waitingForInput",
1512
- }));
1513
- queueProjectsRefresh();
1514
- return;
1515
- }
1516
- if (type === "agent:run:start") {
1517
- const normalizedAgentId = agentId.toLowerCase();
1518
- setAgentStatusesById((prev) => ({
1519
- ...prev,
1520
- [normalizedAgentId]: "running",
1521
- }));
1522
- queueProjectsRefresh();
1523
- return;
1524
- }
1525
- if (type === "agent:run:complete") {
1526
- const normalizedAgentId = agentId.toLowerCase();
1527
- setAgentStatusesById((prev) => ({
1528
- ...prev,
1529
- [normalizedAgentId]: "completed",
1530
- }));
1531
- queueProjectsRefresh();
1532
- return;
1533
- }
1534
- if (type === "agent:run:error") {
1535
- const normalizedAgentId = agentId.toLowerCase();
1536
- setAgentStatusesById((prev) => ({
1537
- ...prev,
1538
- [normalizedAgentId]: "error",
1539
- }));
1540
- queueProjectsRefresh();
1541
- return;
1542
- }
1543
- if (type === "agent:run:closed") {
1544
- const normalizedAgentId = agentId.toLowerCase();
1545
- setAgentStatusesById((prev) => ({
1546
- ...prev,
1547
- [normalizedAgentId]: "closed",
1548
- }));
1549
- queueProjectsRefresh();
1550
- }
1551
- });
1552
- return () => unsubscribe();
1553
- }, [editContext]);
1554
1486
  // Clean up pending debounced refresh on unmount only.
1555
1487
  useEffect(() => {
1556
1488
  return () => {
@@ -1562,10 +1494,6 @@ export function TaskBoardWorkspace() {
1562
1494
  window.clearTimeout(wsSubprojectRefreshTimeoutRef.current);
1563
1495
  wsSubprojectRefreshTimeoutRef.current = null;
1564
1496
  }
1565
- if (wsProjectRefreshTimeoutRef.current !== null) {
1566
- window.clearTimeout(wsProjectRefreshTimeoutRef.current);
1567
- wsProjectRefreshTimeoutRef.current = null;
1568
- }
1569
1497
  if (wizardCloseTransitionTimeoutRef.current !== null) {
1570
1498
  window.clearTimeout(wizardCloseTransitionTimeoutRef.current);
1571
1499
  wizardCloseTransitionTimeoutRef.current = null;
@@ -1577,21 +1505,28 @@ export function TaskBoardWorkspace() {
1577
1505
  if (!selectedTaskId)
1578
1506
  return;
1579
1507
  if (!taskSelectionUniverse.some((t) => t.taskId === selectedTaskId)) {
1580
- setSelectedTaskId(null);
1508
+ setTaskSelection(null);
1581
1509
  }
1582
- }, [selectedTaskId, taskSelectionUniverse]);
1510
+ }, [
1511
+ isWizardMode,
1512
+ selectedTaskId,
1513
+ setTaskSelection,
1514
+ taskSelectionUniverse,
1515
+ ]);
1583
1516
  // No special auto-selection by project status — the agent panel is driven by
1584
1517
  // whichever task the user selects.
1585
1518
  // The backend now creates the Plan task and triggers the planner agent
1586
1519
  // during project creation (TaskService.CreateProjectAsync), so we no longer
1587
1520
  // need to auto-trigger planning from the frontend.
1588
- const handleCloseWizardTask = useCallback(() => {
1521
+ const handleCloseWizardTask = useCallback((options) => {
1589
1522
  const closingProjectId = selectedProjectId;
1590
1523
  const closedTaskId = wizardDisplayedTask?.taskId ?? selectedTaskId ?? wizardPinnedTaskId;
1591
1524
  clearWizardCloseTransitionTimeout();
1592
- setSelectedTaskId(null);
1593
- setWizardPinnedTaskId(null);
1525
+ setTaskSelection(null, { pinInWizard: false });
1594
1526
  setWizardForceOverview(true);
1527
+ if (!options?.advanceToNext) {
1528
+ return;
1529
+ }
1595
1530
  wizardCloseTransitionTimeoutRef.current = window.setTimeout(() => {
1596
1531
  wizardCloseTransitionTimeoutRef.current = null;
1597
1532
  if (!closingProjectId)
@@ -1601,8 +1536,10 @@ export function TaskBoardWorkspace() {
1601
1536
  const nextAttentionTaskId = findNextAttentionTaskId(closedTaskId);
1602
1537
  setWizardForceOverview(false);
1603
1538
  if (nextAttentionTaskId) {
1604
- setWizardPinnedTaskId(nextAttentionTaskId);
1605
- setSelectedTaskId(nextAttentionTaskId);
1539
+ setTaskSelection(nextAttentionTaskId, {
1540
+ source: "system",
1541
+ pinInWizard: true,
1542
+ });
1606
1543
  }
1607
1544
  }, 1000);
1608
1545
  }, [
@@ -1612,33 +1549,30 @@ export function TaskBoardWorkspace() {
1612
1549
  wizardPinnedTaskId,
1613
1550
  clearWizardCloseTransitionTimeout,
1614
1551
  findNextAttentionTaskId,
1552
+ setTaskSelection,
1615
1553
  ]);
1616
- const wizardCommunicationPanel = useMemo(() => (_jsx(WizardCommunicationCenter, { task: wizardDisplayedTask, agentId: wizardDisplayedAgentId, agentStatus: wizardDisplayedAgentStatus, executionDisplay: wizardDisplayedExecutionDisplay, summaryState: wizardAttentionState.summaryState, stats: wizardAttentionState.stats, projectTitle: selectedProject?.project.title ?? "Current project", projectDescription: selectedProject?.project.description, taskProjectTitle: wizardDisplayedTask &&
1554
+ const wizardCommunicationPanel = useMemo(() => (_jsx(WizardCommunicationCenter, { task: wizardDisplayedTask, agentId: wizardDisplayedAgentId, agentStatus: undefined, executionDisplay: wizardDisplayedExecutionDisplay, summaryState: wizardAttentionState.summaryState, stats: wizardAttentionState.stats, projectTitle: selectedProject?.project.title ?? "No project selected", projectDescription: selectedProject?.project.description, taskProjectTitle: wizardDisplayedTask &&
1617
1555
  wizardDisplayedTask.projectId !== selectedProjectId
1618
1556
  ? directSubprojects.find((subproject) => subproject.project.projectId ===
1619
1557
  wizardDisplayedTask.projectId)?.project.title
1620
- : undefined, allTasks: wizardTasksWithDisplayAssignees, dependencies: dependencies, canEdit: canEditTasks, onTaskChanged: () => {
1621
- handleCloseWizardTask();
1558
+ : undefined, allTasks: wizardTasksWithDisplayAssignees, dependencies: dependencies, canEdit: canEditTasks, onContentChanged: () => {
1622
1559
  void refreshVisibleTaskData();
1560
+ }, onTaskChanged: () => {
1561
+ handleCloseWizardTask({ advanceToNext: true });
1562
+ void refreshVisibleTaskData();
1563
+ }, onTaskApproved: async () => {
1564
+ await refreshVisibleTaskData();
1565
+ handleCloseWizardTask({ advanceToNext: true });
1623
1566
  }, onInteractionSubmitted: () => {
1624
- if (wizardDisplayedAgentId) {
1625
- const normalizedId = wizardDisplayedAgentId.toLowerCase();
1626
- setAgentStatusesById((prev) => ({
1627
- ...prev,
1628
- [normalizedId]: "running",
1629
- }));
1630
- }
1631
- handleCloseWizardTask();
1632
- }, onCloseTask: handleCloseWizardTask, onSelectTask: (taskId) => {
1567
+ handleCloseWizardTask({ advanceToNext: true });
1568
+ }, onCloseTask: () => handleCloseWizardTask(), onSelectTask: (taskId) => {
1633
1569
  clearWizardCloseTransitionTimeout();
1634
1570
  setWizardForceOverview(false);
1635
- setWizardPinnedTaskId(taskId);
1636
- setSelectedTaskId(taskId);
1571
+ setTaskSelection(taskId, { source: "user", pinInWizard: true });
1637
1572
  } })), [
1638
1573
  wizardAttentionState,
1639
1574
  wizardDisplayedTask,
1640
1575
  wizardDisplayedAgentId,
1641
- wizardDisplayedAgentStatus,
1642
1576
  wizardDisplayedExecutionDisplay,
1643
1577
  canEditTasks,
1644
1578
  selectedProjectId,
@@ -1646,6 +1580,8 @@ export function TaskBoardWorkspace() {
1646
1580
  refreshVisibleTaskData,
1647
1581
  clearWizardCloseTransitionTimeout,
1648
1582
  handleCloseWizardTask,
1583
+ selectedTaskId,
1584
+ wizardPinnedTaskId,
1649
1585
  selectedProject?.project.title,
1650
1586
  selectedProject?.project.description,
1651
1587
  ]);
@@ -1654,30 +1590,31 @@ export function TaskBoardWorkspace() {
1654
1590
  if (isProjectViewLoading) {
1655
1591
  return (_jsx("div", { className: "flex h-full min-h-[240px] items-center justify-center rounded-lg border border-dashed border-slate-200 bg-slate-50/70", "data-testid": "taskboard-project-loading-state", children: _jsxs("div", { className: "flex flex-col items-center gap-3 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-6 w-6 animate-spin" }), _jsx("div", { children: "Loading project..." })] }) }));
1656
1592
  }
1657
- if (!selectedProjectId) {
1593
+ if (!selectedProjectId || (!selectedProject && !isProjectsLoading)) {
1658
1594
  return (_jsx("div", { className: "text-muted-foreground p-6 text-sm", children: "Select a project to view tasks." }));
1659
1595
  }
1660
1596
  if (isWizardMode) {
1661
1597
  return (_jsx(WizardView, { projectId: selectedProjectId, tasks: tasksWithDisplayAssignees, projectTasksLoading: isTasksLoading, dependencies: dependencies, subprojectTaskLists: wizardSubprojectTaskLists, subprojectTasksLoading: subprojectCountsLoading, selectedTaskId: wizardDisplayedTask?.taskId ?? null, onSelectTask: (id) => {
1662
1598
  setWizardForceOverview(false);
1663
- setWizardPinnedTaskId(id);
1664
- setSelectedTaskId(id);
1665
- }, agentStatusesById: agentStatusesById, projectTitle: selectedProject?.project.title ?? "Current project", projectDescription: selectedProject?.project.description, canEditProperties: selectedProject?.permission === "Owner", communicationPanel: wizardCommunicationPanel }));
1599
+ setTaskSelection(id, { source: "user", pinInWizard: true });
1600
+ }, projectTitle: selectedProject?.project.title ?? "No project selected", projectDescription: selectedProject?.project.description, canEditProperties: selectedProject?.permission === "Owner", communicationPanel: wizardCommunicationPanel }));
1666
1601
  }
1667
1602
  if (isListView) {
1668
- return (_jsxs("div", { className: "grid gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "Current project", selectedProjectDescription: selectedProject?.project.description, selectedProjectStatus: selectedProject?.project.status, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, canEditProjectStatus: selectedProject?.permission === "Owner", savingProjectStatus: savingProjectStatus, onStatusChange: handleProjectStatusChange, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, loading: subprojectCountsLoading, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canEditProperties: selectedProject?.permission === "Owner" }), _jsx(ListView, { tasks: tasksWithDisplayAssignees, onSelectTask: (id) => setSelectedTaskId(id), selectedTaskId: selectedTaskId, onTasksChanged: () => {
1603
+ return (_jsxs("div", { className: "grid gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "No project selected", selectedProjectDescription: selectedProject?.project.description, selectedProjectStatus: selectedProject?.project.status, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, canEditProjectStatus: selectedProject?.permission === "Owner", savingProjectStatus: savingProjectStatus, onStatusChange: handleProjectStatusChange, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, loading: subprojectCountsLoading, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canEditProperties: selectedProject?.permission === "Owner" }), _jsx(ListView, { tasks: tasksWithDisplayAssignees, onSelectTask: (id) => setTaskSelection(id, { source: "user" }), selectedTaskId: selectedTaskId, onTasksChanged: () => {
1669
1604
  void refreshVisibleTaskData();
1670
- }, projectId: selectedProjectId, permission: selectedProject?.permission, agentStatusesById: agentStatusesById, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
1605
+ }, projectId: selectedProjectId, permission: selectedProject?.permission, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
1671
1606
  }
1672
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "Current project", selectedProjectDescription: selectedProject?.project.description, selectedProjectStatus: selectedProject?.project.status, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, canEditProjectStatus: selectedProject?.permission === "Owner", savingProjectStatus: savingProjectStatus, onStatusChange: handleProjectStatusChange, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, loading: subprojectCountsLoading, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canEditProperties: selectedProject?.permission === "Owner" }), _jsx(KanbanView, { tasks: tasksWithDisplayAssignees, dependencies: dependencies, onSelectTask: (id) => setSelectedTaskId(id), selectedTaskId: selectedTaskId, onTasksChanged: () => {
1607
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "No project selected", selectedProjectDescription: selectedProject?.project.description, selectedProjectStatus: selectedProject?.project.status, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, canEditProjectStatus: selectedProject?.permission === "Owner", savingProjectStatus: savingProjectStatus, onStatusChange: handleProjectStatusChange, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, loading: subprojectCountsLoading, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canEditProperties: selectedProject?.permission === "Owner" }), _jsx(KanbanView, { tasks: tasksWithDisplayAssignees, dependencies: dependencies, onSelectTask: (id) => setTaskSelection(id, { source: "user" }), selectedTaskId: selectedTaskId, onTasksChanged: () => {
1673
1608
  void refreshVisibleTaskData();
1674
- }, projectId: selectedProjectId, permission: selectedProject?.permission, agentStatusesById: agentStatusesById, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
1609
+ }, projectId: selectedProjectId, permission: selectedProject?.permission, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
1675
1610
  }, [
1676
1611
  isProjectViewLoading,
1677
1612
  selectedProjectId,
1613
+ selectedProject,
1678
1614
  isWizardMode,
1679
1615
  isListView,
1680
1616
  isTasksLoading,
1617
+ isProjectsLoading,
1681
1618
  tasksWithDisplayAssignees,
1682
1619
  wizardSubprojectTaskLists,
1683
1620
  selectedTaskId,
@@ -1688,7 +1625,6 @@ export function TaskBoardWorkspace() {
1688
1625
  selectedProject?.project.status,
1689
1626
  selectedProject?.project.costLimit,
1690
1627
  selectedProjectCumulativeCostUsed,
1691
- agentStatusesById,
1692
1628
  dependencies,
1693
1629
  directSubprojects,
1694
1630
  parentProject?.project.projectId,
@@ -1716,14 +1652,13 @@ export function TaskBoardWorkspace() {
1716
1652
  selectedTask?.assigneeDisplayName,
1717
1653
  selectedTask?.assigneeId,
1718
1654
  ]);
1719
- const taskDetailPanel = useMemo(() => (_jsx(TaskDetailPanel, { project: selectedTaskProject, task: selectedTask, allTasks: taskSelectionUniverse, dependencies: dependencies, agentStatusesById: agentStatusesById, onTaskChanged: () => {
1655
+ const taskDetailPanel = useMemo(() => (_jsx(TaskDetailPanel, { project: selectedTaskProject, task: selectedTask, allTasks: taskSelectionUniverse, dependencies: dependencies, onTaskChanged: () => {
1720
1656
  void refreshVisibleTaskData();
1721
- }, onSelectTask: (taskId) => setSelectedTaskId(taskId), onClose: () => setSelectedTaskId(null), variant: "panel" })), [
1657
+ }, onSelectTask: (taskId) => setTaskSelection(taskId, { source: "user" }), onClose: () => setTaskSelection(null), variant: "panel" })), [
1722
1658
  selectedTaskProject,
1723
1659
  selectedTask,
1724
1660
  taskSelectionUniverse,
1725
1661
  dependencies,
1726
- agentStatusesById,
1727
1662
  refreshVisibleTaskData,
1728
1663
  ]);
1729
1664
  const agentTerminalPanel = useMemo(() => (_jsx(TaskAgentPanel, { agentId: displayAgentId, mode: agentPanelMode, label: selectedTaskIsPlan ? "Planner" : "Task Agent", hasAssignedAgentProfile: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile, assignedAgentProfileName: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile
@@ -1741,7 +1676,7 @@ export function TaskBoardWorkspace() {
1741
1676
  agentPanelMode === "no-agent" &&
1742
1677
  !selectedTaskHasAssignedAgentProfile
1743
1678
  ? () => setAssignAgentDialogOpen(true)
1744
- : undefined, assignedAgentStatus: displayAgentId ? currentAgentStatus : undefined, onReopenTask: selectedTask && canEditTasks
1679
+ : undefined, assignedAgentStatus: undefined, onReopenTask: selectedTask && canEditTasks
1745
1680
  ? () => void handleReopenSelectedTask()
1746
1681
  : undefined })), [
1747
1682
  displayAgentId,
@@ -1755,11 +1690,9 @@ export function TaskBoardWorkspace() {
1755
1690
  handleStartPlanning,
1756
1691
  selectedTaskIsBlocked,
1757
1692
  canEditTasks,
1758
- currentAgentStatus,
1759
1693
  selectedTask,
1760
1694
  handleReopenSelectedTask,
1761
1695
  ]);
1762
- const slotContext = editContext?.getActiveSlotContext();
1763
1696
  const previewItemVersion = useMemo(() => {
1764
1697
  if (!currentItemDescriptor)
1765
1698
  return null;
@@ -1917,12 +1850,24 @@ export function TaskBoardWorkspace() {
1917
1850
  ]);
1918
1851
  const itemPreviewPanel = useMemo(() => {
1919
1852
  const hasCurrentItem = !!currentItemDescriptor;
1920
- if (!slotContext) {
1921
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx("div", { className: "border-b border-gray-200 bg-white px-3 py-2", children: hasCurrentItem ? (_jsxs("div", { className: "min-w-0", children: [hasMultipleContextItems ? (_jsx(Select, { value: selectedContextItemValue, onValueChange: handleSelectContextItem, options: contextItemOptions, placeholder: "Select context item", size: "xs", searchable: true, className: "h-7 rounded-md bg-white", maxWidth: 420, "data-testid": "taskboard-context-item-selector" })) : (_jsx("div", { className: "truncate text-sm font-medium text-slate-900", children: previewItemName || "Loading item..." })), _jsxs("div", { className: "mt-0.5 flex items-center gap-2", children: [_jsx("span", { className: "truncate text-xs text-slate-500", children: previewItemPath || "Path unavailable" }), _jsx("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: currentItemDescriptor?.language }), previewItemVersion != null && (_jsxs("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: ["v", previewItemVersion] }))] })] })) : (_jsx("div", { className: "text-xs text-slate-500", children: "No item loaded" })) }), _jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-3 bg-gray-50 p-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm font-medium", children: "Editor preview unavailable" }), _jsx("div", { className: "text-muted-foreground text-xs", children: "Open the editor context item to preview and edit it here." })] })] }));
1853
+ const previewSidebar = (_jsx("div", { className: "relative z-10 flex h-full min-h-0 flex-col bg-white", children: _jsx("div", { className: "min-h-0 flex-1", children: _jsx(MainContentTree, { mode: "normal" }) }) }));
1854
+ const renderPreviewSplit = (editorContent) => (_jsx(Splitter, { localStorageKey: "taskboard-preview-tree-splitter", panels: [
1855
+ {
1856
+ name: "content-tree",
1857
+ defaultSize: 320,
1858
+ content: previewSidebar,
1859
+ },
1860
+ {
1861
+ name: "editor-preview",
1862
+ defaultSize: "auto",
1863
+ content: (_jsx("div", { className: "min-h-0 min-w-0 h-full overflow-hidden bg-gray-50", children: editorContent })),
1864
+ },
1865
+ ], splitterClassName: "bg-gray-200 hover:bg-gray-300 w-[6px]", className: "h-full" }));
1866
+ if (!currentItemDescriptor) {
1867
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx("div", { className: "border-b border-gray-200 bg-white px-3 py-2", children: hasCurrentItem ? (_jsxs("div", { className: "min-w-0", children: [hasMultipleContextItems ? (_jsx(Select, { value: selectedContextItemValue, onValueChange: handleSelectContextItem, options: contextItemOptions, placeholder: "Select context item", size: "xs", searchable: true, className: "h-7 rounded-md bg-white", maxWidth: 420, "data-testid": "taskboard-context-item-selector" })) : (_jsx("div", { className: "truncate text-sm font-medium text-slate-900", children: previewItemName || "Loading item..." })), _jsxs("div", { className: "mt-0.5 flex items-center gap-2", children: [_jsx("span", { className: "truncate text-xs text-slate-500", children: previewItemPath || "Path unavailable" }), _jsx("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: selectedContextItemValue.split("|")[1] || "" }), previewItemVersion != null && (_jsxs("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: ["v", previewItemVersion] }))] })] })) : (_jsx("div", { className: "text-xs text-slate-500", children: "No item loaded" })) }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden bg-gray-50", children: renderPreviewSplit(_jsxs("div", { className: "flex min-h-0 h-full flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("div", { className: "text-muted-foreground text-sm font-medium", children: "Editor preview unavailable" }), _jsx("div", { className: "text-muted-foreground text-xs", children: "Open the editor context item to preview and edit it here." })] })) })] }));
1922
1868
  }
1923
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx("div", { className: "border-b border-gray-200 bg-white px-3 py-2", children: hasCurrentItem ? (_jsxs("div", { className: "min-w-0", children: [hasMultipleContextItems ? (_jsx(Select, { value: selectedContextItemValue, onValueChange: handleSelectContextItem, options: contextItemOptions, placeholder: "Select context item", size: "xs", searchable: true, className: "h-7 rounded-md bg-white", maxWidth: 420, "data-testid": "taskboard-context-item-selector" })) : (_jsx("div", { className: "truncate text-sm font-medium text-slate-900", children: previewItemName || "Loading item..." })), _jsxs("div", { className: "mt-0.5 flex items-center gap-2", children: [_jsx("span", { className: "truncate text-xs text-slate-500", children: previewItemPath || "Path unavailable" }), _jsx("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: currentItemDescriptor?.language }), previewItemVersion != null && (_jsxs("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: ["v", previewItemVersion] }))] })] })) : (_jsx("div", { className: "text-xs text-slate-500", children: "No item loaded" })) }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden bg-gray-50", children: _jsx(EditorSlotContextProvider, { value: slotContext, children: _jsx(SingleEditView, { compareView: false, name: "taskboard-workspace-preview", view: "primary" }) }) })] }));
1869
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx("div", { className: "border-b border-gray-200 bg-white px-3 py-2", children: hasCurrentItem ? (_jsxs("div", { className: "min-w-0", children: [hasMultipleContextItems ? (_jsx(Select, { value: selectedContextItemValue, onValueChange: handleSelectContextItem, options: contextItemOptions, placeholder: "Select context item", size: "xs", searchable: true, className: "h-7 rounded-md bg-white", maxWidth: 420, "data-testid": "taskboard-context-item-selector" })) : (_jsx("div", { className: "truncate text-sm font-medium text-slate-900", children: previewItemName || "Loading item..." })), _jsxs("div", { className: "mt-0.5 flex items-center gap-2", children: [_jsx("span", { className: "truncate text-xs text-slate-500", children: previewItemPath || "Path unavailable" }), _jsx("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: currentItemDescriptor?.language }), previewItemVersion != null && (_jsxs("span", { className: "shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-600", children: ["v", previewItemVersion] }))] })] })) : (_jsx("div", { className: "text-xs text-slate-500", children: "No item loaded" })) }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden bg-gray-50", children: renderPreviewSplit(_jsx(TaskboardPreviewEditor, { itemDescriptor: currentItemDescriptor })) })] }));
1924
1870
  }, [
1925
- slotContext,
1926
1871
  currentItemDescriptor,
1927
1872
  contextItemOptions,
1928
1873
  hasMultipleContextItems,
@@ -2052,6 +1997,7 @@ export function TaskBoardWorkspace() {
2052
1997
  ? "task-board-wizard-splitter-v2"
2053
1998
  : "task-board-main-splitter" })) }), selectedProjectId && (_jsx(CreateTaskDialog, { open: createDialogOpen, onOpenChange: setCreateDialogOpen, projectId: selectedProjectId, onCreated: () => {
2054
1999
  void refreshTasks(selectedProjectId);
2000
+ void refreshExecutionState(selectedProjectId);
2055
2001
  void refreshDependencies(selectedProjectId);
2056
2002
  } })), _jsx(CreateProjectDialog, { open: createProjectOpen, onOpenChange: setCreateProjectOpen, projects: projects, onCreated: async ({ projectId: newProjectId, openInWizardMode }) => {
2057
2003
  if (openInWizardMode) {
@@ -2068,6 +2014,7 @@ export function TaskBoardWorkspace() {
2068
2014
  if (!selectedProjectId)
2069
2015
  return;
2070
2016
  await refreshTasks(selectedProjectId);
2017
+ await refreshExecutionState(selectedProjectId);
2071
2018
  await refreshDependencies(selectedProjectId);
2072
2019
  } })] }));
2073
2020
  }