package handler /* 会话作用域拒绝的文案必须**指出 id 从哪来**(2026-10-04 实测驱动)。 ★ 为什么要这一格: 实测「独立 MCP 宿主 E2E」时,第一版客户端卡在这里 —— 它收到了 new_mail 事件、知道该读那封信,却**不知道该传什么 session_id**, 于是一封信都读不到,报的是: 这封信不在你当前所在的那条会话里:…你当前在会话 。 原文案说了「就在它那条会话里读」(去哪读),却没说 **id 从哪来** (怎么去读)。缺的那半句正是宿主唯一缺的信息。 ★ 判据盯两件事,缺一不可: ① 提到 session_id(名字本身) ② 指出**来源**(new_mail 事件的 payload / session/default 端点) —— 只加「请传 session_id」等于把问题原样推回去,宿主仍不知道去哪拿。 */ import ( "os" "strings" "testing" ) func TestSessionScopeHintTellsWhereToGetSessionID(t *testing.T) { b, err := os.ReadFile("agent_discovery.go") if err != nil { t.Fatalf("读 agent_discovery.go: %v", err) } src := string(b) i := strings.Index(src, "这封信不在你当前所在的那条会话里") if i < 0 { t.Fatal("找不到会话作用域的拒绝文案") } // ★ 只取这一条 Error(...) 调用,不能按固定窗口往后截。 // 初版用「起点 + 2200 字符」,而**同一文件别处的文档注释**里也写了 // `/agent/session/default`(那是 `agentScope` 上方的说明)⇒ // 删掉提示后窗口里仍有它 ⇒ 判据假绿。 // 这与「变异必须确认真的应用」同族,但更隐蔽:变异应用了、判据却因为 // **看错了范围**而没反应。 seg := src[i:] if end := strings.Index(seg, `Error(w, http.StatusForbidden, "无权访问该会话"`); end > 0 { seg = seg[:end] } // ① 提到 session_id if !strings.Contains(seg, "session_id") { t.Error("★ 拒绝文案里没有 session_id —— 调用方不知道该传什么") } // ② 指出来源:事件载荷 或 默认会话语义端点 hasEvent := strings.Contains(seg, "new_mail") hasEndpoint := strings.Contains(seg, "/agent/session/default") if !hasEvent && !hasEndpoint { t.Error("★ 只说了「要传 session_id」却没说**从哪拿** ——" + " 那等于把问题原样推回去,宿主仍然读不到信(实测卡点)") } // ③ 不得泄露目标会话 id(安全边界:那是别人的会话) if strings.Contains(seg, `self = (*scope).String()`) && strings.Contains(seg, "self+"+"\" 目标会话") { t.Error("★ 不得把目标会话 id 拼进文案 —— 那是安全边界(只报自己所在的会话)") } } /* ★ 「你当前在会话 。」—— 一个值管三种拒绝,文案却只有一句(2026-10-05)。 `AgentMayReadSession` 用**同一个 reason** 覆盖三种拒绝: ① 没声明 session_id(scope == nil) ② 声明了别人的会话 ③ 声明了但**不是参与方**(session_id 由请求方提供,自述不是事实) 原文案无条件拼 `"你当前在会话 " + self + "。"`,于是 ① 拼出: 这封信不在你当前所在的那条会话里:…你当前在会话 。要读它,… 实测(独立 MCP 宿主读自己刚收到的那封信)确实读到这句。 残缺的话会让模型以为自己在一个**无名会话**里,转而去猜/去搜 —— 比不说更糟。 ★ 这条判据盯的是**结构**:文案必须按 scope 有无分句, 而不是靠手测一次。 */ func TestSessionScopeHintNeverLeavesEmptySessionSlot(t *testing.T) { b, err := os.ReadFile("agent_discovery.go") if err != nil { t.Fatalf("读 agent_discovery.go: %v", err) } src := string(b) // ★ 回归:不得再出现「无条件拼接 self」的写法 —— // 它就是那句残缺文案的唯一来源。 if strings.Contains(src, `"你当前在会话 "+self+"。`) { t.Error("★ 又用回「你当前在会话 \" + self」的无条件拼接 —— " + "scope 为 nil 时会拼出「你当前在会话 。」,模型会以为自己在一个无名会话里") } // 替代写法必须按 scope 有无分句 if !strings.Contains(src, "if scope != nil {") { t.Error("文案没有按 scope 有无分句") } if !strings.Contains(src, "你当前**没有**声明") { t.Error("★ 没有「未声明会话」这一句的文案 —— " + "调用方不声明 session_id 时读不到任何提示该传什么") } } func min2(a, b int) int { if a < b { return a } return b }