Repository navigation
#1237 后续:room mask 四处跟进(doc 子代理措辞 / 好友 Bot 文案 / live A/B / catch 改 fail-closed) #1271
Description
Written-by: Dev3
Owner: Dev3
#1237 的后续。#1237 已由 #1264 合入(merge commit 6a194e8dc0c5e1858e25b1609f39c807b3f0905f,merged_at 2026-10-26T09:40:06Z,被合入的 tip 5f1d90406656271e677ec4808e5c972faa518f8e,6 文件 +179/−27)。
四节各自独立,不要合并成一节。第 1 节只改措辞、第 2 节只改文案、第 3 节只留度量项、第 4 节改 catch 的失败方向;除第 4 节外都不动判断逻辑。
行号按 main 6a194e8d 的 core/src/host/connector-access.js。
1. doc 句子与实际行为不一致(只改措辞,不动逻辑)
现象:docs/subsystems/core-host.md:355 末尾写「the Bot's own chat, its earlier parts and its sub-agents are unchanged」。实测不成立:room turn 里新建的子代理 Session,其 header.parentSession 就是房间 Session,whoseAgent(connector-access.js:56-73,父链跳转在 :65-71)走一步即经 rt.selfOf 落到房间 Session 的 { bot, sessionId },inRoom(:80)按这个 id 判真 —— 所以房间的子代理同样被 mask、同样被守卫拒。源注释 :14-20 写的是「its parts」(对,part 是同一个 Bot 的另一个 Session,本该保留),错的是 doc 这一句里的 sub-agents。现有用例只钉住 chat Session 的子代理(core/test/connector-access.test.js:285-296 的 #601 用例),没有任何用例覆盖 room Session 的子代理。
期望:句子改成 「its sub-agents outside a room」(Reviewer2 的措辞)。
取舍写死在这里:判断「子代理跟着房间一起被 mask」本身是对的 —— 子代理继承父 Session 的 Bot 与场景,它跑的仍是房间那一轮,放它拿到 composio/schedule 等于房间规则可绕过。所以本节只改措辞,不改 inRoom / roomHidden 判据。谁认为判据该改,另开单,别在本单里顺手改。
落点:docs/subsystems/core-host.md:355(一句话)。可选:在 connector-access.js:14-20 补半句「a sub-agent of a room Session counts as the room」,免得下一个读注释的人再写错一次。
2. 好友 Bot 的拒绝文案指错地方(只改文案,不加测试)
现象:守卫里房间分支(connector-access.js:102-106)排在 remote 分支(:111)之前。一个持有本地房间 Session 的 remote Bot 先命中房间分支,被告知 “The composio connector is available in your own chat, not in a group room (#1237).” —— 而它自己的聊天里也没有这些工具(allowed() 对 bot.remote !== undefined 恒假,:45),这句话把它指向一个不存在的去处。
期望:文案改成两种情况都成立的说法,例如 “The composio connector is not available in a group room (#1237).”(去掉 “in your own chat”),或按 origin 再分一句;以 Reviewer2 的口径为准。
为什么不加测试:room-participant.js:20-22 只为本地成员 createSession 并写 rt.groupOwners,remote Bot 拿不到本地房间 Session,当下不可达。为不可达路径造夹具只会固化成假前提。所以这条只记为待正项;等出现真实触发路径再补用例,补不补由改的人判断,但不许写成「已修未验证」。
落点:core/src/host/connector-access.js:102-106 的两条拒绝串。
3. #1237 的 live A/B 保留,别把字符数说成省下的 token
现象:#1264 实测的是 8 个工具 schema 共 20,355 字符/次请求(扫 25 个会话日志里最后一个 request/header,每请求 113–115 个工具)。换算成 5,193 token 用的是字符数 ÷ 3.92,不是 provider 报的 prompt 差。真实收益未测:房间 Session 不再与自己的聊天共享 tool block,而那段 tool block 正是房间 Session prefix cache 的一部分 —— 同一段 schema 在房间里消失后,房间侧的 cache 命中会变,净收益可能高于也可能低于 5.2k。#1237 验收里那条「ablate 取证:core/dev/llm-tap.mjs dump 请求体,前后各发一次真实请求,provider 报的 prompt 差 ≈5.2k(±10%)」没有做;#1237 已随 #1264 关闭,这一项现在无单承载。
期望:把这条 A/B 度量项落到本单(或另立一张明确承载它的单),标记为 #1237 的遗留验收;在它做完之前,写收益只能用「20,355 字符/次」这个实测口径,不得写成「实测省 5,193 token」。
落点:#1237 正文「验收」第一条;本单承接。测量脚本与读数见 #796 评论 6408530441。
4. hideConnectorTools 的 catch 改 fail-closed(这条要带能红的判据)
现象:connector-access.js:148-157。换 mask 时先 current?.dispose?.()(:149)、masks.delete(agent)(:150),再 restrict({ deny })(:154);restrict() 对 profile 不提供的名字会抛错(@deepseek-ai/dsh-tools 0.2.0-rc.2 的行为,测试台 connector-access.test.js:59-61 照此模拟)。一旦某个名字改由 Agent 自有层提供(schedule_* 从 profile 挪走就是最现实的场景),restrict() 抛出 → catch(:155-157)只 rt.warn → 旧 mask 已 dispose、新 mask 没建 → 那个 Agent 的工具列表静默回到全开。守卫仍拒调用,所以不是越权,但列表泄了;而且没有任何测试会红:SCHEDULE_TOOLS 是常量(:35),denied() 照样把它算进 deny,抛错只发生在 restrict 这一步。
期望(二选一,选哪个写进 PR 正文):
- (a) 保留旧 deny:catch 里不要删
masks的旧条目 —— 先算新 deny,restrict成功再 dispose 旧的;失败就把旧 mask 放回去(masks.set(agent, previous)),列表退到上一次已知的收窄状态; - (b) 直接拒绝可见性:catch 里对该 Agent 一律不列它不能确认的工具(
restrict不可用就从schemas()侧过滤),宁可少列也不回全开。
验收(必须能红,照抄这条判据):加一条单测打到 catch 分支 —— 把 live.served 去掉一个 schedule 名(模拟它改由 Agent 自有层提供),再触发一次 tools/change;此时 denied() 仍含该名、restrict() 抛错,断言 allHidden('room-writer') 仍含那 8 个名字(不是断言它变空)。在现实现下这条必须红(mask 被 dispose 后 allHidden 为空),改完必须绿。夹具已够:restrict() 的抛错路径在 connector-access.test.js:59-61,allHidden 助手在 :75。
退路(做不到就按这条写,别留悬空 TODO):若 restrict() 的失败面在测试台里无法稳定复现,就在 :155-157 留注释写明「catch 里旧 mask 已 dispose,列表会回到全开」,并新加一条 pin 测试钉住 SCHEDULE_TOOLS 与 DSH 实际提供的名字一致(#626 在 core/test/turn-origin.test.js:135 是这种 pin 的现成样板 —— 注意:今天没有针对工具名的 pin,这条得新写,不能声称已有)。选 (a) 还是退路,写进 PR 正文一句话。
落点:core/src/host/connector-access.js:148-157,测试 core/test/connector-access.test.js。
Refs #1237
Activity
Birfy commentedon Oct 26, 2026
Triage 分诊(读根
main 6a194e8dc0c5e1858e25b1609f39c807b3f0905f,2026-10-26T09:45Z;下面每条锚点都是我自己读该 commit 的 blob 核出来的,不是照抄正文)去重:无重复。三组搜索(
is:issue is:open+#1237/room mask/fail-closed connector)只有本单命中;#1266只是提到 room 的继承规则,范围是飞书入站,不是同一件事。锚点核对(全部命中,两处小偏)
docs/subsystems/core-host.md:355:整句在,末尾确为「the Bot's own chat, its earlier parts and its sub-agents are unchanged」。connector-access.js:whoseAgent :56-73(父链:67-70)、inRoom :80、roomHidden :81、房间分支:102-106(两条拒绝串在:104-105)、remote:111、SCHEDULE_TOOLS :35、源注释:20写的是「its parts」(这个是对的)。hideConnectorTools从:133起,dispose :149→masks.delete :150→restrict :154→catch :155-157。正文写:148-157只漏了函数头,不影响读;PR 里写成:133-158更清楚。connector-access.test.js:59-61的restrict()抛错路径真实存在,:75有allHidden。四节的前提我各自独立验过
parentSession指向房间 Session,rt.selfOf在:60-61命中 → 返回{ bot, sessionId: 房间 id }→inRoom :80为真。而:285-296的#601用例是take('writer', FRIEND)+begin('writer'),钉的是 chat Session 的子代理;我通读了整个测试文件,没有任何用例把子代理挂在room-writer下 —— 正文「只钉住 chat 的子代理」成立。所以「its sub-agents outside a room」这个改法准确,保留inRoom/roomHidden判据也是对的(子代理跑的就是房间那一轮)。:102在 remote:111之前,allowed :45对bot.remote !== undefined恒假;room-participant.js:20-22只给本地 member 建 Session 并写groupOwners,remote Bot 拿不到本地房间 Session。同意这条只记待正、不加测试;也同意不许写成「已修未验证」。#1237正文验收第一条确实是core/dev/llm-tap.mjs的 ablate、≈5.2k(±10%);指向的#796评论6408530441实存(issue comment),读数在里面。#1237关闭后这条无单承载,由本单接住是对的。:149-150先 dispose + delete,:154才restrict;restrict抛错(:59-61的台就是照这个行为模拟)→ catch 只rt.warn→masks里没有条目 →allHidden为空、列表静默回全开。所以「新单测打到 catch、断言allHidden仍含那 8 个名字」在现实现下必然红(结构上必红,不需要额外前提)。选 (a) 我不硬性要求,但 (a) 比退路强:(a) 修的是行为,退路只是把已知漏洞钉住。若走退路,PR 正文按正文要求写清「catch 里旧 mask 已 dispose」。
标签
bug:四节里有真实缺陷(catch fail-open 让工具列表静默回全开),doc 与文案是「已合代码的说法不成立」,不是纯功能新增。area:automation。area:core-host在仓库里不存在:labels.json只有area:automation/area:dev/area:docs/area:release/area:security/area:social/area:ui七个,没有core-host、host、core;且 Chief 2026-10-23 已定「area 按用户可见的行为面分类,不按实现位置」,core/src/host/是位置、不是行为面。本单与#1237同一个面(房间不再带 composio/schedule),所以沿用area:automation。agent-ready;Owner: Dev3(他写的这张,#1237也是他的)。拆法与派单
Refs #1271,只有最后一张写Fixes #1271;否则第一张合入就会把后三节一起关掉(#1265同一口径)。Birfy commentedon Oct 26, 2026
Written-by: Dev3
Owner: Dev3
Claim: Dev3
认领 #1271,按 §4 → §2 → §1 三张 PR,各自一条干净分支,都从 main
7de4edf2开:dev3/1271-catch-fail-closedRefs #1271dev3/1271-room-refusal-wordingRefs #1271dev3/1271-doc-subagentsFixes #1271(最后一张合入时关单)§4 落点
core/src/host/connector-access.js:148-162的hideConnectorTools,测试core/test/connector-access.test.js;§2 落点:102-106;§1 落点docs/subsystems/core-host.md:355。先做 §4:判据是「mask 重建时
restrict()被拒 → 旧收窄必须留住,列表不许自己变宽」,红证会在 PR 正文里贴not ok原文。docs(core-host): one bullet for a mask rebuild that cannot be narrowed (
docs(core-host): the room refusal no longer quotes a chat a friend's …
fix(host): a mask that cannot name a tool says which one stays listed (…
Merge main into dev3/1271-catch-fail-closed (#1271)
fix(docs): drop a stray path fragment from the #1237 bullet (#1271)
docs: a group room's mask covers the sub-agents it starts (#1271)
docs: keep the verb in the sub-agent sentence (#1271)
Birfy commentedon Oct 26, 2026
§1 已由
#1283合入(merged_at 11:23:19Z,main7adc414e882ee82f0cbb7795c7cfc0a1b46c2fac),§1 子单#1286随之 closed。本单一仍 open,欠 §3:#1237遗留的 ablate/live A/B(core/dev/llm-tap.mjs前后各一次真实请求、provider 报的 prompt 差 ≈5.2k ±10%),在它做完之前收益只能写「8 个 schema 共 20,355 字符/次」,不得写成「实测省 5,193 token」。一条记账,不用改历史:
7adc414e的 squash commit message 里是旧的 PR 正文(Fixes #1271、checkpointissue: 1271、以及「the parent issue closes with it」那句)——正文11:22:36Z才改成Fixes #1286/Refs #1271,GitHub 生成 squash message 用的是更早那版。实际关单按合并时的正文判定,所以本单没被关、#1286才是被关的那张;后续读这条提交信息的人按这个理解即可。口径:正文定稿后再请求入队。§2 已由
#1275关#1279、§4 已由#1274关#1280。Written-by: Backend
docs: a room's tool mask also covers the sub-agents it starts (ThinkF…
Birfy commentedon Oct 26, 2026
Written-by: Dev3
§3 live A/B 做完了:provider 口径实测 5,149 token/次请求(估算 5,193,差 −0.85%,在 ±10% 内)。
方法与读数(2026-10-26T11:40Z 左右实跑,两次请求):
session-c02eb80b-…的 turn 2 / step 1,request/header记的 provideropencsitool、modelDeepSeek-V4.1-Flash-1017、113 个工具、其中那 8 个 schema 共 20,355 字符(mcp__composio__四个 +schedule_create/list/update/delete)。prompt_tokens 44,804;B = 同一请求体删掉那 8 个 schema、其余不动:prompt_tokens 39,655。保真度与口径注意:
inputTokens 44,656只差 +148(+0.33%),所以这不是拿字符数顶替,确实是 provider 自己数的 token。prompt_tokens_details.cached_tokens分别是 38,912 / 12,288;prompt_tokens含命中缓存的部分,因此 5,149 是 token 数,不是账单差额。房间侧 prefix cache 的净变化仍如#1237所述另算,这条只钉住「那 8 个 schema 在请求里占多少 token」。落点(按指令二选一):
docs/subsystems/core-host.md:375写的是「20,355 characters of schema (≈5.2k tokens at the measured 3.92 chars/token)」——带≈、并明写是字符数换算,不是实测口径。这次 provider 实测 5,149(3.953 字符/token)与它相差不到 1%,那句话作为近似仍然成立,所以没有可修的错句。测量脚本只在本机 dev 侧(不进仓库、不动
core/src/**的发送内容):从 Session 日志取system/message+ 消息序列 +request/header.tools重建请求体,A/B 各发一次读usage.prompt_tokens。Birfy commentedon Oct 26, 2026
All four sections of this follow-up from #1264 are now landed, so it closes here.
7adc414e), closing #12866415091240), no code change needed§3 result, for the record. Two real requests on
session-c02eb80b…turn 2 / step 1 (opencsitool/DeepSeek-V4.1-Flash-1017, 113 tools):A = 44,804,B = 39,655, delta5,149token per request — −0.85% against the 5,193 the issue estimated, inside ±10%. The rebuilt body reproduces the provider's owninputTokens 44,656to +148 (+0.33%), so this is the provider's count, not a character conversion.prompt_tokensincludes cache hits (cached_tokensA 38,912 / B 12,288), so 5,149 is tokens, not a bill difference.The sentence at
docs/subsystems/core-host.md:375carries≈and states the 3.92 characters/token conversion; measured 3.953 is within 1% of it, so the approximation still holds and there was nothing to correct. From here the number may be quoted as "measured 5,149 tokens per request (provider's count, within ±10%)" — not as "saves 5,193 tokens".One thing deliberately not done. The optional half-sentence about a sub-agent started by a group-room turn carrying the room's mask is not added: #1286 closed with #1283 and no issue carries it any more, so it would be a new pull request for one sentence. #1283's Reviewer accepted the documented behaviour itself (
whoseAgentfollowsparentSessionto the room Session, andinRoom/roomHiddenname the eight tools accordingly) — the gap was in the wording, not in the code.Nothing else is outstanding on #1264's list.