dsh-subagent-mgm
No description
- Stars
- 1
- Language
- JavaScript
- Created
- Oct 4, 2026
- Updated
- Oct 6, 2026
Introduction
dsh-subagent-mgm
DeepSeek Harness Web UI 插件(bundle):管理子智能体的目录顺序与右侧栏面板。
它做什么
-
子智能体目录按创建时间倒序。会话顶部「N 个子智能体」的展开菜单里,最新的子智能体排在最上面;展开的每一层下级目录同样按创建时间倒序。行的内容(状态点、标题、模式、活跃耗时、token 数、在侧边栏打开按钮、键盘导航、悬停展开)与原版一致,只改了顺序。点进某个子智能体会话后,会话顶部那枚面包屑切换器(列出父会话的兄弟子智能体)同样被接管,也按创建时间倒序;它旁边保留「这个子智能体自己的子目录」计数入口,行为与原版一致。
-
跟随子智能体自动开合右侧栏面板。当前屏幕上这个会话有子智能体在运行时,自动在右侧栏打开它的会话;子智能体结束时自动关闭该面板。
- 结束时的关闭不限于当前屏幕上的会话:子智能体在后台会话里结束,也会把它留在这个会话侧边栏里的面板关掉,切回该会话时不会再看到已经结束的子智能体聊天。
- 已经存在的子智能体被父智能体再次派活(可继续模式)、重新开始工作时同样会自动打开;若它的面板还开在后台标签里,则把它拎到前台并展开右栏,而不是再开一个。
- 每次运行只自动处理一次:同一个子智能体连续运行期间不会反复把右栏抢回前台,用户可以自己切走;切换会话再回来也不会重新抢。
- 用户手动提前关闭:该子智能体本次运行期间不会再被自动打开(跨会话切换同样不会),下次重新运行时会恢复自动打开。
- 用户手动打开:插件不会去关它,除非该子智能体结束。
- 打开只处理当前会话的直接子智能体;面板打开方式是「已在则复用」,不会为每次自动打开强行新建分栏。
-
设置页:设置里多一页「子智能体管理」(排在官方插件设置页之后)。四个开关——
- 子智能体目录/面包屑按创建时间倒序(关掉即回到官方原顺序);
- 子智能体开始运行时自动打开右侧栏面板;
- 子智能体结束时自动关闭该面板;
- 把已开在后台的面板拎到前台(关掉就完全不抢焦点,只保证面板已存在)。
保存会写入本插件的设置文件,可一键恢复默认;刷新后仍然生效,页面上同时显示当前生效值与设置文件路径。
-
查看某个子智能体「有什么」。目录里每一行多一个证件卡按钮,点开该行的能力面板(菜单会临时加宽),四段只读信息——
- 这份工作是什么:模式(可继续 / 一次性)、此刻是否在运行、唤醒方式、预设、它这一次运行所用的模型与推理档位、工作目录、层级深度、父会话、创建时间,以及委派时设定的工具过滤(allow / deny);
- 工具:它在最后一次请求里实际拿到的工具清单与描述,每个工具可展开看它的参数 schema;
- SKILL:这个会话(它的预设 + 工作目录)当前可见的技能清单,并标出哪些是「仅智能体可调用」;宿主没挂技能注册表时会明说,而不是显示成空;这次委派没把
skill工具给它时,这一段不再列技能,而是直接说明它看不到任何技能——技能目录只发给手里有skill工具的智能体,所以「这个会话可见」并不等于「这个子智能体可见」; - 人格:委派记录下来的 persona(权威);没有记录时,由它的系统提示与该父会话首次组成时的系统提示做差分恢复,并注明这是推断;如果恢复到的这一行其实是宿主按运行模型填进人格槽位的,会标注「按运行模型生成」。
内容全部由宿主只读推导(浏览器读不到别的会话的日志)。面板有返回按钮,Esc 退回目录,加载失败可重试。
-
在目录里找子智能体,并看清这一屏的用量。展开菜单顶部多了一行控制条和一条汇总带——
- 搜索与筛选:按名字 / 标题 / 工作目录做大小写不敏感的文本匹配,另有「全部 / 已结束 / 仅一次性」两个筛选维度(可叠加)。命中行会连同它的祖先一起显示(祖先只是路径,否则你就看不到它挂在谁下面),并且层级自动展开、不可再折叠;过滤期间展开箭头不再可点,避免「展开了但看不到东西」。什么都没命中时给一句话,而不是一片空白。
- 汇总:分两行摆,各自换行也不会互相挤压——上面一行是计数(这一屏已加载的行数、其中在运行的个数,以及拿不到投影的「N 个没有用量数据」),下面一行是用量(token 合计、缓存命中率、活跃耗时合计,悬停都给精确值)。当前这一屏的会话自己也算进合计,并在合计里点名它的份额(「合计 1.2M(本会话 320K)」),悬停给出它自己的缓存读 / 缓存写 / 提示 / 未命中:它不在目录里,所以不算一行、计数不变,但它的 token 和缓存桶和这些子智能体花在同一处。拿不到投影的行不会算成 0。
- 缓存命中:命中率 = 缓存读 / 提示侧用量(提示侧用量 = 未缓存输入 + 缓存读 + 缓存写,输出 token 不算),与聊天页底部的口径一致。命中不足 100% 时不会四舍五入成 100%(99.99% 就显示 99.99%),悬停给出「缓存读 / 缓存写 / 提示 / 未命中」的精确拆分。整棵树先按桶求和再取比值,所以它是「这一屏的真实命中率」,不是各行命中率的平均;没有用量的行不进分母,一行用量都没有时干脆不显示这一项。
- 按模型分组:点一下按钮才去读——模型只写在各自会话的日志里,浏览器拿不到,所以这是一次按需的只读批量请求,读完就按模型分好组(每个模型一行:token 合计、该组的缓存命中率、命中行数;由委派指定、还没跑过的行标注出来)。读不到就报错并可重试,不假装成「未知模型」。这一屏的会话自己也在被统计的节点里(它有自己的模型),所以某个模型行末尾的 × 计数把它算作一个。
-
侧边栏会话行的悬浮摘要。鼠标停在侧边栏任意会话行上,弹出的卡片里会在官方那行「N 个子智能体运行中」上方多出一段摘要——
- 计数:这一屏的子智能体数、其中在运行的个数;拿不到投影的按「N 个没有用量数据」另算,不算成 0。
- 用量:token 合计(含这个会话自己,并点名它的份额)、缓存命中率、活跃耗时合计。口径与目录里那条汇总带完全一致(同一个折算函数,不重新实现一遍),悬停同样给精确值与缓存拆分。
- 最多 4 个直接子行:状态点、标题(长了省略)、token、命中率;其余只报个数。卡片是悬浮出来的,不宜长成一棵树。
- 纯客户端、只读:只读浏览器已经持有的那两份 store,不读日志、不发请求、不写任何状态;行也不做成按钮——卡片上的点击已经被工作区用来复制会话标题了。
- 没有子智能体的会话也有内容:这类会话原来的卡片只有官方的「空闲」一行,现在摘要改报它自己的用量——「本会话 320K · 缓存命中 92.3% · 总用时 12m30s」。总用时来自宿主折叠好的
sessionStats投影(模型时间 + 工具时间),与子行那个「工作时间」是同一类数字;三项各自独立,算不出就不显示,全都没有(比如刚建出来还没说过话的会话)则整段不出现,不报 0。 - 立即弹出、且不会叠着:鼠标停在会话行上不再等官方那 800ms,卡片马上出现;新卡一弹出,先前那张立刻收掉(不等官方那 200ms 指针宽限),所以连续划过多个标题只会剩最后停下的那张。只划过一行、没有新卡弹出时,移开鼠标后的消失延迟保持原样。
注意它只列直接子行(更深的下级只计入计数),也不做模型分组(模型只在日志里,那要用上面那个按需按钮)。官方那行「N 个子智能体运行中」由工作区自己渲染,插件删不掉它,只是排在它前面。立即弹出是运行期包住那支开卡定时器做到的(没改官方文件,也没替换任何导出——原语导出是 shell 冻结的),所以共用那个原语的其他卡片(工作区行、预览卡)同样立即展开;「新卡替掉旧卡」则是在同一处把官方那支 200ms 指针宽限一并接管:只有“旁边又弹出一张卡”时才提前收,单纯把鼠标移开仍是官方那 200ms。
实现要点
- 浏览器半边通过
window.__ModuleLoader__.load({ id: 'dsh-subagent-mgm', factory })注册,exports.inject声明所需服务:sessions、uiWorkspace、slots、locale、sidebarRight。 - 目录:以 list slot 的 shadow 机制(同一
id、更低priority生效)接管conversation.session.header.actions里的subagent-catalog座位,复用官方@deepseek-ai/dsh-client-ui-subagent的标记结构、样式与行为,类名前缀改为smgm-,文案注册在自有命名空间subagentMgr。 - 面包屑:子智能体会话的标题位是 single slot
conversation.session.header.lineage,官方在这里放的就是那枚兄弟切换器。single slot 没有id可复用,只能用更低的priority: -1抢在官方(priority 0)前面,否则进入子智能体后会退回原版顺序。 - 面板:订阅
ctx.sidebarRight.mounted、ctx.sidebarRight.openTabs、ctx.sessions.list、ctx.uiSession.sessionStatus,每次变更后先按「openTabs里每个会话的已开标签 ∩ 运行状态」关掉已经结束的子智能体面板(对屏幕上的会话和后台会话一视同仁),再按「当前会话的目录 ∩ 运行状态 ∩ 已打开标签」对账自动打开;标签地址形如dsh-resource://subagentchat/session/<child>?parent=<parent>&mode=<mode>,与官方子智能体聊天资源提供者一致。运行态记录(是否见过它运行、是否被用户手动关过)按子会话 id 全局记账、跨会话切换保留,只有「本次已拎到前台」和投影重试记录会在切换会话时重置。 - 设置页:在官方
settings.section(list slot,scope root)注册一页,id: subagent-mgm、order: 37、label: () => t('settings.nav')。开关存在浏览器侧的订阅式 store 里,目录排序和面板对账各自在每次计算时读一次,所以改完立即生效,不需要重启,也不需要刷新页面。 - 宿主半边:用
ctx.inject(['webServer'], …)注册prefix路由/api/subagent-mgm,其/settings支持 GET / POST / DELETE;设置写入<DSH_PROFILE_DIR|DSH_HOME|~/.dsh>/subagent-mgm.json(先写临时文件再 rename 原子替换,权限 0600),字段优先级为「已保存文件 → 本行 Config → 内置默认」,行默认值写在本 bundle 的cordis.patch.yml。路由沿用与 connection 服务相同的同源栅栏:没有认证的非浏览器客户端收到 401,跨站 Origin / 伪造 Host 收到 403。 - 能力面板:同一前缀下还有
/face?sessionId=…&parent=…(GET,只读)。宿主用sessionQuery.readSession读该会话的持久日志,折叠出首个subagent/descriptor、最后一次request/header的工具与模型、以及人格;技能来自可选的sessionSkillCatalog.list(服务缺失 →unavailable,抛错 →error,都不影响其余字段);宿主给的是这个会话可见的整份目录,而「是否列给用户看」由客户端按同一次委派的工具面定:skill不在那次工具面里(allow 里没写它、deny 里写了它,或最后一次请求的工具清单里根本没有它)时,这一段只印一句说明、不列技能,于是同一次委派的面目与技能段不会自相矛盾。人格三级来源:descriptor 里的记录(权威)→ 与父会话系统提示做行级差分(公共行前缀 ≤ 2 行才认,因为 persona 槽位紧跟 harness 身份行;恢复到的通常是宿主按模型生成的那一行,用返回体里的generated标注)→ 都没有就为null。差分只比双方日志里的第一份系统提示:会话中途换模型会重写人格槽位,那份晚到的组成与「这次委派给了它什么」无关——真实日志里确实出现过,按最后一份比会凭空造出人格。父会话 id 优先取会话头里的parentSession,前端再把当前打开的会话作为parent提示兜底(老日志没有该字段时仍能恢复)。客户端只渲染返回体,自己不读日志。 - 过滤与汇总:全部是客户端行为,只在这一屏已加载的目录投影上做,不读任何日志、不发任何请求。过滤是每层独立裁剪(子层是自己按
catalogOf(childId)现取的,不是从父层继承),谓词是「自身命中,或任一已加载的后代命中」——所以祖先会作为路径留下,也计入汇总:汇总说的是「屏幕上这些行」,因此按「未运行」筛出来的结果里仍可能报 1 行在运行(那是被留下的祖先)。当前这一屏的会话本身也走同一套记账(catalogTotals里只有一个内部折算函数,行与它共用,两边不会漂开):它不占行数,但 token 与缓存桶进合计;去留也由同一个过滤谓词决定,于是「仅一次性」会把它排除(它不是委派),状态筛选只在它自己匹配时留下它,而它一旦被留下就不会再作为行被重复计数(先记进去重集合)。没装宿主新半边时按模型分组会显示读取失败。缓存命中率也在这批行上算(含这一屏会话),但先按桶求和再取比值:比例之和 ≠ 比例的平均,逐行算好再相加会让整屏数字和单份日志的口径漂开;没有用量的行不进分母,分母 ≤ 0 时返回null(这一项不显示),部分命中朝下取整而不是进位到 100%——99.99% 就是 99.99%。 - 模型批量:
GET /api/subagent-mgm/models?sessionIds=a,b,c(GET,只读)。每个 id 过一次SESSION_ID校验、去重、上限 64 条(超了 400),逐 id 读一次sessionQuery.readSession折叠出「委派指定的agentModel(descriptor)」与「这一轮实际在跑的model(最后一次request/header)」,外加模式与预设。单条失败只影响那条:日志不存在 →state: "missing",读失败 →state: "error"带上原因,整批照样 200。客户端按totals.nodes里出现的 id 发起,拿回来按 sessionId 归档;用户在条上改了筛选后再点一次就重新读。 - 悬浮摘要:占用工作区的
sidebar.session.row.hover座位(list slot、scope root,id: subagent-digest、order: 5,排在同样占这个座位的官方排程插件order: 10之前)。座位只把这一行的sessionId交给占用者,没有 store 可以注入,所以插件在ctx.inject(['uiSession'], …)里现挂订阅:sessions.list的projectionsBySession/byId与uiSession.sessionStatus各订阅一次,卸载时一并退订(宿主半边与本插件其余的座位都不受影响)。数字直接复用catalogTotals(selfId就是这一行),所以它和目录里那条汇总带永远不会漂开。样式沿用工作区卡片自己的字面色(#cfd3d6/#adb2b8/#8c9299),不跟主题 token——那张卡片本身是字面暗色绘制的,跟 token 反而会在浅色主题下变成一块异色。摘要是只读的:没有按钮、没有请求、不写状态。没有子智能体时它报的「本会话」一行读的是宿主已经折叠好的sessionStats投影(llmMs + toolMs),与 token 用量在同一份projectionValues上,所以不需要新请求;会话摘要里并没有createdAt,因此「从建会话到现在的跨度」根本算不出来,只有工作时长。 - 立即弹出:同样不改官方包,也不替换任何导出——
@deepseek-ai/dsh-client-ui-primitives那份命名空间是 shell 用Object.freeze造出来发给每个 bundle 的,往它上面写HoverCard是一次静默空写(上一版实现就是这么失效的:写没生效,返回的却是「装上了」)。改在定时器上:installInstantHover(window, 0)同时包住window.setTimeout与window.clearTimeout。开卡那支认「回调正文里出现("open")(原语开卡就是() => { setPhase("open") },压缩后是()=>{D("open")})且延迟大于 0」,压成 0,其余参数原样透传;宽限那支认「回调先把某个 ref 写 null、紧接着调用另一个 ref」(原语usePointerGrace是setTimeout(() => { timerRef.current = null; closeRef.current(); }, 200),压缩后是setTimeout(()=>{n.current=null,o.current()},200)),延迟不写死、只认这个属性名形状——压缩器不改属性名,所以官方改毫秒数它照样命中。记下的宽限在开新卡时就地提前执行(先clearTimeout再照原样调用回调),新旧两卡因此落在同一次 React 提交里:旧的先卸载、新的再挂载。clearTimeout也包着,所以卡片自己取消掉的那次宽限(例如鼠标又移回原来那行)不会被背后补一刀,已经自然到点的也不会补第二刀。侧边栏会话行的卡片是 compact 变体,close()直接进"closed"、没有淡出,所以「当场收掉」就是真的当场消失。幂等(两个包装都带 Symbol 标记,二次安装直接返回 null);装不上(缺clearTimeout、只读、写入即抛错、写进去没生效)只报一条 console 警告、把已经写下的那一半放回去并保留官方延迟,绝不拖垮插件;卸载只在两个定时器都仍是我们那层时才各自还原,不掀掉后装的层。
自检
node verify.mjs(随包保留,不参与 bundle 装载):从 client.js 原文抽出 sortNewestFirst、chatAddress、panelChildId 与 startSubagentPanels,用假上下文驱动对账状态机,覆盖排序、面板身份解析,以及“运行即开 / 结束即关 / 用户关掉不再抢回 / 父智能体再次派活重新打开 / 已开面板只拎到前台不重复开”等断言;第二组专门覆盖“子智能体在后台会话里结束”——面板照关、切回该会话看不到残留、屏幕上没有会话(如设置页)时同样关,而用户手动为已结束子智能体打开的面板不受影响;第三组是针对面包屑半边的静态接线检查(shadow 优先级、兄弟切换器与自身计数入口、展开时补拉子目录、两侧字典与样式类齐全)。另有针对能力面板的两组:静态组检查行内按钮、加宽菜单、dialog 分支、两个字典里每个 face.* 键、四个分区的四态文案与 4000 字符截断;行为组用假日志直接驱动 /face 路由,覆盖 400 / 404 / 405 / 403、字段折叠(工具取最后一次请求、schema 原样传递)、人格的三种来源(委派记录 / 从系统提示恢复 / 不可恢复)、父会话被中途重组时不得凭空造出人格,以及技能注册表缺失或报错时的降级。第四组针对搜索/过滤与汇总:静态检查接线(控制条在树上方、树的 role 与键盘处理分居两层、过滤期间不可折叠、过滤沿递归下传、模型批量只按需发起),行为检查把 filterActive / entryMatches / subtreeMatches / catalogTotals / modelOf / groupModels 从原文抽出来,用一棵固定目录树(含嵌套的一次性子智能体、没有投影的行、指回祖先的环、同一父下重复列出的行)验证:命中祖先连带祖先一起出现、祖先也计入汇总、无用量算「未知」而不是 0、环不会无限展开、重复行只算一次、没答复的 id 归入「未知模型」且单独计数。第五组直接驱动 /models 路由处理器:顺序与去重、空参数 / 非法 id / 超过 64 条各自的 400、POST 405、跨站 403、未挂 session-query 时 503,以及「一条日志读不出来只让那一条变成 error,整批仍然 200」。缓存口径单独成组:分母只含提示侧用量(输出 token 再多也不进分母)、null 的三种成因(分母为 0、分母缺失、缓存读为 0)、部分命中不得进位成 100%、整批与每个模型分组都是先求和再取比值、没有用量的行不加分母,以及汇总带确实分成「计数」「用量」两行。父会话计入统计单独成组:用同一棵目录树加一份「树所依附的会话」的摘要,行为上验证它进合计、进缓存桶、排在行之前、只算一次,并且被同一过滤规则决定去留(文本/状态命中它才留,「仅一次性」必然排除它);静态上验证它和行走同一个折算函数、不会作为行重复计数、无用量时那两行仍然各自只有一行。悬浮摘要单独成组(纯静态——它渲染成 React 节点,没法像纯函数那样在 Node 里驱动):座位名与 order、占用者确实拿到 store 订阅与翻译函数、只订阅那两份 store、组件正文里没有 fetch( / readSession / 模型路由、数字确实复用 catalogTotals 并带上这一行的 id、没有子智能体时改报会话自己的用量(读 projectionValues.sessionStats、三项各自独立、全空才不出声)、只列 4 行其余报数、跟随排序开关、状态点与目录同款、会话自己的份额仍被点名、命中率仍是「算不出来就不显示」、七个样式类与卡片配色齐全。工作耗时与立即弹出两件小事另成一组,行为上直接驱动:sessionWork 把模型时间与工具时间折成一个时长、投影为空 / 全是垃圾值时不编数字、只数了轮次也照样保留;installInstantHover + uninstallInstantHover 用一只假 window 驱动(每次 setTimeout 记下实参、可以按需点燃,clearTimeout 也从待办里划掉)——开卡形状的回调(setPhase("open")、压缩体 D("open")、多空格的 setPhase( "open" )、成员调用)无论原本要等多久都压成 0,且替换掉的那个回调仍按原样带实参跑;宽限形状(压缩体 x.current=null,y.current()、源码式 ref.current = null; next.current();)被记下,开卡时先替它们收尾再开新卡(原本还要等 200ms 的那张当场收掉、同批只收一次),被 clearTimeout 取消过或已经自己到点的绝不再补一刀;形似而结尾是普通调用的 200ms 兄弟、setPhase("closed") / show() / setCopied / 字符串或 null 回调一律不动,延迟本来就不大于 0(0 / undefined / 负数 / 字符串)也不动;二次安装返回 null 且不叠第二层;没有 setTimeout、没有 clearTimeout、不是对象、只读属性、写入即抛错的目标一律安全返回 null 且不留半成品;卸载把两个真身都还回去,但绝不掀掉后装的层,重装也不背着上一层没烧完的待办;包装里不碰淡出路径(PREVIEW_FADE_MS / "closed" / closeDelay),强制的毫秒数只有参数、没有写死那个 800。共 333 项:除上述行为断言外,还包括设置页开关的行为与静态接线,以及直接驱动宿主路由处理器的 GET / POST / DELETE、越权与方法/字段校验、字段优先级链(临时目录里跑,不碰你自己的设置文件)。页面渲染本身是 React/DOM,只做静态接线检查,不驱动浏览器。
安装 / 卸载
安装(在 DSH 里让 agent 执行,或直接用插件管理器的 bundle 安装):
plugin_manager action: install_bundle target: D:\dsh\dsh-subagent-mgm
安装后刷新页面生效;若改动的是宿主半边(index.js / cordis.patch.yml),需要重装 bundle 或重启 dsh 才会装载(只改浏览器半边 client.js 刷新即可)。卸载用插件管理器的 remove_bundle,目标 dsh-subagent-mgm;卸载后官方原版目录立刻恢复(本插件只是 shadow 了那个座位,没有改动官方包)。
已知边界
- 「跟随自动打开」只覆盖当前会话的直接子智能体;更深的层级请用目录里的「在侧边栏打开」按钮。关闭不受此限:任何会话里已开的面板,只要插件见过它的子智能体在运行,结束时都会关。
- 刷新页面后从侧边栏布局里恢复出来的面板,如果其子智能体在刷新前就已经结束,插件不会去关它——它没看见那个子智能体运行过,只能当作你手动打开的面板。
- 自动打开的面板与用户自己打开的面板是同一个标签,因此关闭时两者无差别。
- 需要官方子智能体 UI 的聊天资源(
dsh-resource://subagentchat/...);若该提供者不可用,插件只保留目录倒序,并在控制台报错而不是反复重试。 - 能力面板里的人格只有三种结局:委派记录里写了(最常见)、从系统提示差分恢复、或者判定为「恢复不了」而显示为空。差分只认 persona 槽位——差异必须从双方首次系统提示的第 3 行开始,否则宁可显示为空,也不把别的 section 当成人格。没有委派人格的子智能体,恢复到的往往是宿主按模型生成的那一行,面板会明确标注,不要当成「谁指派的人格」。
- 能力面板里的工具是最后一次请求的清单:会话中途换预设或工具集后,显示的是当前生效的那一份,不是委派当时的那一份。SKILL 清单同理,是「此刻」这个会话可见的技能——但只有这次委派的工具面里确实有
skill时才会列出来:委派时没给它skill(allow 里没写、deny 里写了,或最后一次请求的工具清单里就没有),这一段只说明它看不到任何技能,因为技能目录只发给手里有skill工具的智能体。 - 过滤只能筛客户端确实拿得到的字段:名字、标题、工作目录、运行状态、模式。按模型或预设筛选做不到——会话列表给浏览器的那份摘要里没有模型/预设字段(预设只在会话头里,投影里也没有模型),模型只能靠上面那条按需批量接口读出来,所以它用于「按模型看用量」,不参与筛选。
- 汇总与过滤都只覆盖已经加载进这一屏的行:目录是按需分层的,没展开过的下级不在其中,条上的数字是「已加载」而不是「全部」。要给全部子智能体算总量,得先把目录展开到那一层。
- 计进合计的只有当前这一屏的会话自己(token 与缓存桶进合计),不是「整棵祖先链」:它就是这个目录挂靠的那个会话,也就是你正在看的那一个。它的活跃耗时没有记录(那边只有子委派的计时),所以「合计」的时长只覆盖真的被委派跑过的那些行,它只贡献 token 与缓存桶;而在子智能体里的面包屑切换器上,计进来的就是那个子会话自己。它的 token 只在会话摘要的投影里,摘要还没到时不显示(不会算成 0)。
- 缓存命中率是已经计费的那部分提示的命中率:分母只含未缓存输入 + 缓存读 + 缓存写,输出 token(以及没有投影的行)不进分母。所以它和聊天页底部那一行是同一口径,但和「token 合计」不是同一批数字——合计含输出,命中率不含。缓存读为 0 或整屏都没有用量时不显示这一项,而不是显示 0%。
- 过滤状态在菜单关闭后保留(搜索框与筛选片下次打开时仍是原样,一眼可见),想要清掉点控制条上的 ×。
- 悬浮摘要只列直接子行(最多 4 个),更深的下级只体现在计数里;它也不含模型分组——模型要读日志,那是目录里那个按需按钮的事。
- 悬浮摘要里的行不可点:卡片整块的点击已经被工作区用于复制会话标题,同一张卡里再放导航会互相打架。想打开某个子智能体,用目录里的按钮。
- 官方那行「N 个子智能体运行中」不会被替换掉:它由工作区自己渲染,插件只是在它上方补了一段。因此这两处的「N」含义不同——官方那行是正在运行的个数,摘要里的计数是已加载的子智能体数。
- 立即弹出与「新卡替旧卡」作用在共用的那支开卡定时器与那支指针宽限上,所以侧边栏会话行、工作区分组行以及预览卡会一并立即展开,别的弹出层(如下拉菜单)若正好处在 200ms 宽限里、旁边又弹出一张卡,也会跟着提前收。命中靠的是回调形状(开卡那支认正文里的
("open"),宽限那支认「ref 写 null 后紧接着调用另一个 ref」这个属性名形状),而不是那个 800ms / 200ms,所以官方改毫秒数也不影响它;代价是形状变了它就安静失效(控制台仍会报安装成功——它确实装上了,只是不再命中)。想恢复官方那 800ms / 200ms:把apply里那次installInstantHover(...)去掉即可(INSTANT_HOVER_OPEN_MS就是强制使用的等待毫秒数,改成别的数字也行)。 - 没有子智能体时显示的「总用时」是宿主
sessionStats折叠出来的模型时间 + 工具时间,不含排队与等待输入的时间,也不等于「这个会话存在了多久」(会话摘要里根本没有创建时间)。投影还没到、或这个会话什么都没记时,这一项不显示,而不是显示 0。 - 那一行的 token 与命中率同样来自会话摘要的投影:摘要还没到时只显示还剩得下的项;三项都拿不到时整段不出现(与「空会话」表现一致)。