Version: 0.7.9.dev.260326

后端:
1.把最后一块拼图:schedule_refine也搬迁到了agent2,此时agent已经完全解耦。但是它没融入新架构,Codex只尝试把它调整了一部分,回退了一些错误的更改,保持着现在的可运行状态。下次继续改。
2.agent目录先保留,直到refine彻底融入新架构。
3.改善Codex主导的新史山结构:node文件夹里面大量文件,转而改成了module.go+module_tool.go的双文件格局,极大提升架构整洁度和代码可读性。
前端:
1.新开了日历界面,正在保持往前推进。做了很多更改,感觉越来越好了。
This commit is contained in:
Losita
2026-03-26 00:38:17 +08:00
parent aa04bfb452
commit a243154e23
32 changed files with 11481 additions and 1239 deletions

View File

@@ -0,0 +1,377 @@
package schedulerefine
import (
"sort"
"strings"
"time"
"github.com/LoveLosita/smartflow/backend/model"
)
const (
// 固定业务时区,避免“今天/明天”在容器默认时区下偏移。
timezoneName = "Asia/Shanghai"
// 统一分钟级时间文本格式。
datetimeLayout = "2006-01-02 15:04"
// 预算默认值。
defaultPlanMax = 2
defaultExecuteMax = 24
defaultPerTaskBudget = 4
defaultReplanMax = 2
defaultCompositeRetry = 2
defaultRepairReserve = 1
)
// RefineContract 表示本轮微调意图契约。
type RefineContract struct {
Intent string `json:"intent"`
Strategy string `json:"strategy"`
HardRequirements []string `json:"hard_requirements"`
HardAssertions []RefineAssertion `json:"hard_assertions,omitempty"`
KeepRelativeOrder bool `json:"keep_relative_order"`
OrderScope string `json:"order_scope"`
}
// RefineAssertion 表示可由后端直接判定的结构化硬断言。
//
// 字段说明:
// 1. Metric断言指标名例如 source_move_ratio_percent
// 2. Operator比较操作符支持 == / <= / >= / between
// 3. Value/Min/Max阈值
// 4. Week/TargetWeek可选周次上下文。
type RefineAssertion struct {
Metric string `json:"metric"`
Operator string `json:"operator"`
Value int `json:"value,omitempty"`
Min int `json:"min,omitempty"`
Max int `json:"max,omitempty"`
Week int `json:"week,omitempty"`
TargetWeek int `json:"target_week,omitempty"`
}
// HardCheckReport 表示终审硬校验结果。
type HardCheckReport struct {
PhysicsPassed bool `json:"physics_passed"`
PhysicsIssues []string `json:"physics_issues,omitempty"`
IntentPassed bool `json:"intent_passed"`
IntentReason string `json:"intent_reason,omitempty"`
IntentUnmet []string `json:"intent_unmet,omitempty"`
OrderPassed bool `json:"order_passed"`
OrderIssues []string `json:"order_issues,omitempty"`
RepairTried bool `json:"repair_tried"`
}
// ReactRoundObservation 记录每轮 ReAct 的关键观察。
type ReactRoundObservation struct {
Round int `json:"round"`
GoalCheck string `json:"goal_check,omitempty"`
Decision string `json:"decision,omitempty"`
ToolName string `json:"tool_name,omitempty"`
ToolParams map[string]any `json:"tool_params,omitempty"`
ToolSuccess bool `json:"tool_success"`
ToolErrorCode string `json:"tool_error_code,omitempty"`
ToolResult string `json:"tool_result,omitempty"`
Reflect string `json:"reflect,omitempty"`
}
// PlannerPlan 表示 Planner 生成的阶段执行计划。
type PlannerPlan struct {
Summary string `json:"summary"`
Steps []string `json:"steps,omitempty"`
}
// RefineSlicePlan 表示切片节点输出。
type RefineSlicePlan struct {
WeekFilter []int `json:"week_filter,omitempty"`
SourceDays []int `json:"source_days,omitempty"`
TargetDays []int `json:"target_days,omitempty"`
ExcludeSections []int `json:"exclude_sections,omitempty"`
Reason string `json:"reason,omitempty"`
}
// RefineObjective 表示“可执行且可校验”的目标约束。
//
// 设计说明:
// 1. 由 contract/slice 从自然语言编译得到;
// 2. 执行阶段done 收口与终审阶段hard_check共用同一份约束
// 3. 避免“执行逻辑与终审逻辑各说各话”。
type RefineObjective struct {
Mode string `json:"mode,omitempty"` // none | move_all | move_ratio
SourceWeeks []int `json:"source_weeks,omitempty"`
TargetWeeks []int `json:"target_weeks,omitempty"`
SourceDays []int `json:"source_days,omitempty"`
TargetDays []int `json:"target_days,omitempty"`
ExcludeSections []int `json:"exclude_sections,omitempty"`
BaselineSourceTaskCount int `json:"baseline_source_task_count,omitempty"`
RequiredMoveMin int `json:"required_move_min,omitempty"`
RequiredMoveMax int `json:"required_move_max,omitempty"`
Reason string `json:"reason,omitempty"`
}
// ScheduleRefineState 是连续微调图的统一状态。
type ScheduleRefineState struct {
// 1) 请求上下文
TraceID string
UserID int
ConversationID string
UserMessage string
RequestNow time.Time
RequestNowText string
// 2) 继承自预览快照的数据
TaskClassIDs []int
Constraints []string
// InitialHybridEntries 保存本轮微调开始前的基线,用于终审做“前后对比”。
// 说明:
// 1. 只读语义,不参与执行期改写;
// 2. 终审可基于它判断“来源任务是否真正迁移到目标区域”。
InitialHybridEntries []model.HybridScheduleEntry
HybridEntries []model.HybridScheduleEntry
AllocatedItems []model.TaskClassItem
CandidatePlans []model.UserWeekSchedule
// 3) 本轮执行状态
UserIntent string
Contract RefineContract
PlanMax int
PerTaskBudget int
ExecuteMax int
ReplanMax int
// CompositeRetryMax 表示复合路由失败后的最大重试次数(不含首次尝试)。
CompositeRetryMax int
PlanUsed int
ReplanUsed int
MaxRounds int
RepairReserve int
RoundUsed int
ActionLogs []string
ConsecutiveFailures int
ThinkingBoostArmed bool
ObservationHistory []ReactRoundObservation
CurrentPlan PlannerPlan
BatchMoveAllowed bool
// DisableCompositeTools=true 表示已进入 ReAct 兜底,禁止再调用复合工具。
DisableCompositeTools bool
// CompositeRouteTried 标记是否尝试过“复合批处理路由”。
CompositeRouteTried bool
// CompositeRouteSucceeded 标记复合批处理路由是否已完成“复合分支出站”。
//
// 说明:
// 1. true 表示当前链路可以跳过 ReAct 兜底,直接进入 hard_check
// 2. 它不等价于“终审已通过”,终审是否通过仍以后续 HardCheck 结果为准;
// 3. 这样区分是为了避免“复合工具已成功执行,但业务目标要等终审裁决”时被误判为失败。
CompositeRouteSucceeded bool
TaskActionUsed map[int]int
EntriesVersion int
SeenSlotQueries map[string]struct{}
// RequiredCompositeTool 表示本轮策略要求“必须至少成功一次”的复合工具。
// 取值约定:"" | "SpreadEven" | "MinContextSwitch"。
RequiredCompositeTool string
// CompositeToolCalled 记录复合工具是否至少调用过一次(不区分成功失败)。
CompositeToolCalled map[string]bool
// CompositeToolSuccess 记录复合工具是否至少成功过一次。
CompositeToolSuccess map[string]bool
SlicePlan RefineSlicePlan
Objective RefineObjective
WorksetTaskIDs []int
WorksetCursor int
CurrentTaskID int
CurrentTaskAttempt int
LastFailedCallSignature string
OriginOrderMap map[int]int
// 4) 终审状态
HardCheck HardCheckReport
// 5) 最终输出
FinalSummary string
Completed bool
}
// NewScheduleRefineState 基于上一版预览快照初始化状态。
//
// 职责边界:
// 1. 负责初始化预算、上下文字段与可变状态容器;
// 2. 负责拷贝 preview 数据,避免跨请求引用污染;
// 3. 不负责做任何调度动作。
func NewScheduleRefineState(traceID string, userID int, conversationID string, userMessage string, preview *model.SchedulePlanPreviewCache) *ScheduleRefineState {
now := nowToMinute()
st := &ScheduleRefineState{
TraceID: strings.TrimSpace(traceID),
UserID: userID,
ConversationID: strings.TrimSpace(conversationID),
UserMessage: strings.TrimSpace(userMessage),
RequestNow: now,
RequestNowText: now.In(loadLocation()).Format(datetimeLayout),
PlanMax: defaultPlanMax,
PerTaskBudget: defaultPerTaskBudget,
ExecuteMax: defaultExecuteMax,
ReplanMax: defaultReplanMax,
CompositeRetryMax: defaultCompositeRetry,
RepairReserve: defaultRepairReserve,
MaxRounds: defaultExecuteMax + defaultRepairReserve,
ActionLogs: make([]string, 0, 32),
ObservationHistory: make([]ReactRoundObservation, 0, 24),
TaskActionUsed: make(map[int]int),
SeenSlotQueries: make(map[string]struct{}),
OriginOrderMap: make(map[int]int),
CompositeToolCalled: map[string]bool{
"SpreadEven": false,
"MinContextSwitch": false,
},
CompositeToolSuccess: map[string]bool{
"SpreadEven": false,
"MinContextSwitch": false,
},
CurrentPlan: PlannerPlan{
Summary: "初始化完成,等待 Planner 生成执行计划。",
},
SlicePlan: RefineSlicePlan{
Reason: "尚未切片",
},
}
if preview == nil {
return st
}
st.TaskClassIDs = append([]int(nil), preview.TaskClassIDs...)
st.InitialHybridEntries = cloneHybridEntries(preview.HybridEntries)
st.HybridEntries = cloneHybridEntries(preview.HybridEntries)
st.AllocatedItems = cloneTaskClassItems(preview.AllocatedItems)
st.CandidatePlans = cloneWeekSchedules(preview.CandidatePlans)
st.OriginOrderMap = buildOriginOrderMap(st.HybridEntries)
return st
}
func loadLocation() *time.Location {
loc, err := time.LoadLocation(timezoneName)
if err != nil {
return time.Local
}
return loc
}
func nowToMinute() time.Time {
return time.Now().In(loadLocation()).Truncate(time.Minute)
}
func cloneHybridEntries(src []model.HybridScheduleEntry) []model.HybridScheduleEntry {
if len(src) == 0 {
return nil
}
dst := make([]model.HybridScheduleEntry, len(src))
copy(dst, src)
return dst
}
func cloneTaskClassItems(src []model.TaskClassItem) []model.TaskClassItem {
if len(src) == 0 {
return nil
}
dst := make([]model.TaskClassItem, 0, len(src))
for _, item := range src {
copied := item
if item.CategoryID != nil {
v := *item.CategoryID
copied.CategoryID = &v
}
if item.Order != nil {
v := *item.Order
copied.Order = &v
}
if item.Content != nil {
v := *item.Content
copied.Content = &v
}
if item.Status != nil {
v := *item.Status
copied.Status = &v
}
if item.EmbeddedTime != nil {
t := *item.EmbeddedTime
copied.EmbeddedTime = &t
}
dst = append(dst, copied)
}
return dst
}
func cloneWeekSchedules(src []model.UserWeekSchedule) []model.UserWeekSchedule {
if len(src) == 0 {
return nil
}
dst := make([]model.UserWeekSchedule, 0, len(src))
for _, week := range src {
eventsCopy := make([]model.WeeklyEventBrief, len(week.Events))
copy(eventsCopy, week.Events)
dst = append(dst, model.UserWeekSchedule{
Week: week.Week,
Events: eventsCopy,
})
}
return dst
}
// buildOriginOrderMap 构建 suggested 任务的初始顺序基线task_item_id -> rank
func buildOriginOrderMap(entries []model.HybridScheduleEntry) map[int]int {
orderMap := make(map[int]int)
if len(entries) == 0 {
return orderMap
}
suggested := make([]model.HybridScheduleEntry, 0, len(entries))
for _, entry := range entries {
if isMovableSuggestedTask(entry) {
suggested = append(suggested, entry)
}
}
sort.SliceStable(suggested, func(i, j int) bool {
left := suggested[i]
right := suggested[j]
if left.Week != right.Week {
return left.Week < right.Week
}
if left.DayOfWeek != right.DayOfWeek {
return left.DayOfWeek < right.DayOfWeek
}
if left.SectionFrom != right.SectionFrom {
return left.SectionFrom < right.SectionFrom
}
if left.SectionTo != right.SectionTo {
return left.SectionTo < right.SectionTo
}
return left.TaskItemID < right.TaskItemID
})
for i, entry := range suggested {
orderMap[entry.TaskItemID] = i + 1
}
return orderMap
}
// FinalHardCheckPassed 判断“最终终审”是否整体通过。
//
// 职责边界:
// 1. 负责聚合 physics/order/intent 三类硬校验结果,给服务层与总结阶段统一复用;
// 2. 不负责触发终审,也不负责推导修复动作;
// 3. nil state 视为未通过,避免上层把缺失结果误判为成功。
func FinalHardCheckPassed(st *ScheduleRefineState) bool {
if st == nil {
return false
}
return st.HardCheck.PhysicsPassed && st.HardCheck.OrderPassed && st.HardCheck.IntentPassed
}