mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-28 09:13:49 +08:00
任务页新建任务左侧加「并发设置」齿轮:开关(默认关) + 并发数量(开启默认5)。 开启后新建任务若「运行中」数已达上限,进入「排队中」不启动;有空位时后台 每 5s 自动把最早排队的任务补位启动。 - db: tasks 加持久化 queued 列(schema + ALTER 补旧库);Task.Queued/SetQueued。 - manager: ConcurrencyLimit/SetConcurrency(settings KV);SetTaskQueued(落库+同步内存)。 - 准入与促进器(goals.go):launchTask 统一准入,满则挂起排队;抽出 startTaskEngine 复用;occupiesSlot/runningTaskCount/queueIfAtCapacity;reconcileConcurrency 每 scheduler tick 补位(关闭则放行全部排队),concMu 串行化防超发;启动重载/setActive/ reviveTask 三处'排队则不启动'门。 - settings: /api/settings 增 task_concurrency_enabled/limit;PUT 后立即协调一次。 - web: 齿轮 Dialog(Switch+数量);新增 queued/「排队中」状态(类型/状态色/筛选)。 - 超限只排队新任务,不打断在跑的;排队任务不消耗 LLM(目标拆解推迟到出队)。 go build/vet、tsc 通过;DB queued 往返测试通过。
78 lines
1.9 KiB
Go
78 lines
1.9 KiB
Go
package db
|
|
|
|
import "testing"
|
|
|
|
func TestTaskLifecycleAndDeleteCascade(t *testing.T) {
|
|
d, err := Open(testDSN(t))
|
|
if err != nil {
|
|
t.Skipf("postgres unavailable (%v) — skipping", err)
|
|
}
|
|
defer d.Close()
|
|
|
|
tk, err := d.CreateTask("迁移测试", "目标X", nil, 0, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// populate the exploration subgraph
|
|
es := d.Exploration(tk.ExplorationID)
|
|
if _, err := es.AddIntent(map[string]any{"summary": "x"}, 5, nil, "planner"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// pause + status
|
|
if err := d.SetPaused(tk.ID, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := d.GetTask(tk.ID)
|
|
if err != nil || got == nil || !got.Paused {
|
|
t.Fatalf("paused not persisted: %+v err=%v", got, err)
|
|
}
|
|
if got.Queued {
|
|
t.Fatalf("new task should not be queued: %+v", got)
|
|
}
|
|
|
|
// queued (concurrency-hold) flag round-trips independently of paused
|
|
if err := d.SetQueued(tk.ID, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if g, _ := d.GetTask(tk.ID); g == nil || !g.Queued {
|
|
t.Fatalf("queued not persisted: %+v", g)
|
|
}
|
|
if err := d.SetQueued(tk.ID, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if g, _ := d.GetTask(tk.ID); g == nil || g.Queued {
|
|
t.Fatalf("queued not cleared: %+v", g)
|
|
}
|
|
|
|
// list contains it
|
|
list, _ := d.ListTasks()
|
|
found := false
|
|
for _, x := range list {
|
|
if x.ID == tk.ID {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("task not in list")
|
|
}
|
|
|
|
// delete cascades exploration subgraph
|
|
if err := d.DeleteTask(tk.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if g, _ := d.GetTask(tk.ID); g != nil {
|
|
t.Fatalf("task should be gone")
|
|
}
|
|
var nodes int
|
|
d.QueryRow(`SELECT count(*) FROM exploration_nodes WHERE exploration_id=$1`, tk.ExplorationID).Scan(&nodes)
|
|
if nodes != 0 {
|
|
t.Fatalf("exploration nodes should be cascade-deleted, got %d", nodes)
|
|
}
|
|
var exps int
|
|
d.QueryRow(`SELECT count(*) FROM explorations WHERE id=$1`, tk.ExplorationID).Scan(&exps)
|
|
if exps != 0 {
|
|
t.Fatalf("exploration should be deleted, got %d", exps)
|
|
}
|
|
}
|