feat(goals): 总览新增目标管理(增/删/改),同步通知 planner 并复活任务

- 总览页新增「目标管理」卡片,支持人工查看/新增/修改/删除目标
- 新增/修改:通知 planner(新增了目标/由 old 变为 new)并复活任务
- 删除:硬删除 goal 节点(级联删边/锚点),通知 planner,不复活任务
- 无 schema 变更:目标仍存 exploration_nodes(kind='goal'),仅加查询方法
This commit is contained in:
Autumn-27 committed 2026-08-23 11:41:53 -04:00
1 parent 025e1ce445
commit d28ecd67fe
9 files changed
+506 -2

No files matched your search

+34
View File
@@ -228,6 +228,40 @@ func (s *ExplorationStore) SetNodeState(id int64, state string) error {
return err
}
// UpdateGoalPayload rewrites a goal node's payload text (and optional vulnclass);
// scoped to kind='goal' so it can never mutate an intent/fact by id. Returns an
// error if no such goal exists in this exploration.
func (s *ExplorationStore) UpdateGoalPayload(id int64, text, vulnclass string) error {
payload := map[string]any{"text": text}
if vulnclass != "" {
payload["vulnclass"] = vulnclass
}
raw, _ := json.Marshal(payload)
res, err := s.db.Exec(`UPDATE exploration_nodes SET payload=$1 WHERE id=$2 AND exploration_id=$3 AND kind='goal'`, string(raw), id, s.expID)
if err != nil {
return err
}
if n, _ := res.RowsAffected(); n == 0 {
return fmt.Errorf("目标不存在")
}
return nil
}
// DeleteGoal hard-deletes a goal node; scoped to kind='goal' so it can never drop
// an intent/fact by id. The edges (spawns) and anchors reference it with ON DELETE
// CASCADE, so they go with it; activity.node_id is ON DELETE SET NULL. Returns an
// error if no such goal exists in this exploration.
func (s *ExplorationStore) DeleteGoal(id int64) error {
res, err := s.db.Exec(`DELETE FROM exploration_nodes WHERE id=$1 AND exploration_id=$2 AND kind='goal'`, id, s.expID)
if err != nil {
return err
}
if n, _ := res.RowsAffected(); n == 0 {
return fmt.Errorf("目标不存在")
}
return nil
}
// SetIntentState updates an intent node's state.
// ResetRunningIntents returns any intent left in 'running' back to 'open' so it
// is re-claimed. Called on startup: a 'running' intent with no live worker (a