mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-21 18:37:33 +02:00
修复多会话任务栏抖动、会话跳回与停止失效 (#264)
* fix(web): stabilize active task ordering * fix(chat): preserve navigation during conversation startup * fix(tasks): guarantee hard cancellation * fix(chat): bind navigation and hard stop targets * fix(chat): prevent replay from reclaiming navigation
This commit is contained in:
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -484,7 +485,10 @@ func (m *AgentTaskManager) CancelTask(conversationID string, cause error) (bool,
|
||||
if runtimeCancel != nil {
|
||||
runtimeHandled = runtimeCancel(cause)
|
||||
}
|
||||
if cancel != nil && !runtimeHandled {
|
||||
// 「彻底停止」必须同时取消宿主 context:原生 Agent Cancel 即使已受理,
|
||||
// 也可能只在安全点返回或报告超时,不能据此让整条任务继续存活。
|
||||
// 中断并继续仍保留原语义:原生取消已处理时由运行时负责恢复。
|
||||
if cancel != nil && (!runtimeHandled || errors.Is(cause, ErrTaskCancelled)) {
|
||||
cancel(cause)
|
||||
}
|
||||
if toolCanceler != nil {
|
||||
@@ -591,6 +595,12 @@ func (m *AgentTaskManager) GetActiveTasks() []*AgentTask {
|
||||
Status: task.Status,
|
||||
})
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
if result[i].StartedAt.Equal(result[j].StartedAt) {
|
||||
return result[i].ConversationID < result[j].ConversationID
|
||||
}
|
||||
return result[i].StartedAt.Before(result[j].StartedAt)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetActiveTasksUsesStableCreationOrder(t *testing.T) {
|
||||
m := NewAgentTaskManager()
|
||||
started := time.Date(2026, 8, 19, 10, 0, 0, 0, time.UTC)
|
||||
m.mu.Lock()
|
||||
m.tasks = map[string]*AgentTask{
|
||||
"conversation-z": {ConversationID: "conversation-z", StartedAt: started, Status: "running"},
|
||||
"conversation-late": {ConversationID: "conversation-late", StartedAt: started.Add(time.Minute), Status: "running"},
|
||||
"conversation-a": {ConversationID: "conversation-a", StartedAt: started, Status: "running"},
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
want := []string{"conversation-a", "conversation-z", "conversation-late"}
|
||||
for attempt := 0; attempt < 20; attempt++ {
|
||||
gotTasks := m.GetActiveTasks()
|
||||
if len(gotTasks) != len(want) {
|
||||
t.Fatalf("GetActiveTasks() length = %d, want %d", len(gotTasks), len(want))
|
||||
}
|
||||
for i, task := range gotTasks {
|
||||
if task.ConversationID != want[i] {
|
||||
t.Fatalf("attempt %d order[%d] = %q, want %q", attempt, i, task.ConversationID, want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func TestCancelTaskInvokesToolCancelerOnFullStop(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelTaskUsesAgentRuntimeCancelAsPrimaryPath(t *testing.T) {
|
||||
func TestCancelTaskFullStopCancelsRuntimeAndParentContext(t *testing.T) {
|
||||
tm := NewAgentTaskManager()
|
||||
var order []string
|
||||
tm.SetToolCanceler(func(conversationID string) {
|
||||
@@ -61,7 +61,7 @@ func TestCancelTaskUsesAgentRuntimeCancelAsPrimaryPath(t *testing.T) {
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("CancelTask: ok=%v err=%v", ok, err)
|
||||
}
|
||||
want := []string{"runtime", "tool"}
|
||||
want := []string{"runtime", "context", "tool"}
|
||||
if len(order) != len(want) {
|
||||
t.Fatalf("order length got %d want %d: %#v", len(order), len(want), order)
|
||||
}
|
||||
@@ -72,6 +72,29 @@ func TestCancelTaskUsesAgentRuntimeCancelAsPrimaryPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelTaskInterruptContinueKeepsParentWhenRuntimeHandlesIt(t *testing.T) {
|
||||
tm := NewAgentTaskManager()
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
if _, err := tm.StartTask("conv-interrupt-native", "hello", cancel); err != nil {
|
||||
t.Fatalf("StartTask: %v", err)
|
||||
}
|
||||
unregister := tm.BindAgentRuntimeCancel("conv-interrupt-native", func(err error) bool {
|
||||
if !errors.Is(err, multiagent.ErrInterruptContinue) {
|
||||
t.Fatalf("runtime cancel got %v", err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
defer unregister()
|
||||
|
||||
ok, err := tm.CancelTask("conv-interrupt-native", multiagent.ErrInterruptContinue)
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("CancelTask: ok=%v err=%v", ok, err)
|
||||
}
|
||||
if cause := context.Cause(ctx); cause != nil {
|
||||
t.Fatalf("interrupt-continue parent context cause = %v, want nil", cause)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelTaskFallsBackToContextWhenAgentRuntimeCancelMisses(t *testing.T) {
|
||||
tm := NewAgentTaskManager()
|
||||
var order []string
|
||||
|
||||
Reference in New Issue
Block a user