feat(swarm): add goal_summary to swarm status view (#45/#28 consumer ask)

@Mem0ried 客户端 consumer 验收(#59)指出 GET /swarms/:id 缺 goal_summary —— Run 列表
只能显示 deployment_id/status,体验差。补:swarmDeploymentView 增加 goal_summary,从持久化
plan_json 顶层 objective 提取,折叠空白为单行 + 截断(200 rune)+ RedactText 兜底;取不到
(无 plan / 无 objective / 坏 JSON)返回空串,不臆造。list 与 detail 同走 swarmDeploymentView,
两处都带上。

文档 docs/integration/heicode-desktop-client-api.md §5.2 状态样例补 goal_summary 字段说明。
测试 TestSwarmGoalSummary 覆盖:空/坏 JSON/无 objective→空;多行多空格折叠;误入 sk- 被脱敏;
超长截断带省略号。go build ./... 与 controller 测试全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 21:17:32 +08:00
co-authored by Claude Opus 4.8
parent 56a9b02a25
commit 292504735e
3 changed files with 52 additions and 2 deletions
@@ -2,6 +2,7 @@ package controller
import (
"encoding/json"
"strings"
"testing"
"github.com/heicode/manager/model"
@@ -91,3 +92,25 @@ func TestSwarmFrozenEventTypesRegistered(t *testing.T) {
require.True(t, inReq, "event_type %s 应在 requiredFields 注册", et)
}
}
// #28(@Mem0ried 消费需求):goal_summary 从 plan_json 顶层 objective 提取 —— 折叠空白为单行、
// 截断、脱敏;无 plan / 无 objective / 坏 JSON 返回空串(不臆造)。
func TestSwarmGoalSummary(t *testing.T) {
require.Empty(t, swarmGoalSummary(""))
require.Empty(t, swarmGoalSummary("not-json"))
require.Empty(t, swarmGoalSummary(`{"sub_mode":"swarm"}`)) // 无 objective
// 多行/多空格折叠成单行(JSON 里的 \n 是合法转义,用原始串避免再转义)
got := swarmGoalSummary(`{"objective":" 迁移 支付服务\n 到 K8s "}`)
require.Equal(t, "迁移 支付服务 到 K8s", got)
// 误入的 sk- 被 RedactText 兜底
red := swarmGoalSummary(`{"objective":"deploy with key sk-abcdEFGH1234567890XYZ now"}`)
require.NotContains(t, red, "sk-abcdEFGH1234567890XYZ")
// 超长截断带省略号
long := `{"objective":"` + strings.Repeat("x", 400) + `"}`
out := swarmGoalSummary(long)
require.LessOrEqual(t, len([]rune(out)), 201) // 200 + …
require.True(t, strings.HasSuffix(out, "…"))
}