Skip to content

#1237 后续:room mask 四处跟进(doc 子代理措辞 / 好友 Bot 文案 / live A/B / catch 改 fail-closed) #1271

Closed
3 of 3 issues completed

Description

@Birfy

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

  1. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    area:automationScheduled tasks and automation
    bugSomething isn't working
    on Oct 26, 2026
  2. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    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」(这个是对的)。
    • 第 4 节落点: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。

    四节的前提我各自独立验过

    1. 房间子代理确实同被 mask:房间 Session 的子代理 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 判据也是对的(子代理跑的就是房间那一轮)。
    2. 守卫顺序确实是房间分支 :102 在 remote :111 之前,allowed :45 对 bot.remote !== undefined 恒假;room-participant.js:20-22 只给本地 member 建 Session 并写 groupOwners,remote Bot 拿不到本地房间 Session。同意这条只记待正、不加测试;也同意不许写成「已修未验证」。
    3. #1237 正文验收第一条确实是 core/dev/llm-tap.mjs 的 ablate、≈5.2k(±10%);指向的 #796 评论 6408530441 实存(issue comment),读数在里面。#1237 关闭后这条无单承载,由本单接住是对的。
    4. catch 的失败方向确实是「先销毁旧掩码、再建新掩码」::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 也是他的)。

    拆法与派单

    • 四节各自独立 = 四个 PR,前三张写 Refs #1271,只有最后一张写 Fixes #1271;否则第一张合入就会把后三节一起关掉(#1265 同一口径)。
    • 第 4 节的 PR 正文必须有一句写清选了 (a) 还是退路。
  3. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev3
    Owner: Dev3
    Claim: Dev3

    认领 #1271,按 §4 → §2 → §1 三张 PR,各自一条干净分支,都从 main 7de4edf2 开:

    节 分支 收尾
    §4 catch 改 fail-closed(唯一动逻辑) dev3/1271-catch-fail-closed Refs #1271
    §2 好友 Bot 拒绝文案 dev3/1271-room-refusal-wording Refs #1271
    §1 doc 子代理措辞 dev3/1271-doc-subagents Fixes #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 原文。

  4. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    and removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 26, 2026
  5. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    §1 已由 #1283 合入(merged_at 11:23:19Z,main 7adc414e882ee82f0cbb7795c7cfc0a1b46c2fac),§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、checkpoint issue: 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

  6. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev3

    §3 live A/B 做完了:provider 口径实测 5,149 token/次请求(估算 5,193,差 −0.85%,在 ±10% 内)。

    方法与读数(2026-10-26T11:40Z 左右实跑,两次请求):

    • 抓的是一次真实请求面:Session session-c02eb80b-… 的 turn 2 / step 1,request/header 记的 provider opencsitool、model DeepSeek-V4.1-Flash-1017、113 个工具、其中那 8 个 schema 共 20,355 字符(mcp__composio__ 四个 + schedule_create/list/update/delete)。
    • 请求体按该 Session 的日志逐字节重建:system 27,706 字符 + 该步已追加的 6 条消息 13,907 字符 + 工具块 116,404 字符。
    • A = 原样(含 8 个 schema):prompt_tokens 44,804;B = 同一请求体删掉那 8 个 schema、其余不动:prompt_tokens 39,655。
    • delta = 5,149 token / 次请求,即 20,355 字符 ÷ 3.953 字符/token = 5,149 —— 与「20,355 ÷ 3.92 ≈ 5,193」相比 −0.85%。

    保真度与口径注意:

    • 重建体 A 的 44,804 与同一调用日志里 provider 报的 inputTokens 44,656 只差 +148(+0.33%),所以这不是拿字符数顶替,确实是 provider 自己数的 token。
    • A/B 两次 prompt_tokens_details.cached_tokens 分别是 38,912 / 12,288;prompt_tokens 含命中缓存的部分,因此 5,149 是 token 数,不是账单差额。房间侧 prefix cache 的净变化仍如 #1237 所述另算,这条只钉住「那 8 个 schema 在请求里占多少 token」。

    落点(按指令二选一):

    • 没有开 PR。仓库里没有把估算写成「实测省 5,193 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%,那句话作为近似仍然成立,所以没有可修的错句。
    • 本评论即本次结论的落点;自本次起,收益可以写「实测 5,149 token/次请求(provider 口径,±10% 内)」,但仍不得写成「省 5,193 token」(那是换算值)。

    测量脚本只在本机 dev 侧(不进仓库、不动 core/src/** 的发送内容):从 Session 日志取 system/message + 消息序列 + request/header.tools 重建请求体,A/B 各发一次读 usage.prompt_tokens。

  7. Birfy commented on Oct 26, 2026

    @Birfy
    CollaboratorAuthor

    All four sections of this follow-up from #1264 are now landed, so it closes here.

    § what landed as
    1 a room's tool mask also covers the sub-agents it starts #1283 (7adc414e), closing #1286
    2 the tool list a refusal cannot narrow #1275, closing #1279
    3 the ablate/live A/B left over from #1237 measured in the comment above (6415091240), no code change needed
    4 a tool list that cannot be narrowed keeps its previous mask #1274, closing #1280

    §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, delta 5,149 token per request — −0.85% against the 5,193 the issue estimated, inside ±10%. The rebuilt body reproduces the provider's own inputTokens 44,656 to +148 (+0.33%), so this is the provider's count, not a character conversion. prompt_tokens includes cache hits (cached_tokens A 38,912 / B 12,288), so 5,149 is tokens, not a bill difference.

    The sentence at docs/subsystems/core-host.md:375 carries ≈ 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 (whoseAgent follows parentSession to the room Session, and inRoom/roomHidden name the eight tools accordingly) — the gap was in the wording, not in the code.

    Nothing else is outstanding on #1264's list.

  8. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    agent-prPull request opened by an agent
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:automationScheduled tasks and automationbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions