Version: 0.9.75.dev.260505

后端:
1.收口阶段 6 agent 结构迁移,将 newAgent 内核与 agentsvc 编排层迁入 services/agent
- 切换 Agent 启动装配与 HTTP handler 直连 agent sv,移除旧 service agent bridge
- 补齐 Agent 对 memory、task、task-class、schedule 的 RPC 适配与契约字段
- 扩展 schedule、task、task-class RPC/contract 支撑 Agent 查询、写入与 provider 切流
- 更新迁移文档、README 与相关注释,明确 agent 当前切流点和剩余 memory 迁移面
This commit is contained in:
Losita
2026-05-05 16:00:57 +08:00
parent e1819c5653
commit d7184b776b
174 changed files with 2189 additions and 1236 deletions

View File

@@ -0,0 +1,184 @@
package schedule
import "fmt"
// validateLocalOrderForSinglePlacement 校验单个任务落到目标时段后,是否仍满足同任务类内部顺序约束。
//
// 职责边界:
// 1. 只负责“同任务类内部顺序”这一条规则,不负责冲突、锁定、范围合法性;
// 2. 采用“克隆态 + 假设落位”方式校验,避免直接污染真实 state
// 3. 若任务不属于 task_item / 缺少 task_order / 当前无边界约束,直接放行。
func validateLocalOrderForSinglePlacement(state *ScheduleState, taskID int, targetSlots []TaskSlot) error {
if len(targetSlots) == 0 {
return nil
}
return validateLocalOrderBatchPlacement(state, map[int][]TaskSlot{
taskID: cloneScheduleTaskSlots(targetSlots),
})
}
// validateLocalOrderBatchPlacement 在“多任务同时变更”的假设下做顺序约束校验。
//
// 职责边界:
// 1. 先把所有候选落位一次性写入克隆态,再统一校验,避免批量局部调整时出现伪冲突;
// 2. 只校验 proposals 中涉及的任务,因为只要这些任务仍处于各自前驱/后继之间,就不会破坏同类整体顺序;
// 3. 返回首个命中的中文错误,供写工具直接透传给 LLM。
func validateLocalOrderBatchPlacement(state *ScheduleState, proposals map[int][]TaskSlot) error {
if state == nil || len(proposals) == 0 {
return nil
}
clone := state.Clone()
for taskID, slots := range proposals {
task := clone.TaskByStateID(taskID)
if task == nil {
return fmt.Errorf("顺序约束校验失败任务ID %d 不存在", taskID)
}
task.Slots = cloneScheduleTaskSlots(slots)
}
for taskID := range proposals {
if err := validateTaskLocalOrderOnState(clone, taskID); err != nil {
return err
}
}
return nil
}
// validateTaskLocalOrderOnState 判断某个任务在当前假设态下,是否仍处于同任务类前驱/后继之间。
func validateTaskLocalOrderOnState(state *ScheduleState, taskID int) error {
task := state.TaskByStateID(taskID)
if task == nil {
return fmt.Errorf("顺序约束校验失败任务ID %d 不存在", taskID)
}
if !shouldEnforceTaskLocalOrder(*task) || len(task.Slots) == 0 {
return nil
}
prevTask, nextTask := findTaskClassNeighbors(state, *task)
targetStartDay, targetStartSlot, _ := earliestScheduleTaskSlot(task.Slots)
targetEndDay, _, targetEndSlot := latestScheduleTaskSlot(task.Slots)
if prevTask != nil && len(prevTask.Slots) > 0 {
prevEndDay, _, prevEndSlot := latestScheduleTaskSlot(prevTask.Slots)
if !isStrictlyAfter(targetStartDay, targetStartSlot, prevEndDay, prevEndSlot) {
return fmt.Errorf(
"顺序约束不满足:[%d]%s 不能放到%s。它必须晚于同任务类前一个任务 %s 的结束位置(%s。",
task.StateID,
task.Name,
formatTaskSlotsBriefWithState(state, task.Slots),
formatTaskLabel(*prevTask),
formatTaskSlotsBriefWithState(state, prevTask.Slots),
)
}
}
if nextTask != nil && len(nextTask.Slots) > 0 {
nextStartDay, nextStartSlot, _ := earliestScheduleTaskSlot(nextTask.Slots)
if !isStrictlyBefore(targetEndDay, targetEndSlot, nextStartDay, nextStartSlot) {
return fmt.Errorf(
"顺序约束不满足:[%d]%s 不能放到%s。它必须早于同任务类后一个任务 %s 的开始位置(%s。",
task.StateID,
task.Name,
formatTaskSlotsBriefWithState(state, task.Slots),
formatTaskLabel(*nextTask),
formatTaskSlotsBriefWithState(state, nextTask.Slots),
)
}
}
return nil
}
// shouldEnforceTaskLocalOrder 判断任务是否需要参与“同任务类内部顺序”约束。
func shouldEnforceTaskLocalOrder(task ScheduleTask) bool {
return task.Source == "task_item" && task.TaskClassID > 0 && task.TaskOrder > 0
}
// findTaskClassNeighbors 查找同任务类中 order 紧邻当前任务的前驱与后继。
func findTaskClassNeighbors(state *ScheduleState, task ScheduleTask) (prevTask *ScheduleTask, nextTask *ScheduleTask) {
if state == nil || !shouldEnforceTaskLocalOrder(task) {
return nil, nil
}
for i := range state.Tasks {
candidate := &state.Tasks[i]
if candidate.StateID == task.StateID {
continue
}
if !shouldEnforceTaskLocalOrder(*candidate) {
continue
}
if candidate.TaskClassID != task.TaskClassID {
continue
}
if candidate.TaskOrder < task.TaskOrder {
if prevTask == nil || candidate.TaskOrder > prevTask.TaskOrder {
prevTask = candidate
}
continue
}
if candidate.TaskOrder > task.TaskOrder {
if nextTask == nil || candidate.TaskOrder < nextTask.TaskOrder {
nextTask = candidate
}
}
}
return prevTask, nextTask
}
func earliestScheduleTaskSlot(slots []TaskSlot) (day int, slotStart int, slotEnd int) {
if len(slots) == 0 {
return 0, 0, 0
}
best := slots[0]
for i := 1; i < len(slots); i++ {
current := slots[i]
if current.Day < best.Day ||
(current.Day == best.Day && current.SlotStart < best.SlotStart) ||
(current.Day == best.Day && current.SlotStart == best.SlotStart && current.SlotEnd < best.SlotEnd) {
best = current
}
}
return best.Day, best.SlotStart, best.SlotEnd
}
func latestScheduleTaskSlot(slots []TaskSlot) (day int, slotStart int, slotEnd int) {
if len(slots) == 0 {
return 0, 0, 0
}
best := slots[0]
for i := 1; i < len(slots); i++ {
current := slots[i]
if current.Day > best.Day ||
(current.Day == best.Day && current.SlotEnd > best.SlotEnd) ||
(current.Day == best.Day && current.SlotEnd == best.SlotEnd && current.SlotStart > best.SlotStart) {
best = current
}
}
return best.Day, best.SlotStart, best.SlotEnd
}
func isStrictlyAfter(dayA, slotA, dayB, slotB int) bool {
if dayA != dayB {
return dayA > dayB
}
return slotA > slotB
}
func isStrictlyBefore(dayA, slotA, dayB, slotB int) bool {
if dayA != dayB {
return dayA < dayB
}
return slotA < slotB
}
func cloneScheduleTaskSlots(src []TaskSlot) []TaskSlot {
if len(src) == 0 {
return nil
}
dst := make([]TaskSlot, len(src))
copy(dst, src)
return dst
}