后端: 1. 主动调度预览确认主链路落地——新增主动调度数据模型、DAO 与事件契约;接入 dry-run pipeline 与任务触发的 job upsert/cancel;新增 preview 查询与 confirm API,支持 apply_id 幂等确认并同步写入 task_pool 日程 2. 同步更新主动调度实施文档的阶段状态与验收记录 前端: 3. AssistantPanel 脚本层继续解耦——私有类型迁移到独立类型文件,并抽离会话、工具轨迹、思考摘要、任务表单等纯函数辅助逻辑;保持助手面板模板与样式不变,降低表现层回归风险
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
|
||
"github.com/LoveLosita/smartflow/backend/active_scheduler/candidate"
|
||
schedulercontext "github.com/LoveLosita/smartflow/backend/active_scheduler/context"
|
||
"github.com/LoveLosita/smartflow/backend/active_scheduler/observe"
|
||
"github.com/LoveLosita/smartflow/backend/active_scheduler/ports"
|
||
"github.com/LoveLosita/smartflow/backend/active_scheduler/trigger"
|
||
)
|
||
|
||
// DryRunResult 是 API dry-run / worker 测试入口可直接消费的同步结果。
|
||
type DryRunResult struct {
|
||
Context *schedulercontext.ActiveScheduleContext
|
||
Observation observe.Result
|
||
Candidates []candidate.Candidate
|
||
}
|
||
|
||
// DryRunService 编排主动调度 dry-run 主链路。
|
||
//
|
||
// 职责边界:
|
||
// 1. 固定执行 BuildContext -> Observe -> GenerateCandidates;
|
||
// 2. 不调用 LLM、不写 preview、不发 notification、不正式写日程;
|
||
// 3. 后续 API / worker 应复用该入口,避免出现第二套 dry-run 诊断逻辑。
|
||
type DryRunService struct {
|
||
builder *schedulercontext.Builder
|
||
analyzer *observe.Analyzer
|
||
generator *candidate.Generator
|
||
}
|
||
|
||
// NewDryRunService 创建主动调度 dry-run 服务。
|
||
func NewDryRunService(readers ports.Readers) (*DryRunService, error) {
|
||
builder, err := schedulercontext.NewBuilder(readers)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &DryRunService{
|
||
builder: builder,
|
||
analyzer: observe.NewAnalyzer(),
|
||
generator: candidate.NewGenerator(),
|
||
}, nil
|
||
}
|
||
|
||
// SetClock 注入测试时钟。
|
||
func (s *DryRunService) SetClock(clock func() time.Time) {
|
||
if s != nil && s.builder != nil {
|
||
s.builder.SetClock(clock)
|
||
}
|
||
}
|
||
|
||
// DryRun 执行主动调度同步诊断。
|
||
func (s *DryRunService) DryRun(ctx context.Context, trig trigger.ActiveScheduleTrigger) (*DryRunResult, error) {
|
||
if s == nil || s.builder == nil || s.analyzer == nil || s.generator == nil {
|
||
return nil, errors.New("DryRunService 尚未正确初始化")
|
||
}
|
||
|
||
// 1. 构造上下文:读取 task / schedule / feedback 的只读事实快照。
|
||
activeContext, err := s.builder.BuildContext(ctx, trig)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 2. 主动观测:生成 metrics、issues 和初步裁决,不生成正式变更。
|
||
observation := s.analyzer.Observe(activeContext)
|
||
|
||
// 3. 候选生成:只枚举第一版允许的确定性候选,压缩融合保持关闭。
|
||
candidates := s.generator.GenerateCandidates(activeContext, observation)
|
||
fallbackCandidateID := ""
|
||
if len(candidates) > 0 {
|
||
fallbackCandidateID = candidates[0].CandidateID
|
||
}
|
||
observation = s.analyzer.FinalizeDecision(observation, len(applicableCandidates(candidates)), fallbackCandidateID)
|
||
|
||
return &DryRunResult{
|
||
Context: activeContext,
|
||
Observation: observation,
|
||
Candidates: candidates,
|
||
}, nil
|
||
}
|
||
|
||
func applicableCandidates(candidates []candidate.Candidate) []candidate.Candidate {
|
||
result := make([]candidate.Candidate, 0, len(candidates))
|
||
for _, item := range candidates {
|
||
if item.CandidateType == candidate.TypeAddTaskPoolToSchedule || item.CandidateType == candidate.TypeCreateMakeup {
|
||
result = append(result, item)
|
||
}
|
||
}
|
||
return result
|
||
}
|