fix(server): complete generator task when thread settles or project ready
Treat a generator task as completed when it produces artifacts, when a task message reaches a terminal state, or when the project is ready — even if the project is still exploring another concurrent thread. Adds generatorTaskMessageCompleted to classify terminal task messages and covers ready-without-messages and concurrent-thread cases with tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -384,7 +384,7 @@ func generatorTaskMetadataFromMessages(messages []design.Message, taskID string)
|
|||||||
func generatorTaskStatus(project design.Project, taskID string) string {
|
func generatorTaskStatus(project design.Project, taskID string) string {
|
||||||
messages := project.Messages
|
messages := project.Messages
|
||||||
status := "submitted"
|
status := "submitted"
|
||||||
completed := false
|
completed := len(generatorTaskArtifacts(messages, taskID)) > 0
|
||||||
for _, message := range messages {
|
for _, message := range messages {
|
||||||
if message.ThreadID != taskID {
|
if message.ThreadID != taskID {
|
||||||
continue
|
continue
|
||||||
@@ -392,9 +392,11 @@ func generatorTaskStatus(project design.Project, taskID string) string {
|
|||||||
if message.Role == "error" || message.Status == "failed" {
|
if message.Role == "error" || message.Status == "failed" {
|
||||||
return "failed"
|
return "failed"
|
||||||
}
|
}
|
||||||
|
if generatorTaskMessageCompleted(message) {
|
||||||
|
completed = true
|
||||||
|
}
|
||||||
switch message.Status {
|
switch message.Status {
|
||||||
case "success", "cancelled":
|
case "success", "cancelled":
|
||||||
completed = true
|
|
||||||
case "running", "streaming", "submitted":
|
case "running", "streaming", "submitted":
|
||||||
status = "submitted"
|
status = "submitted"
|
||||||
}
|
}
|
||||||
@@ -405,15 +407,30 @@ func generatorTaskStatus(project design.Project, taskID string) string {
|
|||||||
if project.Status == design.StatusFailed {
|
if project.Status == design.StatusFailed {
|
||||||
return "failed"
|
return "failed"
|
||||||
}
|
}
|
||||||
if project.Status == design.StatusExploring {
|
if completed || project.Status == design.StatusReady {
|
||||||
return status
|
|
||||||
}
|
|
||||||
if len(generatorTaskArtifacts(messages, taskID)) > 0 || completed {
|
|
||||||
return "completed"
|
return "completed"
|
||||||
}
|
}
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generatorTaskMessageCompleted(message design.Message) bool {
|
||||||
|
status := strings.ToLower(strings.TrimSpace(message.Status))
|
||||||
|
if status != "success" && status != "cancelled" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
name := strings.TrimSpace(message.Name)
|
||||||
|
if name == "agent_planner" || name == "generator_task" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if isToolArtifactsMessage(message) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return name == "canvas_action" ||
|
||||||
|
name == "text_extraction" ||
|
||||||
|
name == "mockup_model" ||
|
||||||
|
strings.TrimSpace(message.Role) == "assistant"
|
||||||
|
}
|
||||||
|
|
||||||
func generatorTaskHasActiveCanvasWork(project design.Project, taskID string) bool {
|
func generatorTaskHasActiveCanvasWork(project design.Project, taskID string) bool {
|
||||||
taskID = strings.TrimSpace(taskID)
|
taskID = strings.TrimSpace(taskID)
|
||||||
lastThreadID := strings.TrimSpace(project.LastThreadID)
|
lastThreadID := strings.TrimSpace(project.LastThreadID)
|
||||||
|
|||||||
@@ -114,3 +114,77 @@ func TestGeneratorTaskStatusCompletesWhenCanvasSettles(t *testing.T) {
|
|||||||
t.Fatalf("expected settled canvas task to complete, got %s", task.Status)
|
t.Fatalf("expected settled canvas task to complete, got %s", task.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGeneratorTaskStatusCompletesReadyProjectWithoutTaskMessages(t *testing.T) {
|
||||||
|
threadID := "thread-without-messages"
|
||||||
|
project := design.Project{
|
||||||
|
ID: "project-ready-without-messages",
|
||||||
|
LastThreadID: threadID,
|
||||||
|
Status: design.StatusReady,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
Nodes: []design.Node{
|
||||||
|
{
|
||||||
|
ID: "image-1",
|
||||||
|
Type: design.NodeTypeImage,
|
||||||
|
Title: "Generated image",
|
||||||
|
Content: "http://localhost:19000/canvas-assets/done.png",
|
||||||
|
Status: "success",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
task := generatorTaskFromProject(project, threadID)
|
||||||
|
if task.Status != "completed" {
|
||||||
|
t.Fatalf("expected ready project without task messages to complete, got %s", task.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneratorTaskStatusCompletesFinishedThreadWhileProjectStillExploring(t *testing.T) {
|
||||||
|
threadID := "thread-done"
|
||||||
|
activeThreadID := "thread-active"
|
||||||
|
now := time.Now()
|
||||||
|
project := design.Project{
|
||||||
|
ID: "project-concurrent",
|
||||||
|
LastThreadID: activeThreadID,
|
||||||
|
Status: design.StatusExploring,
|
||||||
|
UpdatedAt: now,
|
||||||
|
Nodes: []design.Node{
|
||||||
|
{
|
||||||
|
ID: "done-image",
|
||||||
|
Type: design.NodeTypeImage,
|
||||||
|
Title: "Done image",
|
||||||
|
Content: "http://localhost:19000/canvas-assets/done.png",
|
||||||
|
Status: "success",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "active-image",
|
||||||
|
Type: design.NodeTypeImage,
|
||||||
|
Title: "Active image",
|
||||||
|
Content: "pending prompt",
|
||||||
|
Status: "generating",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Messages: []design.Message{
|
||||||
|
generatorTaskMetadataMessage(threadID, removeBackgroundGeneratorName, design.GeneratorTaskInputArgs{}, 4, now.Add(-2*time.Second)),
|
||||||
|
{ID: "user", Role: "user", Type: "user", Name: "canvas_action", Status: "running", ThreadID: threadID},
|
||||||
|
toolArtifactsMessage(threadID, "remove background", []design.Node{
|
||||||
|
{
|
||||||
|
ID: "done-image",
|
||||||
|
Type: design.NodeTypeImage,
|
||||||
|
Title: "Done image",
|
||||||
|
Content: "http://localhost:19000/canvas-assets/done.png",
|
||||||
|
Status: "success",
|
||||||
|
Width: 1024,
|
||||||
|
Height: 1024,
|
||||||
|
},
|
||||||
|
}, now.Add(-time.Second)),
|
||||||
|
{ID: "assistant", Role: "assistant", Type: "assistant", Name: "canvas_action", Status: "success", ThreadID: threadID},
|
||||||
|
{ID: "active-tool", Role: "tool", Type: "tool", Title: "GPT Image 2", Status: "running", ThreadID: activeThreadID},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
task := generatorTaskFromProject(project, threadID)
|
||||||
|
if task.Status != "completed" {
|
||||||
|
t.Fatalf("expected finished thread to complete while another task runs, got %s", task.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user