@parhelia/core 0.1.12241 → 0.1.12259
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.
- package/dist/agents-view/AgentsWorkspaceView.js +9 -1
- package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
- package/dist/components/MarkdownDisplay.d.ts +10 -0
- package/dist/components/MarkdownDisplay.js +197 -0
- package/dist/components/MarkdownDisplay.js.map +1 -0
- package/dist/config/config.js +4 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/notificationRoutes.d.ts +2 -0
- package/dist/config/notificationRoutes.js +195 -0
- package/dist/config/notificationRoutes.js.map +1 -0
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.js.map +1 -1
- package/dist/editor/ContentTree.d.ts +2 -1
- package/dist/editor/ContentTree.js +11 -3
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/Editor.js +12 -3
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/GlobalMenuBar.js +29 -1
- package/dist/editor/GlobalMenuBar.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +9 -1
- package/dist/editor/ai/AgentTerminal.js +233 -53
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/dialogs/AgentDialogHandler.js +5 -0
- package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +3 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js +5 -5
- package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
- package/dist/editor/client/EditorShell.js +5 -1
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +4 -1
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/hooks/useEditorWebSocket.js +36 -4
- package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +2 -0
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/operations.d.ts +2 -1
- package/dist/editor/client/operations.js +8 -0
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/commands/itemCommands.d.ts +1 -0
- package/dist/editor/commands/itemCommands.js +23 -1
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +2 -134
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/notifications/NotificationCenter.d.ts +1 -0
- package/dist/editor/notifications/NotificationCenter.js +69 -0
- package/dist/editor/notifications/NotificationCenter.js.map +1 -0
- package/dist/editor/notifications/NotificationItem.d.ts +14 -0
- package/dist/editor/notifications/NotificationItem.js +40 -0
- package/dist/editor/notifications/NotificationItem.js.map +1 -0
- package/dist/editor/notifications/WatchButton.d.ts +7 -0
- package/dist/editor/notifications/WatchButton.js +191 -0
- package/dist/editor/notifications/WatchButton.js.map +1 -0
- package/dist/editor/notifications/notificationListState.d.ts +25 -0
- package/dist/editor/notifications/notificationListState.js +38 -0
- package/dist/editor/notifications/notificationListState.js.map +1 -0
- package/dist/editor/notifications/notificationRoutes.d.ts +15 -0
- package/dist/editor/notifications/notificationRoutes.js +58 -0
- package/dist/editor/notifications/notificationRoutes.js.map +1 -0
- package/dist/editor/notifications/useNotificationSubscriptions.d.ts +14 -0
- package/dist/editor/notifications/useNotificationSubscriptions.js +88 -0
- package/dist/editor/notifications/useNotificationSubscriptions.js.map +1 -0
- package/dist/editor/notifications/useNotifications.d.ts +28 -0
- package/dist/editor/notifications/useNotifications.js +166 -0
- package/dist/editor/notifications/useNotifications.js.map +1 -0
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/reviews/CreateReviewConfirmStep.d.ts +2 -1
- package/dist/editor/reviews/CreateReviewConfirmStep.js +3 -3
- package/dist/editor/reviews/CreateReviewConfirmStep.js.map +1 -1
- package/dist/editor/reviews/CreateReviewDialog.js +24 -9
- package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
- package/dist/editor/reviews/ReviewDetail.js +2 -1
- package/dist/editor/reviews/ReviewDetail.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +6 -0
- package/dist/editor/services/agentService.js +32 -10
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +1 -0
- package/dist/editor/services/contentService.js +3 -0
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/notificationService.d.ts +73 -0
- package/dist/editor/services/notificationService.js +72 -0
- package/dist/editor/services/notificationService.js.map +1 -0
- package/dist/editor/ui/PublishItemDialog.d.ts +8 -0
- package/dist/editor/ui/PublishItemDialog.js +193 -0
- package/dist/editor/ui/PublishItemDialog.js.map +1 -0
- package/dist/editor/ui/PublishRestrictionsDialog.js +165 -75
- package/dist/editor/ui/PublishRestrictionsDialog.js.map +1 -1
- package/dist/editor/ui/TreeListSelector.d.ts +2 -1
- package/dist/editor/ui/TreeListSelector.js +2 -2
- package/dist/editor/ui/TreeListSelector.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/ParheliaAssistantChat.js +9 -9
- package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
- package/dist/task-board/TaskBoardWorkspace.js +716 -402
- package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
- package/dist/task-board/components/AssignAgentDialog.js +2 -3
- package/dist/task-board/components/AssignAgentDialog.js.map +1 -1
- package/dist/task-board/components/CreateProjectDialog.d.ts +4 -1
- package/dist/task-board/components/CreateProjectDialog.js +23 -8
- package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
- package/dist/task-board/components/TaskAgentPanel.js +10 -6
- package/dist/task-board/components/TaskAgentPanel.js.map +1 -1
- package/dist/task-board/components/TaskBoardMyTasksSidebar.js +48 -2
- package/dist/task-board/components/TaskBoardMyTasksSidebar.js.map +1 -1
- package/dist/task-board/components/TaskBoardTitlebar.js +4 -4
- package/dist/task-board/components/TaskBoardTitlebar.js.map +1 -1
- package/dist/task-board/components/TaskDetailPanel.js +2 -1
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/dist/task-board/components/TaskReviewActions.d.ts +8 -0
- package/dist/task-board/components/TaskReviewActions.js +110 -0
- package/dist/task-board/components/TaskReviewActions.js.map +1 -0
- package/dist/task-board/components/TaskRow.js +1 -1
- package/dist/task-board/components/TaskRow.js.map +1 -1
- package/dist/task-board/components/WizardCommunicationCenter.d.ts +30 -0
- package/dist/task-board/components/WizardCommunicationCenter.js +185 -0
- package/dist/task-board/components/WizardCommunicationCenter.js.map +1 -0
- package/dist/task-board/taskAgentConfig.d.ts +1 -3
- package/dist/task-board/taskAgentConfig.js +5 -15
- package/dist/task-board/taskAgentConfig.js.map +1 -1
- package/dist/task-board/taskAgentLink.js +4 -2
- package/dist/task-board/taskAgentLink.js.map +1 -1
- package/dist/task-board/taskBoardNavStore.d.ts +0 -1
- package/dist/task-board/taskBoardNavStore.js +0 -1
- package/dist/task-board/taskBoardNavStore.js.map +1 -1
- package/dist/task-board/taskExecutionStatus.js +13 -3
- package/dist/task-board/taskExecutionStatus.js.map +1 -1
- package/dist/task-board/types.d.ts +2 -0
- package/dist/task-board/views/KanbanView.js +3 -0
- package/dist/task-board/views/KanbanView.js.map +1 -1
- package/dist/task-board/views/ListView.js +3 -0
- package/dist/task-board/views/ListView.js.map +1 -1
- package/dist/task-board/views/WizardView.d.ts +9 -7
- package/dist/task-board/views/WizardView.js +164 -39
- package/dist/task-board/views/WizardView.js.map +1 -1
- package/dist/types.d.ts +19 -1
- package/package.json +1 -2
- package/styles.css +6 -10
|
@@ -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,
|
|
6
|
-
import { getActiveAgents, getAgent,
|
|
5
|
+
import { deleteProject, getProjects, getTasks, getDependencies, runOrchestrator, triggerPlanning, updateProject, updateTask, } from "./services/taskService";
|
|
6
|
+
import { getActiveAgents, 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";
|
|
@@ -20,11 +20,13 @@ import { ProjectSettingsDialog } from "./components/ProjectSettingsDialog";
|
|
|
20
20
|
import { EditorSlotContextProvider } from "../editor/views/editorSlotContext";
|
|
21
21
|
import { SingleEditView } from "../editor/views/SingleEditView";
|
|
22
22
|
import { Select } from "../components/ui/select";
|
|
23
|
-
import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "../components/ui/dialog";
|
|
24
23
|
import { getLinkedAgentId } from "./taskAgentLink";
|
|
25
24
|
import { setTaskBoardNavState, resetTaskBoardNavState, } from "./taskBoardNavStore";
|
|
26
25
|
import { flattenProjectsHierarchy } from "./utils/projectHierarchy";
|
|
27
26
|
import { normalizeTaskStatus } from "./taskStatus";
|
|
27
|
+
import { getTaskExecutionDisplay, } from "./taskExecutionStatus";
|
|
28
|
+
import { WizardCommunicationCenter } from "./components/WizardCommunicationCenter";
|
|
29
|
+
import { Loader2 } from "lucide-react";
|
|
28
30
|
const TASKBOARD_PROJECT_QUERY_KEY = "tbProjectId";
|
|
29
31
|
const TASKBOARD_TASK_QUERY_KEY = "tbTaskId";
|
|
30
32
|
const TASKBOARD_VIEW_QUERY_KEY = "tbView";
|
|
@@ -43,13 +45,25 @@ function getInitialWizardMode() {
|
|
|
43
45
|
const value = getInitialQueryValue(TASKBOARD_WIZARD_QUERY_KEY)?.toLowerCase();
|
|
44
46
|
return value === "true" || value === "1";
|
|
45
47
|
}
|
|
48
|
+
function getTaskBoardStateFromUrl() {
|
|
49
|
+
const projectId = getInitialQueryValue(TASKBOARD_PROJECT_QUERY_KEY);
|
|
50
|
+
return {
|
|
51
|
+
projectId,
|
|
52
|
+
taskId: projectId ? getInitialQueryValue(TASKBOARD_TASK_QUERY_KEY) : null,
|
|
53
|
+
activeTab: getInitialViewTabIndex(),
|
|
54
|
+
isWizardMode: getInitialWizardMode(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
46
57
|
function normalizeAgentStatus(status) {
|
|
47
58
|
if (status === undefined || status === null)
|
|
48
59
|
return undefined;
|
|
49
60
|
if (typeof status === "number") {
|
|
50
61
|
return status;
|
|
51
62
|
}
|
|
52
|
-
const normalized = String(status)
|
|
63
|
+
const normalized = String(status)
|
|
64
|
+
.trim()
|
|
65
|
+
.replace(/[^a-z0-9]/gi, "")
|
|
66
|
+
.toLowerCase();
|
|
53
67
|
switch (normalized) {
|
|
54
68
|
case "running":
|
|
55
69
|
return "running";
|
|
@@ -97,12 +111,71 @@ function normalizeAgentStatus(status) {
|
|
|
97
111
|
return status;
|
|
98
112
|
}
|
|
99
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)
|
|
125
|
+
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);
|
|
135
|
+
}
|
|
136
|
+
function addAgentDisplayNames(taskList, agentProfileTitlesById) {
|
|
137
|
+
return taskList.map((task) => {
|
|
138
|
+
if (task.assigneeType !== "Agent" ||
|
|
139
|
+
!task.assigneeId ||
|
|
140
|
+
task.assigneeDisplayName) {
|
|
141
|
+
return task;
|
|
142
|
+
}
|
|
143
|
+
const profileTitle = agentProfileTitlesById[task.assigneeId.toLowerCase()];
|
|
144
|
+
if (!profileTitle)
|
|
145
|
+
return task;
|
|
146
|
+
return {
|
|
147
|
+
...task,
|
|
148
|
+
assigneeDisplayName: profileTitle,
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function buildTaskCounts(taskList) {
|
|
153
|
+
const counts = {
|
|
154
|
+
total: taskList.length,
|
|
155
|
+
todo: 0,
|
|
156
|
+
inProgress: 0,
|
|
157
|
+
review: 0,
|
|
158
|
+
done: 0,
|
|
159
|
+
};
|
|
160
|
+
for (const task of taskList) {
|
|
161
|
+
const normalizedStatus = normalizeTaskStatus(task.status, task.executionStatus);
|
|
162
|
+
if (normalizedStatus === "Todo")
|
|
163
|
+
counts.todo += 1;
|
|
164
|
+
else if (normalizedStatus === "InProgress")
|
|
165
|
+
counts.inProgress += 1;
|
|
166
|
+
else if (normalizedStatus === "Review")
|
|
167
|
+
counts.review += 1;
|
|
168
|
+
else if (normalizedStatus === "Done")
|
|
169
|
+
counts.done += 1;
|
|
170
|
+
}
|
|
171
|
+
return counts;
|
|
172
|
+
}
|
|
100
173
|
// ── component ────────────────────────────────────────────────────────
|
|
101
174
|
export function TaskBoardWorkspace() {
|
|
102
175
|
const editContext = useEditContext();
|
|
103
176
|
const isAdministrator = editContext?.user?.isAdministrator === true;
|
|
104
177
|
const isMobile = editContext?.isMobile ?? false;
|
|
105
|
-
const showEditorPanel = editContext?.showAgentsWorkspaceEditor ??
|
|
178
|
+
const showEditorPanel = editContext?.showAgentsWorkspaceEditor ?? false;
|
|
106
179
|
useEffect(() => {
|
|
107
180
|
if (editContext?.isMobile && !showEditorPanel) {
|
|
108
181
|
editContext?.setShowAgentsWorkspaceEditor(true);
|
|
@@ -113,6 +186,7 @@ export function TaskBoardWorkspace() {
|
|
|
113
186
|
const [mobileActiveTab, setMobileActiveTab] = useState(0);
|
|
114
187
|
const [selectedProjectId, setSelectedProjectId] = useState(() => getInitialQueryValue(TASKBOARD_PROJECT_QUERY_KEY));
|
|
115
188
|
const [tasks, setTasks] = useState([]);
|
|
189
|
+
const [isTasksLoading, setIsTasksLoading] = useState(false);
|
|
116
190
|
const [dependencies, setDependencies] = useState([]);
|
|
117
191
|
const [selectedTaskId, setSelectedTaskId] = useState(() => getInitialQueryValue(TASKBOARD_TASK_QUERY_KEY));
|
|
118
192
|
const [activeTab, setActiveTab] = useState(() => getInitialViewTabIndex());
|
|
@@ -125,25 +199,28 @@ export function TaskBoardWorkspace() {
|
|
|
125
199
|
const [runningOrchestrator, setRunningOrchestrator] = useState(false);
|
|
126
200
|
const [showAllProjects, setShowAllProjects] = useState(false);
|
|
127
201
|
const [isProjectsLoading, setIsProjectsLoading] = useState(false);
|
|
202
|
+
const [projectViewLoadingId, setProjectViewLoadingId] = useState(null);
|
|
128
203
|
const [agentStatusesById, setAgentStatusesById] = useState({});
|
|
129
204
|
const [agentProfileTitlesById, setAgentProfileTitlesById] = useState({});
|
|
130
205
|
const [subprojectTaskCounts, setSubprojectTaskCounts] = useState({});
|
|
206
|
+
const [subprojectTasksByProjectId, setSubprojectTasksByProjectId] = useState({});
|
|
131
207
|
const [subprojectCountsLoading, setSubprojectCountsLoading] = useState(false);
|
|
132
208
|
const [previewItemName, setPreviewItemName] = useState("");
|
|
133
209
|
const [previewItemPath, setPreviewItemPath] = useState("");
|
|
134
210
|
const [agentContextItems, setAgentContextItems] = useState([]);
|
|
135
211
|
const [contextItemNamesByKey, setContextItemNamesByKey] = useState({});
|
|
136
212
|
const [contextItemPathsByKey, setContextItemPathsByKey] = useState({});
|
|
137
|
-
const [
|
|
138
|
-
const [
|
|
139
|
-
const [wizardActionInFlightId, setWizardActionInFlightId] = useState(null);
|
|
140
|
-
const [wizardAgentDialogOpen, setWizardAgentDialogOpen] = useState(false);
|
|
213
|
+
const [wizardPinnedTaskId, setWizardPinnedTaskId] = useState(null);
|
|
214
|
+
const [wizardForceOverview, setWizardForceOverview] = useState(false);
|
|
141
215
|
const wsRefreshTimeoutRef = useRef(null);
|
|
142
216
|
const wsSubprojectRefreshTimeoutRef = useRef(null);
|
|
143
217
|
const wsProjectRefreshTimeoutRef = useRef(null);
|
|
144
|
-
const
|
|
145
|
-
const wizardGuidanceRequestSeqRef = useRef(0);
|
|
218
|
+
const wizardCloseTransitionTimeoutRef = useRef(null);
|
|
146
219
|
const projectsRequestCountRef = useRef(0);
|
|
220
|
+
const tasksRequestCountRef = useRef(0);
|
|
221
|
+
const latestTasksProjectIdRef = useRef(null);
|
|
222
|
+
const latestDependenciesProjectIdRef = useRef(null);
|
|
223
|
+
const latestSubprojectCountsProjectIdRef = useRef(null);
|
|
147
224
|
const refreshProjectsRef = useRef(null);
|
|
148
225
|
const refreshTasksRef = useRef(null);
|
|
149
226
|
const refreshDependenciesRef = useRef(null);
|
|
@@ -155,6 +232,14 @@ export function TaskBoardWorkspace() {
|
|
|
155
232
|
const autoSelectedProjectIdsRef = useRef(new Set());
|
|
156
233
|
const taskBoardSubscribedAgentIdsRef = useRef(new Set());
|
|
157
234
|
const taskBoardSocketVersionRef = useRef(null);
|
|
235
|
+
const syncTaskBoardStateFromUrl = useCallback(() => {
|
|
236
|
+
const nextState = getTaskBoardStateFromUrl();
|
|
237
|
+
setSelectedProjectId(nextState.projectId);
|
|
238
|
+
setSelectedTaskId(nextState.taskId);
|
|
239
|
+
setActiveTab(nextState.activeTab);
|
|
240
|
+
setIsWizardMode(nextState.isWizardMode);
|
|
241
|
+
setWizardForceOverview(false);
|
|
242
|
+
}, []);
|
|
158
243
|
useEffect(() => {
|
|
159
244
|
let cancelled = false;
|
|
160
245
|
loadAiProfiles()
|
|
@@ -177,50 +262,35 @@ export function TaskBoardWorkspace() {
|
|
|
177
262
|
cancelled = true;
|
|
178
263
|
};
|
|
179
264
|
}, []);
|
|
180
|
-
const tasksWithDisplayAssignees = useMemo(() =>
|
|
181
|
-
return tasks.map((task) => {
|
|
182
|
-
if (task.assigneeType !== "Agent" ||
|
|
183
|
-
!task.assigneeId ||
|
|
184
|
-
task.assigneeDisplayName) {
|
|
185
|
-
return task;
|
|
186
|
-
}
|
|
187
|
-
const profileTitle = agentProfileTitlesById[task.assigneeId.toLowerCase()];
|
|
188
|
-
if (!profileTitle)
|
|
189
|
-
return task;
|
|
190
|
-
return {
|
|
191
|
-
...task,
|
|
192
|
-
assigneeDisplayName: profileTitle,
|
|
193
|
-
};
|
|
194
|
-
});
|
|
195
|
-
}, [tasks, agentProfileTitlesById]);
|
|
265
|
+
const tasksWithDisplayAssignees = useMemo(() => addAgentDisplayNames(tasks, agentProfileTitlesById), [tasks, agentProfileTitlesById]);
|
|
196
266
|
const selectedProject = useMemo(() => projects.find((p) => p.project.projectId === selectedProjectId) ?? null, [projects, selectedProjectId]);
|
|
197
267
|
const settingsProject = useMemo(() => projects.find((p) => p.project.projectId === settingsProjectId) ?? null, [projects, settingsProjectId]);
|
|
198
|
-
const selectedProjectTaskCounts = useMemo(() =>
|
|
199
|
-
const counts = {
|
|
200
|
-
total: tasks.length,
|
|
201
|
-
todo: 0,
|
|
202
|
-
inProgress: 0,
|
|
203
|
-
review: 0,
|
|
204
|
-
done: 0,
|
|
205
|
-
};
|
|
206
|
-
for (const task of tasks) {
|
|
207
|
-
const normalizedStatus = normalizeTaskStatus(task.status, task.executionStatus);
|
|
208
|
-
if (normalizedStatus === "Todo")
|
|
209
|
-
counts.todo += 1;
|
|
210
|
-
else if (normalizedStatus === "InProgress")
|
|
211
|
-
counts.inProgress += 1;
|
|
212
|
-
else if (normalizedStatus === "Review")
|
|
213
|
-
counts.review += 1;
|
|
214
|
-
else if (normalizedStatus === "Done")
|
|
215
|
-
counts.done += 1;
|
|
216
|
-
}
|
|
217
|
-
return counts;
|
|
218
|
-
}, [tasks]);
|
|
268
|
+
const selectedProjectTaskCounts = useMemo(() => buildTaskCounts(tasks), [tasks]);
|
|
219
269
|
const directSubprojects = useMemo(() => selectedProjectId
|
|
220
270
|
? projects
|
|
221
271
|
.filter((project) => project.project.parentProjectId === selectedProjectId)
|
|
222
272
|
.sort((a, b) => a.project.title.localeCompare(b.project.title))
|
|
223
273
|
: [], [projects, selectedProjectId]);
|
|
274
|
+
const subprojectTasksWithDisplayAssignees = useMemo(() => {
|
|
275
|
+
const nextTasks = {};
|
|
276
|
+
for (const [projectId, taskList] of Object.entries(subprojectTasksByProjectId)) {
|
|
277
|
+
nextTasks[projectId] = addAgentDisplayNames(taskList, agentProfileTitlesById);
|
|
278
|
+
}
|
|
279
|
+
return nextTasks;
|
|
280
|
+
}, [subprojectTasksByProjectId, agentProfileTitlesById]);
|
|
281
|
+
const wizardSubprojectTaskLists = useMemo(() => directSubprojects.map((subproject) => ({
|
|
282
|
+
projectId: subproject.project.projectId,
|
|
283
|
+
title: subproject.project.title,
|
|
284
|
+
tasks: subprojectTasksWithDisplayAssignees[subproject.project.projectId] ||
|
|
285
|
+
[],
|
|
286
|
+
})), [directSubprojects, subprojectTasksWithDisplayAssignees]);
|
|
287
|
+
const wizardTasksWithDisplayAssignees = useMemo(() => [
|
|
288
|
+
...tasksWithDisplayAssignees,
|
|
289
|
+
...wizardSubprojectTaskLists.flatMap((subproject) => subproject.tasks),
|
|
290
|
+
], [tasksWithDisplayAssignees, wizardSubprojectTaskLists]);
|
|
291
|
+
const taskSelectionUniverse = useMemo(() => isWizardMode
|
|
292
|
+
? wizardTasksWithDisplayAssignees
|
|
293
|
+
: tasksWithDisplayAssignees, [isWizardMode, wizardTasksWithDisplayAssignees, tasksWithDisplayAssignees]);
|
|
224
294
|
const selectedProjectCumulativeCostUsed = useMemo(() => {
|
|
225
295
|
if (!selectedProjectId)
|
|
226
296
|
return 0;
|
|
@@ -308,14 +378,28 @@ export function TaskBoardWorkspace() {
|
|
|
308
378
|
setSelectedProjectId((previousProjectId) => {
|
|
309
379
|
if (previousProjectId !== projectId) {
|
|
310
380
|
setSelectedTaskId(null);
|
|
381
|
+
setWizardForceOverview(false);
|
|
311
382
|
}
|
|
312
383
|
return projectId;
|
|
313
384
|
});
|
|
314
385
|
}, []);
|
|
386
|
+
const clearSelectedProjectData = useCallback(() => {
|
|
387
|
+
setTasks([]);
|
|
388
|
+
setDependencies([]);
|
|
389
|
+
setSubprojectTaskCounts({});
|
|
390
|
+
setSubprojectTasksByProjectId({});
|
|
391
|
+
setAgentStatusesById({});
|
|
392
|
+
}, []);
|
|
315
393
|
const isPlanning = selectedProject?.project.status === "Planning";
|
|
316
|
-
const
|
|
317
|
-
const
|
|
318
|
-
|
|
394
|
+
const selectedTask = useMemo(() => taskSelectionUniverse.find((t) => t.taskId === selectedTaskId) ?? null, [taskSelectionUniverse, selectedTaskId]);
|
|
395
|
+
const wizardPinnedTask = useMemo(() => wizardPinnedTaskId
|
|
396
|
+
? (taskSelectionUniverse.find((task) => task.taskId === wizardPinnedTaskId) ?? null)
|
|
397
|
+
: null, [taskSelectionUniverse, wizardPinnedTaskId]);
|
|
398
|
+
const selectedTaskProject = useMemo(() => {
|
|
399
|
+
if (!selectedTask)
|
|
400
|
+
return selectedProject;
|
|
401
|
+
return (projects.find((project) => project.project.projectId === selectedTask.projectId) ?? selectedProject);
|
|
402
|
+
}, [projects, selectedProject, selectedTask]);
|
|
319
403
|
const planningTask = useMemo(() => tasksWithDisplayAssignees.find((t) => String(t.taskType ?? "").toLowerCase() === "plan") ??
|
|
320
404
|
tasksWithDisplayAssignees.find((t) => t.title === "Project Plan") ??
|
|
321
405
|
null, [tasksWithDisplayAssignees]);
|
|
@@ -326,6 +410,8 @@ export function TaskBoardWorkspace() {
|
|
|
326
410
|
const selectedTaskIsBlocked = useMemo(() => {
|
|
327
411
|
if (!selectedTask)
|
|
328
412
|
return false;
|
|
413
|
+
if (selectedTask.projectId !== selectedProjectId)
|
|
414
|
+
return false;
|
|
329
415
|
const blockerIds = dependencies
|
|
330
416
|
.filter((dependency) => dependency.taskId === selectedTask.taskId &&
|
|
331
417
|
dependency.dependencyType === "BlockedBy")
|
|
@@ -337,38 +423,80 @@ export function TaskBoardWorkspace() {
|
|
|
337
423
|
const blockerTask = taskById.get(blockerId);
|
|
338
424
|
return !blockerTask || blockerTask.status !== "Done";
|
|
339
425
|
});
|
|
340
|
-
}, [
|
|
426
|
+
}, [
|
|
427
|
+
selectedTask,
|
|
428
|
+
selectedProjectId,
|
|
429
|
+
dependencies,
|
|
430
|
+
tasksWithDisplayAssignees,
|
|
431
|
+
]);
|
|
341
432
|
const selectedTaskHasAssignedAgentProfile = useMemo(() => {
|
|
342
433
|
if (!selectedTask)
|
|
343
434
|
return false;
|
|
344
435
|
return selectedTask.assigneeType === "Agent" && !!selectedTask.assigneeId;
|
|
345
436
|
}, [selectedTask]);
|
|
346
437
|
const canRunAssignedAgent = useMemo(() => {
|
|
347
|
-
return !selectedTaskIsBlocked
|
|
348
|
-
}, [selectedTaskIsBlocked
|
|
438
|
+
return !selectedTaskIsBlocked;
|
|
439
|
+
}, [selectedTaskIsBlocked]);
|
|
349
440
|
const runAssignedAgentDisabledReason = useMemo(() => {
|
|
350
441
|
if (selectedTaskIsBlocked) {
|
|
351
442
|
return "This task is blocked by unfinished dependencies.";
|
|
352
443
|
}
|
|
353
|
-
if (!selectedProjectIsActive) {
|
|
354
|
-
return "Project is not active. Set the project status to Active to run agents.";
|
|
355
|
-
}
|
|
356
444
|
return undefined;
|
|
357
|
-
}, [selectedTaskIsBlocked
|
|
445
|
+
}, [selectedTaskIsBlocked]);
|
|
358
446
|
// ── current agent ID and panel mode for the right panel ──
|
|
359
447
|
const currentAgentId = useMemo(() => {
|
|
360
448
|
return getLinkedAgentId(selectedTask);
|
|
361
449
|
}, [selectedTask]);
|
|
450
|
+
const selectedTaskStatus = useMemo(() => {
|
|
451
|
+
if (!selectedTask)
|
|
452
|
+
return null;
|
|
453
|
+
return normalizeTaskStatus(selectedTask.status, selectedTask.executionStatus);
|
|
454
|
+
}, [selectedTask]);
|
|
455
|
+
const selectedTaskHasResultData = useMemo(() => {
|
|
456
|
+
return !!selectedTask?.resultData?.trim();
|
|
457
|
+
}, [selectedTask?.resultData]);
|
|
458
|
+
const shouldHideAgentPanelForSelectedTask = useMemo(() => {
|
|
459
|
+
return (!!selectedTask &&
|
|
460
|
+
selectedTaskIsPlan &&
|
|
461
|
+
selectedTaskStatus === "Done" &&
|
|
462
|
+
selectedTaskHasResultData);
|
|
463
|
+
}, [
|
|
464
|
+
selectedTask,
|
|
465
|
+
selectedTaskIsPlan,
|
|
466
|
+
selectedTaskStatus,
|
|
467
|
+
selectedTaskHasResultData,
|
|
468
|
+
]);
|
|
469
|
+
const currentAgentStatus = useMemo(() => {
|
|
470
|
+
return getTaskAgentStatus(selectedTask, agentStatusesById);
|
|
471
|
+
}, [selectedTask, agentStatusesById]);
|
|
472
|
+
const displayAgentId = useMemo(() => {
|
|
473
|
+
if (!currentAgentId)
|
|
474
|
+
return null;
|
|
475
|
+
if (isClosedAgentStatus(currentAgentStatus) &&
|
|
476
|
+
selectedTaskStatus !== "Done") {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
return currentAgentId;
|
|
480
|
+
}, [currentAgentId, currentAgentStatus, selectedTaskStatus]);
|
|
362
481
|
useEffect(() => {
|
|
363
482
|
if (isMobile && selectedTaskId) {
|
|
364
483
|
setMobileActiveTab(1); // Task tab
|
|
365
484
|
}
|
|
366
485
|
}, [selectedTaskId, isMobile]);
|
|
367
486
|
useEffect(() => {
|
|
368
|
-
if (isMobile &&
|
|
487
|
+
if (isMobile && displayAgentId && !shouldHideAgentPanelForSelectedTask) {
|
|
369
488
|
setMobileActiveTab(2); // Agent tab
|
|
370
489
|
}
|
|
371
|
-
}, [
|
|
490
|
+
}, [displayAgentId, isMobile, shouldHideAgentPanelForSelectedTask]);
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (!isMobile)
|
|
493
|
+
return;
|
|
494
|
+
if (!shouldHideAgentPanelForSelectedTask)
|
|
495
|
+
return;
|
|
496
|
+
if (mobileActiveTab !== 2)
|
|
497
|
+
return;
|
|
498
|
+
setMobileActiveTab(1);
|
|
499
|
+
}, [isMobile, mobileActiveTab, shouldHideAgentPanelForSelectedTask]);
|
|
372
500
|
useEffect(() => {
|
|
373
501
|
if (!showEditorPanel && mobileActiveTab === 3) {
|
|
374
502
|
setMobileActiveTab(0);
|
|
@@ -382,10 +510,165 @@ export function TaskBoardWorkspace() {
|
|
|
382
510
|
const agentPanelMode = useMemo(() => {
|
|
383
511
|
if (!selectedTask)
|
|
384
512
|
return "no-task-selected";
|
|
385
|
-
if (
|
|
513
|
+
if (displayAgentId)
|
|
386
514
|
return "agent";
|
|
387
515
|
return "no-agent";
|
|
388
|
-
}, [selectedTask,
|
|
516
|
+
}, [selectedTask, displayAgentId]);
|
|
517
|
+
const wizardAttentionState = useMemo(() => {
|
|
518
|
+
const sortedTasks = [...wizardTasksWithDisplayAssignees].sort((a, b) => {
|
|
519
|
+
if (a.sortOrder !== b.sortOrder)
|
|
520
|
+
return a.sortOrder - b.sortOrder;
|
|
521
|
+
return a.createdAt.localeCompare(b.createdAt);
|
|
522
|
+
});
|
|
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
|
+
}
|
|
545
|
+
let runningCount = 0;
|
|
546
|
+
let approvalCount = 0;
|
|
547
|
+
let attentionTask = null;
|
|
548
|
+
let attentionAgentId = null;
|
|
549
|
+
let attentionAgentStatus;
|
|
550
|
+
let attentionExecutionDisplay = null;
|
|
551
|
+
for (const task of openTasks) {
|
|
552
|
+
const normalizedTaskStatus = normalizeTaskStatus(task.status, task.executionStatus);
|
|
553
|
+
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");
|
|
562
|
+
if (isRunning) {
|
|
563
|
+
runningCount += 1;
|
|
564
|
+
}
|
|
565
|
+
if (normalizedTaskStatus === "Review") {
|
|
566
|
+
approvalCount += 1;
|
|
567
|
+
}
|
|
568
|
+
if (!attentionTask && !isBlocked && executionDisplay?.needsAttention) {
|
|
569
|
+
attentionTask = task;
|
|
570
|
+
attentionAgentId = agentId;
|
|
571
|
+
attentionAgentStatus = agentStatus;
|
|
572
|
+
attentionExecutionDisplay = executionDisplay;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
let summaryState = "idle";
|
|
576
|
+
if (openTasks.length === 0) {
|
|
577
|
+
summaryState = "complete";
|
|
578
|
+
}
|
|
579
|
+
else if (runningCount > 0) {
|
|
580
|
+
summaryState = "working";
|
|
581
|
+
}
|
|
582
|
+
else if (blockedTaskIds.size === openTasks.length) {
|
|
583
|
+
summaryState = "blocked";
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
task: attentionTask,
|
|
587
|
+
agentId: attentionAgentId,
|
|
588
|
+
agentStatus: attentionAgentStatus,
|
|
589
|
+
executionDisplay: attentionExecutionDisplay,
|
|
590
|
+
summaryState,
|
|
591
|
+
stats: {
|
|
592
|
+
totalCount: sortedTasks.length,
|
|
593
|
+
completedCount: sortedTasks.length - openTasks.length,
|
|
594
|
+
approvalCount,
|
|
595
|
+
blockedCount: blockedTaskIds.size,
|
|
596
|
+
runningCount,
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
}, [wizardTasksWithDisplayAssignees, dependencies, agentStatusesById]);
|
|
600
|
+
const wizardDisplayedTask = wizardForceOverview
|
|
601
|
+
? null
|
|
602
|
+
: selectedTask ?? wizardPinnedTask ?? wizardAttentionState.task;
|
|
603
|
+
const wizardDisplayedAgentId = wizardDisplayedTask
|
|
604
|
+
? getLinkedAgentId(wizardDisplayedTask)
|
|
605
|
+
: null;
|
|
606
|
+
const wizardDisplayedAgentStatus = getAgentStatusById(agentStatusesById, wizardDisplayedAgentId);
|
|
607
|
+
const wizardDisplayedExecutionDisplay = wizardDisplayedTask
|
|
608
|
+
? getTaskExecutionDisplay(wizardDisplayedTask.executionStatus, wizardDisplayedAgentStatus, wizardDisplayedTask.status)
|
|
609
|
+
: null;
|
|
610
|
+
const clearWizardCloseTransitionTimeout = useCallback(() => {
|
|
611
|
+
if (wizardCloseTransitionTimeoutRef.current !== null) {
|
|
612
|
+
window.clearTimeout(wizardCloseTransitionTimeoutRef.current);
|
|
613
|
+
wizardCloseTransitionTimeoutRef.current = null;
|
|
614
|
+
}
|
|
615
|
+
}, []);
|
|
616
|
+
useEffect(() => {
|
|
617
|
+
if (!isWizardMode) {
|
|
618
|
+
clearWizardCloseTransitionTimeout();
|
|
619
|
+
}
|
|
620
|
+
}, [isWizardMode, clearWizardCloseTransitionTimeout]);
|
|
621
|
+
useEffect(() => {
|
|
622
|
+
clearWizardCloseTransitionTimeout();
|
|
623
|
+
}, [selectedProjectId, clearWizardCloseTransitionTimeout]);
|
|
624
|
+
const findNextAttentionTaskId = useCallback((excludedTaskId) => {
|
|
625
|
+
const sortedTasks = [...wizardTasksWithDisplayAssignees].sort((a, b) => {
|
|
626
|
+
if (a.sortOrder !== b.sortOrder)
|
|
627
|
+
return a.sortOrder - b.sortOrder;
|
|
628
|
+
return a.createdAt.localeCompare(b.createdAt);
|
|
629
|
+
});
|
|
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
|
+
const openTasks = sortedTasks.filter((task) => {
|
|
640
|
+
if (excludedTaskId && task.taskId === excludedTaskId)
|
|
641
|
+
return false;
|
|
642
|
+
return (normalizeTaskStatus(task.status, task.executionStatus) !== "Done");
|
|
643
|
+
});
|
|
644
|
+
const blockedTaskIds = new Set();
|
|
645
|
+
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) {
|
|
667
|
+
return task.taskId;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}, [wizardTasksWithDisplayAssignees, dependencies, agentStatusesById]);
|
|
389
672
|
// ── data fetching ──
|
|
390
673
|
const refreshProjects = useCallback(async (options) => {
|
|
391
674
|
projectsRequestCountRef.current += 1;
|
|
@@ -454,17 +737,44 @@ export function TaskBoardWorkspace() {
|
|
|
454
737
|
window.history.replaceState(null, "", newUrl);
|
|
455
738
|
}
|
|
456
739
|
}, [selectedProjectId, selectedTaskId, activeTab, isWizardMode]);
|
|
740
|
+
useEffect(() => {
|
|
741
|
+
const handlePopState = () => {
|
|
742
|
+
syncTaskBoardStateFromUrl();
|
|
743
|
+
};
|
|
744
|
+
window.addEventListener("popstate", handlePopState);
|
|
745
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
746
|
+
}, [syncTaskBoardStateFromUrl]);
|
|
457
747
|
const refreshTasks = useCallback(async (projectId) => {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
748
|
+
tasksRequestCountRef.current += 1;
|
|
749
|
+
latestTasksProjectIdRef.current = projectId;
|
|
750
|
+
const requestProjectId = projectId;
|
|
751
|
+
setIsTasksLoading(true);
|
|
752
|
+
try {
|
|
753
|
+
const result = await getTasks(projectId);
|
|
754
|
+
if (latestTasksProjectIdRef.current !== requestProjectId) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (result.type !== "success") {
|
|
758
|
+
toast.error(result.summary || "Failed to load tasks");
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
setTasks(result.data || []);
|
|
762
|
+
}
|
|
763
|
+
finally {
|
|
764
|
+
tasksRequestCountRef.current = Math.max(0, tasksRequestCountRef.current - 1);
|
|
765
|
+
if (tasksRequestCountRef.current === 0) {
|
|
766
|
+
setIsTasksLoading(false);
|
|
767
|
+
}
|
|
462
768
|
}
|
|
463
|
-
setTasks(result.data || []);
|
|
464
769
|
}, []);
|
|
465
770
|
const refreshSubprojectTaskCounts = useCallback(async () => {
|
|
771
|
+
const requestProjectId = selectedProjectId;
|
|
772
|
+
latestSubprojectCountsProjectIdRef.current = requestProjectId;
|
|
466
773
|
if (directSubprojects.length === 0) {
|
|
467
|
-
|
|
774
|
+
if (latestSubprojectCountsProjectIdRef.current === requestProjectId) {
|
|
775
|
+
setSubprojectTaskCounts({});
|
|
776
|
+
setSubprojectTasksByProjectId({});
|
|
777
|
+
}
|
|
468
778
|
return;
|
|
469
779
|
}
|
|
470
780
|
setSubprojectCountsLoading(true);
|
|
@@ -473,40 +783,33 @@ export function TaskBoardWorkspace() {
|
|
|
473
783
|
const projectId = subproject.project.projectId;
|
|
474
784
|
const response = await getTasks(projectId);
|
|
475
785
|
if (response.type !== "success") {
|
|
476
|
-
return [projectId, null];
|
|
786
|
+
return [projectId, null, null];
|
|
477
787
|
}
|
|
478
788
|
const tasksForProject = response.data || [];
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
done: 0,
|
|
485
|
-
};
|
|
486
|
-
for (const task of tasksForProject) {
|
|
487
|
-
const normalizedStatus = normalizeTaskStatus(task.status, task.executionStatus);
|
|
488
|
-
if (normalizedStatus === "Todo")
|
|
489
|
-
counts.todo += 1;
|
|
490
|
-
else if (normalizedStatus === "InProgress")
|
|
491
|
-
counts.inProgress += 1;
|
|
492
|
-
else if (normalizedStatus === "Review")
|
|
493
|
-
counts.review += 1;
|
|
494
|
-
else if (normalizedStatus === "Done")
|
|
495
|
-
counts.done += 1;
|
|
496
|
-
}
|
|
497
|
-
return [projectId, counts];
|
|
789
|
+
return [
|
|
790
|
+
projectId,
|
|
791
|
+
buildTaskCounts(tasksForProject),
|
|
792
|
+
tasksForProject,
|
|
793
|
+
];
|
|
498
794
|
}));
|
|
795
|
+
if (latestSubprojectCountsProjectIdRef.current !== requestProjectId) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
499
798
|
const nextCounts = {};
|
|
500
|
-
|
|
799
|
+
const nextTasksByProjectId = {};
|
|
800
|
+
for (const [projectId, counts, taskList] of results) {
|
|
501
801
|
if (counts)
|
|
502
802
|
nextCounts[projectId] = counts;
|
|
803
|
+
if (taskList)
|
|
804
|
+
nextTasksByProjectId[projectId] = taskList;
|
|
503
805
|
}
|
|
504
806
|
setSubprojectTaskCounts(nextCounts);
|
|
807
|
+
setSubprojectTasksByProjectId(isWizardMode ? nextTasksByProjectId : {});
|
|
505
808
|
}
|
|
506
809
|
finally {
|
|
507
810
|
setSubprojectCountsLoading(false);
|
|
508
811
|
}
|
|
509
|
-
}, [directSubprojects]);
|
|
812
|
+
}, [directSubprojects, isWizardMode, selectedProjectId]);
|
|
510
813
|
// If a project only has a single task, auto-select it once when loading.
|
|
511
814
|
// Do not auto-reselect after the user explicitly closes task details.
|
|
512
815
|
useEffect(() => {
|
|
@@ -524,47 +827,89 @@ export function TaskBoardWorkspace() {
|
|
|
524
827
|
autoSelectedProjectIdsRef.current.add(selectedProjectId);
|
|
525
828
|
setSelectedTaskId(firstTask.taskId);
|
|
526
829
|
}, [selectedProjectId, selectedTaskId, tasks]);
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
830
|
+
useEffect(() => {
|
|
831
|
+
if (!wizardPinnedTaskId)
|
|
832
|
+
return;
|
|
833
|
+
if (taskSelectionUniverse.some((task) => task.taskId === wizardPinnedTaskId)) {
|
|
531
834
|
return;
|
|
532
835
|
}
|
|
533
|
-
|
|
534
|
-
}, []);
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
836
|
+
setWizardPinnedTaskId(null);
|
|
837
|
+
}, [taskSelectionUniverse, wizardPinnedTaskId]);
|
|
838
|
+
useEffect(() => {
|
|
839
|
+
if (!isWizardMode)
|
|
840
|
+
return;
|
|
841
|
+
if (wizardForceOverview)
|
|
842
|
+
return;
|
|
843
|
+
if (selectedTaskId || wizardPinnedTaskId)
|
|
844
|
+
return;
|
|
845
|
+
const attentionTaskId = wizardAttentionState.task?.taskId;
|
|
846
|
+
if (!attentionTaskId)
|
|
847
|
+
return;
|
|
848
|
+
setWizardPinnedTaskId(attentionTaskId);
|
|
849
|
+
}, [
|
|
850
|
+
isWizardMode,
|
|
851
|
+
selectedTaskId,
|
|
852
|
+
wizardPinnedTaskId,
|
|
853
|
+
wizardForceOverview,
|
|
854
|
+
wizardAttentionState.task?.taskId,
|
|
855
|
+
]);
|
|
856
|
+
useEffect(() => {
|
|
857
|
+
if (isWizardMode)
|
|
858
|
+
return;
|
|
859
|
+
if (!wizardPinnedTaskId)
|
|
540
860
|
return;
|
|
861
|
+
setWizardPinnedTaskId(null);
|
|
862
|
+
}, [isWizardMode, wizardPinnedTaskId]);
|
|
863
|
+
useEffect(() => {
|
|
864
|
+
if (!isWizardMode && wizardForceOverview) {
|
|
865
|
+
setWizardForceOverview(false);
|
|
541
866
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
867
|
+
}, [isWizardMode, wizardForceOverview]);
|
|
868
|
+
useEffect(() => {
|
|
869
|
+
setWizardPinnedTaskId(null);
|
|
870
|
+
setWizardForceOverview(false);
|
|
871
|
+
}, [selectedProjectId]);
|
|
872
|
+
const refreshDependencies = useCallback(async (projectId) => {
|
|
873
|
+
latestDependenciesProjectIdRef.current = projectId;
|
|
874
|
+
const requestProjectId = projectId;
|
|
875
|
+
const visibleProjectIds = Array.from(new Set([
|
|
876
|
+
projectId,
|
|
877
|
+
...directSubprojects.map((subproject) => subproject.project.projectId),
|
|
878
|
+
]));
|
|
879
|
+
const results = await Promise.all(visibleProjectIds.map(async (visibleProjectId) => {
|
|
880
|
+
const result = await getDependencies(visibleProjectId);
|
|
881
|
+
return [visibleProjectId, result];
|
|
882
|
+
}));
|
|
883
|
+
const primaryResult = results.find(([visibleProjectId]) => visibleProjectId === projectId)?.[1] ?? null;
|
|
884
|
+
if (latestDependenciesProjectIdRef.current !== requestProjectId) {
|
|
550
885
|
return;
|
|
551
886
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
887
|
+
if (!primaryResult || primaryResult.type !== "success") {
|
|
888
|
+
toast.error(primaryResult?.summary || "Failed to load dependencies");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const mergedDependencies = new Map();
|
|
892
|
+
for (const [, result] of results) {
|
|
893
|
+
if (result.type !== "success")
|
|
894
|
+
continue;
|
|
895
|
+
for (const dependency of result.data || []) {
|
|
896
|
+
mergedDependencies.set(dependency.dependencyId, dependency);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
setDependencies(Array.from(mergedDependencies.values()));
|
|
900
|
+
}, [directSubprojects]);
|
|
901
|
+
const refreshVisibleTaskData = useCallback(async () => {
|
|
902
|
+
if (!selectedProjectId)
|
|
903
|
+
return;
|
|
904
|
+
await refreshTasks(selectedProjectId);
|
|
905
|
+
await refreshDependencies(selectedProjectId);
|
|
906
|
+
await refreshSubprojectTaskCounts();
|
|
907
|
+
}, [
|
|
908
|
+
selectedProjectId,
|
|
909
|
+
refreshTasks,
|
|
910
|
+
refreshDependencies,
|
|
911
|
+
refreshSubprojectTaskCounts,
|
|
912
|
+
]);
|
|
568
913
|
const refreshAgentStatuses = useCallback(async (taskList) => {
|
|
569
914
|
const agentIds = Array.from(new Set(taskList
|
|
570
915
|
.map((task) => getLinkedAgentId(task))
|
|
@@ -585,10 +930,33 @@ export function TaskBoardWorkspace() {
|
|
|
585
930
|
for (const agent of response.agents || []) {
|
|
586
931
|
const normalizedAgentId = agent.id.toLowerCase();
|
|
587
932
|
if (wanted.has(normalizedAgentId)) {
|
|
588
|
-
apiStatuses[normalizedAgentId] =
|
|
933
|
+
apiStatuses[normalizedAgentId] =
|
|
934
|
+
normalizeAgentStatus(agent.status) ?? agent.status;
|
|
589
935
|
}
|
|
590
936
|
}
|
|
591
|
-
setAgentStatusesById((prev) =>
|
|
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
|
+
});
|
|
592
960
|
}
|
|
593
961
|
catch {
|
|
594
962
|
// best-effort only
|
|
@@ -602,7 +970,7 @@ export function TaskBoardWorkspace() {
|
|
|
602
970
|
refreshSubprojectTaskCountsRef.current = refreshSubprojectTaskCounts;
|
|
603
971
|
selectedProjectIdRef.current = selectedProjectId;
|
|
604
972
|
directSubprojectIdsRef.current = new Set(directSubprojects.map((subproject) => subproject.project.projectId));
|
|
605
|
-
tasksRef.current =
|
|
973
|
+
tasksRef.current = taskSelectionUniverse;
|
|
606
974
|
}, [
|
|
607
975
|
refreshProjects,
|
|
608
976
|
refreshTasks,
|
|
@@ -610,7 +978,7 @@ export function TaskBoardWorkspace() {
|
|
|
610
978
|
refreshSubprojectTaskCounts,
|
|
611
979
|
selectedProjectId,
|
|
612
980
|
directSubprojects,
|
|
613
|
-
|
|
981
|
+
taskSelectionUniverse,
|
|
614
982
|
]);
|
|
615
983
|
// Ensure task-board receives live agent status updates (including WaitingForInput)
|
|
616
984
|
// without requiring the terminal panel to be opened. Re-run on socket reconnect
|
|
@@ -623,7 +991,7 @@ export function TaskBoardWorkspace() {
|
|
|
623
991
|
}
|
|
624
992
|
if (!selectedProjectId)
|
|
625
993
|
return;
|
|
626
|
-
const desiredAgentIds = new Set(
|
|
994
|
+
const desiredAgentIds = new Set(taskSelectionUniverse
|
|
627
995
|
.map((task) => getLinkedAgentId(task))
|
|
628
996
|
.filter((id) => typeof id === "string" && id.length > 0)
|
|
629
997
|
.map((id) => id.toLowerCase()));
|
|
@@ -646,27 +1014,10 @@ export function TaskBoardWorkspace() {
|
|
|
646
1014
|
return () => {
|
|
647
1015
|
window.clearTimeout(subscribeTimeout);
|
|
648
1016
|
};
|
|
649
|
-
}, [selectedProjectId, tasks, editContext?.socketConnectionVersion]);
|
|
650
|
-
useEffect(() => {
|
|
651
|
-
if (!selectedProjectId) {
|
|
652
|
-
setWizardGuidance(null);
|
|
653
|
-
setWizardGuidanceLoading(false);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
if (!isWizardMode) {
|
|
657
|
-
setWizardGuidanceLoading(false);
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
// Recompute guidance when project/task state changes, but not on task selection alone.
|
|
661
|
-
// Selecting a recommended task should not immediately trigger another guidance run.
|
|
662
|
-
scheduleWizardGuidanceRefresh(selectedProjectId, selectedTaskId);
|
|
663
1017
|
}, [
|
|
664
1018
|
selectedProjectId,
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
dependencies,
|
|
668
|
-
agentStatusesById,
|
|
669
|
-
scheduleWizardGuidanceRefresh,
|
|
1019
|
+
taskSelectionUniverse,
|
|
1020
|
+
editContext?.socketConnectionVersion,
|
|
670
1021
|
]);
|
|
671
1022
|
const handleRunOrchestrator = useCallback(async (options) => {
|
|
672
1023
|
if (!selectedProjectId)
|
|
@@ -702,28 +1053,27 @@ export function TaskBoardWorkspace() {
|
|
|
702
1053
|
}
|
|
703
1054
|
}
|
|
704
1055
|
}
|
|
705
|
-
|
|
706
|
-
await refreshTasks(selectedProjectId);
|
|
707
|
-
await refreshDependencies(selectedProjectId);
|
|
1056
|
+
await refreshVisibleTaskData();
|
|
708
1057
|
}
|
|
709
1058
|
finally {
|
|
710
1059
|
setRunningOrchestrator(false);
|
|
711
1060
|
}
|
|
712
|
-
}, [selectedProjectId,
|
|
1061
|
+
}, [selectedProjectId, refreshVisibleTaskData]);
|
|
713
1062
|
const handleRunSelectedTaskAgent = useCallback(async () => {
|
|
714
|
-
if (!selectedProjectId)
|
|
715
|
-
return;
|
|
716
1063
|
const task = tasksRef.current.find((t) => t.taskId === selectedTaskId &&
|
|
717
1064
|
t.assigneeType === "Agent" &&
|
|
718
1065
|
!!t.assigneeId);
|
|
719
1066
|
if (!task)
|
|
720
1067
|
return;
|
|
1068
|
+
const taskProjectId = task.projectId || selectedProjectId;
|
|
1069
|
+
if (!taskProjectId)
|
|
1070
|
+
return;
|
|
721
1071
|
setRunningOrchestrator(true);
|
|
722
1072
|
try {
|
|
723
1073
|
if (task.executionStatus === "Idle") {
|
|
724
1074
|
await updateTask({ taskId: task.taskId, executionStatus: "Queued" });
|
|
725
1075
|
}
|
|
726
|
-
const result = await runOrchestrator(
|
|
1076
|
+
const result = await runOrchestrator(taskProjectId);
|
|
727
1077
|
if (result.type !== "success") {
|
|
728
1078
|
toast.error(result.summary || "Failed to launch agent");
|
|
729
1079
|
return;
|
|
@@ -741,13 +1091,80 @@ export function TaskBoardWorkspace() {
|
|
|
741
1091
|
toast.info("Could not launch agent. The task may be blocked or already running.");
|
|
742
1092
|
}
|
|
743
1093
|
}
|
|
744
|
-
await
|
|
745
|
-
|
|
1094
|
+
await refreshVisibleTaskData();
|
|
1095
|
+
}
|
|
1096
|
+
finally {
|
|
1097
|
+
setRunningOrchestrator(false);
|
|
1098
|
+
}
|
|
1099
|
+
}, [selectedProjectId, selectedTaskId, refreshVisibleTaskData]);
|
|
1100
|
+
const handleReopenSelectedTask = useCallback(async () => {
|
|
1101
|
+
if (!selectedTask)
|
|
1102
|
+
return;
|
|
1103
|
+
const taskProjectId = selectedTask.projectId || selectedProjectId;
|
|
1104
|
+
const reopenResult = await updateTask({
|
|
1105
|
+
taskId: selectedTask.taskId,
|
|
1106
|
+
status: "Todo",
|
|
1107
|
+
executionStatus: "Idle",
|
|
1108
|
+
});
|
|
1109
|
+
if (reopenResult.type !== "success") {
|
|
1110
|
+
toast.error(reopenResult.summary || "Failed to reopen task");
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (selectedTaskIsPlan) {
|
|
1114
|
+
toast.success("Task reopened");
|
|
1115
|
+
await refreshVisibleTaskData();
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (!selectedTaskHasAssignedAgentProfile ||
|
|
1119
|
+
!canRunAssignedAgent ||
|
|
1120
|
+
!taskProjectId) {
|
|
1121
|
+
toast.success("Task reopened");
|
|
1122
|
+
await refreshVisibleTaskData();
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
setRunningOrchestrator(true);
|
|
1126
|
+
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
|
+
const result = await runOrchestrator(taskProjectId);
|
|
1137
|
+
if (result.type !== "success") {
|
|
1138
|
+
toast.error(result.summary || "Failed to launch agent");
|
|
1139
|
+
await refreshVisibleTaskData();
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const data = result.data;
|
|
1143
|
+
if (data?.errors?.length) {
|
|
1144
|
+
toast.error(data.errors.join(", "));
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
const launched = data?.tasksLaunched?.length || 0;
|
|
1148
|
+
if (launched > 0) {
|
|
1149
|
+
toast.success("Task reopened and agent launched.");
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
toast.info("Task reopened, but the agent could not be launched. The task may be blocked or already running.");
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
await refreshVisibleTaskData();
|
|
746
1156
|
}
|
|
747
1157
|
finally {
|
|
748
1158
|
setRunningOrchestrator(false);
|
|
749
1159
|
}
|
|
750
|
-
}, [
|
|
1160
|
+
}, [
|
|
1161
|
+
selectedTask,
|
|
1162
|
+
selectedProjectId,
|
|
1163
|
+
selectedTaskIsPlan,
|
|
1164
|
+
selectedTaskHasAssignedAgentProfile,
|
|
1165
|
+
canRunAssignedAgent,
|
|
1166
|
+
refreshVisibleTaskData,
|
|
1167
|
+
]);
|
|
751
1168
|
const handleStartPlanning = useCallback(async () => {
|
|
752
1169
|
if (!selectedProjectId)
|
|
753
1170
|
return;
|
|
@@ -770,130 +1187,6 @@ export function TaskBoardWorkspace() {
|
|
|
770
1187
|
toast.error("Failed to start planning");
|
|
771
1188
|
}
|
|
772
1189
|
}, [selectedProjectId, refreshTasks, refreshDependencies, tasks]);
|
|
773
|
-
const handleWizardAction = useCallback(async (action) => {
|
|
774
|
-
if (!action?.type)
|
|
775
|
-
return;
|
|
776
|
-
const actionType = String(action.type).trim().toLowerCase();
|
|
777
|
-
const resolvedTaskId = action.taskId || wizardGuidance?.guidance?.targetTaskId || null;
|
|
778
|
-
const resolvedTask = resolvedTaskId
|
|
779
|
-
? tasks.find((task) => task.taskId === resolvedTaskId) || null
|
|
780
|
-
: null;
|
|
781
|
-
const resolvedTaskIsPlan = !!resolvedTask &&
|
|
782
|
-
String(resolvedTask.taskType || "").toLowerCase() === "plan";
|
|
783
|
-
const resolvedAgentId = action.agentId ||
|
|
784
|
-
wizardGuidance?.guidance?.targetAgentId ||
|
|
785
|
-
getLinkedAgentId(resolvedTask) ||
|
|
786
|
-
null;
|
|
787
|
-
const actionId = action.actionId ||
|
|
788
|
-
`${action.type}-${action.taskId || "none"}-${action.agentId || "none"}`;
|
|
789
|
-
if (resolvedTaskId) {
|
|
790
|
-
setSelectedTaskId(resolvedTaskId);
|
|
791
|
-
}
|
|
792
|
-
if (actionType === "open_task" ||
|
|
793
|
-
actionType === "open-task" ||
|
|
794
|
-
actionType === "open") {
|
|
795
|
-
setWizardAgentDialogOpen(true);
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
if (actionType === "focus_agent" || actionType === "focus-agent") {
|
|
799
|
-
setWizardAgentDialogOpen(true);
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
if (actionType === "copy_reply" || actionType === "copy-reply") {
|
|
803
|
-
const text = action.promptText || wizardGuidance?.guidance?.suggestedUserReply;
|
|
804
|
-
if (!text) {
|
|
805
|
-
toast.error("No suggested reply available to copy");
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
try {
|
|
809
|
-
await navigator.clipboard.writeText(text);
|
|
810
|
-
toast.success("Suggested reply copied");
|
|
811
|
-
}
|
|
812
|
-
catch {
|
|
813
|
-
toast.error("Failed to copy suggested reply");
|
|
814
|
-
}
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
if (actionType !== "send_prompt_to_agent" &&
|
|
818
|
-
actionType !== "send-prompt-to-agent") {
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
if (!editContext?.sessionId) {
|
|
822
|
-
toast.error("Cannot send prompt: missing editor session");
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
if (!resolvedAgentId && resolvedTaskIsPlan) {
|
|
826
|
-
await handleStartPlanning();
|
|
827
|
-
setWizardAgentDialogOpen(true);
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
if (!resolvedAgentId) {
|
|
831
|
-
toast.error("Cannot send prompt: missing target agent");
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
const promptText = (action.promptText || "").trim();
|
|
835
|
-
if (!promptText) {
|
|
836
|
-
toast.error("Cannot send prompt: prompt text is empty");
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
setWizardActionInFlightId(actionId);
|
|
840
|
-
try {
|
|
841
|
-
let agent = null;
|
|
842
|
-
try {
|
|
843
|
-
agent = await getAgent(resolvedAgentId);
|
|
844
|
-
}
|
|
845
|
-
catch {
|
|
846
|
-
if (resolvedTaskIsPlan) {
|
|
847
|
-
await handleStartPlanning();
|
|
848
|
-
setWizardAgentDialogOpen(true);
|
|
849
|
-
setWizardActionInFlightId(null);
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
throw new Error("Target agent was not found");
|
|
853
|
-
}
|
|
854
|
-
const profileId = agent?.profileId ||
|
|
855
|
-
(resolvedTaskId && selectedTask?.taskId === resolvedTaskId
|
|
856
|
-
? selectedTask?.assigneeId
|
|
857
|
-
: undefined) ||
|
|
858
|
-
"";
|
|
859
|
-
if (!profileId) {
|
|
860
|
-
toast.error("Cannot send prompt: target agent profile is missing");
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
await startAgent({
|
|
864
|
-
agentId: resolvedAgentId,
|
|
865
|
-
message: promptText,
|
|
866
|
-
sessionId: editContext.sessionId,
|
|
867
|
-
profileId,
|
|
868
|
-
mode: "autonomous",
|
|
869
|
-
});
|
|
870
|
-
toast.success("Prompt sent to task agent");
|
|
871
|
-
setWizardAgentDialogOpen(true);
|
|
872
|
-
if (selectedProjectId) {
|
|
873
|
-
await refreshTasks(selectedProjectId);
|
|
874
|
-
await refreshDependencies(selectedProjectId);
|
|
875
|
-
await refreshWizardGuidance(selectedProjectId, selectedTaskId);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
catch (error) {
|
|
879
|
-
const message = error instanceof Error ? error.message : "Failed to send prompt";
|
|
880
|
-
toast.error(message);
|
|
881
|
-
}
|
|
882
|
-
finally {
|
|
883
|
-
setWizardActionInFlightId(null);
|
|
884
|
-
}
|
|
885
|
-
}, [
|
|
886
|
-
editContext?.sessionId,
|
|
887
|
-
selectedTask,
|
|
888
|
-
tasks,
|
|
889
|
-
wizardGuidance,
|
|
890
|
-
handleStartPlanning,
|
|
891
|
-
selectedProjectId,
|
|
892
|
-
selectedTaskId,
|
|
893
|
-
refreshTasks,
|
|
894
|
-
refreshDependencies,
|
|
895
|
-
refreshWizardGuidance,
|
|
896
|
-
]);
|
|
897
1190
|
const handleProjectStatusChange = useCallback(async (status) => {
|
|
898
1191
|
if (!selectedProject)
|
|
899
1192
|
return;
|
|
@@ -993,12 +1286,6 @@ export function TaskBoardWorkspace() {
|
|
|
993
1286
|
onToggleWizardMode: (enabled) => setIsWizardMode(enabled),
|
|
994
1287
|
onCreateTask: () => setCreateDialogOpen(true),
|
|
995
1288
|
onRunOrchestrator: handleRunOrchestrator,
|
|
996
|
-
onRefresh: () => {
|
|
997
|
-
if (selectedProjectId) {
|
|
998
|
-
void refreshTasks(selectedProjectId);
|
|
999
|
-
void refreshDependencies(selectedProjectId);
|
|
1000
|
-
}
|
|
1001
|
-
},
|
|
1002
1289
|
});
|
|
1003
1290
|
}, [
|
|
1004
1291
|
selectedProject?.project.title,
|
|
@@ -1020,65 +1307,43 @@ export function TaskBoardWorkspace() {
|
|
|
1020
1307
|
handleSelectMyTask,
|
|
1021
1308
|
handleSelectProject,
|
|
1022
1309
|
handleRunOrchestrator,
|
|
1023
|
-
refreshTasks,
|
|
1024
|
-
refreshDependencies,
|
|
1025
1310
|
]);
|
|
1026
1311
|
useEffect(() => {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1312
|
+
return () => {
|
|
1313
|
+
resetTaskBoardNavState();
|
|
1314
|
+
};
|
|
1315
|
+
}, []);
|
|
1316
|
+
useEffect(() => {
|
|
1317
|
+
void refreshProjects();
|
|
1318
|
+
}, [refreshProjects]);
|
|
1319
|
+
useEffect(() => {
|
|
1320
|
+
if (!selectedProjectId) {
|
|
1321
|
+
setProjectViewLoadingId(null);
|
|
1322
|
+
clearSelectedProjectData();
|
|
1030
1323
|
return;
|
|
1031
1324
|
}
|
|
1325
|
+
clearSelectedProjectData();
|
|
1326
|
+
setProjectViewLoadingId(selectedProjectId);
|
|
1032
1327
|
let cancelled = false;
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
return (result.data ?? [])
|
|
1040
|
-
.filter((task) => task.assigneeType === "Human" &&
|
|
1041
|
-
(task.assigneeId ?? "").trim().toLowerCase() === currentUserName)
|
|
1042
|
-
.map((task) => ({
|
|
1043
|
-
taskId: task.taskId,
|
|
1044
|
-
projectId: project.project.projectId,
|
|
1045
|
-
title: task.title,
|
|
1046
|
-
projectTitle: project.project.title,
|
|
1047
|
-
status: normalizeTaskStatus(task.status, task.executionStatus),
|
|
1048
|
-
updatedAt: task.updatedAt,
|
|
1049
|
-
}));
|
|
1050
|
-
});
|
|
1051
|
-
const list = (await Promise.all(taskRequests)).flat().sort((a, b) => {
|
|
1052
|
-
const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
1053
|
-
const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
1054
|
-
return bTime - aTime;
|
|
1055
|
-
});
|
|
1328
|
+
const refreshTasksFn = refreshTasksRef.current;
|
|
1329
|
+
const refreshDependenciesFn = refreshDependenciesRef.current;
|
|
1330
|
+
void Promise.all([
|
|
1331
|
+
refreshTasksFn?.(selectedProjectId),
|
|
1332
|
+
refreshDependenciesFn?.(selectedProjectId),
|
|
1333
|
+
]).finally(() => {
|
|
1056
1334
|
if (cancelled)
|
|
1057
1335
|
return;
|
|
1058
|
-
|
|
1059
|
-
})().catch(() => {
|
|
1060
|
-
if (cancelled)
|
|
1336
|
+
if (selectedProjectIdRef.current !== selectedProjectId)
|
|
1061
1337
|
return;
|
|
1062
|
-
|
|
1338
|
+
setProjectViewLoadingId(null);
|
|
1063
1339
|
});
|
|
1064
1340
|
return () => {
|
|
1065
1341
|
cancelled = true;
|
|
1066
1342
|
};
|
|
1067
|
-
}, [
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
};
|
|
1072
|
-
}, []);
|
|
1073
|
-
useEffect(() => {
|
|
1074
|
-
void refreshProjects();
|
|
1075
|
-
}, [refreshProjects]);
|
|
1076
|
-
useEffect(() => {
|
|
1077
|
-
if (!selectedProjectId)
|
|
1078
|
-
return;
|
|
1079
|
-
void refreshTasks(selectedProjectId);
|
|
1080
|
-
void refreshDependencies(selectedProjectId);
|
|
1081
|
-
}, [refreshTasks, refreshDependencies, selectedProjectId]);
|
|
1343
|
+
}, [
|
|
1344
|
+
clearSelectedProjectData,
|
|
1345
|
+
selectedProjectId,
|
|
1346
|
+
]);
|
|
1082
1347
|
useEffect(() => {
|
|
1083
1348
|
void refreshSubprojectTaskCounts();
|
|
1084
1349
|
}, [refreshSubprojectTaskCounts]);
|
|
@@ -1087,8 +1352,8 @@ export function TaskBoardWorkspace() {
|
|
|
1087
1352
|
setAgentStatusesById({});
|
|
1088
1353
|
return;
|
|
1089
1354
|
}
|
|
1090
|
-
void refreshAgentStatuses(
|
|
1091
|
-
}, [selectedProjectId,
|
|
1355
|
+
void refreshAgentStatuses(taskSelectionUniverse);
|
|
1356
|
+
}, [selectedProjectId, taskSelectionUniverse, refreshAgentStatuses]);
|
|
1092
1357
|
useEffect(() => {
|
|
1093
1358
|
previousTaskStatusesRef.current = {};
|
|
1094
1359
|
}, [selectedProjectId]);
|
|
@@ -1109,18 +1374,13 @@ export function TaskBoardWorkspace() {
|
|
|
1109
1374
|
}
|
|
1110
1375
|
}
|
|
1111
1376
|
previousTaskStatusesRef.current = currentStatuses;
|
|
1112
|
-
if (movedToDone &&
|
|
1113
|
-
canCreate &&
|
|
1114
|
-
selectedProjectIsActive &&
|
|
1115
|
-
!isPlanning &&
|
|
1116
|
-
!runningOrchestrator) {
|
|
1377
|
+
if (movedToDone && canCreate && !isPlanning && !runningOrchestrator) {
|
|
1117
1378
|
void handleRunOrchestrator({ silentWhenNothingToLaunch: true });
|
|
1118
1379
|
}
|
|
1119
1380
|
}, [
|
|
1120
1381
|
selectedProjectId,
|
|
1121
1382
|
tasks,
|
|
1122
1383
|
canCreate,
|
|
1123
|
-
selectedProjectIsActive,
|
|
1124
1384
|
isPlanning,
|
|
1125
1385
|
runningOrchestrator,
|
|
1126
1386
|
handleRunOrchestrator,
|
|
@@ -1306,9 +1566,9 @@ export function TaskBoardWorkspace() {
|
|
|
1306
1566
|
window.clearTimeout(wsProjectRefreshTimeoutRef.current);
|
|
1307
1567
|
wsProjectRefreshTimeoutRef.current = null;
|
|
1308
1568
|
}
|
|
1309
|
-
if (
|
|
1310
|
-
window.clearTimeout(
|
|
1311
|
-
|
|
1569
|
+
if (wizardCloseTransitionTimeoutRef.current !== null) {
|
|
1570
|
+
window.clearTimeout(wizardCloseTransitionTimeoutRef.current);
|
|
1571
|
+
wizardCloseTransitionTimeoutRef.current = null;
|
|
1312
1572
|
}
|
|
1313
1573
|
};
|
|
1314
1574
|
}, []);
|
|
@@ -1316,50 +1576,113 @@ export function TaskBoardWorkspace() {
|
|
|
1316
1576
|
useEffect(() => {
|
|
1317
1577
|
if (!selectedTaskId)
|
|
1318
1578
|
return;
|
|
1319
|
-
if (!
|
|
1579
|
+
if (!taskSelectionUniverse.some((t) => t.taskId === selectedTaskId)) {
|
|
1320
1580
|
setSelectedTaskId(null);
|
|
1321
|
-
}, [selectedTaskId, tasks]);
|
|
1322
|
-
useEffect(() => {
|
|
1323
|
-
if (!isWizardMode || !selectedProjectId) {
|
|
1324
|
-
setWizardAgentDialogOpen(false);
|
|
1325
1581
|
}
|
|
1326
|
-
}, [
|
|
1582
|
+
}, [selectedTaskId, taskSelectionUniverse]);
|
|
1327
1583
|
// No special auto-selection by project status — the agent panel is driven by
|
|
1328
1584
|
// whichever task the user selects.
|
|
1329
1585
|
// The backend now creates the Plan task and triggers the planner agent
|
|
1330
1586
|
// during project creation (TaskService.CreateProjectAsync), so we no longer
|
|
1331
1587
|
// need to auto-trigger planning from the frontend.
|
|
1588
|
+
const handleCloseWizardTask = useCallback(() => {
|
|
1589
|
+
const closingProjectId = selectedProjectId;
|
|
1590
|
+
const closedTaskId = wizardDisplayedTask?.taskId ?? selectedTaskId ?? wizardPinnedTaskId;
|
|
1591
|
+
clearWizardCloseTransitionTimeout();
|
|
1592
|
+
setSelectedTaskId(null);
|
|
1593
|
+
setWizardPinnedTaskId(null);
|
|
1594
|
+
setWizardForceOverview(true);
|
|
1595
|
+
wizardCloseTransitionTimeoutRef.current = window.setTimeout(() => {
|
|
1596
|
+
wizardCloseTransitionTimeoutRef.current = null;
|
|
1597
|
+
if (!closingProjectId)
|
|
1598
|
+
return;
|
|
1599
|
+
if (selectedProjectIdRef.current !== closingProjectId)
|
|
1600
|
+
return;
|
|
1601
|
+
const nextAttentionTaskId = findNextAttentionTaskId(closedTaskId);
|
|
1602
|
+
setWizardForceOverview(false);
|
|
1603
|
+
if (nextAttentionTaskId) {
|
|
1604
|
+
setWizardPinnedTaskId(nextAttentionTaskId);
|
|
1605
|
+
setSelectedTaskId(nextAttentionTaskId);
|
|
1606
|
+
}
|
|
1607
|
+
}, 1000);
|
|
1608
|
+
}, [
|
|
1609
|
+
selectedProjectId,
|
|
1610
|
+
wizardDisplayedTask?.taskId,
|
|
1611
|
+
selectedTaskId,
|
|
1612
|
+
wizardPinnedTaskId,
|
|
1613
|
+
clearWizardCloseTransitionTimeout,
|
|
1614
|
+
findNextAttentionTaskId,
|
|
1615
|
+
]);
|
|
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 &&
|
|
1617
|
+
wizardDisplayedTask.projectId !== selectedProjectId
|
|
1618
|
+
? directSubprojects.find((subproject) => subproject.project.projectId ===
|
|
1619
|
+
wizardDisplayedTask.projectId)?.project.title
|
|
1620
|
+
: undefined, allTasks: wizardTasksWithDisplayAssignees, dependencies: dependencies, canEdit: canEditTasks, onTaskChanged: () => {
|
|
1621
|
+
handleCloseWizardTask();
|
|
1622
|
+
void refreshVisibleTaskData();
|
|
1623
|
+
}, 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) => {
|
|
1633
|
+
clearWizardCloseTransitionTimeout();
|
|
1634
|
+
setWizardForceOverview(false);
|
|
1635
|
+
setWizardPinnedTaskId(taskId);
|
|
1636
|
+
setSelectedTaskId(taskId);
|
|
1637
|
+
} })), [
|
|
1638
|
+
wizardAttentionState,
|
|
1639
|
+
wizardDisplayedTask,
|
|
1640
|
+
wizardDisplayedAgentId,
|
|
1641
|
+
wizardDisplayedAgentStatus,
|
|
1642
|
+
wizardDisplayedExecutionDisplay,
|
|
1643
|
+
canEditTasks,
|
|
1644
|
+
selectedProjectId,
|
|
1645
|
+
directSubprojects,
|
|
1646
|
+
refreshVisibleTaskData,
|
|
1647
|
+
clearWizardCloseTransitionTimeout,
|
|
1648
|
+
handleCloseWizardTask,
|
|
1649
|
+
selectedProject?.project.title,
|
|
1650
|
+
selectedProject?.project.description,
|
|
1651
|
+
]);
|
|
1652
|
+
const isProjectViewLoading = !!selectedProjectId && projectViewLoadingId === selectedProjectId;
|
|
1332
1653
|
const taskBoardContent = useMemo(() => {
|
|
1654
|
+
if (isProjectViewLoading) {
|
|
1655
|
+
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
|
+
}
|
|
1333
1657
|
if (!selectedProjectId) {
|
|
1334
1658
|
return (_jsx("div", { className: "text-muted-foreground p-6 text-sm", children: "Select a project to view tasks." }));
|
|
1335
1659
|
}
|
|
1336
1660
|
if (isWizardMode) {
|
|
1337
|
-
return (_jsx(WizardView, { projectId: selectedProjectId, tasks: tasksWithDisplayAssignees,
|
|
1661
|
+
return (_jsx(WizardView, { projectId: selectedProjectId, tasks: tasksWithDisplayAssignees, projectTasksLoading: isTasksLoading, dependencies: dependencies, subprojectTaskLists: wizardSubprojectTaskLists, subprojectTasksLoading: subprojectCountsLoading, selectedTaskId: wizardDisplayedTask?.taskId ?? null, onSelectTask: (id) => {
|
|
1662
|
+
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 }));
|
|
1338
1666
|
}
|
|
1339
1667
|
if (isListView) {
|
|
1340
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: () => {
|
|
1341
|
-
|
|
1342
|
-
void refreshTasks(selectedProjectId);
|
|
1343
|
-
void refreshDependencies(selectedProjectId);
|
|
1344
|
-
void refreshSubprojectTaskCounts();
|
|
1345
|
-
}
|
|
1669
|
+
void refreshVisibleTaskData();
|
|
1346
1670
|
}, projectId: selectedProjectId, permission: selectedProject?.permission, agentStatusesById: agentStatusesById, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
|
|
1347
1671
|
}
|
|
1348
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: () => {
|
|
1349
|
-
|
|
1350
|
-
void refreshTasks(selectedProjectId);
|
|
1351
|
-
void refreshDependencies(selectedProjectId);
|
|
1352
|
-
void refreshSubprojectTaskCounts();
|
|
1353
|
-
}
|
|
1673
|
+
void refreshVisibleTaskData();
|
|
1354
1674
|
}, projectId: selectedProjectId, permission: selectedProject?.permission, agentStatusesById: agentStatusesById, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
|
|
1355
1675
|
}, [
|
|
1676
|
+
isProjectViewLoading,
|
|
1356
1677
|
selectedProjectId,
|
|
1357
1678
|
isWizardMode,
|
|
1358
1679
|
isListView,
|
|
1680
|
+
isTasksLoading,
|
|
1359
1681
|
tasksWithDisplayAssignees,
|
|
1682
|
+
wizardSubprojectTaskLists,
|
|
1360
1683
|
selectedTaskId,
|
|
1361
|
-
|
|
1362
|
-
|
|
1684
|
+
wizardDisplayedTask?.taskId,
|
|
1685
|
+
refreshVisibleTaskData,
|
|
1363
1686
|
selectedProject?.permission,
|
|
1364
1687
|
selectedProject?.project.description,
|
|
1365
1688
|
selectedProject?.project.status,
|
|
@@ -1374,7 +1697,7 @@ export function TaskBoardWorkspace() {
|
|
|
1374
1697
|
selectedProjectTaskCounts,
|
|
1375
1698
|
subprojectTaskCounts,
|
|
1376
1699
|
subprojectCountsLoading,
|
|
1377
|
-
|
|
1700
|
+
wizardCommunicationPanel,
|
|
1378
1701
|
savingProjectStatus,
|
|
1379
1702
|
handleProjectStatusChange,
|
|
1380
1703
|
activeTab,
|
|
@@ -1392,27 +1715,18 @@ export function TaskBoardWorkspace() {
|
|
|
1392
1715
|
canEditTasks,
|
|
1393
1716
|
selectedTask?.assigneeDisplayName,
|
|
1394
1717
|
selectedTask?.assigneeId,
|
|
1395
|
-
wizardGuidance,
|
|
1396
|
-
wizardGuidanceLoading,
|
|
1397
|
-
wizardActionInFlightId,
|
|
1398
|
-
handleWizardAction,
|
|
1399
1718
|
]);
|
|
1400
|
-
const taskDetailPanel = useMemo(() => (_jsx(TaskDetailPanel, { project:
|
|
1401
|
-
|
|
1402
|
-
void refreshTasks(selectedProjectId);
|
|
1403
|
-
void refreshDependencies(selectedProjectId);
|
|
1404
|
-
}
|
|
1719
|
+
const taskDetailPanel = useMemo(() => (_jsx(TaskDetailPanel, { project: selectedTaskProject, task: selectedTask, allTasks: taskSelectionUniverse, dependencies: dependencies, agentStatusesById: agentStatusesById, onTaskChanged: () => {
|
|
1720
|
+
void refreshVisibleTaskData();
|
|
1405
1721
|
}, onSelectTask: (taskId) => setSelectedTaskId(taskId), onClose: () => setSelectedTaskId(null), variant: "panel" })), [
|
|
1406
|
-
|
|
1722
|
+
selectedTaskProject,
|
|
1407
1723
|
selectedTask,
|
|
1408
|
-
|
|
1724
|
+
taskSelectionUniverse,
|
|
1409
1725
|
dependencies,
|
|
1410
1726
|
agentStatusesById,
|
|
1411
|
-
|
|
1412
|
-
refreshTasks,
|
|
1413
|
-
refreshDependencies,
|
|
1727
|
+
refreshVisibleTaskData,
|
|
1414
1728
|
]);
|
|
1415
|
-
const agentTerminalPanel = useMemo(() => (_jsx(TaskAgentPanel, { agentId:
|
|
1729
|
+
const agentTerminalPanel = useMemo(() => (_jsx(TaskAgentPanel, { agentId: displayAgentId, mode: agentPanelMode, label: selectedTaskIsPlan ? "Planner" : "Task Agent", hasAssignedAgentProfile: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile, assignedAgentProfileName: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile
|
|
1416
1730
|
? selectedTask?.assigneeDisplayName ||
|
|
1417
1731
|
selectedTask?.assigneeId ||
|
|
1418
1732
|
null
|
|
@@ -1427,16 +1741,10 @@ export function TaskBoardWorkspace() {
|
|
|
1427
1741
|
agentPanelMode === "no-agent" &&
|
|
1428
1742
|
!selectedTaskHasAssignedAgentProfile
|
|
1429
1743
|
? () => setAssignAgentDialogOpen(true)
|
|
1430
|
-
: undefined, assignedAgentStatus:
|
|
1431
|
-
?
|
|
1432
|
-
await updateTask({
|
|
1433
|
-
taskId: selectedTask.taskId,
|
|
1434
|
-
status: "InProgress",
|
|
1435
|
-
});
|
|
1436
|
-
await refreshTasks(selectedTask.projectId);
|
|
1437
|
-
}
|
|
1744
|
+
: undefined, assignedAgentStatus: displayAgentId ? currentAgentStatus : undefined, onReopenTask: selectedTask && canEditTasks
|
|
1745
|
+
? () => void handleReopenSelectedTask()
|
|
1438
1746
|
: undefined })), [
|
|
1439
|
-
|
|
1747
|
+
displayAgentId,
|
|
1440
1748
|
agentPanelMode,
|
|
1441
1749
|
selectedTaskIsPlan,
|
|
1442
1750
|
selectedTaskHasAssignedAgentProfile,
|
|
@@ -1447,9 +1755,9 @@ export function TaskBoardWorkspace() {
|
|
|
1447
1755
|
handleStartPlanning,
|
|
1448
1756
|
selectedTaskIsBlocked,
|
|
1449
1757
|
canEditTasks,
|
|
1450
|
-
|
|
1758
|
+
currentAgentStatus,
|
|
1451
1759
|
selectedTask,
|
|
1452
|
-
|
|
1760
|
+
handleReopenSelectedTask,
|
|
1453
1761
|
]);
|
|
1454
1762
|
const slotContext = editContext?.getActiveSlotContext();
|
|
1455
1763
|
const previewItemVersion = useMemo(() => {
|
|
@@ -1643,14 +1951,14 @@ export function TaskBoardWorkspace() {
|
|
|
1643
1951
|
const wizardPanels = [
|
|
1644
1952
|
{
|
|
1645
1953
|
name: "wizard-view",
|
|
1646
|
-
defaultSize:
|
|
1954
|
+
defaultSize: 70,
|
|
1647
1955
|
content: (_jsx("div", { className: "h-full overflow-x-hidden overflow-y-auto bg-slate-50/50", children: taskBoardContent })),
|
|
1648
1956
|
},
|
|
1649
1957
|
];
|
|
1650
1958
|
if (showEditorPanel) {
|
|
1651
1959
|
wizardPanels.push({
|
|
1652
1960
|
name: "editor-preview",
|
|
1653
|
-
defaultSize:
|
|
1961
|
+
defaultSize: 30,
|
|
1654
1962
|
content: (_jsx("div", { className: "h-full overflow-hidden border-l border-gray-200 bg-gray-50", children: itemPreviewPanel })),
|
|
1655
1963
|
});
|
|
1656
1964
|
}
|
|
@@ -1673,7 +1981,7 @@ export function TaskBoardWorkspace() {
|
|
|
1673
1981
|
name: "agent-terminal",
|
|
1674
1982
|
defaultSize: 560,
|
|
1675
1983
|
content: agentTerminalPanel,
|
|
1676
|
-
hidden: !selectedProjectId,
|
|
1984
|
+
hidden: !selectedProjectId || shouldHideAgentPanelForSelectedTask,
|
|
1677
1985
|
},
|
|
1678
1986
|
];
|
|
1679
1987
|
if (showEditorPanel) {
|
|
@@ -1701,7 +2009,7 @@ export function TaskBoardWorkspace() {
|
|
|
1701
2009
|
name: "agent-terminal",
|
|
1702
2010
|
defaultSize: 560,
|
|
1703
2011
|
content: agentTerminalPanel,
|
|
1704
|
-
hidden: !selectedProjectId,
|
|
2012
|
+
hidden: !selectedProjectId || shouldHideAgentPanelForSelectedTask,
|
|
1705
2013
|
},
|
|
1706
2014
|
];
|
|
1707
2015
|
if (showEditorPanel) {
|
|
@@ -1721,6 +2029,7 @@ export function TaskBoardWorkspace() {
|
|
|
1721
2029
|
selectedTask,
|
|
1722
2030
|
showEditorPanel,
|
|
1723
2031
|
itemPreviewPanel,
|
|
2032
|
+
shouldHideAgentPanelForSelectedTask,
|
|
1724
2033
|
]);
|
|
1725
2034
|
return (_jsxs("div", { className: "text-foreground flex h-full w-full flex-col bg-white select-text", children: [_jsx("div", { className: "min-h-0 flex-1", children: isMobile ? (_jsxs("div", { className: "flex h-full flex-col overflow-hidden", children: [_jsx("div", { className: "shrink-0 border-b border-gray-100 bg-white px-4 shadow-[0_1px_2px_rgba(0,0,0,0.03)]", children: _jsx(SimpleTabs, { tabs: [
|
|
1726
2035
|
{ id: "project", label: "Project", content: null },
|
|
@@ -1734,19 +2043,24 @@ export function TaskBoardWorkspace() {
|
|
|
1734
2043
|
id: "agent",
|
|
1735
2044
|
label: "Agent",
|
|
1736
2045
|
content: null,
|
|
1737
|
-
disabled: !currentAgentId,
|
|
2046
|
+
disabled: !currentAgentId || shouldHideAgentPanelForSelectedTask,
|
|
1738
2047
|
},
|
|
1739
2048
|
...(showEditorPanel
|
|
1740
2049
|
? [{ id: "editor", label: "Editor", content: null }]
|
|
1741
2050
|
: []),
|
|
1742
|
-
], activeTab: mobileActiveTab, setActiveTab: setMobileActiveTab, hideContent: true, variant: "elegant", className: "gap-6" }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [mobileActiveTab === 0 && (_jsx("div", { className: "h-full overflow-y-auto bg-slate-50/50 p-2 pt-2 sm:p-4 sm:pt-3", children: taskBoardContent })), mobileActiveTab === 1 && (_jsx("div", { className: "h-full overflow-y-auto bg-white", children: taskDetailPanel })), mobileActiveTab === 2 && (_jsx("div", { className: "h-full overflow-hidden bg-white", children: agentTerminalPanel })), mobileActiveTab === 3 && showEditorPanel && (_jsx("div", { className: "h-full overflow-hidden bg-gray-50", children: itemPreviewPanel }))] })] })) : (_jsx(Splitter, { panels: panels, direction: "horizontal", localStorageKey:
|
|
1743
|
-
|
|
2051
|
+
], activeTab: mobileActiveTab, setActiveTab: setMobileActiveTab, hideContent: true, variant: "elegant", className: "gap-6" }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [mobileActiveTab === 0 && (_jsx("div", { className: "h-full overflow-y-auto bg-slate-50/50 p-2 pt-2 sm:p-4 sm:pt-3", children: taskBoardContent })), mobileActiveTab === 1 && (_jsx("div", { className: "h-full overflow-y-auto bg-white", children: taskDetailPanel })), mobileActiveTab === 2 && (_jsx("div", { className: "h-full overflow-hidden bg-white", children: shouldHideAgentPanelForSelectedTask ? taskDetailPanel : agentTerminalPanel })), mobileActiveTab === 3 && showEditorPanel && (_jsx("div", { className: "h-full overflow-hidden bg-gray-50", children: itemPreviewPanel }))] })] })) : (_jsx(Splitter, { panels: panels, direction: "horizontal", localStorageKey: isWizardMode
|
|
2052
|
+
? "task-board-wizard-splitter-v2"
|
|
2053
|
+
: "task-board-main-splitter" })) }), selectedProjectId && (_jsx(CreateTaskDialog, { open: createDialogOpen, onOpenChange: setCreateDialogOpen, projectId: selectedProjectId, onCreated: () => {
|
|
1744
2054
|
void refreshTasks(selectedProjectId);
|
|
1745
2055
|
void refreshDependencies(selectedProjectId);
|
|
1746
|
-
} })), _jsx(CreateProjectDialog, { open: createProjectOpen, onOpenChange: setCreateProjectOpen, projects: projects, onCreated: async (newProjectId) => {
|
|
1747
|
-
|
|
2056
|
+
} })), _jsx(CreateProjectDialog, { open: createProjectOpen, onOpenChange: setCreateProjectOpen, projects: projects, onCreated: async ({ projectId: newProjectId, openInWizardMode }) => {
|
|
2057
|
+
if (openInWizardMode) {
|
|
2058
|
+
setIsWizardMode(true);
|
|
2059
|
+
setWizardForceOverview(false);
|
|
2060
|
+
}
|
|
1748
2061
|
if (newProjectId)
|
|
1749
2062
|
handleSelectProject(newProjectId);
|
|
2063
|
+
await refreshProjects();
|
|
1750
2064
|
} }), _jsx(ProjectSettingsDialog, { open: !!settingsProjectId, onOpenChange: (open) => {
|
|
1751
2065
|
if (!open)
|
|
1752
2066
|
setSettingsProjectId(null);
|