- ActiveWorkflowScope resolves live instance task keys for the project - chat document reader matches inside the active pool; cross-flow hits return ErrWrongWorkflow with switch guidance - enrichSessionWorkflowStage injects ActiveWorkflowKey into UserCtx - TRIGGER_TASK (coordinator + task-run API) blocks out-of-scope task keys Co-authored-by: Cursor <cursoragent@cursor.com>
72 lines
2.4 KiB
Go
72 lines
2.4 KiB
Go
package projectinstance
|
||
|
||
import (
|
||
"context"
|
||
|
||
"chat-agent-system/internal/workflowdef"
|
||
)
|
||
|
||
// ActiveWorkflowScope 返回项目当前激活工作流的键与文档任务键集合。
|
||
//
|
||
// 语义(供意图填槽与文档正文读取收窄作用域):
|
||
// - 从未激活(无 live 实例)→ workflowKey 为空、taskKeys 为 nil → 调用方不得过滤(legacy / planning)
|
||
// - live 系实例 → workflowKey 取自 projects.active_workflow_key(回落实例快照定义键),
|
||
// taskKeys 收集全部启用文档任务,并补齐旧键/新键双向别名
|
||
// - completed / terminated 不属于激活:active_workflow_key 已在终止时清空,视为未激活
|
||
// - 存储错误原样上抛(fail-closed)
|
||
func (s *Service) ActiveWorkflowScope(ctx context.Context, projectID int64) (workflowKey string, taskKeys map[string]bool, err error) {
|
||
if s == nil || s.store == nil || projectID <= 0 {
|
||
return "", nil, nil
|
||
}
|
||
_, activeKey, _, _, err := s.store.GetProjectMeta(ctx, projectID)
|
||
if err != nil {
|
||
return "", nil, err
|
||
}
|
||
if activeKey == "" {
|
||
// 未激活:不过滤,保持现状行为。
|
||
return "", nil, nil
|
||
}
|
||
live, err := s.store.GetLiveInstance(ctx, projectID)
|
||
if err != nil {
|
||
return "", nil, err
|
||
}
|
||
if live == nil {
|
||
// 有激活键但无 live 快照:仅返回键供话术,任务集合交由调用方按空处理(不过滤)。
|
||
return activeKey, nil, nil
|
||
}
|
||
if live.ConfigSnapshot.WorkflowDefinitionKey != "" {
|
||
workflowKey = live.ConfigSnapshot.WorkflowDefinitionKey
|
||
} else {
|
||
workflowKey = activeKey
|
||
}
|
||
taskKeys = make(map[string]bool)
|
||
for _, st := range live.ConfigSnapshot.Stages {
|
||
if !st.Enabled {
|
||
continue
|
||
}
|
||
for _, dt := range st.DocTasks {
|
||
if !dt.Enabled || dt.TaskKey == "" {
|
||
continue
|
||
}
|
||
taskKeys[dt.TaskKey] = true
|
||
// 补齐旧键/新键双向别名,冻结实例与发布清单互指。
|
||
if canonical := workflowdef.CanonicalTaskKey(dt.TaskKey); canonical != dt.TaskKey {
|
||
taskKeys[canonical] = true
|
||
}
|
||
if legacy := workflowdef.ImplementationTaskKey(dt.TaskKey); legacy != dt.TaskKey {
|
||
taskKeys[legacy] = true
|
||
}
|
||
}
|
||
}
|
||
return workflowKey, taskKeys, nil
|
||
}
|
||
|
||
// TaskKeyInScope 判断任务键是否落在激活工作流任务集合内。
|
||
// taskKeys 为空(未激活 / 无快照)时一律放行,保持现状。
|
||
func TaskKeyInScope(taskKeys map[string]bool, taskKey string) bool {
|
||
if len(taskKeys) == 0 {
|
||
return true
|
||
}
|
||
return mapHasTaskKey(taskKeys, taskKey)
|
||
}
|