dsh-panellist

uitested: passnpmMIT

Panellist manager: reorder the entries on the left rail, rename them, replace their icons, hide them, and fold several into a Group with a flyout menu, from a Settings tab.

下载量 / Downloads
1,281
依赖体积 / node_modules
1.73 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-panellist

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-panellist

[English](README.md) | 简体中文

DSH(DeepSeek Harness)客户端「面板列表」的管理插件。

面板列表(panellist)是会话界面左侧那一列由各个插件贡献的全局入口——插件、自动化任务、技能中心、记忆系统、SSH、MCP 连接器……它们来自不同的插件,顺序由各自声明,图标与名称也由各自决定。

本插件让用户接管这件事:哪些页签出现在轨道上、以什么顺序出现、叫什么名字、用什么图标,以及把若干页签收进一个分组。所有配置都在「设置」里的一项新页签内完成,改动即时生效。

状态:已实现。这套 README(中英各一份)就是完整的规范——不再有单独的设计文档:行为见 §2–§3,配置与接口见 §4,兼容契约见 §5,安装见 §6,实现方式见 §7。配置的权威形状是 [docs/schema/config.schema.json](docs/schema/config.schema.json)。
与最初设计的三处细化(都是实现期间发现的,已回填本文):分组的轨道 id 是 dsh-panellist:<groupId>;未配置页签始终列在整条轨道里,但只有序列非空(即你做过结构性改动)时才写 order;/panellist/api/reveal 这个端点是为了实现「打开配置文件」而新增的。

截图

设置 → 面板列表 · 轨道上的分组菜单

![设置 → 面板列表](docs/images/settings-panel-list.png) · ![轨道上的分组菜单](docs/images/rail-group-menu.png)


1. 为什么需要它

DSH 的面板列表是只读投影:外壳枚举 sidebar.panellist 槽位里的注册项,按每一项注册时自报的顺序排序渲染。官方没有提供任何改写他人注册的 API:

  • 顺序只读——插件只能决定自己那一项的 order;
  • 没有「分组」概念——一行就是一个页签;
  • 图标由贡献者提供的组件决定,名称由贡献者的 label 决定;
  • 「设置」导航页签的图标只能从内核内置的一份清单里挑。

因此本插件必须同时用两个杠杆,并且把 DOM 介入压到最小:

1. 注册表改写——在运行时改写注册项上的顺序、名称与图标组件(注册项对象未被冻结,且由槽位注册表原样交回,所以事后写入对下一次投影可见);本插件的改动不触碰任何插件的页面本身。

2. 最小 DOM/CSS 重投影——只有一件事无法靠注册表完成:把某一行从轨道上「拿掉」。分组行本身也是注册项(由本插件注册),因此它的行框、高亮、提示与收起态行为依旧由外壳负责;弹出菜单是覆盖层,不改动原生行。


2. 行为规范(已确认)

2.1 管理对象

  • 面板列表里的所有注册项一视同仁:内核自带项(插件、自动化任务、技能中心、记忆系统、SSH、MCP 连接器)与第三方插件注册的项都可被管理。
  • 本插件自己的设置页签不在面板列表内,因此不参与管理。
  • 真实页签 id 在运行时发现,不硬编码;本文档示例里的 id 仅是示意。
  • 轨道自带的「更多」行也会列出,虽然它由外壳渲染、并非 Panellist 贡献者(§2.12)。

2.2 序列模型

轨道序列是一串有序项,每一项要么是单个页签,要么是一个分组:

  • 每个页签在整条序列中恰好出现一次(序列是对页签集合的划分,不重复);
  • 分组自身占据序列里的一个位置,并含有一个有序的页签子集;
  • 只允许一层嵌套(分组内不能再放分组);
  • 空分组非法;只含 1 项的允许,但管理面板会提示。

设计讨论里确认过的写法按此模型解释如下(G{…} = 一个分组):

写法 · 解释

a c b d e · [a][c][b][d][e]

a b c · [a][b][c]

a 下拉(b c d e) · [a][G{b,c,d,e}]

a 下拉(b d e c) · [a][G{b,d,e,c}]

a 下拉(b e c)d · [a][G{b,e,c}][d]

下拉(a b c)d e · [G{a,b,c}][d][e]

2.3 显示 / 隐藏

  • 只有两态:显示 / 隐藏(没有「禁用」第三态)。
  • 隐藏 = 不再出现在轨道上,但页签本身依然存在、依然可用(可从管理面板选中、可被其它插件或快捷键激活),它所属插件的功能不受影响。
  • 例外:被隐藏的页签若正处于激活态,它那一行临时显示——避免出现「人还在这个页面,轨道上却没有入口」。
  • 分组内的成员也支持隐藏,含义换成「不出现在分组菜单里」:成员的轨道行本来就被分组抑制掉了,所以对成员而言隐藏只作用于下拉菜单。成员仍留在组内——计数、排序、组头高亮都照旧看见它。
  • 已缺失的成员自动不出现在分组菜单里(注册项没了,菜单里留一行禁用的「已缺失」只是占地方,用户也无从操作);它仍留在配置里、仍列在设置页的「已缺失的面板」里,等你清理。

2.4 名称覆盖

  • 纯显示层覆盖,只影响三处:轨道行的文字、收起状态的提示文字、无障碍名称。
  • 不改动 DSH 其它位置(设置、命令面板等)中的同名显示,也不改变贡献者声明的原始名称。
  • 首版为单字符串,对所有语言生效。

2.5 图标覆盖

三种来源,可随时切回原图标:

1. 保留原图标(默认);

2. 内置图标集——本插件自带的 24 个内联矢量图形(零依赖,离线可用)。它们按轨道上邻居们所用的规范绘制,即 Icon*OutlineRegular 那一档:viewBox="0 0 16 16"、fill="none"、stroke="currentColor"、线宽 1px、几何落在 16 网格的半像素坐标上并大致收在 2…14 之内、圆头圆角、aria-hidden、无 id 与外部引用。其中三个直接采用外壳自己那几颗默认图标的美术数据,因此选上去和它要替代的项看起来完全一样:puzzle 是插件风车(IconPluginPinwheelOutline)、clock 是时钟(IconClockOutline)、more 是省略号(IconEllipsisOutline)。这套图形在 16px 下与上述官方美术并排比对,并逐个量过着墨包围盒;

3. 用户上传图标——粘贴 SVG 源码;

  • 白名单净化:移除 script、foreignObject、事件属性、外部引用(href/xlink:href 指向外部的用例);
  • 单个 SVG 上限 64 KB;
  • 内联渲染(不是 <img>),因此能随主题变色。

设置导航那一行有它自己专用的图形(一个「面板列表」,线宽取那一列所用的 Icon*OutlineMedium 1.3)——见 §3。

2.6 分组与组头

  • 组头有独立的图标与名称,默认预填「组内第一项」的图标与名称(常用名如「更多」),用户可改。没设名称时,组头文字在渲染时按「第一个活着的成员」解析——配置里不烘焙字面量。
  • 组头整行可点,点击即展开分组菜单;整行点击一律用于展开菜单,不会跳转页面。菜单里只列该分组的成员:标题行里原本还有一个指向分组自己那一页的「概览」入口,已按用户反馈删除。那一页仍保留为分组 main 的兜底页(这样程序化选中分组 id 时不会报错),但不再有任何自己的入口。
  • 组内任一项处于激活态时,组头高亮,用户不展开菜单也能看出当前页面在哪个组里。
  • 拖拽新建分组时,分组插到「最靠上的那个成员」原来的位置,即分组「长在」它成员的原地。这条规则由模型保证(createGroup 以「最靠上成员上方那一行」的身份为锚点),因此与具体手势无关。

2.7 分组菜单

  • 点击展开(悬停不展开,避免误触)。
  • 只列用户能操作的成员:隐藏的(§2.3)与已缺失的都不出现;组内成员全部隐藏时,菜单写明这一点,而不是给一张空卡片。
  • 键盘可达:Enter/Space 展开、Esc 关闭、上下方向键在组内移动。
  • 侧栏收起成 56 px 轨道时,菜单一律向右弹出(flyout)。
  • 组内项数超过可视高度时菜单可滚动;首版不做组内搜索框。

2.8 未配置项与孤儿

  • 新装的插件注册了一个你还没动过的页签 → 它直接出现在序列里(在已配置项之后,按它自身的顺序),不需要先「纳入」它。
  • 某个页签的贡献者被卸载、而配置里还留着它 → 配置保留,管理面板标注「已缺失」,并提供「清理孤儿」按钮。
  • 在你做结构性改动之前(拖动、建组/解散分组),本插件不写任何顺序:轨道保持贡献者自己的顺序。改名、换图标、隐藏只覆盖那一项、不动顺序——这正是「不碰你没让我碰的东西」的落点。

2.9 生效方式

配置改动即时生效:不需要重启 DSH,不需要重载任何插件,也不需要重新加载页面。这对每一次改动都成立,包括那些「什么都不用写」的改动:改写环节会比较一份「轨道渲染所依赖的一切」(顺序、名称、图标、显示/隐藏)的摘要,只要它变了就重新投影——所以第二次换图标、第二次改名,和第一次一样立刻上轨道。

2.10 接管范围

管理面板里从第一次打开起就列出整条轨道:不需要先把某个页签「纳入」才能碰它,也不存在一份「还没接管」的清单。(中途曾有过「未配置清单 + 逐项接管 / 恢复原生」的设计,已按用户反馈删除——它们只是让人多点一步;后来那条「还没接管顺序」的说明横幅与「以当前轨道顺序作为起点」按钮也一并删掉了:列表本身就是轨道,这道门槛不可见。)

真正仍然成立的「最小干预」性质落在写入上,而不是界面上:

  • 配置里的 sequence 是部分权威:只记你实际改过的东西。
  • sequence 为空时不写任何 order(新装即如此,纯 no-op),未配置项的名字/图标/隐藏一概不碰;它们保持贡献者自己的顺序。
  • 你第一次做结构性改动(拖动、合并分组)时,当前顺序会被写成显式序列;此后未配置项仍按各自的原生顺序列在已配置块之后(§2.8)。

实现见 src/shared/model.ts:rows 永远是整条轨道(未配置的行 order 为 null,即「不改」),而 reordering 决定是否真的写 order。

2.11 每页只允许一个挂载

外壳可能在同一页里把客户端 bundle 应用两次(文件变了会重新求值,HMR 也会再应用一次)。两个控制器同时操作同一份账本会互相打架:后来者会把前一个装上去的图标包装当成贡献者自己的组件,双方还都往同一批注册项上写。因此 apply 在构建自己的挂载之前先退役上一个(src/client/mount.ts),并且本插件装上去的每个包装都是可识别的——遇到还挂着包装的注册项会先拆开再接管(§7.2)。


2.12 外壳自带的「更多」行

轨道最后一行「更多」不是 Panellist 的注册项:它由 DSH 自己渲染(来自 sidebar.panellist.actions 槽位),点开的侧边菜单被 portal 到 document.body。所以它不是 sidebar.panellist 的条目——没有 order、没有名称 thunk、没有图标组件可以让本插件改写(§7.2)。但它同样是那个 nav 里的普通 flex 子节点,因此仍然出现在序列里:

  • 位置。 该行(shell:more)在序列里占一个位置,可以拖拽或上移/下移。改写环节按 DOM 顺序写轨道,所以它在序列里的下标就说明了它前面有多少行;注入的规则给「更多」锚点 order: 1、给它所在位置及其后的每一行 order: 2,用 flex 把它精确停在那里。不改动任何其他行选择器;当它本来就在最后时,一条规则也不写。
  • 隐藏。 隐藏它就等于去掉面板菜单的入口,所以只在你不用那个菜单时才隐藏。

按设计它不能被改名、换图标或并入分组:名称、图标与行为都属于外壳,而且它没有可打开的页面,既不能作为分组的开头,也不能被折进分组——它的外观编辑会直接说明这一点,而不是给出那些控件。还有一个注意点:移动的是它画出来的位置,键盘顺序并不跟随,Tab 仍按 DOM 位置最后到达它。(其他行不同:它们的顺序写进注册对象本身,外壳按新顺序渲染,键盘顺序也随之改变。)

旧版本靠探测处理,不比较版本号。 探测 5(§5)如实报告轨道里到底有什么,只有两种结果表示「没有这一行」:面板列表里除了行以外什么都没有,以及列表里那个非行的子节点不再匹配。这两种情况下该行不出现在序列里,于是不支持的版本上不会留下一条死条目。如果这个配置里已经写过它——存过位置,或点过隐藏——该行仍会列出,并带一个「当前版本无此行」标记,因为直接丢掉等于把它交给孤儿清理。仅仅「轨道没挂载」(侧边栏收起)属于未知而不是「没有」,未知时保留该行。

3. 设置页签

在「设置」对话框左侧新增一项:

  • 名称:面板列表;位置:排在设置导航靠后;
  • 图标:自己专用的图形——一个「面板列表」,线宽取那一列统一的 Icon*OutlineMedium(1.3),以 currentColor 蒙版画在外壳原本的兜底齿轮位置。设置导航的契约里没有图标字段,所以这一行按可见文本定位并打标记;探测失败则保

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::4,最后更新 2026-10-26

Links

GitHub · Homepage · npm

Same category