Merge branch 'main' into fix-win-0.0.82-add-mac-intel

This commit is contained in:
4pmtong 2026-02-03 20:26:07 +08:00
commit f007da58ed
20 changed files with 536 additions and 264 deletions

View file

@ -23,8 +23,10 @@ import camel
from camel.societies.workforce.events import (LogEvent, TaskAssignedEvent,
TaskCompletedEvent,
TaskCreatedEvent,
TaskDecomposedEvent,
TaskFailedEvent,
TaskStartedEvent,
TaskUpdatedEvent,
WorkerCreatedEvent)
from camel.societies.workforce.workforce_metrics import WorkforceMetrics
from opentelemetry import trace
@ -58,11 +60,16 @@ ATTR_TASK_DESCRIPTION = "eigent.task.description"
ATTR_TASK_PARENT_ID = "eigent.task.parent_id"
ATTR_TASK_TYPE = "eigent.task.type"
ATTR_TASK_STATUS = "eigent.task.status"
ATTR_TASK_UPDATE_TYPE = "eigent.task.update_type"
ATTR_TASK_UPDATE_OLD_VALUE = "eigent.task.update.old_value"
ATTR_TASK_UPDATE_NEW_VALUE = "eigent.task.update.new_value"
ATTR_TASK_UPDATE_METADATA = "eigent.task.update.metadata"
ATTR_TASK_QUEUE_TIME_SECONDS = "eigent.task.queue_time_seconds"
ATTR_TASK_PROCESSING_TIME_SECONDS = "eigent.task.processing_time_seconds"
ATTR_TASK_QUALITY_SCORE = "eigent.task.quality_score"
ATTR_TASK_TIMESTAMP = "eigent.task.timestamp"
ATTR_TASK_DEPENDENCIES = "eigent.task.dependencies"
ATTR_TASK_SUBTASK_IDS = "eigent.task.subtask_ids"
# Attribute keys for eigent.worker namespace
ATTR_WORKER_ID = "eigent.worker.id"
@ -86,7 +93,9 @@ TRACER_NAME_WORKFORCE = "eigent.workforce"
SPAN_WORKFORCE_EXECUTION = "workforce.execution"
SPAN_WORKER_CREATED = "worker.created"
SPAN_TASK_CREATED = "task.created"
SPAN_TASK_DECOMPOSED = "task.decomposed"
SPAN_TASK_ASSIGNED = "task.assigned"
SPAN_TASK_UPDATED = "task.updated"
SPAN_TASK_EXECUTION = "task.execution"
SPAN_LOG_MESSAGE = "log.message"
SPAN_ALL_TASKS_COMPLETED = "workforce.all_tasks_completed"
@ -318,6 +327,27 @@ class WorkforceMetricsCallback(WorkforceMetrics):
span.set_status(Status(StatusCode.OK))
def log_task_decomposed(self, event: TaskDecomposedEvent) -> None:
"""Log task decomposition as a span.
Args:
event (TaskDecomposedEvent): Task decomposed event from CAMEL
"""
if not self.enabled:
return
ctx = trace.set_span_in_context(self.root_span)
with self.tracer.start_as_current_span(SPAN_TASK_DECOMPOSED,
context=ctx) as span:
span.set_attribute(ATTR_TASK_PARENT_ID, event.parent_task_id)
span.set_attribute(ATTR_PROJECT_ID, self.project_id)
if event.subtask_ids:
span.set_attribute(ATTR_TASK_SUBTASK_IDS,
json.dumps(event.subtask_ids))
span.set_status(Status(StatusCode.OK))
def log_task_assigned(self, event: TaskAssignedEvent) -> None:
"""Log task assignment as a span.
@ -345,6 +375,41 @@ class WorkforceMetricsCallback(WorkforceMetrics):
span.set_status(Status(StatusCode.OK))
def log_task_updated(self, event: TaskUpdatedEvent) -> None:
"""Log task update events (replan/reassign/manual) as a span.
Args:
event: Task updated event from CAMEL
"""
if not self.enabled:
return
ctx = trace.set_span_in_context(self.root_span)
with self.tracer.start_as_current_span(SPAN_TASK_UPDATED,
context=ctx) as span:
span.set_attribute(ATTR_TASK_ID, event.task_id)
span.set_attribute(ATTR_PROJECT_ID, self.project_id)
span.set_attribute(ATTR_TASK_UPDATE_TYPE, event.update_type)
if event.worker_id:
span.set_attribute(ATTR_WORKER_ID, event.worker_id)
if event.parent_task_id:
span.set_attribute(ATTR_TASK_PARENT_ID, event.parent_task_id)
if event.old_value is not None:
span.set_attribute(ATTR_TASK_UPDATE_OLD_VALUE,
event.old_value)
if event.new_value is not None:
span.set_attribute(ATTR_TASK_UPDATE_NEW_VALUE,
event.new_value)
if event.metadata:
span.set_attribute(ATTR_TASK_UPDATE_METADATA,
json.dumps(event.metadata))
if hasattr(event, 'timestamp') and event.timestamp:
span.set_attribute(ATTR_TASK_TIMESTAMP,
event.timestamp.isoformat())
span.set_status(Status(StatusCode.OK))
def log_task_started(self, event: TaskStartedEvent) -> None:
"""Log task start and create a span for the task execution.

View file

@ -1,3 +1,17 @@
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
"""Tests for workforce metrics telemetry."""
from datetime import datetime
from unittest.mock import MagicMock, Mock, patch
@ -8,8 +22,10 @@ from app.utils.telemetry.workforce_metrics import WorkforceMetricsCallback
from camel.societies.workforce.events import (LogEvent, TaskAssignedEvent,
TaskCompletedEvent,
TaskCreatedEvent,
TaskDecomposedEvent,
TaskFailedEvent,
TaskStartedEvent,
TaskUpdatedEvent,
WorkerCreatedEvent)
@ -90,6 +106,24 @@ def test_log_task_created(metrics_callback):
assert mock_span.set_status.called
def test_log_task_decomposed(metrics_callback):
"""Test log_task_decomposed function."""
event = TaskDecomposedEvent(
parent_task_id="parent_1",
subtask_ids=["task_1", "task_2"],
)
mock_span = Mock()
metrics_callback.tracer.start_as_current_span = Mock(return_value=Mock(
__enter__=Mock(return_value=mock_span), __exit__=Mock()))
metrics_callback.log_task_decomposed(event)
# Verify span attributes were set
assert mock_span.set_attribute.called
assert mock_span.set_status.called
def test_log_task_assigned(metrics_callback):
"""Test log_task_assigned function."""
event = TaskAssignedEvent(
@ -110,6 +144,29 @@ def test_log_task_assigned(metrics_callback):
assert mock_span.set_status.called
def test_log_task_updated(metrics_callback):
"""Test log_task_updated function."""
event = TaskUpdatedEvent(
task_id="task_1",
worker_id="worker_1",
update_type="replan",
old_value="old plan",
new_value="new plan",
parent_task_id="parent_1",
metadata={"source": "recovery"},
)
mock_span = Mock()
metrics_callback.tracer.start_as_current_span = Mock(return_value=Mock(
__enter__=Mock(return_value=mock_span), __exit__=Mock()))
metrics_callback.log_task_updated(event)
# Verify span attributes were set
assert mock_span.set_attribute.called
assert mock_span.set_status.called
def test_log_task_started(metrics_callback):
"""Test log_task_started function."""
event = TaskStartedEvent(task_id="task_1", worker_id="worker_1")

View file

@ -29,6 +29,7 @@ import {
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { TaskState } from '../TaskState';
import { Button } from '../ui/button';
import { TaskStatus } from "@/types/constants";
export default function Home() {
//Get Chatstore for the active project's task
@ -223,26 +224,26 @@ export default function Home() {
}
done={
activeAgent?.tasks?.filter(
(task) => task.status === 'completed'
(task) => task.status === TaskStatus.COMPLETED
).length || 0
}
progress={
activeAgent?.tasks?.filter(
(task) =>
task.status !== 'failed' &&
task.status !== 'completed' &&
task.status !== 'skipped' &&
task.status !== 'waiting'
task.status !== TaskStatus.FAILED &&
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.SKIPPED &&
task.status !== TaskStatus.WAITING
).length || 0
}
failed={
activeAgent?.tasks?.filter((task) => task.status === 'failed')
activeAgent?.tasks?.filter((task) => task.status === TaskStatus.FAILED)
.length || 0
}
skipped={
activeAgent?.tasks?.filter(
(task) =>
task.status === 'skipped' || task.status === 'waiting'
task.status === TaskStatus.SKIPPED || task.status === TaskStatus.WAITING
).length || 0
}
/>

View file

@ -16,6 +16,7 @@ import { BoxAction } from './BoxAction';
import { BoxHeaderConfirm, BoxHeaderSplitting } from './BoxHeader';
import { FileAttachment, Inputbox, InputboxProps } from './InputBox';
import { QueuedBox, QueuedMessage } from './QueuedBox';
import { type ChatTaskStatusType } from "@/types/constants";
export type BottomBoxState =
| 'input'
@ -42,7 +43,7 @@ interface BottomBoxProps {
// Task info
tokens?: number;
taskTime?: string;
taskStatus?: 'running' | 'finished' | 'pending' | 'pause';
taskStatus?: ChatTaskStatusType;
// Replay
onReplay?: () => void;

View file

@ -14,10 +14,11 @@
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { ChatTaskStatus, type ChatTaskStatusType } from '@/types/constants';
export interface FloatingActionProps {
/** Current task status */
status: 'running' | 'pause' | 'pending' | 'finished';
status: ChatTaskStatusType;
/** Callback when pause button is clicked */
// onPause?: () => void; // Commented out - temporary not needed
/** Callback when resume button is clicked */
@ -39,7 +40,7 @@ export const FloatingAction = ({
className,
}: FloatingActionProps) => {
// Only show when task is running (removed pause state)
if (status !== 'running') {
if (status !== ChatTaskStatus.RUNNING) {
return null;
}

View file

@ -17,6 +17,7 @@ import { motion } from 'framer-motion';
import React from 'react';
import { FloatingAction } from './FloatingAction';
import { UserQueryGroup } from './UserQueryGroup';
import { AgentStep } from '@/types/constants';
interface ProjectSectionProps {
chatId: string;
@ -161,7 +162,7 @@ function groupMessagesByQuery(messages: any[]) {
userMessage: message,
otherMessages: [],
};
} else if (message.step === 'to_sub_tasks') {
} else if (message.step === AgentStep.TO_SUB_TASKS) {
// Task planning message - each should get its own panel
// Skip if we've already processed this to_sub_tasks

View file

@ -37,6 +37,7 @@ import {
import { useMemo, useState, useRef, useEffect } from "react";
import { TaskState, TaskStateType } from "@/components/TaskState";
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
import { TaskStatus, ChatTaskStatus } from "@/types/constants";
interface TaskCardProps {
taskInfo: any[];
@ -87,24 +88,24 @@ export function TaskCard({
const newFiltered = tasks.filter((task) => {
switch (selectedState) {
case "done":
return task.status === "completed" && !task.reAssignTo;
return task.status === TaskStatus.COMPLETED && !task.reAssignTo;
case "ongoing":
return (
task.status !== "failed" &&
task.status !== "completed" &&
task.status !== "skipped" &&
task.status !== "waiting" &&
task.status !== ""
task.status !== TaskStatus.FAILED &&
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.SKIPPED &&
task.status !== TaskStatus.WAITING &&
task.status !== TaskStatus.EMPTY
);
case "pending":
return (
(task.status === "skipped" ||
task.status === "waiting" ||
task.status === "") &&
(task.status === TaskStatus.SKIPPED ||
task.status === TaskStatus.WAITING ||
task.status === TaskStatus.EMPTY) &&
!task.reAssignTo
);
case "failed":
return task.status === "failed";
return task.status === TaskStatus.FAILED;
default:
return false;
}
@ -115,7 +116,7 @@ export function TaskCard({
const isAllTaskFinished = useMemo(() => {
return (
chatStore.tasks[chatStore.activeTaskId as string].status === "finished"
chatStore.tasks[chatStore.activeTaskId as string].status === ChatTaskStatus.FINISHED
);
}, [chatStore.tasks[chatStore.activeTaskId as string].status]);
@ -207,32 +208,32 @@ export function TaskCard({
done={
taskInfo.filter(
(task) =>
task.content !== "" && task.status === "completed"
task.content !== "" && task.status === TaskStatus.COMPLETED
).length || 0
}
progress={
taskInfo.filter(
(task) =>
task.content !== "" &&
task.status !== "completed" &&
task.status !== "failed" &&
task.status !== "skipped" &&
task.status !== "waiting" &&
task.status !== ""
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.FAILED &&
task.status !== TaskStatus.SKIPPED &&
task.status !== TaskStatus.WAITING &&
task.status !== TaskStatus.EMPTY
).length || 0
}
skipped={
taskInfo.filter(
(task) =>
task.content !== "" &&
(task.status === "skipped" ||
task.status === "waiting" ||
task.status === "")
(task.status === TaskStatus.SKIPPED ||
task.status === TaskStatus.WAITING ||
task.status === TaskStatus.EMPTY)
).length || 0
}
failed={
taskInfo.filter(
(task) => task.content !== "" && task.status === "failed"
(task) => task.content !== "" && task.status === TaskStatus.FAILED
).length || 0
}
forceVisible={true}
@ -243,29 +244,29 @@ export function TaskCard({
<TaskState
all={taskRunning?.length || 0}
done={
taskRunning?.filter((task) => task.status === "completed")
taskRunning?.filter((task) => task.status === TaskStatus.COMPLETED)
.length || 0
}
progress={
taskRunning?.filter(
(task) =>
task.status !== "completed" &&
task.status !== "failed" &&
task.status !== "skipped" &&
task.status !== "waiting" &&
task.status !== ""
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.FAILED &&
task.status !== TaskStatus.SKIPPED &&
task.status !== TaskStatus.WAITING &&
task.status !== TaskStatus.EMPTY
).length || 0
}
skipped={
taskRunning?.filter(
(task) =>
task.status === "skipped" ||
task.status === "waiting" ||
task.status === ""
task.status === TaskStatus.SKIPPED ||
task.status === TaskStatus.WAITING ||
task.status === TaskStatus.EMPTY
).length || 0
}
failed={
taskRunning?.filter((task) => task.status === "failed")
taskRunning?.filter((task) => task.status === TaskStatus.FAILED)
.length || 0
}
forceVisible={true}
@ -288,8 +289,8 @@ export function TaskCard({
<div className="text-text-tertiary text-xs font-medium leading-17">
{taskRunning?.filter(
(task) =>
task.status === "completed" ||
task.status === "failed"
task.status === TaskStatus.COMPLETED ||
task.status === TaskStatus.FAILED
).length || 0}
/{taskRunning?.length || 0}
</div>
@ -371,70 +372,70 @@ export function TaskCard({
}
}}
key={`taskList-${task.id}`}
className={`rounded-lg flex gap-2 py-sm px-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${task.status === "completed"
className={`rounded-lg flex gap-2 py-sm px-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${task.status === TaskStatus.COMPLETED
? "bg-task-fill-success"
: task.status === "failed"
: task.status === TaskStatus.FAILED
? "bg-task-fill-error"
: task.status === "running"
: task.status === TaskStatus.RUNNING
? "bg-task-fill-running"
: task.status === "blocked"
: task.status === TaskStatus.BLOCKED
? "bg-task-fill-warning"
: "bg-task-fill-running"
} border border-solid border-transparent cursor-pointer ${task.status === "completed"
} border border-solid border-transparent cursor-pointer ${task.status === TaskStatus.COMPLETED
? "hover:border-bg-fill-success-primary"
: task.status === "failed"
: task.status === TaskStatus.FAILED
? "hover:border-task-border-focus-error"
: task.status === "running"
: task.status === TaskStatus.RUNNING
? "hover:border-border-primary"
: task.status === "blocked"
: task.status === TaskStatus.BLOCKED
? "hover:border-task-border-focus-warning"
: "border-transparent"
}
`}
>
<div className="pt-0.5">
{task.status === "running" && (
{task.status === TaskStatus.RUNNING && (
<LoaderCircle
size={16}
className={`text-icon-information ${chatStore.tasks[
chatStore.activeTaskId as string
].status === "running" && "animate-spin"
].status === ChatTaskStatus.RUNNING && "animate-spin"
} `}
/>
)}
{task.status === "skipped" && (
{task.status === TaskStatus.SKIPPED && (
<LoaderCircle
size={16}
className={`text-icon-secondary `}
/>
)}
{task.status === "completed" && (
{task.status === TaskStatus.COMPLETED && (
<CircleCheckBig
size={16}
className="text-icon-success"
/>
)}
{task.status === "failed" && (
{task.status === TaskStatus.FAILED && (
<CircleSlash
size={16}
className="text-icon-cuation"
/>
)}
{task.status === "blocked" && (
{task.status === TaskStatus.BLOCKED && (
<TriangleAlert
size={16}
className="text-icon-warning"
/>
)}
{task.status === "" && (
{task.status === TaskStatus.EMPTY && (
<Circle size={16} className="text-icon-secondary" />
)}
</div>
<div className="flex-1 flex flex-col items-start justify-center">
<div
className={` w-full break-words whitespace-pre-line ${task.status === "failed"
className={` w-full break-words whitespace-pre-line ${task.status === TaskStatus.FAILED
? "text-text-cuation-default"
: task.status === "blocked"
: task.status === TaskStatus.BLOCKED
? "text-text-body"
: "text-text-primary"
} text-sm font-medium leading-13 `}

View file

@ -27,6 +27,7 @@ import { UserMessageCard } from './MessageItem/UserMessageCard';
import { StreamingTaskList } from './TaskBox/StreamingTaskList';
import { TaskCard } from './TaskBox/TaskCard';
import { TypeCardSkeleton } from './TaskBox/TypeCardSkeleton';
import { AgentStep, ChatTaskStatus } from '@/types/constants';
interface QueryGroup {
queryId: string;
@ -86,7 +87,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
if (userMessageIndex > 0) {
// Check the previous message - if it's an agent message with step 'ask', this is a human-reply
const prevMessage = messages[userMessageIndex - 1];
return prevMessage?.role === 'agent' && prevMessage?.step === 'ask';
return prevMessage?.role === 'agent' && prevMessage?.step === AgentStep.ASK;
}
return false;
})());
@ -102,7 +103,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
.filter((m: any) => m.role === 'user')
.pop()?.id &&
// Only show during active phases (not finished)
chatState.tasks[activeTaskId].status !== 'finished';
chatState.tasks[activeTaskId].status !== ChatTaskStatus.FINISHED;
// Only show the fallback task box for the newest query while the agent is still splitting work.
// Simple Q&A sessions set hasWaitComfirm to true, so we should not render an empty task box there.
@ -185,11 +186,11 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
// Check if we're in skeleton phase
const anyToSubTasksMessage = task?.messages.find(
(m: any) => m.step === 'to_sub_tasks'
(m: any) => m.step === AgentStep.TO_SUB_TASKS
);
const isSkeletonPhase =
task &&
((task.status !== 'finished' &&
((task.status !== ChatTaskStatus.FINISHED &&
!anyToSubTasksMessage &&
!task.hasWaitComfirm &&
task.messages.length > 0) ||
@ -282,7 +283,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
{/* Other Messages */}
{queryGroup.otherMessages.map((message) => {
if (message.content.length > 0) {
if (message.step === 'end') {
if (message.step === AgentStep.END) {
return (
<motion.div
key={`end-${message.id}`}
@ -375,7 +376,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
</motion.div>
);
}
} else if (message.step === 'end' && message.content === '') {
} else if (message.step === AgentStep.END && message.content === '') {
return (
<motion.div
key={`end-empty-${message.id}`}
@ -423,7 +424,7 @@ export const UserQueryGroup: React.FC<UserQueryGroupProps> = ({
// Notice Card
if (
message.step === 'notice_card' &&
message.step === AgentStep.NOTICE_CARD &&
!task?.isTakeControl &&
task?.cotList &&
task.cotList.length > 0

View file

@ -30,6 +30,7 @@ import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { toast } from 'sonner';
import BottomBox from './BottomBox';
import { ProjectChatContainer } from './ProjectChatContainer';
import { AgentStep, ChatTaskStatus } from '@/types/constants';
export default function ChatBox(): JSX.Element {
const [message, setMessage] = useState<string>('');
@ -191,7 +192,7 @@ export default function ChatBox(): JSX.Element {
if (!chatStore) return;
const _hasSubTask = chatStore.tasks[
chatStore.activeTaskId as string
]?.messages?.find((message) => message.step === 'to_sub_tasks')
]?.messages?.find((message) => message.step === AgentStep.TO_SUB_TASKS)
? true
: false;
setHasSubTask(_hasSubTask);
@ -253,12 +254,12 @@ export default function ChatBox(): JSX.Element {
const task = chatStore.tasks[chatStore.activeTaskId];
return (
// running or paused
task.status === 'running' ||
task.status === 'pause' ||
task.status === ChatTaskStatus.RUNNING ||
task.status === ChatTaskStatus.PAUSE ||
// splitting phase
task.messages.some((m) => m.step === 'to_sub_tasks' && !m.isConfirm) ||
task.messages.some((m) => m.step === AgentStep.TO_SUB_TASKS && !m.isConfirm) ||
// skeleton/computing phase
(!task.messages.find((m) => m.step === 'to_sub_tasks') &&
(!task.messages.find((m) => m.step === AgentStep.TO_SUB_TASKS) &&
!task.hasWaitComfirm &&
task.messages.length > 0) ||
task.isTakeControl
@ -424,17 +425,17 @@ export default function ChatBox(): JSX.Element {
const requiresHumanReply = Boolean(task?.activeAsk);
const isTaskBusy =
// running or paused counts as busy
(task.status === 'running' && task.hasMessages) ||
task.status === 'pause' ||
(task.status === ChatTaskStatus.RUNNING && task.hasMessages) ||
task.status === ChatTaskStatus.PAUSE ||
// splitting phase: has to_sub_tasks not confirmed OR skeleton computing
task.messages.some((m) => m.step === 'to_sub_tasks' && !m.isConfirm) ||
(!task.messages.find((m) => m.step === 'to_sub_tasks') &&
task.messages.some((m) => m.step === AgentStep.TO_SUB_TASKS && !m.isConfirm) ||
(!task.messages.find((m) => m.step === AgentStep.TO_SUB_TASKS) &&
!task.hasWaitComfirm &&
task.messages.length > 0) ||
task.isTakeControl ||
// explicit confirm wait while task is pending but card not confirmed yet
(!!task.messages.find((m) => m.step === 'to_sub_tasks' && !m.isConfirm) &&
task.status === 'pending');
(!!task.messages.find((m) => m.step === AgentStep.TO_SUB_TASKS && !m.isConfirm) &&
task.status === ChatTaskStatus.PENDING);
const isReplayChatStore = task?.type === 'replay';
if (!requiresHumanReply && isTaskBusy && !isReplayChatStore) {
toast.error(
@ -489,7 +490,7 @@ export default function ChatBox(): JSX.Element {
const hasMessages =
chatStore.tasks[_taskId as string].messages.length > 0;
const isFinished =
chatStore.tasks[_taskId as string].status === 'finished';
chatStore.tasks[_taskId as string].status === ChatTaskStatus.FINISHED;
const hasWaitComfirm =
chatStore.tasks[_taskId as string]?.hasWaitComfirm;
@ -497,7 +498,7 @@ export default function ChatBox(): JSX.Element {
const wasTaskStopped =
isFinished &&
!chatStore.tasks[_taskId as string].messages.some(
(m) => m.step === 'end' // Natural completion has an "end" step message
(m) => m.step === AgentStep.END // Natural completion has an "end" step message
);
// Continue conversation if:
@ -508,16 +509,16 @@ export default function ChatBox(): JSX.Element {
(hasWaitComfirm && !wasTaskStopped) ||
(isFinished && !wasTaskStopped) ||
(hasMessages &&
chatStore.tasks[_taskId as string].status === 'pending');
chatStore.tasks[_taskId as string].status === ChatTaskStatus.PENDING);
if (shouldContinueConversation) {
// Check if this is the very first message and task hasn't started
const hasSimpleResponse = chatStore.tasks[
_taskId as string
].messages.some((m) => m.step === 'wait_confirm');
].messages.some((m) => m.step === AgentStep.WAIT_CONFIRM);
const hasComplexTask = chatStore.tasks[
_taskId as string
].messages.some((m) => m.step === 'to_sub_tasks');
].messages.some((m) => m.step === AgentStep.TO_SUB_TASKS);
const hasErrorMessage = chatStore.tasks[
_taskId as string
].messages.some(
@ -527,7 +528,7 @@ export default function ChatBox(): JSX.Element {
// Only start a new task if: pending, no messages processed yet
// OR while or after replaying a project
if (
(chatStore.tasks[_taskId as string].status === 'pending' &&
(chatStore.tasks[_taskId as string].status === ChatTaskStatus.PENDING &&
!hasSimpleResponse &&
!hasComplexTask &&
!isFinished) ||
@ -692,7 +693,7 @@ export default function ChatBox(): JSX.Element {
const handlePauseResume = () => {
const taskId = chatStore.activeTaskId as string;
const task = chatStore.tasks[taskId];
const type = task.status === 'running' ? 'pause' : 'resume';
const type = task.status === ChatTaskStatus.RUNNING ? 'pause' : 'resume';
setIsPauseResumeLoading(true);
if (type === 'pause') {
@ -701,10 +702,10 @@ export default function ChatBox(): JSX.Element {
elapsed += now - taskTime;
chatStore.setElapsed(taskId, elapsed);
chatStore.setTaskTime(taskId, 0);
chatStore.setStatus(taskId, 'pause');
chatStore.setStatus(taskId, ChatTaskStatus.PAUSE);
} else {
chatStore.setTaskTime(taskId, Date.now());
chatStore.setStatus(taskId, 'running');
chatStore.setStatus(taskId, ChatTaskStatus.RUNNING);
}
fetchPut(`/task/${projectStore.activeProjectId}/take-control`, {
@ -802,7 +803,7 @@ export default function ChatBox(): JSX.Element {
// Get question and attachments before any deletions
const messageIndex = chatStore.tasks[taskId].messages.findLastIndex(
(item) => item.step === 'to_sub_tasks'
(item) => item.step === AgentStep.TO_SUB_TASKS
);
const questionMessage = chatStore.tasks[taskId].messages[messageIndex - 2];
const question = questionMessage.content;
@ -856,16 +857,16 @@ export default function ChatBox(): JSX.Element {
// Check for any to_sub_tasks message (confirmed or not)
const anyToSubTasksMessage = task.messages.find(
(m) => m.step === 'to_sub_tasks'
(m) => m.step === AgentStep.TO_SUB_TASKS
);
const toSubTasksMessage = task.messages.find(
(m) => m.step === 'to_sub_tasks' && !m.isConfirm
(m) => m.step === AgentStep.TO_SUB_TASKS && !m.isConfirm
);
// Determine if we're in the "splitting in progress" phase (skeleton visible)
// Only show splitting if there's NO to_sub_tasks message yet (not even confirmed)
const isSkeletonPhase =
(task.status !== 'finished' &&
(task.status !== ChatTaskStatus.FINISHED &&
!anyToSubTasksMessage &&
!task.hasWaitComfirm &&
task.messages.length > 0) ||
@ -879,7 +880,7 @@ export default function ChatBox(): JSX.Element {
if (
toSubTasksMessage &&
!toSubTasksMessage.isConfirm &&
task.status === 'pending'
task.status === ChatTaskStatus.PENDING
) {
return 'confirm';
}
@ -890,11 +891,11 @@ export default function ChatBox(): JSX.Element {
}
// Check task status
if (task.status === 'running' || task.status === 'pause') {
if (task.status === ChatTaskStatus.RUNNING || task.status === ChatTaskStatus.PAUSE) {
return 'running';
}
if (task.status === 'finished' && task.type !== '') {
if (task.status === ChatTaskStatus.FINISHED && task.type !== '') {
return 'finished';
}
@ -938,7 +939,7 @@ export default function ChatBox(): JSX.Element {
// Note: Replay creates a new chatstore, so no conflicts
const task = chatStore.tasks[chatStore.activeTaskId as string];
// Only skip backend call if task is finished or hasn't started yet (no messages)
if (task && task.messages.length > 0 && task.status !== 'finished') {
if (task && task.messages.length > 0 && task.status !== ChatTaskStatus.FINISHED) {
try {
await fetchDelete(`/chat/${project_id}/remove-task/${task_id}`, {
project_id: project_id,
@ -1010,7 +1011,7 @@ export default function ChatBox(): JSX.Element {
taskStatus={chatStore.tasks[chatStore.activeTaskId]?.status}
onReplay={handleReplay}
replayDisabled={
chatStore.tasks[chatStore.activeTaskId]?.status !== 'finished'
chatStore.tasks[chatStore.activeTaskId]?.status !== ChatTaskStatus.FINISHED
}
replayLoading={isReplayLoading}
onPauseResume={handlePauseResume}

View file

@ -25,6 +25,7 @@ import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
import { replayProject } from '@/lib/replay';
import { useProjectStore } from '@/store/projectStore';
import { ProjectGroup as ProjectGroupType } from '@/types/history';
import { ChatTaskStatus } from "@/types/constants";
import { motion } from 'framer-motion';
import {
Edit,
@ -100,7 +101,7 @@ export default function ProjectGroup({
// Check if any task in chatStore with matching task_id has pending status
return Object.entries(chatStore.tasks).some(
([taskId, task]) =>
projectTaskIds.includes(taskId) && task.status === 'pending'
projectTaskIds.includes(taskId) && task.status === ChatTaskStatus.PENDING
);
}, [chatStore?.tasks, project.tasks]);
const _hasIssue = hasHumanInLoop;

View file

@ -22,6 +22,7 @@ import {
import { Tag } from '@/components/ui/tag';
import { TooltipSimple } from '@/components/ui/tooltip';
import { HistoryTask } from '@/types/history';
import { ChatTaskStatus } from '@/types/constants';
import {
CheckCircle,
CirclePause,
@ -63,7 +64,7 @@ export default function TaskItem({
const { t } = useTranslation();
// Check if task is paused (for ongoing tasks)
const isPaused = (task as any)._taskData?.status === 'pause';
const isPaused = (task as any)._taskData?.status === ChatTaskStatus.PAUSE;
const getStatusTag = (status: number) => {
// ChatStatus enum: ongoing = 1, done = 2

View file

@ -45,6 +45,7 @@ import {
import { Tag } from '../ui/tag';
import { TooltipSimple } from '../ui/tooltip';
import SearchInput from './SearchInput';
import { ChatTaskStatus } from "@/types/constants";
export default function HistorySidebar() {
const { t } = useTranslation();
@ -90,7 +91,7 @@ export default function HistorySidebar() {
Object.keys(csState.tasks || {}).forEach((taskId) => {
const task = csState.tasks[taskId];
// Only include ongoing tasks
if (task.status !== 'finished' && !task.type) {
if (task.status !== ChatTaskStatus.FINISHED && !task.type) {
hasOngoingTasks = true;
taskCount++;
if (task.tokens) {

View file

@ -45,6 +45,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { ChatTaskStatus } from '@/types/constants';
function HeaderWin() {
const { t } = useTranslation();
@ -144,7 +145,7 @@ function HeaderWin() {
const task = chatStore.tasks[taskId];
// Stop the task if it's running
if (task && task.status === 'running') {
if (task && task.status === ChatTaskStatus.RUNNING) {
await fetchPut(`/task/${taskId}/take-control`, {
action: 'stop',
});
@ -158,7 +159,7 @@ function HeaderWin() {
}
// Delete from history using historyId
if (historyId && task.status !== 'finished') {
if (historyId && task.status !== ChatTaskStatus.FINISHED) {
try {
await proxyFetchDelete(`/api/chat/history/${historyId}`);
// Remove from local store
@ -333,7 +334,7 @@ function HeaderWin() {
chatStore.tasks[chatStore.activeTaskId as string]
?.hasMessages ||
chatStore.tasks[chatStore.activeTaskId as string]?.status !==
'pending') && (
ChatTaskStatus.PENDING) && (
<TooltipSimple
content={t('layout.end-project')}
side="bottom"
@ -352,7 +353,7 @@ function HeaderWin() {
)}
{chatStore.activeTaskId &&
chatStore.tasks[chatStore.activeTaskId as string]?.status ===
'finished' && (
ChatTaskStatus.FINISHED && (
<TooltipSimple
content={t('layout.share')}
side="bottom"

View file

@ -49,6 +49,7 @@ import {
} from "../ui/popover";
import { AddWorker } from "@/components/AddWorker";
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
import { TaskStatus, ChatTaskStatus, AgentStatusValue } from "@/types/constants";
interface NodeProps {
id: string;
@ -84,27 +85,27 @@ export function Node({ id, data }: NodeProps) {
const newFiltered = tasks.filter((task) => {
switch (selectedState) {
case "done":
return task.status === "completed" && !task.reAssignTo;
return task.status === TaskStatus.COMPLETED && !task.reAssignTo;
case "reassigned":
return !!task.reAssignTo;
case "ongoing":
return (
task.status !== "failed" &&
task.status !== "completed" &&
task.status !== "skipped" &&
task.status !== "waiting" &&
task.status !== "" &&
task.status !== TaskStatus.FAILED &&
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.SKIPPED &&
task.status !== TaskStatus.WAITING &&
task.status !== TaskStatus.EMPTY &&
!task.reAssignTo
);
case "pending":
return (
(task.status === "skipped" ||
task.status === "waiting" ||
task.status === "") &&
(task.status === TaskStatus.SKIPPED ||
task.status === TaskStatus.WAITING ||
task.status === TaskStatus.EMPTY) &&
!task.reAssignTo
);
case "failed":
return task.status === "failed";
return task.status === TaskStatus.FAILED;
default:
return false;
}
@ -136,13 +137,13 @@ export function Node({ id, data }: NodeProps) {
// Find running task with active toolkits
const runningTaskWithToolkits = tasks.find(
(task) =>
task.status === "running" &&
task.status === TaskStatus.RUNNING &&
task.toolkits &&
task.toolkits.length > 0
);
// Reset tracking when no tasks are running
const hasRunningTasks = tasks.some((task) => task.status === "running");
const hasRunningTasks = tasks.some((task) => task.status === TaskStatus.RUNNING);
if (!hasRunningTasks && lastAutoExpandedTaskIdRef.current) {
lastAutoExpandedTaskIdRef.current = null;
}
@ -164,8 +165,8 @@ export function Node({ id, data }: NodeProps) {
data.agent?.tasks,
// Add specific dependencies that actually change
data.agent?.tasks?.length,
data.agent?.tasks?.find((t) => t.status === "running")?.id,
data.agent?.tasks?.find((t) => t.status === "running")?.toolkits?.length,
data.agent?.tasks?.find((t) => t.status === TaskStatus.RUNNING)?.id,
data.agent?.tasks?.find((t) => t.status === TaskStatus.RUNNING)?.toolkits?.length,
id,
data.onExpandChange,
isExpanded,
@ -198,7 +199,7 @@ export function Node({ id, data }: NodeProps) {
const handleShowLog = () => {
if (!isExpanded) {
setSelectedTask(
data.agent?.tasks.find((task) => task.status === "running") ||
data.agent?.tasks.find((task) => task.status === TaskStatus.RUNNING) ||
data.agent?.tasks[0]
);
}
@ -318,7 +319,7 @@ export function Node({ id, data }: NodeProps) {
borderColor: "border-bg-fill-multimodal-active",
bgColorLight: "bg-fuchsia-200",
},
social_medium_agent: {
social_media_agent: {
name: "Social Media Agent",
icon: <Bird size={16} className="text-text-primary" />,
textColor: "text-purple-700",
@ -549,7 +550,7 @@ export function Node({ id, data }: NodeProps) {
all={data.agent.tasks?.length || 0}
done={
data.agent?.tasks?.filter(
(task) => task.status === "completed" && !task.reAssignTo
(task) => task.status === TaskStatus.COMPLETED && !task.reAssignTo
).length || 0
}
reAssignTo={
@ -559,26 +560,26 @@ export function Node({ id, data }: NodeProps) {
progress={
data.agent?.tasks?.filter(
(task) =>
task.status !== "failed" &&
task.status !== "completed" &&
task.status !== "skipped" &&
task.status !== "waiting" &&
task.status !== "" &&
task.status !== TaskStatus.FAILED &&
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.SKIPPED &&
task.status !== TaskStatus.WAITING &&
task.status !== TaskStatus.EMPTY &&
!task.reAssignTo
).length || 0
}
skipped={
data.agent?.tasks?.filter(
(task) =>
(task.status === "skipped" ||
task.status === "waiting" ||
task.status === "") &&
(task.status === TaskStatus.SKIPPED ||
task.status === TaskStatus.WAITING ||
task.status === TaskStatus.EMPTY) &&
!task.reAssignTo
).length || 0
}
failed={
data.agent?.tasks?.filter(
(task) => task.status === "failed"
(task) => task.status === TaskStatus.FAILED
).length || 0
}
selectedState={selectedState}
@ -625,32 +626,32 @@ export function Node({ id, data }: NodeProps) {
key={`taskList-${task.id}-${task.failure_count}`}
className={`rounded-lg flex gap-2 py-sm px-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${task.reAssignTo
? "bg-task-fill-warning"
: task.status === "completed"
: task.status === TaskStatus.COMPLETED
? "bg-task-fill-success"
: task.status === "failed"
: task.status === TaskStatus.FAILED
? "bg-task-fill-error"
: task.status === "running"
: task.status === TaskStatus.RUNNING
? "bg-task-fill-running"
: task.status === "blocked"
: task.status === TaskStatus.BLOCKED
? "bg-task-fill-warning"
: "bg-task-fill-running"
} border border-solid border-transparent cursor-pointer ${task.status === "completed"
} border border-solid border-transparent cursor-pointer ${task.status === TaskStatus.COMPLETED
? "hover:border-bg-fill-success-primary"
: task.status === "failed"
: task.status === TaskStatus.FAILED
? "hover:border-task-border-focus-error"
: task.status === "running"
: task.status === TaskStatus.RUNNING
? "hover:border-border-primary"
: task.status === "blocked"
: task.status === TaskStatus.BLOCKED
? "hover:border-task-border-focus-warning"
: "border-transparent"
} ${selectedTask?.id === task.id
? task.status === "completed"
? task.status === TaskStatus.COMPLETED
? "!border-bg-fill-success-primary"
: task.status === "failed"
: task.status === TaskStatus.FAILED
? "!border-text-cuation-primary"
: task.status === "running"
: task.status === TaskStatus.RUNNING
? "!border-border-primary"
: task.status === "blocked"
: task.status === TaskStatus.BLOCKED
? "!border-text-warning-primary"
: "border-transparent"
: "border-transparent"
@ -663,41 +664,41 @@ export function Node({ id, data }: NodeProps) {
) : (
// normal task
<>
{task.status === "running" && (
{task.status === TaskStatus.RUNNING && (
<LoaderCircle
size={16}
className={`text-icon-information ${chatStore.tasks[
chatStore.activeTaskId as string
].status === "running" && "animate-spin"
].status === ChatTaskStatus.RUNNING && "animate-spin"
}`}
/>
)}
{task.status === "skipped" && (
{task.status === TaskStatus.SKIPPED && (
<LoaderCircle
size={16}
className={`text-icon-secondary `}
/>
)}
{task.status === "completed" && (
{task.status === TaskStatus.COMPLETED && (
<CircleCheckBig
size={16}
className="text-icon-success"
/>
)}
{task.status === "failed" && (
{task.status === TaskStatus.FAILED && (
<CircleSlash
size={16}
className="text-icon-cuation"
/>
)}
{task.status === "blocked" && (
{task.status === TaskStatus.BLOCKED && (
<TriangleAlert
size={16}
className="text-icon-warning"
/>
)}
{(task.status === "" ||
task.status === "waiting") && (
{(task.status === TaskStatus.EMPTY ||
task.status === TaskStatus.WAITING) && (
<Circle size={16} className="text-slate-400" />
)}
</>
@ -705,9 +706,9 @@ export function Node({ id, data }: NodeProps) {
</div>
<div className="flex-1 flex flex-col items-start justify-center">
<div
className={`w-full flex-grow-0 ${task.status === "failed"
className={`w-full flex-grow-0 ${task.status === TaskStatus.FAILED
? "text-text-cuation-default"
: task.status === "blocked"
: task.status === TaskStatus.BLOCKED
? "text-text-body"
: "text-text-primary"
} text-xs font-medium leading-13 select-text pointer-events-auto break-all text-wrap whitespace-pre-line`}
@ -723,9 +724,9 @@ export function Node({ id, data }: NodeProps) {
) : (
(task.failure_count ?? 0) > 0 && (
<div
className={`${task.status === "failed"
className={`${task.status === TaskStatus.FAILED
? "bg-surface-error-subtle text-text-cuation"
: task.status === "completed"
: task.status === TaskStatus.COMPLETED
? "bg-tag-fill-developer text-text-success-default"
: "bg-tag-surface-hover text-text-label"
} text-xs font-bold leading-none rounded-lg px-1 py-0.5`}
@ -737,7 +738,7 @@ export function Node({ id, data }: NodeProps) {
</div>
<div>{task.content}</div>
</div>
{task?.status === "running" && (
{task?.status === TaskStatus.RUNNING && (
<div className="flex items-center gap-2 mt-xs animate-in fade-in-0 slide-in-from-bottom-2 duration-400">
{/* active toolkit */}
{task.toolkits &&
@ -746,7 +747,7 @@ export function Node({ id, data }: NodeProps) {
.filter(
(tool: any) => tool.toolkitName !== "notice"
)
.at(-1)?.toolkitStatus === "running" && (
.at(-1)?.toolkitStatus === AgentStatusValue.RUNNING && (
<div className="flex-1 min-w-0 flex justify-start items-center gap-sm animate-in fade-in-0 slide-in-from-right-2 duration-300">
{agentMap[data.type]?.icon ?? (
<Bot className="w-3 h-3" />
@ -830,12 +831,12 @@ export function Node({ id, data }: NodeProps) {
>
{/* {toolkit.toolkitStatus} */}
<div>
{toolkit.toolkitStatus === "running" ? (
{toolkit.toolkitStatus === AgentStatusValue.RUNNING ? (
<LoaderCircle
size={16}
className={`${chatStore.tasks[
chatStore.activeTaskId as string
].status === "running" && "animate-spin"
].status === ChatTaskStatus.RUNNING && "animate-spin"
}`}
/>
) : (

View file

@ -22,6 +22,7 @@ import { Bird, CodeXml, FileText, Globe, Image } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ChatTaskStatus } from '@/types/constants';
export default function Project() {
const { t } = useTranslation();
@ -214,9 +215,9 @@ export default function Project() {
action: type,
});
if (type === 'pause') {
chatStore.setStatus(taskId, 'pause');
chatStore.setStatus(taskId, ChatTaskStatus.PAUSE);
} else {
chatStore.setStatus(taskId, 'running');
chatStore.setStatus(taskId, ChatTaskStatus.RUNNING);
}
};

View file

@ -12,6 +12,7 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { ChatTaskStatus } from "@/types/constants";
import ChatBox from "@/components/ChatBox";
import Workflow from "@/components/WorkFlow";
import Folder from "@/components/Folder";
@ -88,7 +89,7 @@ export default function Home() {
// capture webview
const captureWebview = async () => {
const activeTask = chatStore.tasks[chatStore.activeTaskId as string];
if (!activeTask || activeTask.status === "finished") {
if (!activeTask || activeTask.status === ChatTaskStatus.FINISHED) {
return;
}
webviews.map((webview) => {

View file

@ -26,6 +26,14 @@ import {
import { showCreditsToast } from '@/components/Toast/creditsToast';
import { showStorageToast } from '@/components/Toast/storageToast';
import { generateUniqueId, uploadLog } from '@/lib';
import {
AgentMessageStatus,
AgentStatusValue,
AgentStep,
ChatTaskStatus,
TaskStatus,
type ChatTaskStatusType,
} from '@/types/constants';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { FileText } from 'lucide-react';
import { toast } from 'sonner';
@ -50,7 +58,7 @@ interface Task {
activeWorkSpace: string | null;
hasMessages: boolean;
activeAgent: string;
status: 'running' | 'finished' | 'pending' | 'pause';
status: ChatTaskStatusType;
taskTime: number;
elapsed: number;
tokens: number;
@ -77,10 +85,7 @@ export interface ChatStore {
create: (id?: string, type?: any) => string;
removeTask: (taskId: string) => void;
stopTask: (taskId: string) => void;
setStatus: (
taskId: string,
status: 'running' | 'finished' | 'pending' | 'pause'
) => void;
setStatus: (taskId: string, status: ChatTaskStatusType) => void;
setActiveTaskId: (taskId: string) => void;
replay: (taskId: string, question: string, time: number) => Promise<void>;
startTask: (
@ -251,7 +256,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
activeWorkSpace: 'workflow',
hasMessages: false,
activeAgent: '',
status: 'pending',
status: ChatTaskStatus.PENDING,
taskTime: 0,
tokens: 0,
elapsed: 0,
@ -275,7 +280,9 @@ const chatStore = (initial?: Partial<ChatStore>) =>
const { tasks, setProgressValue, activeTaskId } = get();
const taskRunning = [...tasks[taskId].taskRunning];
const finishedTask = taskRunning?.filter(
(task) => task.status === 'completed' || task.status === 'failed'
(task) =>
task.status === TaskStatus.COMPLETED ||
task.status === TaskStatus.FAILED
).length;
const taskProgress = (
((finishedTask || 0) / (taskRunning?.length || 0)) *
@ -380,7 +387,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
...state.tasks,
[taskId]: {
...state.tasks[taskId],
status: 'finished',
status: ChatTaskStatus.FINISHED,
},
},
};
@ -752,11 +759,12 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// - Task switching: confirmed, new_task_state, end
// - Multi-turn simple answer: wait_confirm
const isTaskSwitchingEvent =
agentMessages.step === 'confirmed' ||
agentMessages.step === 'new_task_state' ||
agentMessages.step === 'end';
agentMessages.step === AgentStep.CONFIRMED ||
agentMessages.step === AgentStep.NEW_TASK_STATE ||
agentMessages.step === AgentStep.END;
const isMultiTurnSimpleAnswer = agentMessages.step === 'wait_confirm';
const isMultiTurnSimpleAnswer =
agentMessages.step === AgentStep.WAIT_CONFIRM;
if (!currentTask) {
console.log(
@ -766,7 +774,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
}
if (
currentTask.status === 'finished' &&
currentTask.status === ChatTaskStatus.FINISHED &&
!isTaskSwitchingEvent &&
!isMultiTurnSimpleAnswer
) {
@ -795,7 +803,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
*/
let currentTaskId = getCurrentTaskId();
const previousChatStore = getCurrentChatStore();
if (agentMessages.step === 'confirmed') {
if (agentMessages.step === AgentStep.CONFIRMED) {
const { question } = agentMessages.data;
const shouldCreateNewChat =
project_id && (question || messageContent);
@ -890,7 +898,10 @@ const chatStore = (initial?: Partial<ChatStore>) =>
} else {
//NOTE: Triggered only with first "confirmed" in the project
//Handle Original cases - with old chatStore
previousChatStore.setStatus(currentTaskId, 'pending');
previousChatStore.setStatus(
currentTaskId,
ChatTaskStatus.PENDING
);
previousChatStore.setHasWaitComfirm(currentTaskId, false);
}
@ -940,8 +951,8 @@ const chatStore = (initial?: Partial<ChatStore>) =>
} = getCurrentChatStore();
currentTaskId = getCurrentTaskId();
// if (tasks[currentTaskId].status === 'finished') return
if (agentMessages.step === 'decompose_text') {
// if (tasks[currentTaskId].status === ChatTaskStatus.FINISHED) return
if (agentMessages.step === AgentStep.DECOMPOSE_TEXT) {
const { content } = agentMessages.data;
const text = content;
const currentId = getCurrentTaskId();
@ -992,7 +1003,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
if (agentMessages.step === 'to_sub_tasks') {
if (agentMessages.step === AgentStep.TO_SUB_TASKS) {
// Clear streaming decompose text when task splitting is done
clearStreamingDecomposeText(currentTaskId);
// Clean up TTFT tracking
@ -1001,18 +1012,20 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Check if task is already confirmed - don't overwrite user edits
const existingToSubTasksMessage = tasks[
currentTaskId
].messages.findLast((m: Message) => m.step === 'to_sub_tasks');
].messages.findLast(
(m: Message) => m.step === AgentStep.TO_SUB_TASKS
);
if (existingToSubTasksMessage?.isConfirm) {
return;
}
// Check if this is a multi-turn scenario after task completion
const isMultiTurnAfterCompletion =
tasks[currentTaskId].status === 'finished';
tasks[currentTaskId].status === ChatTaskStatus.FINISHED;
// Reset status for multi-turn complex tasks to allow splitting panel to show
if (isMultiTurnAfterCompletion) {
setStatus(currentTaskId, 'pending');
setStatus(currentTaskId, ChatTaskStatus.PENDING);
}
// Each splitting round starts in a clean editing state
@ -1020,7 +1033,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
const messages = [...tasks[currentTaskId].messages];
const toSubTaskIndex = messages.findLastIndex(
(message: Message) => message.step === 'to_sub_tasks'
(message: Message) => message.step === AgentStep.TO_SUB_TASKS
);
// For multi-turn scenarios, always create a new to_sub_tasks message
// even if one already exists from a previous task
@ -1044,7 +1057,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
const { tasks, handleConfirmTask, setIsTaskEdit } =
currentStore;
const message = tasks[currentId].messages.findLast(
(item) => item.step === 'to_sub_tasks'
(item) => item.step === AgentStep.TO_SUB_TASKS
);
const isConfirm = message?.isConfirm || false;
const isTakeControl = tasks[currentId].isTakeControl;
@ -1076,7 +1089,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
id: generateUniqueId(),
role: 'agent',
content: '',
step: 'notice_card',
step: AgentStep.NOTICE_CARD,
};
addMessages(currentTaskId, newNoticeMessage);
const shouldAutoConfirm = !!type && !isMultiTurnAfterCompletion;
@ -1102,7 +1115,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
}
agentMessages.data.sub_tasks = agentMessages.data.sub_tasks?.map(
(item) => {
item.status = '';
item.status = TaskStatus.EMPTY;
return item;
}
);
@ -1132,7 +1145,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
// Create agent
if (agentMessages.step === 'create_agent') {
if (agentMessages.step === AgentStep.CREATE_AGENT) {
const { agent_name, agent_id } = agentMessages.data;
if (!agent_name || !agent_id) return;
@ -1188,7 +1201,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
}
return;
}
if (agentMessages.step === 'wait_confirm') {
if (agentMessages.step === AgentStep.WAIT_CONFIRM) {
const { content, question } = agentMessages.data;
setHasWaitComfirm(currentTaskId, true);
setIsPending(currentTaskId, false);
@ -1213,7 +1226,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
id: generateUniqueId(),
role: 'user',
content: question as string,
step: 'wait_confirm',
step: AgentStep.WAIT_CONFIRM,
isConfirm: false,
});
}
@ -1221,13 +1234,13 @@ const chatStore = (initial?: Partial<ChatStore>) =>
id: generateUniqueId(),
role: 'agent',
content: content as string,
step: 'wait_confirm',
step: AgentStep.WAIT_CONFIRM,
isConfirm: false,
});
return;
}
// Task State
if (agentMessages.step === 'task_state') {
if (agentMessages.step === AgentStep.TASK_STATE) {
const { state, task_id, result, failure_count } =
agentMessages.data;
if (!state && !task_id) return;
@ -1247,7 +1260,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
targetTaskAssigningIndex
].tasks.findIndex((task: TaskInfo) => task.id === task_id);
taskAssigning[targetTaskAssigningIndex].tasks[taskIndex].status =
state === 'DONE' ? 'completed' : 'failed';
state === 'DONE' ? TaskStatus.COMPLETED : TaskStatus.FAILED;
taskAssigning[targetTaskAssigningIndex].tasks[
taskIndex
].failure_count = failure_count || 0;
@ -1288,7 +1301,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
id: generateUniqueId(),
role: 'agent',
content: targetResult,
step: 'failed',
step: AgentStep.FAILED,
});
}
}
@ -1296,7 +1309,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
if (targetTaskIndex !== -1) {
console.log('targetTaskIndex', targetTaskIndex, state);
taskRunning[targetTaskIndex].status =
state === 'DONE' ? 'completed' : 'failed';
state === 'DONE' ? TaskStatus.COMPLETED : TaskStatus.FAILED;
}
setTaskRunning(currentTaskId, taskRunning);
setTaskAssigning(currentTaskId, taskAssigning);
@ -1306,7 +1319,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
* @deprecated
* Side effect handled on top of the message handler
*/
if (agentMessages.step === 'new_task_state') {
if (agentMessages.step === AgentStep.NEW_TASK_STATE) {
const {
task_id,
content,
@ -1323,8 +1336,8 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Activate agent
if (
agentMessages.step === 'activate_agent' ||
agentMessages.step === 'deactivate_agent'
agentMessages.step === AgentStep.ACTIVATE_AGENT ||
agentMessages.step === AgentStep.DEACTIVATE_AGENT
) {
let taskAssigning = [...tasks[currentTaskId].taskAssigning];
let taskRunning = [...tasks[currentTaskId].taskRunning];
@ -1346,32 +1359,33 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// }
const message = filterMessage(agentMessages);
if (agentMessages.step === 'activate_agent') {
taskAssigning[agentIndex].status = 'running';
if (agentMessages.step === AgentStep.ACTIVATE_AGENT) {
taskAssigning[agentIndex].status = AgentStatusValue.RUNNING;
if (message) {
taskAssigning[agentIndex].log.push({
...agentMessages,
status: 'running',
status: AgentMessageStatus.RUNNING,
});
}
const taskIndex = taskRunning.findIndex(
(task) => task.id === process_task_id
);
if (taskIndex !== -1 && taskRunning![taskIndex].status) {
taskRunning![taskIndex].agent!.status = 'running';
taskRunning![taskIndex]!.status = 'running';
taskRunning![taskIndex].agent!.status =
AgentStatusValue.RUNNING;
taskRunning![taskIndex]!.status = TaskStatus.RUNNING;
const task = taskAssigning[agentIndex].tasks.find(
(task: TaskInfo) => task.id === process_task_id
);
if (task) {
task.status = 'running';
task.status = TaskStatus.RUNNING;
}
}
setTaskRunning(currentTaskId, [...taskRunning]);
setTaskAssigning(currentTaskId, [...taskAssigning]);
}
if (agentMessages.step === 'deactivate_agent') {
if (agentMessages.step === AgentStep.DEACTIVATE_AGENT) {
if (message) {
const index = taskAssigning[agentIndex].log.findLastIndex(
(log) =>
@ -1379,7 +1393,8 @@ const chatStore = (initial?: Partial<ChatStore>) =>
log.data.toolkit_name === agentMessages.data.toolkit_name
);
if (index != -1) {
taskAssigning[agentIndex].log[index].status = 'completed';
taskAssigning[agentIndex].log[index].status =
AgentMessageStatus.COMPLETED;
setTaskAssigning(currentTaskId, [...taskAssigning]);
}
}
@ -1405,7 +1420,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
// Assign task
if (agentMessages.step === 'assign_task') {
if (agentMessages.step === AgentStep.ASSIGN_TASK) {
if (
!agentMessages.data?.assignee_id ||
!agentMessages.data?.task_id
@ -1475,21 +1490,25 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Clear logs from the assignee agent that are related to this task
// This prevents logs from previous attempts appearing in the reassigned task
// This needs to happen whether it's a reassignment to a different agent or a retry with the same agent
if (taskState !== 'waiting' && failure_count && failure_count > 0) {
if (
taskState !== TaskStatus.WAITING &&
failure_count &&
failure_count > 0
) {
taskAssigning[assigneeAgentIndex].log = taskAssigning[
assigneeAgentIndex
].log.filter((log) => log.data.process_task_id !== task_id);
}
// Handle task assignment to taskAssigning based on state
if (taskState === 'waiting') {
if (taskState === TaskStatus.WAITING) {
if (
!taskAssigning[assigneeAgentIndex].tasks.find(
(item) => item.id === task_id
)
) {
taskAssigning[assigneeAgentIndex].tasks.push(
task ?? { id: task_id, content, status: 'waiting' }
task ?? { id: task_id, content, status: TaskStatus.WAITING }
);
}
setTaskAssigning(currentTaskId, [...taskAssigning]);
@ -1505,7 +1524,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Task already exists, update its status
taskAssigning[assigneeAgentIndex].tasks[
existingTaskIndex
].status = 'running';
].status = TaskStatus.RUNNING;
if (failure_count !== 0) {
taskAssigning[assigneeAgentIndex].tasks[
existingTaskIndex
@ -1517,12 +1536,16 @@ const chatStore = (initial?: Partial<ChatStore>) =>
if (task) {
taskTemp = JSON.parse(JSON.stringify(task));
taskTemp.failure_count = 0;
taskTemp.status = 'running';
taskTemp.status = TaskStatus.RUNNING;
taskTemp.toolkits = [];
taskTemp.report = '';
}
taskAssigning[assigneeAgentIndex].tasks.push(
taskTemp ?? { id: task_id, content, status: 'running' }
taskTemp ?? {
id: task_id,
content,
status: TaskStatus.RUNNING,
}
);
}
}
@ -1531,13 +1554,19 @@ const chatStore = (initial?: Partial<ChatStore>) =>
if (taskRunningIndex === -1) {
// Task not in taskRunning, add it
if (task) {
task.status = taskState === 'waiting' ? 'waiting' : 'running';
task.status =
taskState === TaskStatus.WAITING
? TaskStatus.WAITING
: TaskStatus.RUNNING;
}
taskRunning!.push(
task ?? {
id: task_id,
content,
status: taskState === 'waiting' ? 'waiting' : 'running',
status:
taskState === TaskStatus.WAITING
? TaskStatus.WAITING
: TaskStatus.RUNNING,
agent: JSON.parse(JSON.stringify(taskAgent)),
}
);
@ -1545,7 +1574,10 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Task already in taskRunning, update it
taskRunning![taskRunningIndex] = {
...taskRunning![taskRunningIndex],
status: taskState === 'waiting' ? 'waiting' : 'running',
status:
taskState === TaskStatus.WAITING
? TaskStatus.WAITING
: TaskStatus.RUNNING,
agent: JSON.parse(JSON.stringify(taskAgent)),
};
}
@ -1555,7 +1587,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
// Activate Toolkit
if (agentMessages.step === 'activate_toolkit') {
if (agentMessages.step === AgentStep.ACTIVATE_TOOLKIT) {
// add log
let taskAssigning = [...tasks[currentTaskId].taskAssigning];
const resolvedProcessTaskId = resolveProcessTaskIdForToolkitEvent(
@ -1654,7 +1686,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
toolkitName: toolkit_name,
toolkitMethods: method_name,
message: normalizeToolkitMessage(message.data.message),
toolkitStatus: 'running' as AgentStatus,
toolkitStatus: AgentStatusValue.RUNNING,
};
// Update taskAssigning if we found the agent
@ -1665,13 +1697,13 @@ const chatStore = (initial?: Partial<ChatStore>) =>
if (task) {
task.toolkits ??= [];
task.toolkits.push({ ...toolkit });
task.status = 'running';
task.status = TaskStatus.RUNNING;
setTaskAssigning(currentTaskId, [...taskAssigning]);
}
}
// Always update taskRunning (even if assigneeAgentIndex is -1)
taskRunning![taskIndex].status = 'running';
taskRunning![taskIndex].status = TaskStatus.RUNNING;
taskRunning![taskIndex].toolkits ??= [];
taskRunning![taskIndex].toolkits.push({ ...toolkit });
}
@ -1681,7 +1713,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
// Deactivate Toolkit
if (agentMessages.step === 'deactivate_toolkit') {
if (agentMessages.step === AgentStep.DEACTIVATE_TOOLKIT) {
// add log
let taskAssigning = [...tasks[currentTaskId].taskAssigning];
const resolvedProcessTaskId = resolveProcessTaskIdForToolkitEvent(
@ -1709,14 +1741,15 @@ const chatStore = (initial?: Partial<ChatStore>) =>
toolkit.toolkitName === agentMessages.data.toolkit_name &&
toolkit.toolkitMethods ===
agentMessages.data.method_name &&
toolkit.toolkitStatus === 'running'
toolkit.toolkitStatus === AgentStatusValue.RUNNING
);
});
if (task.toolkits && index !== -1 && index !== undefined) {
task.toolkits[index].message =
`${normalizeToolkitMessage(task.toolkits[index].message)}\n${normalizeToolkitMessage(message.data.message)}`.trim();
task.toolkits[index].toolkitStatus = 'completed';
task.toolkits[index].toolkitStatus =
AgentStatusValue.COMPLETED;
}
// task.toolkits?.unshift({
// toolkitName: agentMessages.data.toolkit_name as string,
@ -1756,7 +1789,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
message: normalizeToolkitMessage(
targetMessage.data.message
),
toolkitStatus: 'completed',
toolkitStatus: AgentStatusValue.COMPLETED,
});
}
}
@ -1766,7 +1799,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
// Terminal
if (agentMessages.step === 'terminal') {
if (agentMessages.step === AgentStep.TERMINAL) {
addTerminal(
currentTaskId,
agentMessages.data.process_task_id as string,
@ -1775,7 +1808,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
// Write File
if (agentMessages.step === 'write_file') {
if (agentMessages.step === AgentStep.WRITE_FILE) {
console.log('write_to_file', agentMessages.data);
setNuwFileNum(currentTaskId, tasks[currentTaskId].nuwFileNum + 1);
const { file_path } = agentMessages.data;
@ -1796,15 +1829,15 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
if (agentMessages.step === 'budget_not_enough') {
if (agentMessages.step === AgentStep.BUDGET_NOT_ENOUGH) {
console.log('error', agentMessages.data);
showCreditsToast();
setStatus(currentTaskId, 'pause');
setStatus(currentTaskId, ChatTaskStatus.PAUSE);
uploadLog(currentTaskId, type);
return;
}
if (agentMessages.step === 'context_too_long') {
if (agentMessages.step === AgentStep.CONTEXT_TOO_LONG) {
console.error('Context too long:', agentMessages.data);
const currentLength = agentMessages.data.current_length || 0;
const maxLength = agentMessages.data.max_length || 100000;
@ -1821,12 +1854,12 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Set flag to block input and set status to pause
setIsContextExceeded(currentTaskId, true);
setStatus(currentTaskId, 'pause');
setStatus(currentTaskId, ChatTaskStatus.PAUSE);
uploadLog(currentTaskId, type);
return;
}
if (agentMessages.step === 'error') {
if (agentMessages.step === AgentStep.ERROR) {
try {
console.error('Model error:', agentMessages.data);
@ -1852,8 +1885,11 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Update taskRunning - mark non-completed tasks as failed
taskRunning = taskRunning.map((task) => {
if (task.status !== 'completed' && task.status !== 'failed') {
task.status = 'failed';
if (
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.FAILED
) {
task.status = TaskStatus.FAILED;
}
return task;
});
@ -1861,8 +1897,11 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// Update taskAssigning - mark non-completed tasks as failed
taskAssigning = taskAssigning.map((agent) => {
agent.tasks = agent.tasks.map((task) => {
if (task.status !== 'completed' && task.status !== 'failed') {
task.status = 'failed';
if (
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.FAILED
) {
task.status = TaskStatus.FAILED;
}
return task;
});
@ -1874,7 +1913,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
setTaskAssigning(currentTaskId, taskAssigning);
// Complete the current task with error status
setStatus(currentTaskId, 'finished');
setStatus(currentTaskId, ChatTaskStatus.FINISHED);
setIsPending(currentTaskId, false);
// Add error message to the current task
@ -1927,7 +1966,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
}
// Handle add_task events for project store
if (agentMessages.step === 'add_task') {
if (agentMessages.step === AgentStep.ADD_TASK) {
try {
const taskData = agentMessages.data;
if (taskData && taskData.project_id && taskData.content) {
@ -1964,7 +2003,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
}
// Handle remove_task events for project store
if (agentMessages.step === 'remove_task') {
if (agentMessages.step === AgentStep.REMOVE_TASK) {
try {
const taskIdToRemove = agentMessages.data.task_id as string;
if (taskIdToRemove) {
@ -1999,7 +2038,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
if (agentMessages.step === 'end') {
if (agentMessages.step === AgentStep.END) {
// compute task time
console.log(
'tasks[taskId].snapshotsTemp',
@ -2107,11 +2146,11 @@ const chatStore = (initial?: Partial<ChatStore>) =>
taskAssigning = taskAssigning.map((agent) => {
agent.tasks = agent.tasks.map((task) => {
if (
task.status !== 'completed' &&
task.status !== 'failed' &&
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.FAILED &&
!type
) {
task.status = 'skipped';
task.status = TaskStatus.SKIPPED;
}
return task;
});
@ -2121,11 +2160,11 @@ const chatStore = (initial?: Partial<ChatStore>) =>
taskRunning = taskRunning.map((task) => {
console.log('task.status', task.status);
if (
task.status !== 'completed' &&
task.status !== 'failed' &&
task.status !== TaskStatus.COMPLETED &&
task.status !== TaskStatus.FAILED &&
!type
) {
task.status = 'skipped';
task.status = TaskStatus.SKIPPED;
}
return task;
});
@ -2158,7 +2197,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
let summary = endMessage.match(/<summary>(.*?)<\/summary>/)?.[1];
let newMessage: Message | null = null;
const agent_summary_end = tasks[currentTaskId].messages.findLast(
(message: Message) => message.step === 'agent_summary_end'
(message: Message) => message.step === AgentStep.AGENT_SUMMARY_END
);
console.log('summary', summary);
if (summary) {
@ -2181,7 +2220,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
addMessages(currentTaskId, newMessage);
setIsPending(currentTaskId, false);
setStatus(currentTaskId, 'finished');
setStatus(currentTaskId, ChatTaskStatus.FINISHED);
// completed tasks move to history
setUpdateCount();
@ -2189,7 +2228,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
return;
}
if (agentMessages.step === 'notice') {
if (agentMessages.step === AgentStep.NOTICE) {
if (agentMessages.data.process_task_id !== '') {
let taskAssigning = [...tasks[currentTaskId].taskAssigning];
@ -2209,7 +2248,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
toolkitName: 'notice',
toolkitMethods: '',
message: agentMessages.data.notice as string,
toolkitStatus: 'running' as AgentStatus,
toolkitStatus: AgentStatusValue.RUNNING,
};
if (assigneeAgentIndex !== -1 && task) {
task.toolkits ??= [];
@ -2219,14 +2258,14 @@ const chatStore = (initial?: Partial<ChatStore>) =>
} else {
const messages = [...tasks[currentTaskId].messages];
const noticeCardIndex = messages.findLastIndex(
(message) => message.step === 'notice_card'
(message) => message.step === AgentStep.NOTICE_CARD
);
if (noticeCardIndex === -1) {
const newMessage: Message = {
id: generateUniqueId(),
role: 'agent',
content: '',
step: 'notice_card',
step: AgentStep.NOTICE_CARD,
};
addMessages(currentTaskId, newMessage);
}
@ -2237,8 +2276,8 @@ const chatStore = (initial?: Partial<ChatStore>) =>
}
return;
}
if (['sync'].includes(agentMessages.step)) return;
if (agentMessages.step === 'ask') {
if (agentMessages.step === AgentStep.SYNC) return;
if (agentMessages.step === AgentStep.ASK) {
if (tasks[currentTaskId].activeAsk != '') {
const newMessage: Message = {
id: generateUniqueId(),
@ -2568,10 +2607,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
},
}));
},
setStatus(
taskId: string,
status: 'running' | 'finished' | 'pending' | 'pause'
) {
setStatus(taskId: string, status: ChatTaskStatusType) {
set((state) => ({
...state,
tasks: {
@ -2630,7 +2666,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
// where backend sends to_sub_tasks SSE event before we mark task as confirmed
let messages = [...tasks[taskId].messages];
const cardTaskIndex = messages.findLastIndex(
(message) => message.step === 'to_sub_tasks'
(message) => message.step === AgentStep.TO_SUB_TASKS
);
if (cardTaskIndex !== -1) {
messages[cardTaskIndex] = {
@ -2648,7 +2684,7 @@ const chatStore = (initial?: Partial<ChatStore>) =>
await fetchPost(`/task/${project_id}/start`, {});
setActiveWorkSpace(taskId, 'workflow');
setStatus(taskId, 'running');
setStatus(taskId, ChatTaskStatus.RUNNING);
}
// Reset editing state after manual confirmation so next round can auto-start

View file

@ -15,6 +15,7 @@
import { generateUniqueId } from '@/lib';
import { create } from 'zustand';
import { createChatStoreInstance, VanillaChatStore } from './chatStore';
import { ChatTaskStatus } from '@/types/constants';
export enum ProjectType {
NORMAL = 'normal',
@ -173,7 +174,7 @@ const isEmptyProject = (project: Project): boolean => {
task.summaryTask === '' &&
task.progressValue === 0 &&
task.isPending === false &&
task.status === 'pending' &&
task.status === ChatTaskStatus.PENDING &&
task.taskTime === 0 &&
task.tokens === 0 &&
task.elapsed === 0 &&

View file

@ -12,6 +12,8 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import type { AgentStepType, AgentMessageStatusType, TaskStatusType, ChatTaskStatusType, AgentStatusType } from './constants';
// Global type definitions for ChatBox component
declare global {
@ -40,7 +42,7 @@ declare global {
report?: string | undefined;
id: string;
content: string;
status?: string;
status?: TaskStatusType;
agent?: Agent;
terminal?: string[];
fileList?: FileInfo[];
@ -60,7 +62,7 @@ declare global {
filePath: string;
}
type AgentStatus = 'pending' | 'running' | 'completed' | 'failed';
type AgentStatus = AgentStatusType;
interface ActiveWebView {
id: string;
@ -92,7 +94,7 @@ declare global {
id: string;
role: 'user' | 'agent';
content: string;
step?: string;
step?: AgentStepType;
agent_id?: string;
isConfirm?: boolean;
taskType?: 1 | 2 | 3;
@ -110,7 +112,7 @@ declare global {
}
interface AgentMessage {
step: string;
step: AgentStepType;
data: {
project_id?: string;
failure_count?: number;
@ -140,7 +142,7 @@ declare global {
max_length?: number;
text?: string;
};
status?: 'running' | 'filled' | 'completed';
status?: AgentMessageStatusType;
}
type AgentNameType =

97
src/types/constants.ts Normal file
View file

@ -0,0 +1,97 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
/**
* SSE step values received from the backend in AgentMessage.step.
*/
export const AgentStep = {
CONFIRMED: 'confirmed',
NEW_TASK_STATE: 'new_task_state',
END: 'end',
WAIT_CONFIRM: 'wait_confirm',
DECOMPOSE_TEXT: 'decompose_text',
TO_SUB_TASKS: 'to_sub_tasks',
CREATE_AGENT: 'create_agent',
TASK_STATE: 'task_state',
ACTIVATE_AGENT: 'activate_agent',
DEACTIVATE_AGENT: 'deactivate_agent',
ASSIGN_TASK: 'assign_task',
ACTIVATE_TOOLKIT: 'activate_toolkit',
DEACTIVATE_TOOLKIT: 'deactivate_toolkit',
TERMINAL: 'terminal',
WRITE_FILE: 'write_file',
BUDGET_NOT_ENOUGH: 'budget_not_enough',
CONTEXT_TOO_LONG: 'context_too_long',
ERROR: 'error',
ADD_TASK: 'add_task',
REMOVE_TASK: 'remove_task',
NOTICE: 'notice',
ASK: 'ask',
SYNC: 'sync',
NOTICE_CARD: 'notice_card',
FAILED: 'failed',
AGENT_SUMMARY_END: 'agent_summary_end',
} as const;
export type AgentStepType = (typeof AgentStep)[keyof typeof AgentStep];
/**
* Status values on AgentMessage.status (SSE message lifecycle).
*/
export const AgentMessageStatus = {
RUNNING: 'running',
FILLED: 'filled',
COMPLETED: 'completed',
} as const;
export type AgentMessageStatusType = (typeof AgentMessageStatus)[keyof typeof AgentMessageStatus];
/**
* Status values for TaskInfo (individual sub-task progress).
*/
export const TaskStatus = {
COMPLETED: 'completed',
FAILED: 'failed',
SKIPPED: 'skipped',
WAITING: 'waiting',
RUNNING: 'running',
BLOCKED: 'blocked',
EMPTY: '',
} as const;
export type TaskStatusType = (typeof TaskStatus)[keyof typeof TaskStatus];
/**
* Top-level task status in the ChatStore Task interface.
*/
export const ChatTaskStatus = {
RUNNING: 'running',
FINISHED: 'finished',
PENDING: 'pending',
PAUSE: 'pause',
} as const;
export type ChatTaskStatusType = (typeof ChatTaskStatus)[keyof typeof ChatTaskStatus];
/**
* Status values for individual agent lifecycle (toolkit operations, agent progress).
*/
export const AgentStatusValue = {
PENDING: 'pending',
RUNNING: 'running',
COMPLETED: 'completed',
FAILED: 'failed',
} as const;
export type AgentStatusType = (typeof AgentStatusValue)[keyof typeof AgentStatusValue];