fix(repo): /api/v1/agents 带出 mode_enforcement —— 列表接口静默少了一个事实

# 现象

`/api/v1/agents` 对全部四个 Agent 返回 `mode_enforcement: ""`,
而库里四个值一直是正确的(pi=native / dsh=partial / opencode=advisory /
homeagent=advisory)。这是全功能演练 Phase A 的前置检查抓到的。

# 根因

`ListAgents` 的 SELECT 里没有 `mode_enforcement`,Scan 自然也没扫它。

这与「SELECT 加了列但没加进 Scan」(列数不匹配,直接报错)**不是**一回事:
少取一列不报任何错,只是安静地少一个事实。而「这个 Agent 到底能不能真的拦住
危险操作」是使用者在派活前必须知道的事 —— 缺了它,advisory 档会被当成 native
档用。

# 修法

SELECT 补上 `COALESCE(NULLIF(mode_enforcement, ''), 'advisory')`,与
`permission_mode.go` 同一套兜底(那里也这么写,说明空串确实可能出现)。

# 测试

`repo/agent_mode_list_test.go`:三档各一条 + 一条空串(脏数据),断言**值真的
传出来**而不是「函数没报错」;另加反向对照确认行真的被查到了(避免一个都没查到
也能过)。

反向验证过判据非空转:把修复退掉,测试立刻失败。
This commit is contained in:
2026-09-12 11:19:52 +08:00
parent a696b2a141
commit 0b5fabfd49
2 changed files with 79 additions and 2 deletions

View File

@ -187,8 +187,18 @@ func SetAgentDisabled(ctx context.Context, agentName string, disabled bool) (int
func ListAgents(ctx context.Context, statusFilter string) ([]models.Agent, error) {
// 带上 default_rounds:前端补全收件人时要显示「派给它的任务默认几个来回」,
// 否则人得先去管理员页查一遍才敢派活。
//
// 也要带 mode_enforcement:它是插件自报的**档位强制能力**
// (native / partial / advisory)。"这个 Agent 到底能不能真的拦住危险操作"
// 是使用者在派活前必须知道的事。
//
// 该字段曾在列表接口**静默丢失**(SELECT 里没有,Scan 也就没扫),
// 而库里四个 Agent 的值一直是正确的 —— 表现为 /api/v1/agents 全部返回空串。
// 与列数不匹配不同,这种"少取一列"不会报错,只会安静地少一个事实。
// 回归测试:repo/agent_mode_list_test.go。
q := `SELECT agent_id, agent_name, workspaces, platform, status,
COALESCE(default_rounds, 0) FROM agents`
COALESCE(default_rounds, 0),
COALESCE(NULLIF(mode_enforcement, ''), 'advisory') FROM agents`
args := []any{}
switch statusFilter {
case "":
@ -212,7 +222,7 @@ func ListAgents(ctx context.Context, statusFilter string) ([]models.Agent, error
var a models.Agent
var wsJSON []byte
if err := rows.Scan(&a.ID, &a.Name, &wsJSON, &a.Platform, &a.Status,
&a.DefaultRounds); err != nil {
&a.DefaultRounds, &a.ModeEnforcement); err != nil {
return nil, err
}
if wsJSON != nil {