diff --git a/server/internal/handler/agent_discovery.go b/server/internal/handler/agent_discovery.go index 164938f..9b4045a 100644 --- a/server/internal/handler/agent_discovery.go +++ b/server/internal/handler/agent_discovery.go @@ -105,7 +105,18 @@ func canReadSession(w http.ResponseWriter, r *http.Request, agentName string, sc Error(w, http.StatusForbidden, "这封信不在你当前所在的那条会话里:每个会话各有各的收件箱(会话之间的记忆是隔离的)。"+ "你当前在会话 "+self+"。要读它,就在它那条会话里读 —— 每封来信都会把 worker 唤醒到"+ - "它自己那条会话上,那时你看到的就是那条会话的收件箱。") + "它自己那条会话上,那时你看到的就是那条会话的收件箱。"+ + // ★ 2026-10-04 补:调用方多半是**拿不到**目标会话 id,而不是 + // 「知道却不传」。原来那句「就在它那条会话里读」只说了去哪读, + // 没说 id 从哪来 —— 实测(独立 MCP 宿主 E2E)第一版客户端就卡在 + // 这里:拿到 new_mail 事件、知道该读,却不知道该传什么,于是一封 + // 信都读不到。 + // + // id 就在**那封信的事件载荷**里:new_mail 的 payload 带 session_id + // (实测 16 个键,含 session_id / session_alias / mail_id)。 + // 桥侧另有 GET /api/v1/agent/session/default 可查默认会话。 + " 目标会话 id 在 new_mail 事件的 payload 里(session_id 字段);"+ + "没有事件时可查 GET /api/v1/agent/session/default。") default: Error(w, http.StatusForbidden, "无权访问该会话") } diff --git a/server/internal/handler/session_scope_hint_test.go b/server/internal/handler/session_scope_hint_test.go new file mode 100644 index 0000000..9974631 --- /dev/null +++ b/server/internal/handler/session_scope_hint_test.go @@ -0,0 +1,73 @@ +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 拼进文案 —— 那是安全边界(只报自己所在的会话)") + } +} + +func min2(a, b int) int { + if a < b { + return a + } + return b +}