fix(agent)★★: 会话作用域拒绝文案不再拼出「你当前在会话 。」
2026-10-05,比对 MCP 与插件接入能力时实测发现。
## 症状
独立 MCP 宿主读自己刚收到的那封信,得到:
这封信不在你当前所在的那条会话里:…你当前在会话 。要读它,…
`你当前在会话 。` 中间是空的。
## 根因:一个 reason 值管三种拒绝,文案却只有一句
`AgentMayReadSession` 用**同一个 `reason`** 覆盖三种情形:
① 没声明 session_id(scope == nil)
② 声明了别人的会话
③ 声明了但**不是参与方**(session_id 由请求方提供,自述不是事实)
而 `canReadSession` 无条件拼 `"你当前在会话 " + self + "。"`,
于是 ① 拼出那句残缺文案。
★ 为什么这句残缺文案比「不说」更糟:模型读到「你当前在会话 。」
会以为自己在一个**无名会话**里,于是转去猜/去搜 ——
而正确做法(从 new_mail 事件取 session_id)就在同一句话的后半段,
它却读不到前半段。
## 修法
按 `scope` 有无分句,**不给空值留位置**:
① → 「你当前**没有**声明自己所在的那条会话,所以无从判断这封信是不是你的。」
② → 「你当前在会话 00000000-…」(仍只报自己声明的那条)
②③ 故意不拆开:`session_workspace.go:171` 已经警告过「新增 reason 还要同步改
handler 的 switch,漏改就是 500」,而两者给模型的指引**完全相同**
(都是"去那条会话读"),拆开只增加分支不增加信息。
## 实测(真实请求,部署后)
① 不声明 → 「你当前**没有**声明自己所在的那条会话…」 无残缺 ✓
② 声明他人 → 「你当前在会话 00000000-…」(自己声明的那条)✓
★ 「会话 。」残缺句已消失
## 判据
`TestSessionScopeHintNeverLeavesEmptySessionSlot` —— 盯**结构**而非文案本身:
- 禁止再出现 `"你当前在会话 "+self+"。` 这种无条件拼接
- 要求按 `scope != nil` 分句
- 要求存在「未声明会话」那一句
两个变异都转红:退回无条件拼接 / 删掉「未声明」那句。
## 顺带确认:MCP 与插件的工具面已对齐
实测 `tools/list`(11 个)与 4 个插件的真实注册项做集合比对:
MCP 有、插件没有: 无
插件有、MCP 没有: 无
⚠️ 中途差点误判:grep 到插件里有 `permission_decision` 等 3 个词,
以为 MCP 缺 3 个工具。查下去 `permission_decision` 是 **mail_type**
(收到授权请求邮件后转给 worker),另两个只存在于 `src/pool.mjs`
和测试里,**从不暴露给模型**。
This commit is contained in:
@ -98,13 +98,21 @@ func canReadSession(w http.ResponseWriter, r *http.Request, agentName string, sc
|
||||
// 记忆是隔离的」。所以**不再拿工作区当理由**(旧文案就是 cross-workspace,
|
||||
// 它让 zcode 推断出"那五位 agent 不存在",把错误结论写进了给人看的信里)。
|
||||
// 报出"我当前在哪条会话"是安全的(那是调用方自己的);**不报**目标会话在哪。
|
||||
self := ""
|
||||
//
|
||||
// ★ 2026-10-05:`reason` 是**一个值管三种拒绝**(没声明 / 声明了别人的 /
|
||||
// 不是参与方),而「你当前在会话 <self>」在 self 为空时会拼出
|
||||
// 「你当前在会话 。」—— 实测(独立 MCP 宿主读自己那封信)确实读到这句,
|
||||
// 残缺的话会让模型以为自己在一个无名会话里,转而去猜。
|
||||
// 所以先按 self 有无分句,**不给空值留位置**。
|
||||
var where string
|
||||
if scope != nil {
|
||||
self = (*scope).String()
|
||||
where = "你当前在会话 " + (*scope).String() + "。"
|
||||
} else {
|
||||
where = "你当前**没有**声明自己所在的那条会话,所以无从判断这封信是不是你的。"
|
||||
}
|
||||
Error(w, http.StatusForbidden,
|
||||
"这封信不在你当前所在的那条会话里:每个会话各有各的收件箱(会话之间的记忆是隔离的)。"+
|
||||
"你当前在会话 "+self+"。要读它,就在它那条会话里读 —— 每封来信都会把 worker 唤醒到"+
|
||||
where+"要读它,就在它那条会话里读 —— 每封来信都会把 worker 唤醒到"+
|
||||
"它自己那条会话上,那时你看到的就是那条会话的收件箱。"+
|
||||
// ★ 2026-10-04 补:调用方多半是**拿不到**目标会话 id,而不是
|
||||
// 「知道却不传」。原来那句「就在它那条会话里读」只说了去哪读,
|
||||
|
||||
@ -58,13 +58,56 @@ func TestSessionScopeHintTellsWhereToGetSessionID(t *testing.T) {
|
||||
t.Error("★ 只说了「要传 session_id」却没说**从哪拿** ——" +
|
||||
" 那等于把问题原样推回去,宿主仍然读不到信(实测卡点)")
|
||||
}
|
||||
// ③ 不得泄露目标会话 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
|
||||
|
||||
Reference in New Issue
Block a user