diff --git a/docs/design/2026-08-02-req128-local-plugin-import/frame.html b/docs/design/2026-08-02-req128-local-plugin-import/frame.html new file mode 100644 index 000000000000..7fcd791a5ec6 --- /dev/null +++ b/docs/design/2026-08-02-req128-local-plugin-import/frame.html @@ -0,0 +1,424 @@ + + + + + +定制中心增量 · 本地插件包导入(技能)— 预览屏 / 已装扩展包 / 启用与生效 (2026-08-02) + + + + + + +
+
α 定制中心增量 · 本地插件包导入(技能)
+
+ +
+
+ +
+ + +
+
增量稿 · 基线 = 活稿 v6(四级货架)+ 现行实现 alpha@13a69d2c + 已批方案基线(Phase 3)
+

与上一稿 / 现状的关系

+

本稿只画三样东西:导入本地插件目录时的预览屏、已安装页里新的「扩展包」区块(含每个技能的开关与「移除此扩展包」)、以及装完之后的启用与生效状态流。定制中心的 tab、货架、浏览卡、详情页段顺序、已安装页的 scope 分组一寸不动;导入页不新增第六张卡

+
+ + + + + + + + + +
今天是什么样(实读现行实现)本稿
导入入口导入页 5 张卡:文件夹 / Git / npm / 自定义连接器 / 导入 Agent(extension-hub.tsx:2264-2323)。「文件夹」点下去由主进程自弹选择器,界面不传路径(:440)零改动选中的目录若是一个插件目录,主进程改走新路;界面按返回值分流,自己不判目录形态
非插件目录沿用既有单技能导入:成功 toast / 失败行内红字(:447-448)逐字不变本稿画出这一帧当对照物,防止实现顺手改它
两段式预览已有形状:导入 Agent 的映射预览弹窗(:2774-2819,取消 / 确认两键)继承形状同一个弹窗骨架,内容换成逐技能判定;尺寸 md → lg
已装的「包」看不见。唯一能看到包的地方是目录条目的详情页(extension-detail.tsx:140),而它只能从远程目录进得去;已安装页只有逐个组件的平铺行新增区块本地导入的包没有目录详情页可去,所以包头动作只能挂在区块里
逐组件「会装 / 不会装 + 原因」已有词汇:目录扩展包详情页已经是「主件 / 附带 · 会安装 / 不会安装 + 具名原因」(extension-detail.tsx:234-266)复用预览屏沿用同一套词,不另造一套说法
「已安装 · 未启用」已有:徽标 + 状态行「未启用 —— 打开开关后生效」(extension-hub.tsx:1727)复用本地包装完默认全是这个状态,所以它从边角变成主路径
开关 / 移除已有:行内开关(:1789-1800)与移除按钮(:1818-1828);整包移除按钮与失败呈现已在包详情页(extension-detail.tsx:165-183:306-337)搬位置控件版式全部现役,只是第一次出现在已安装页的包卡里
装完当场生效引擎重载能力已有(use-extensions.ts:136),开关路径已经在用(:605);整包移除今天只刷列表、不重载引擎接线确认安装成功、整包移除成功都要重载;重载没成功如实说「待重载」
+
净效果(用户视角一句话):点「文件夹」挑一个插件目录 → 弹一屏逐条告诉你哪些装得上、哪些装不上以及为什么 → 确认 → 已安装页多出一张扩展包卡,里面的技能默认都是关的,卡上一行字告诉你「打开开关才会生效」→ 打开任意一个,下一条消息里就能用 → 「移除此扩展包」一键连文件带记录一起拿掉。
+
一条不许被磨平的话:本地文件夹里的内容没有经过任何审核。这一屏能给的只有「逐条告诉你」,给不了「已验证」。帧里那句「来自你选择的文件夹,未经审核」不是免责声明,是这个功能的实际保证水平 —— 任何把它改写成「已检查 / 已验证 / 安全」的措辞都是错的。
+
+ + +
+
内部判定 → 用户语言(帧内只出现右列)
+

词汇表:每一条「不装」都要说人话

+

「装不上」不是边角:本机 40 个带技能的插件里,有 10 个从干净机器起就一个技能都装不上。所以下面这张表不是错误码附录,它就是这个功能的主要产出

+
+ + + + + + + + + + + +
判定行内标(帧内)原因文案(帧内)
通过会安装(不出原因行)
同名已存在不会安装 · 已有同名你已经装过一个叫「{名字}」的技能。这次不覆盖它 —— 想换成这一个,请先移除原来的。
说明读不出不会安装 · 说明读不出这个技能开头的说明写成了跨行格式,这一版读不了,所以不装。
引用了自己文件夹外的东西不会安装 · 会缺件它要用到自己文件夹以外的文件({相对路径}),单独装过来会缺件。
带可执行文件不会安装 · 含可执行文件这个技能里带了能直接运行的脚本,这一版不装。
含快捷方式不会安装 · 含快捷方式这个技能里有指向别处的快捷方式,装过来指不到,所以不装。
带兑现不了的调用设置不会安装 · 设置不支持它带了「{设置名}」这类调用设置,Alpha 这一版没有对应开关 —— 装过来它的行为会和作者的意图不一样,所以不装。
摆放位置不认识不会安装 · 位置不认识这个技能放在「{目录}」,这一版只认「skills」里的技能,所以不装。
整个文件夹的结构不认识(整屏终态)这个文件夹的结构这一版还不认识,没有能装的东西。
本版本不安装的内容类型这一版不安装命令 · 子 Agent · 钩子 · 外部服务连接配置 —— 逐类具名列出数量,不静默跳过。
+
三条措辞纪律:①永远说得出原因 —— 没有「其他原因」这一档,也没有「跳过 N 项」这种不点名的汇总;②不猜、不改写 —— 我们一个字节都不改用户的文件,读不了就直说读不了;③不把「不装」写成失败 —— 它是一次说清的取舍,语气是陈述不是报错(整屏一个都装不上时才升级为警示框)。
+
+ + +
+
帧 1 · 导入页 · 现状对照(本稿零改动,画出来是为了钉死「不新增卡」)
+

入口不变:还是那张「文件夹」卡

+
+
+
推荐
连接器
技能
Agent
插件
套件
已安装
导入
云能力
+
+
+
导入
+
把已有的技能、Agent 或连接器带进来。
+
从哪里导入
+
+
📁
文件夹

本地 SKILL.md / agent

+
Git 仓库

https 仓库地址

+
npm
npm 包

上游生态插件

+
自定义连接器

任意 MCP 服务

+
导入 Agent

单个 agent 文件

+
+
+
+
五张卡一张不增一张不改。变化全部发生在「文件夹」卡按下去之后:选中的目录若是一个插件目录,进本稿的预览屏;否则原路走既有单技能导入 —— 成功 toast、失败行内红字,一个字都不动(这一条在实现里由回归用例钉死)。用户不需要事先知道自己挑的是哪一种。
+
+ + +
+
帧 2–4 · 预览屏 · 三种真实终态(数据取自本机真实插件语料)
+

预览屏:先说清楚,再让你按确认

+

弹窗骨架继承导入 Agent 的两段式预览(取消 / 确认两键,确认之前一个字节都不写盘)。内容分三段:会安装不会安装(逐条原因)这一版不安装的内容类型。主按钮上直接写数量 —— 用户在按下去之前就知道到底装几个。

+ +
+
+
导入扩展包本地文件夹
+
plugin-dev·未提供版本·找到 7 个技能
+
+
+
⚠️这些内容来自你自己选择的文件夹,没有经过审核。下面逐条列出会装什么、不装什么。
+ +
会安装1 个技能
+
skill-development技能
+ +
不会安装6 个技能 · 每条都说明原因
+
command-development会缺件

它要用到自己文件夹以外的文件(${CLAUDE_PLUGIN_ROOT} 下的内容),单独装过来会缺件。

+
mcp-integration会缺件

它要用到自己文件夹以外的文件(${CLAUDE_PLUGIN_ROOT} 下的内容),单独装过来会缺件。

+
plugin-structure会缺件

它要用到自己文件夹以外的文件(${CLAUDE_PLUGIN_ROOT} 下的内容),单独装过来会缺件。

+
hook-development含可执行文件会缺件

这个技能里带了能直接运行的脚本,并且要用到自己文件夹以外的文件,这一版不装。

+
plugin-settings含可执行文件

这个技能里带了能直接运行的脚本,这一版不装。

+
agent-development含可执行文件

这个技能里带了能直接运行的脚本,这一版不装。

+ +
这一版不安装的内容2 类
+
命令1 个

这一版只从插件目录里导入技能,命令留在原处不动。

+
子 Agent3 个

这一版只从插件目录里导入技能;单个 Agent 可以用导入页的「导入 Agent」单独带进来。

+
+
取消不会在这台电脑上留下任何东西。
+
+
帧 2 · 部分可装(典型态)。数据 = 本机真实插件 plugin-dev:7 个技能里 6 个因「引用插件根资源 / 带可执行位」被拒,恰好落在方案基线点名的那 6 个上;它的 plugin.json 没有 version 字段(本机 62 个插件里 27 个没有),所以副标题如实写「未提供版本」而不是造一个 1.0.0。原因文案里的 ${CLAUDE_PLUGIN_ROOT}用户文件里的原字符串(照抄不翻译),不是我们的术语。
+ +
+
+
+
+
导入扩展包本地文件夹
+
codex·v1.0.4·找到 3 个技能
+
+
+
这个文件夹里的技能,这一版一个都装不了。原因逐条列在下面。
+
不会安装3 个技能
+
codex-cli-runtime设置不支持

它带了「只允许手动调用」这类调用设置,Alpha 这一版没有对应开关 —— 装过来行为会和作者的意图不一样,所以不装。

+
codex-result-handling设置不支持

同上:带了这一版兑现不了的调用设置。

+
gpt-5-4-prompting设置不支持

同上:带了这一版兑现不了的调用设置。

+
这一版不安装的内容3 类
+
命令7 个·子 Agent1 个·钩子1 项

这一版只从插件目录里导入技能。

+
+
没有可安装的技能。
+
+
帧 3 · 一个都装不上(常态终态,不是边角)。数据 = 本机真实插件 codex,3 个技能全部声明了调用控制设置。本机 40 个带技能的插件里有 10 个是这个结局。确认键保留但禁用并把原因写在键面上,用户看到的仍是同一屏 —— 不换成一个「知道了」把这一屏伪装成提示框(评审点 Q3)。
+
+
+
+
+
导入扩展包本地文件夹
+
receipts·这个文件夹的结构不认识
+
+
+
⚠️这个文件夹里有技能,但它们的摆放方式这一版还不认识,所以一个都没法装。
+
看到了什么
+
receipts位置不认识

这个技能放在这个文件夹的更深一层,而这一版只认识插件根目录下的「skills」。

+
可以怎么办
+
直接选中技能自己的文件夹

如果这个技能自己的文件夹里有 SKILL.md,重新点「文件夹」并直接选中它,可以单独导入。

+
+
没有可安装的技能。
+
+
帧 4 · 结构不认识。数据 = 本机真实插件 receipts(Anthropic 官方现役条目,没有插件清单文件,但确实带技能)。要害在这一句必须是「摆放方式不认识」,绝不能落回「这个文件夹里没有 SKILL.md」 —— 那句话与真因毫无关系,而它正是今天这条路径会说出来的话。这一屏也是唯一给出「下一步可以怎么办」的终态。
+
+
+
为什么会有一屏什么都装不了:这一版收得很窄 —— 只装「自带全部所需文件、没有可执行脚本、没有我们兑现不了的调用设置」的技能。窄不是遗憾,是这条路上唯一诚实的选择:文件夹里的内容没有经过审核,能少猜一点就少猜一点。把这一屏画清楚,比把它藏起来更重要,否则用户只会得到一句「导入失败」。
+
+ + +
+
帧 5–6 · 已安装页 · 新增「扩展包」区块(区块内含每技能开关与整包移除)
+

已装扩展包:看得见整体,也看得见零件

+

已安装页今天只有平铺的组件行 —— 装进来的一包技能会散成 10 行,用户看不出它们是一件东西,也没有任何地方能把它整个拿掉。新区块放在「已安装」清单之前:先看到整体,再往下看到零件。区块里每个技能各有自己的开关(装完默认全关),包头挂「移除此扩展包」。

+
+
+
推荐
连接器
技能
Agent
插件
套件
已安装
导入
云能力
+
+
+
已安装
+
这台电脑上装了什么,以及它们现在开着还是关着。
+ +
扩展包 · 2
+

一个扩展包里的技能属于这个包,只能整包移除。

+ +
+
+
tid
+
+
tide扩展包来自本地文件夹
+
v0.1.0 · 10 个技能 · 全部未启用
+
+ +
+
💡这些技能已经装好了,但都还没启用。打开下面任意一个开关,下一条消息里就能用。
+
+
premarket-briefing已安装 · 未启用
未启用 —— 打开开关后生效
+
postmarket-review已安装 · 未启用
未启用 —— 打开开关后生效
+
portfolio-review已安装 · 未启用
未启用 —— 打开开关后生效
+
chain-analysis已安装 · 未启用
未启用 —— 打开开关后生效
+
event-impact已安装 · 未启用
未启用 —— 打开开关后生效
+
还有 5 个技能
theme-analysis · stock-deep-dive · risk-scan · event-tracking · tide-intro
+
+
+ +
+
+
plu
+
+
plugin-dev扩展包来自本地文件夹
+
未提供版本 · 1 个技能 · 1 个已启用
+
+ +
+
+
skill-development
已启用
+
+
+ +
已安装 · 全局
+
pre
premarket-briefing技能属于扩展包 tide已安装 · 未启用
未启用 —— 打开开关后生效
+
FS
文件系统精选
已启用
+
+
+
帧 5 · 刚装完(默认全关)+ 版本缺失。第一张卡 = 本机真实插件 tide(10 个技能全部可装,版本 0.1.0);第二张 = plugin-dev,没有版本字段,如实写「未提供版本」。包头的「移除此扩展包」与包详情页是同一颗按钮、同一套失败与保留件呈现 —— 本地包没有目录详情页可去,所以这里是它唯一的移除入口。技能超过 5 个时折行收进「还有 N 个」,与包内技能数上限(单插件实测最多 13 个)相称。
+
下方「已安装 · 全局」组零改动:包里的技能本来就有各自的安装记录,会照常出现在这里,只多一枚属于扩展包 tide。保留它有两个理由:一是搜索、更新、逐个启停这些既有能力不该因为「它在一个包里」就消失;二是用户在这里点垃圾桶,正是「单个技能不能单独移除」这条规则唯一说得出口的地方(见下一节失败态)。两处开关翻的是同一个状态,不是两份设置。(评审点 Q1)
+
+ + +
+
帧 7 · 装完 → 启用 → 可用(默认关是产品裁决,所以「装完什么都没发生」是默认路径)
+

启用与生效:装完不等于能用,这件事必须说出来

+

本地扩展包装完之后默认全部关闭。这意味着:如果界面不主动引导,用户按完确认会觉得「什么都没发生」,然后判定这个功能坏了。所以确认成功之后界面自动切到已安装页并把新包定位在最上面,包卡上带一行引导;开关拨开的那一刻引擎当场重载,下一条消息里技能就在。

+
+
+
1装好了,但都还关着
+

确认之后直接落在已安装页的新包卡上,卡里每个技能都是已安装 · 未启用,卡头一行字说明「打开开关才会生效」。

+
premarket-briefing已安装 · 未启用
未启用 —— 打开开关后生效
+
+
+
2你打开它
+

拨开关 = 立刻生效,不需要退出重进,也不需要重启应用。开关翻动的同时后台让引擎重新加载一次。

+
premarket-briefing
已启用
+
+
+
3下一条消息里就能用
+

回到会话,下一条消息发出去时这个技能已经在了 —— 输入 / 也能看到它。

+
/premarket-briefing
盘前简报(环境 → 今日重点 → 观察/回避 → 板块池)
+
+
+
这三步是本增量唯一的行为主张,也是最容易被实现切断的一段:安装成功后要重载引擎、整包移除成功后同样要重载(今天的整包移除只刷新列表、不重载 —— 结果是移除之后技能仍然能用到下次重启)。重载没成功不许装作成功,见下一节。
+
+
+
+
premarket-briefing待重载
已记录为「启用」,但这次没能让引擎立即重新加载 —— 重启 Alpha 后一定生效。
+
+
态 · 开关翻了但引擎没重载成功。设置已经落账(重启必然生效),但不许宣称已经能用。这条状态语是现役词汇,本稿只是把它接到本地包的启用与整包移除两条路径上。
+
+
+
+
+
tid
tide扩展包
v0.1.0 · 10 个技能 · 3 个已启用
+
+
+
态 · 移除进行中。按钮禁用并改词,防止连点造出第二次移除;整包移除是一次事务,过程中不给「部分已移除」的中间呈现。
+
+
+
+ + +
+
帧 8 · 失败态与边界态 —— 这一节的每一格都是「不许假装成功」
+

失败与边界态

+

这一节里有一半的状态票面没有点名,但它们都在真实路径上到得了。共同判据只有一条:凡是没有确认成功的,一律不许呈现为成功;凡是「留下了什么」,一律说得出为什么。

+ +
+
+
+
+
tid
tide扩展包
v0.1.0 · 10 个技能
+
无法移除此扩展包
另一个安装正在进行,这次没有动任何文件。请稍后重试。
+
+
+
态一 · 移除失败(票面 AC)。包仍然留在列表里,错误就在卡上。判据是「只有明确的成功才算成功」—— 任何别的返回都按失败呈现,绝不因为「调用返回了」就把卡拿掉。
+
+
+
+
+
plu
plugin-dev扩展包
已移除
+
⚠️有 1 项留了下来
skill-development —— 保留(你自己也单独装过它)。想彻底删掉它,请在下面的「已安装」里单独移除。
+
+
+
态二 · 移除成功但有保留件。「保留(…)」的四种理由是现役文案,本稿只是把它搬到包卡上并补一句「下一步怎么办」。留下东西而说不出理由,和一个缺陷没有区别。
+
+
+ +
+
+
+
pre
premarket-briefing技能属于扩展包 tide
这个技能属于扩展包「tide」,不能单独移除。
+
+
态三 · 点了包里单个技能的移除(票面 AC)。系统明确拒绝并把用户直接送到正确的动作 —— 不是静默什么都不做,更不是回一句「已移除」而东西还在。拒绝发生在删任何东西之前。注意开关仍然可用:单独停用一个技能是合法的,不能单独移除而已。
+
+
+
+
扩展包
+
!暂时读不出已装扩展包的清单
不代表没有安装 —— 只是这次没读出来。已安装的内容不受影响。
+
+
+
态四 · 清单读不出(票面未点名)。「读不出来」和「一个都没装」是两件事,合并成一句「暂无扩展包」会让用户以为东西丢了,进而去重装 —— 而重装会撞上同名拒绝。所以这两种状态在界面上永远分开。
+
+
+ +
+
+
+
导入扩展包本地文件夹
tide·v0.1.0
+
⚠️你已经装过这个扩展包了。
要换成这个文件夹里的版本,请先在「已安装」里移除它,再重新导入。这一版不做原地更新。
+
+
+
态五 · 重复导入(票面未点名)。这句话必须出现在按确认之前。否则用户按下确认,才被告知「同名已存在」而一个都没装 —— 同一件事说得晚,就从提示变成了失败。
+
+
+
+
导入扩展包本地文件夹
tide·v0.1.0·10 个技能
+
这次什么都没有安装。
刚才预览之后,这些技能在这台电脑上的情况发生了变化。为了不覆盖你现有的内容,这一次整个取消了 —— 请重新预览一遍。
+
这台电脑上没有留下任何改动。
+
+
态六 · 预览过期 / 安装中途失败(票面未点名)。两者共用这一屏,因为对用户来说是同一件事:一个都没装,盘上没有半成品。措辞上不许出现「已装了 3 个」这种中间状态 —— 一次要么全成,要么全不成。
+
+
+ +
+
+
+
导入扩展包本地文件夹
claude-for-financial-services
+
⚠️这个文件夹太大了,这一版没法一次性导入。
里面的技能数量或体积超过了单次导入的上限。可以改成直接选中你要用的那个技能自己的文件夹,单独导入。
+
没有可安装的技能。
+
+
态七 · 超出单次导入上限(票面未点名)。这一屏在预览阶段就给出终态,不会先把内容读进来再失败;并且给了一条真的走得通的出路。
+
+
+
+
取消 = 什么都没发生
预览屏上的「取消」、直接关掉窗口、或者转头去预览另一个文件夹 —— 三者结果相同:这台电脑上一个字节都没写,刚才为了预览读进内存的内容也当场丢掉。下次再点同一个文件夹,是重新读一遍,不是接着上次。
+
非插件目录 = 今天的行为,一字不改
选中的文件夹如果只是一个普通的技能文件夹,用户看到的仍然是今天那条路径的成功提示或红字错误。本增量在这条路径上不新增任何一屏。
+
+
态八 · 取消与旁路(票面点名了取消)。取消没有专属界面 —— 正因为如此,它容易在实现里变成「留着下次用」。这一格把它写成明确的产品承诺,好让它可以被验证。
+
+
+
+ + +
+
待你拍板
+

评审点

+
Q1 包里的技能,要不要照常出现在下面的「已安装 · 全局」清单里?稿采 A = 照常出现 + 一枚「属于扩展包 X」的标。理由:①这是今天的行为,不做任何事就是这样,收起来反而是额外改动;②搜索、逐个启停这些既有能力不该因为进了包就消失;③用户在这里点垃圾桶,是「单个不能单独移除」那句拒绝唯一说得出口的地方(帧 8 态三)。代价 = 同一个技能在页面上出现两次、有两个开关(翻的是同一个状态)。备选 B = 收进包卡内不重复,列表更干净,但拒绝路径从界面上就到不了了。
+
Q2 「扩展包」区块只列本地导入的包,还是所有包?稿采 A = 账本里有什么就列什么(目录来的包也列,用「来自本地文件夹 / 来自官方目录」两枚标区分;目录包多一个「查看详情」入口通向已有的详情页)。理由:目录来的包今天在已安装页同样是隐身的,这个区块本来就是在补这个洞。备选 B = 只列本地导入的,增量更小,但已安装页继续对目录包撒谎。
+
Q3 一个都装不上时,确认键是禁用还是换成「知道了」?稿采 A = 保留确认键但禁用,并把原因写在键面上(「没有可安装的技能」)。理由:这一屏和能装的那一屏是同一屏,用户的心智模型不该在这里分叉。备选 B = 换成单键「知道了」,更像提示框,但会让「装不上」看起来像一次错误弹窗而不是一次说明。
+
Q4 预览用弹窗还是整页?稿采 A = 弹窗(继承导入 Agent 的两段式形状,尺寸放大到 lg,内容区滚动)。单插件技能数实测最多 13 个,弹窗装得下。备选 B = 整页,长清单更舒服,但要新开一条页面路由,而且会让「确认之前不写盘」这件事更难被看清。
+
Q5 确认成功之后跳不跳页?稿采 A = 自动切到已安装页并把新包定位在最上面。理由直接对着裁决:既然默认全关,不跳页的结果就是用户按完确认什么都看不到,然后认为功能坏了。备选 B = 留在导入页 + 一个 toast,改动更小,但把「装完像没装」这个已知风险原样留给用户。
+
Q6 包卡里的引导条什么时候消失?稿采 A = 这个包里只要有任意一个技能被启用过,就不再出现。备选 B = 用户手动关掉。A 更省事,但代价是:如果用户先开后关,引导条不会回来。
+
硬约束(不接受反转):①导入页不新增第六张卡,非插件目录的行为逐字不变;②每一条「不装」都必须带具名原因,没有「其他」这一档;③装完默认全关是产品裁决,界面不得「贴心地」自动打开任何一个;④移除失败必须显示失败,只有明确成功才算成功;⑤包内单个技能的移除请求 = 明确拒绝 + 指向整包移除,不是假成功;⑥取消 = 零写入;⑦「读不出清单」不许显示成「没有安装」;⑧本地文件夹的内容没有经过审核这件事,必须在按确认之前可见,不得写成「已验证 / 已检查 / 安全」。
+
+ +
+ + + + diff --git a/docs/design/README.md b/docs/design/README.md index bdbbc43ee5a8..3d394947387d 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -115,7 +115,7 @@ and indexed in `PAGE-MAP.md`. | Family | Design history | Current authority | | --- | --- | --- | -| Extension Hub / 定制中心 | `extension-hub.md` → `2026-06-22-arch-extension-hub.md` → `2026-07-04-extension-hub-v3-universal.md` → `2026-07-13-req103-hub-governance/` (治理三归位 v3) → `2026-07-15-capability-authorize-dialog/` (authorize 确认框增量) → `2026-07-17-req103-remaining/` (scope 分组做实 + 已授权能力段 v4) → `2026-07-17-req104-pack-facts/` (Pack 整包事实 + 第三方默认关 v5) → `2026-07-18-req104-four-shelf/` (四级货架 v6) → `2026-07-30-req128-extension-package-baseline.md` (标准 package、外部 Plugin 适配与兼容宿主安装) | current extension code/tests and ADR-014/ADR-028/ADR-030 | +| Extension Hub / 定制中心 | `extension-hub.md` → `2026-06-22-arch-extension-hub.md` → `2026-07-04-extension-hub-v3-universal.md` → `2026-07-13-req103-hub-governance/` (治理三归位 v3) → `2026-07-15-capability-authorize-dialog/` (authorize 确认框增量) → `2026-07-17-req103-remaining/` (scope 分组做实 + 已授权能力段 v4) → `2026-07-17-req104-pack-facts/` (Pack 整包事实 + 第三方默认关 v5) → `2026-07-18-req104-four-shelf/` (四级货架 v6) → `2026-07-30-req128-extension-package-baseline.md` (标准 package、外部 Plugin 适配与兼容宿主安装) → `2026-08-02-req128-local-plugin-import/` (本地插件包导入的增量提案 — 提案未批,实现先落地;**其数字与状态划分已被上线实况取代,以 `current/customization-center/design.html` 的 §9 为准**) | current extension code/tests and ADR-014/ADR-028/ADR-030 | | Alpha system surfaces | `2026-07-20-req090-alpha-surfaces/` (Settings / Permission / Model / Dialog / Recovery bundle) | current alpha-ui code and REQ-090 | | Product visual language | `2026-06-25-cool-graphite-visual-system.md` plus protected prototypes under `docs/archive/assets/design-program/` | current UI tokens, components, and visual tests | | Frontend/upstream boundary | `2026-07-03-frontend-decoupling-options.md` | `docs/architecture/upstream-integration.md`, sync workflows, ADR-020/ADR-029 | diff --git a/docs/design/current/customization-center/components.md b/docs/design/current/customization-center/components.md new file mode 100644 index 000000000000..30555108d267 --- /dev/null +++ b/docs/design/current/customization-center/components.md @@ -0,0 +1,68 @@ +--- +title: customization center component ledger +kind: design +status: active +owners: + - alpha-code product and design maintainers +last_reviewed: 2026-08-02 +review_after: 2027-01-16 +--- + +# 定制中心组件台账 + +[`design.html`](design.html) 的组件与其交付生命周期的对照。字段定义与本层的 +用途见 [`../../README.md`](../../README.md#componentsmd-fields)。 + +**这是本页的第一份台账,覆盖是部分的,不要当完备清单读。** 历史行取自活稿现有 +分区(四级货架 v6 及其血统),只登记分区级、不逐个回填叶控件;组件级锚按 +[`../../README.md`](../../README.md) 的增量五步序**在每个组件下次被动到时补**。 + +两条读法约定,与 [`../conversation-timeline/components.md`](../conversation-timeline/components.md) +一致: + +- **`未登记(历史)` 的行不参与「台账没有仍开着的行 = 已对齐」这条判据。** 它们 + 早于本层存在,实现票已不可靠追溯。 +- **`设计中` 的行,锚是已约定但尚未存在的 id** —— 增量帧获批并入活稿的那一刻 + 才写进 `design.html`。这是 README 增量五步序的第 2 步(先定锚再画),不是断链。 + 本文件目前**没有**这样的行。 + +## 组件 + +| 组件 | 锚 | 增量稿 | 设计定稿 | 实现票 | 落地 | 代码入口 | 状态 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 本地插件包导入预览屏 | `#import-preview` | [`2026-08-02-req128-local-plugin-import/`](../../2026-08-02-req128-local-plugin-import/frame.html) | 2026-08-02 | ac#784 | 2026-08-02 | `renderer/extensions/extension-hub.tsx`(`runImportFolder` 分流 + `localPlugin` 预览弹窗)· 判定与文案真源 `main/claude-plugin-intake.ts` | 已落地 | +| 已装扩展包区块(含每技能开关 · 整包移除) | `#installed-packs` | [`2026-08-02-req128-local-plugin-import/`](../../2026-08-02-req128-local-plugin-import/frame.html) | 2026-08-02 | ac#784 | 2026-08-02 | `renderer/extensions/extension-hub.tsx`(`InstalledPackagesSection` / `PackageComponentRow` / `InstalledRow` 的拒绝行) | 已落地 | +| 装后启用与生效状态流(含待重载态) | `#enable-flow` | [`2026-08-02-req128-local-plugin-import/`](../../2026-08-02-req128-local-plugin-import/frame.html) | 2026-08-02 | ac#784 | 2026-08-02 | `renderer/extensions/use-extensions.ts`(`localPluginConfirm` / `uninstallPackage` 各接一次 `refreshEngine`) | 已落地 | +| 推荐页四级货架 | `#featured` | `2026-07-18-req104-four-shelf/` | 2026-07-18 | ac#397 | — | `renderer/extensions/extension-hub.tsx` | 未登记(历史) | +| 浏览卡分级 chip + 「未分级」尾组 | `#browse` | `2026-07-18-req104-four-shelf/` | 2026-07-18 | ac#397 | — | `renderer/extensions/extension-hub.tsx` | 未登记(历史) | +| 详情页事实段 + 组件与来源段 | `#detail` | `2026-07-18-req104-four-shelf/` | 2026-07-18 | ac#397 | — | `renderer/extensions/extension-detail.tsx` | 未登记(历史) | +| 降级与失败态(拉取失败 / 复审过期 / 保守处理) | `#states` | `2026-07-18-req104-four-shelf/` | 2026-07-18 | ac#397 | — | `renderer/extensions/extension-detail.tsx` | 未登记(历史) | +| 已安装行:启用开关 · 会话开关 · 归档警示 | `#policy` | `2026-07-18-req104-four-shelf/` | 2026-07-18 | ac#395 · ac#408 | — | `renderer/extensions/extension-hub.tsx`(`InstalledRow`) | 未登记(历史) | +| 整包事实段 | `#packrel` | `2026-07-17-req104-pack-facts/` | 2026-07-17 | ac#396 | — | `renderer/extensions/extension-detail.tsx` | 未登记(历史) | + +## 登记时记下的两处现状 —— 已随本次实现闭合 + +两条都是登记那一刻的真实缺口,现已被 ac#784 关掉。留在这里是为了让后来人看得出 +`#installed-packs` / `#enable-flow` 这两行**为什么存在**。 + +- **已装的扩展包在已安装页曾经是看不见的。** 唯一能看到「包」的地方是目录条目 + 的详情页(`extension-detail.tsx` 的 `kind === "package"` 分支),而它只从远程 + 目录进得去;本机导入的包没有目录条目,因此结构上到不了。**现在**已安装页有了 + 独立的「扩展包」区块(`InstalledPackagesSection`),排在「已安装条目」之前。 +- **整包移除曾经只刷新列表、不重载引擎**,而同一个仓里的启停路径 + (`use-extensions.ts` 的 `setInstallState`)已经在调 `refreshEngine` —— + 后果是移除之后技能仍然能用到下次重启。**现在** `localPluginConfirm` 与 + `uninstallPackage` 各自接了一次 `refreshEngine`,失败则如实呈现「待重载」。 + +## 落地后核对活稿时发现的两处「代码与稿不一致」 + +活稿按**上线实况**画;下面两条是画的时候发现的、判断属于**代码**该修的部分, +已如实画进 `design.html` 并在帧外标注。处置归 GitHub Issues,不在本文件里裁决。 + +- **预览屏「不会安装」那一行的灰标,写的是内部原因码**(如 + `control-field-unsupported`),而不是给用户看的话;它下面那句人话是对的。 + 同仓的目录扩展包路径早有相反的规矩:每个 skip token **必须**配一句用户读得懂 + 的话,少一条就编译不过(见 `renderer/extensions/ext-package-presentation.ts`)。 +- **「读不出插件说明」那一屏自相矛盾**:标题行写「其中 1 个本版本可以装」并把它 + 列在「会安装」段里,底部确认键却是禁用的「没有可安装的技能」。本机真实语料 + (`receipts` / `session-report`)走得到这个状态。 diff --git a/docs/design/current/customization-center/design.css b/docs/design/current/customization-center/design.css index 40c7c3de3bcd..e20f726cdaf5 100644 --- a/docs/design/current/customization-center/design.css +++ b/docs/design/current/customization-center/design.css @@ -143,3 +143,82 @@ table.matrix{width:100%;min-width:820px;border-collapse:separate;border-spacing: .sw::after{content:"";position:absolute;top:2px;left:2px;width:17px;height:17px;border-radius:50%;background:#fff;box-shadow:var(--a-shadow-xs);transition:left var(--a-dur-fast) var(--a-ease-out)} .sw.on{background:var(--a-accent-solid)}.sw.on::after{left:17px} .sw.session.on{background:var(--a-warning)} +.sw:disabled{opacity:.5;cursor:not-allowed} + +/* ════════════════════════════════════════════════════════════════════════ + 本地扩展包导入(预览屏 / 已装扩展包区块 / 启用与生效)。 + 三组基元,全部是现役 .alpha-ext-* 的稿内等价物 —— 取值照现行实现, + 不在这里另立一套视觉。 + ════════════════════════════════════════════════════════════════════════ */ + +/* 弹窗骨架(现役 Dialog:size=lg + besideSidebar;标题行右侧关闭键) */ +.dlg-stage{background:var(--a-bg-inset);padding:26px 22px;display:flex;justify-content:center} +.dlg{width:100%;max-width:660px;background:var(--a-surface-raised);border:1px solid var(--a-border);border-radius:var(--a-radius-xl);box-shadow:var(--a-shadow-overlay);overflow:hidden} +.dlg-h{display:flex;align-items:center;gap:10px;padding:15px 18px 13px;border-bottom:1px solid var(--a-border-faint)} +.dlg-h b{font-size:var(--a-text-md);font-weight:var(--a-weight-semibold);flex:1} +.dlg-h .x{border:0;background:transparent;color:var(--a-text-tertiary);font-size:15px;line-height:1;padding:2px 4px} +.dlg-b{padding:15px 18px;max-height:470px;overflow:auto} +.dlg-f{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-top:1px solid var(--a-border-faint);background:var(--a-bg-subtle)} +/* 弹窗内的首行:名字 · 版本 · 找到 N 个技能 */ +.dlg-lead{margin:0 0 4px;font-size:var(--a-text-sm);color:var(--a-text-secondary)} +.dlg-lead b{color:var(--a-text)} + +/* 提示条:琥珀底 + 块级粗体标题(现役 .alpha-ext-verify-note) */ +.vnote{margin-top:10px;padding:10px 12px;border-radius:10px;background:color-mix(in srgb,var(--a-warning) 8%,transparent);border:1px solid color-mix(in srgb,var(--a-warning) 25%,transparent);font-size:var(--a-text-sm);line-height:var(--a-leading-snug)} +.vnote:first-child{margin-top:0} +.vnote b{display:block;margin-bottom:3px;color:var(--a-warning);font-size:var(--a-text-xs)} +.vnote p{margin:0;color:var(--a-text-secondary)} +.vnote ul{margin:6px 0 0;padding-left:18px;color:var(--a-text-secondary)} +.vnote li{margin-bottom:2px} +/* 屏内红字(现役 .alpha-ext-import-err / .alpha-ext-card-err) */ +.imperr{margin:10px 0 0;font-size:var(--a-text-sm);color:var(--a-error);line-height:var(--a-leading-snug)} + +/* 区段标题行(现役 .alpha-ext-secrow:「标签 · 数量」) */ +.secrow{display:flex;align-items:center;gap:8px;margin:16px 0 8px} +.secrow:first-child{margin-top:0} +.secrow .ov{font-size:var(--a-text-2xs);font-weight:var(--a-weight-semibold);letter-spacing:var(--a-tracking-wide);text-transform:uppercase;color:var(--a-text-tertiary)} + +/* 预览逐条行(现役 .alpha-ext-man,skip 行 data-dim) */ +.pv{display:flex;gap:11px;align-items:center;padding:10px 12px;border:1px solid var(--a-border);border-radius:var(--a-radius-lg);background:var(--a-surface);margin-bottom:7px} +.pv .bd{min-width:0;flex:1} +.pv .nm{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:var(--a-text-sm);font-weight:var(--a-weight-medium)} +.pv .why{margin:3px 0 0;font-size:var(--a-text-xs);color:var(--a-text-tertiary);line-height:var(--a-leading-snug)} +.pv[data-dim]{opacity:.72} + +/* 扩展包卡(现役 .alpha-ext-pack) */ +.pack{border:1px solid var(--a-border);border-radius:var(--a-radius-lg);background:var(--a-surface);box-shadow:var(--a-edge-light);padding:13px 15px;margin-bottom:10px} +.pack:last-child{margin-bottom:0} +.pack-h{display:flex;align-items:center;gap:13px} +.pack-h .bd{min-width:0;flex:1} +.pack-h .nm{display:flex;align-items:center;gap:8px;flex-wrap:wrap} +.pack-h .nm b{font-size:var(--a-text-md);font-weight:var(--a-weight-medium)} +.pack-h .st{font-size:var(--a-text-sm);color:var(--a-text-tertiary);margin-top:2px} +.pack-lead{margin:10px 0 0;padding:8px 11px;border-radius:var(--a-radius-md);background:var(--a-bg-subtle);color:var(--a-text-secondary);font-size:var(--a-text-sm)} +.pack-cs{margin-top:10px;display:flex;flex-direction:column;gap:6px} +.pc{display:flex;align-items:center;gap:13px;padding:9px 11px;border:1px solid var(--a-border-faint);border-radius:var(--a-radius-md);background:var(--a-bg-subtle)} +.pc .bd{min-width:0;flex:1} +.pc .nm{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:var(--a-text-sm)} +.pc .st{font-size:var(--a-text-xs);color:var(--a-text-tertiary);margin-top:1px;display:flex;align-items:center;gap:6px} +.pc .st .dot{width:6px;height:6px;border-radius:50%;flex:none;background:var(--a-text-disabled)} +.pc .st .dot.ok{background:var(--a-success)} +.pc .st .dot.warn{background:var(--a-warning)} +/* 整包移除键(现役 .alpha-ext-add[data-variant=danger]:描边 + 错误色) */ +.danger{display:inline-flex;align-items:center;height:26px;padding:0 12px;border-radius:var(--a-radius-md);border:1px solid color-mix(in srgb,var(--a-error) 35%,transparent);background:transparent;color:var(--a-error);font-size:var(--a-text-xs);font-weight:var(--a-weight-medium);white-space:nowrap} +.danger:disabled{opacity:.6;cursor:not-allowed} +/* 行内「下一步」键(现役 .alpha-ext-updbtn:琥珀底) */ +.nextbtn{display:inline-flex;align-items:center;height:28px;padding:0 12px;border:0;border-radius:var(--a-radius-md);background:var(--a-warning-subtle);color:var(--a-warning);font-size:var(--a-text-xs);font-weight:var(--a-weight-medium);white-space:nowrap} +.refusal{display:flex;align-items:center;gap:10px;margin-top:6px;flex-wrap:wrap} + +/* toast(现役 .a-toast:右下角浮层) */ +.toast{display:inline-flex;align-items:flex-start;gap:9px;max-width:420px;padding:10px 13px;border-radius:var(--a-radius-lg);background:var(--a-surface-raised);border:1px solid var(--a-border);box-shadow:var(--a-shadow-lg);font-size:var(--a-text-sm);color:var(--a-text);line-height:var(--a-leading-snug)} +.toast .dot{width:7px;height:7px;border-radius:50%;flex:none;margin-top:5px;background:var(--a-success)} +.toast[data-tone="info"] .dot{background:var(--a-info)} + +/* 三步状态流 */ +.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;padding:20px 22px} +@media (max-width:820px){.steps{grid-template-columns:1fr}} +.step{border:1px solid var(--a-border);border-radius:var(--a-radius-lg);background:var(--a-surface);padding:13px 14px} +.step .k{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:var(--a-radius-full);background:var(--a-accent-solid);color:#fff;font-size:var(--a-text-2xs);font-weight:700} +.step .h{display:flex;align-items:center;gap:8px;font-size:var(--a-text-sm);font-weight:var(--a-weight-semibold)} +.step p{margin:7px 0 0;font-size:var(--a-text-xs);color:var(--a-text-secondary);line-height:var(--a-leading-snug)} +.pad{padding:18px 20px} diff --git a/docs/design/current/customization-center/design.html b/docs/design/current/customization-center/design.html index 037c7bc433c7..c97d2fb8a40c 100644 --- a/docs/design/current/customization-center/design.html +++ b/docs/design/current/customization-center/design.html @@ -3,15 +3,15 @@ -alpha-code · REQ-104 四级货架与供应链事实 v6 — #397 消费侧呈现 (2026-07-18) +alpha-code · 定制中心 · 活稿(v6 四级货架 + 本地扩展包导入)
-
α REQ-104 四级货架与供应链事实 · v6
+
α 定制中心 · 活稿
- +
@@ -254,6 +254,276 @@

启用策略:从保守规则切到目录逐条声明

+ +
+
§9 · 本地扩展包导入 · 基线 = 上线实现 alpha@9996d2c9(打包版真机取证 2026-08-02)
+

本地扩展包导入:三块新内容,IA 一寸不动

+

从「文件夹」卡选中一个插件目录时,界面多出三样东西:导入前的预览屏、已安装页里的「扩展包」区块(每个技能一个开关 + 整包移除)、以及装完之后的启用与生效状态流。tab、货架、浏览卡、详情页段顺序、已安装页的 scope 分组全部不动;导入页仍然是五张卡,一张不增一张不改。

+
+ + + + + + +
v6 之前现在
导入入口五张卡:文件夹 / Git / npm / 自定义连接器 / 导入 Agent。「文件夹」由主进程自弹选择器零改动选中的目录若是一个插件目录,按主进程给的分流走预览屏;否则原路走既有单技能导入,成功提示与行内红字逐字不变
预览只有「导入 Agent」有两段式映射预览新增同一个弹窗骨架,尺寸放大;内容换成逐技能判定,确认之前一个字节都不写盘
已装的「包」看不见 —— 已安装页只有逐个组件的平铺行,包只在目录条目的详情页里出现新增区块「扩展包」区块排在「已安装条目」之前;本地导入的包没有目录详情页可去,这里是它唯一的移除入口
包内单个技能与普通已装条目无异,可以单独移除明确拒绝行上标「属于扩展包 X」;点单独移除 = 当场拒绝 + 指向整包移除。单独停用仍然合法,开关照常可用
装完生效启停路径已经会重载引擎;整包移除只刷列表、不重载接线确认安装成功、整包移除成功都重载;没重载成功如实说「待重载」,不宣称已生效
+
净效果(用户视角一句话):点「文件夹」挑一个插件目录 → 弹一屏逐条告诉你哪些装得上、哪些装不上以及为什么 → 确认 → 直接落到已安装页,新包排在最前,里面的技能默认都是关的,卡上一行字告诉你「打开开关才会生效」→ 打开任意一个,下一条消息里就能用 → 「移除此扩展包」一键连文件带记录一起拿掉。
+
一条不许被磨平的话:本地文件夹里的内容没有经过任何审核。这一屏能给的只有「逐条告诉你」,给不了「已验证」。帧里那句「没有经过审核」不是免责声明,是这个功能的实际保证水平 —— 任何把它改写成「已检查 / 已验证 / 安全」的措辞都是错的。
+
与 2026-08-02 增量提案的差(提案未批,实现先落地;本节以上线实现为准):①提案给包卡画了图标、给超过 5 个技能的包画了「还有 N 个 / 展开」折行 —— 上线都没有,包卡无图标、成员全列;②提案把「结构不认识」「超出单次上限」「预览过期」「重复导入」各画成一屏专用终态 —— 上线共用同一屏,差别只在屏内多一条具名说明;③提案给「一个都装不上」以外的终态配了「重新预览」「直接选中技能自己的文件夹」这类出路按钮 —— 上线没有,只给具名原因;④提案在安装进行中把「取消」画成禁用 —— 上线照常可点,因为主进程会如实回「这次中止不了」,而用户得有路径读到那句真话;⑤提案用 plugin-dev 举「7 个里 1 个可装」——实测是 0/7,该插件一个都装不上,本节全部帧已换成实跑数据。
+
+ + +
+
§9.1 · 预览屏 —— 先说清楚,再让你按确认(帧内数据全部取自本机真实插件的实跑判定)
+

预览屏:每一条「不装」都说得出原因

+

弹窗骨架继承「导入 Agent」的两段式预览(取消 / 确认两键,确认之前一个字节都不写盘)。内容按段落排:会安装不会安装(逐条原因)、这一版还不认识的摆放方式这一版不安装的内容。主按钮上直接写数量,用户在按下去之前就知道到底装几个。

+ +
+
导入扩展包
+
+

example-plugin · 未提供版本 · 找到 2 个技能

+

这些内容来自你自己选择的文件夹,没有经过审核。下面逐条列出会装什么、不装什么。

+
会安装 · 1
+
example-skill技能
+
不会安装 · 1
+
example-commandcontrol-field-unsupportednot-self-contained-outside-reference

这个技能带了我们这边没有对应功能的开关,装过去它的行为会变

这个技能引用了它自己文件夹以外的文件,装过去会缺件

+
这一版不安装的内容 · 2
+
commands

这个插件带了斜杠命令,本版本不安装

+
.mcp.json

这个插件带了 MCP 服务配置,本版本不安装

+

取消不会在这台电脑上留下任何东西。

+
+
+
+
帧一 · 部分可装(典型态)。数据 = 本机真实插件 example-plugin,实跑判定 1 装 / 1 拒 / 2 类不安装的内容。它的插件说明没有版本字段(本机 28 个被认成插件目录的文件夹里,18 个没有),所以首行如实写「未提供版本」而不是造一个 1.0.0。一条技能可以同时命中多个原因,每个原因各占一行,不折叠成一句
+
帧内那两枚灰标写的是内部原因码,不是给用户看的话 —— 这是照实画的,不是设计意图。上线代码把原因码原样当标签渲染(control-field-unsupported 这种),而它下面那句人话是对的。同一个仓在目录扩展包那条路上早就立过规矩:每个原因码必须配一句用户读得懂的话,少一条就编译不过。本地导入这条路没接上那套映射。修法归代码,不归稿;活稿在此如实画出今天的样子,改完再回来把这两枚标换成中文。
+ +
+
+
+
导入扩展包
+
+

plugin-dev · 未提供版本 · 找到 7 个技能

+

这些内容来自你自己选择的文件夹,没有经过审核。下面逐条列出会装什么、不装什么。

+

这是一个 Claude 插件目录「plugin-dev」:共 7 个技能,其中 0 个本版本可以装。这个插件里没有本版本能装的技能

+
不会安装 · 7
+
agent-developmentnot-self-contained-executable-bit

这个技能带了可执行脚本,装过去不会保留执行权限

+
command-developmentnot-self-contained-plugin-root-variable

这个技能要用到整个插件目录里的东西,单独装过去会缺件

+
skill-developmentnot-self-contained-parent-reference

这个技能引用了同一插件里的别的技能,装过去会缺件

+
这一版不安装的内容 · 2
+
commands

这个插件带了斜杠命令,本版本不安装

+
+
+
+
帧二 · 一个都装不上(常态终态,不是边角)。数据 = 本机真实插件 plugin-dev,7 个技能全被拒(帧内示 3 条)。本机 28 个「被认成插件目录」的文件夹里,21 个是这个结局。确认键保留但禁用并把原因写在键面上,用户看到的仍是同一屏 —— 不换成一个「知道了」把这一屏伪装成提示框。
+
+
+
+
导入扩展包
+
+

receipts · 未提供版本 · 找到 1 个技能

+

这些内容来自你自己选择的文件夹,没有经过审核。下面逐条列出会装什么、不装什么。

+

这是一个 Claude 插件目录「receipts」:共 1 个技能,其中 1 个本版本可以装。这个文件夹的插件说明我们读不出来,认不出它是哪个插件

+
会安装 · 1
+
receipts技能
+
这一版还不认识的摆放方式 · 1
+
receipts

这个文件夹没有插件说明文件,我们这一版还不认识这种摆法

+
+
+
+
帧三 · 没有插件说明文件。数据 = 本机真实插件 receipts(带一个技能,摆在标准位置,但没有插件说明文件)。要害是这一句必须落在「读不出插件说明」上,绝不能落回「这个文件夹里没有 SKILL.md」 —— 那句话与真因毫无关系。这一屏自己打架:上面写「1 个可以装」并把它列在「会安装」里,底下的键却说「没有可安装的技能」。两句话都来自同一次判定,只是一个说候选、一个说结果 —— 用户读到的是矛盾。修法归代码。
+
+
+ +
+
+
+
导入扩展包
+
+

tide · v0.1.0 · 找到 10 个技能

+

这些内容来自你自己选择的文件夹,没有经过审核。下面逐条列出会装什么、不装什么。

+

这个插件已经装过了。要更新它,请先移除整包,再重新导入。

+
+
+
+
帧四 · 重复导入。这句话出现在按确认之前 —— 说得晚就从提示变成了失败。它不是一屏专用终态,而是同一屏里多出的一条提示;下面照常逐条列出判定(包里每个技能都会命中「你已经有一个同名技能了」)。
+
+
+
+
导入扩展包
+
+

tide · v0.1.0 · 找到 10 个技能

+
会安装 · 10
+

这次安装已经开始了,现在取消不了。装完之后可以整包移除。

+
+
+
+
帧五 · 安装进行中点了「取消」。「取消」照常可点,而且必须可点 —— 主进程会如实回「这次中止不了」,把按钮禁掉等于把那句真话藏起来,用户只会以为界面卡死。拒绝写在屏内,弹窗留在原地:先关弹窗再飞一条提示,用户读到的第一件事就成了「它取消了」。此时标题栏的关闭键也一并不可用。
+
+
+
同一条红字还承载另外三种结局,措辞各自具名,呈现完全一样:预览已失效(请重新选择文件夹)· 这个文件夹在预览过程中被改动了(为安全起见本次不装)· 这一包的内容太大 / 文件太多(本版本不装)。共同点是一个都没装,盘上没有半成品 —— 措辞里不会出现「已装了 3 个」这种中间状态。没有「重新预览」按钮:用户的出路是关掉弹窗重新点「文件夹」。
+ +
为什么会有一屏什么都装不了:这一版收得很窄 —— 只装「自带全部所需文件、没有可执行脚本、没有我们兑现不了的调用设置」的技能。窄不是遗憾,是这条路上唯一诚实的选择:文件夹里的内容没有经过审核,能少猜一点就少猜一点。把这一屏画清楚,比把它藏起来更重要,否则用户只会得到一句「导入失败」。
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
判定用户看到的原因(上线文案,逐字)
不会安装 · 缺件类这个技能要用到整个插件目录里的东西,单独装过去会缺件
这个技能引用了它自己文件夹以外的文件,装过去会缺件
这个技能引用了同一插件里的别的技能,装过去会缺件
这个技能里有快捷方式(符号链接),装过去会缺件
这个技能的文件夹里有构建产物或版本库数据,装过去会缺件
这个技能带了可执行脚本,装过去不会保留执行权限
不会安装 · 行为会变这个技能带了我们这边没有对应功能的开关,装过去它的行为会变
不会安装 · 同名你已经有一个同名技能了,这个不装
这个插件里有两个技能重名,都不装
不会安装 · 读不了这个技能的说明我们读不出来
这个技能的文件读不出来
这个技能的文件夹我们检查不了(读不动或层级太深),为安全起见不装
这个技能是用快捷方式(符号链接)摆的,我们读不了它
不会安装 · 太多这一包技能太多,一次装不下
这一版还不认识的摆放方式这个文件夹没有插件说明文件,我们这一版还不认识这种摆法
技能放在 .claude/skills 里,我们这一版只认 skills 目录
这个目录里也有技能,但不在 skills 目录下,我们这一版不读它
这是一个插件市场目录,不是单个插件
这个插件根目录本身就是一个技能,我们这一版还不认识这种摆法
插件说明里自己声明了技能目录,我们这一版还不读这个声明
这一版不安装的内容这个插件带了斜杠命令,本版本不安装
这个插件带了子 agent,本版本不安装
这个插件带了钩子,本版本不安装
这个插件带了 MCP 服务配置,本版本不安装
整屏终态这个文件夹的插件说明我们读不出来,认不出它是哪个插件
这个插件里没有本版本能装的技能
这一包有 N 个技能,超过一次能装的上限(64 个),本版本不装
名字显示不了这个插件的名字这一版显示不了(超过 128 个字,或含有特殊字符)。装好之后,列表里会用它第一个技能的名字来称呼它。
+
三条措辞纪律:①永远说得出原因 —— 没有「其他原因」这一档,也没有「跳过 N 项」这种不点名的汇总;②不猜、不改写 —— 一个字节都不动用户的文件,读不了就直说读不了;③不把「不装」写成失败 —— 它是一次说清的取舍,行压暗、语气是陈述;整屏一个都装不上时才升为红字。「名字显示不了」这一条不影响能不能装:说完照样能确认,装进去只是列表里换个称呼。
+
+ + +
+
§9.2 · 已安装页新增「扩展包」区块 —— 看得见整体,也看得见零件
+

已装扩展包:整包一张卡,技能各有开关

+

已安装页此前只有平铺的组件行 —— 装进来的一包技能会散成 10 行,用户看不出它们是一件东西,也没有任何地方能把它整个拿掉。新区块排在「已安装条目」之前:先看到整体,再往下看到零件。区块里每个技能各有自己的开关(装完默认全关),包头挂「移除此扩展包」。

+
+
+
推荐
连接器
技能
Agent
插件
套件
已安装
导入
云能力
+
+
+
扩展包 · 1
+

一个扩展包里的技能属于这个包,只能整包移除。

+
+
+
+
tide扩展包来自本地文件夹
+
v0.1.0 · 10 个技能 · 全部未启用
+
+ +
+

这些技能已经装好了,但都还没启用。打开下面任意一个开关,下一条消息里就能用。

+
+
chain-analysis已安装 · 未启用
未启用 —— 打开开关后生效
+
event-impact已安装 · 未启用
未启用 —— 打开开关后生效
+
premarket-briefing已安装 · 未启用
未启用 —— 打开开关后生效
+
+
+
已安装条目 · 11
+
全局 · 对所有项目生效
+
c
chain-analysis技能已安装 · 未启用属于扩展包 tide
运行健康 · 未启用 —— 打开开关后生效
+
FS
文件系统精选
运行健康 · 已启用
+
+
+
帧六 · 刚装完(默认全关)。数据 = 打包版真机取证的那次导入(10 个技能全部装成)。包头的「移除此扩展包」是本地包的唯一移除入口 —— 它没有目录详情页可去。包卡没有图标,包内技能全部列出、不折行(帧内示 3 条)。刚装完的包排在最前;之后按名字排。
+
下方「已安装条目」组零改动:包里的技能本来就有各自的安装记录,照常出现在这里,只多一枚属于扩展包 tide。保留它有两个理由:一是搜索、更新、逐个启停这些既有能力不该因为「它在一个包里」就消失;二是用户在这里点垃圾桶,正是「单个技能不能单独移除」这条规则唯一说得出口的地方。两处开关翻的是同一个状态,不是两份设置 —— 走的是同一条启停通道、同一本账、同一次引擎重载。
+ +
+
+
+
+
tide扩展包来自本地文件夹
v0.1.0 · 10 个技能 · 全部未启用
+

无法移除此扩展包: 另一个安装正在进行,这次没有动任何文件。

+
+
+
态一 · 移除失败。包仍然留在列表里,错误就在卡上。判据是「只有明确的成功才算成功」—— 任何别的返回都按失败呈现,绝不因为「调用返回了」就把卡拿掉,也不飞一条「已移除」的提示。
+
+
+
+
+ 已移除扩展包「tide」· 有 1 项留了下来 +
  • chain-analysis 保留(你自己也单独装过它)
+ +
+
扩展包 · 0
+
+
态二 · 移除成功但有保留件。这条说明落在区块上、不在包卡里 —— 移除成功之后那张卡当场就没了,挂在卡里的解释会跟着一起消失,用户看见技能还在却再也拿不到原因。四种保留理由各有一句话:另一个扩展包仍在使用 / 你自己也单独装过它 / 在 Alpha 开始记录扩展包之前就装了 / 这一项不由 Alpha 管理。说明由用户自己按「知道了」关掉。
+
+
+ +
+
+
+
扩展包
+
+ 暂时读不出已装扩展包的清单 +

本机的已安装清单这次读不出来,所以这里暂时列不出已装的扩展包 —— 这不代表没装。

+ +
+
+
态三 · 清单读不出。「读不出来」和「一个都没装」是两件事:合并成一句「暂无扩展包」会让用户以为东西丢了,进而去重装 —— 而重装会撞上同名拒绝。所以两种状态在界面上永远分开:标题行不带计数连带一条:这一刻整页的「尚未安装任何扩展」空态也一并不显示 —— 清单都读不出来时,「你什么都没装」是一句没有依据的断言。
+
+
+
+
c
chain-analysis技能属于扩展包 tide
运行健康 · 未启用 —— 打开开关后生效
这个技能属于扩展包「tide」,不能单独移除。
+
+
态四 · 点了包里单个技能的移除。系统明确拒绝并把用户送到正确的动作 —— 不是静默什么都不做,更不是回一句「已移除」而东西还在。拒绝发生在删任何东西之前,同时飞一条同文的提示。「移除整个扩展包」把那个包顶到扩展包区块最前面,由用户自己按包头的移除键。注意开关仍然可用:单独停用一个技能是合法的,不能单独移除而已。
+
+
+
+ + +
+
§9.3 · 启用与生效 —— 默认全关是产品裁决,所以「装完什么都没发生」是默认路径
+

启用与生效:装完不等于能用,这件事必须说出来

+

本地扩展包装完之后默认全部关闭。这意味着:如果界面不主动引导,用户按完确认会觉得「什么都没发生」,然后判定这个功能坏了。所以确认成功之后界面自动切到已安装页并把新包排在最上面,包卡上带一行引导;开关拨开的那一刻引擎当场重载,下一条消息里技能就在。

+
+
+
1装好了,但都还关着
+

确认之后直接落在已安装页的新包卡上,卡里每个技能都是已安装 · 未启用,卡头一行字说明「打开开关才会生效」。

+
premarket-briefing已安装 · 未启用
未启用 —— 打开开关后生效
+
+
+
2你打开它
+

拨开关 = 立刻生效,不需要退出重进,也不需要重启应用。开关翻动的同时后台让引擎重新加载一次。

+
premarket-briefing
已启用
+
+
+
3下一条消息里就能用
+

回到会话,下一条消息发出去时这个技能已经在了 —— 输入 / 也能看到它。

+
/premarket-briefing
盘前简报
+
+
+
这三步是本节唯一的行为主张,也是最容易被实现切断的一段:安装成功后要重载引擎、整包移除成功后同样要重载(此前的整包移除只刷新列表、不重载 —— 结果是移除之后技能仍然能用到下次重启)。包头那行引导条在这个包里有任意一个技能被启用过之后就不再出现;先开后关的话它不会回来。
+ +
+
已装好 10 个技能 · 都还没启用,打开开关后生效
+
已装好 10 个技能 · 这次没能让引擎立即重新加载,重启 Alpha 后一定生效
+
扩展包已移除
+
已移除 · 这次没能让引擎立即重新加载,重启 Alpha 后一定生效
+
+
四条提示,两两成对。「待重载」是一条提示,不是行上的徽标:设置已经落账(重启必然生效),但不宣称已经能用。判据只有一条 —— 引擎没有确认重载成功,就不许出现「当场生效」这层意思。
+ +
+
+
tide扩展包来自本地文件夹
v0.1.0 · 10 个技能 · 3 个已启用
+
+
chain-analysis
已启用
+
event-impact已安装 · 未启用
未启用 —— 打开开关后生效
+
+
+
+
态五 · 移除进行中。按钮禁用并改词,防止连点造出第二次移除;整包移除是一次事务,过程中不给「部分已移除」的中间呈现。包头计数随开关实时走(3 个已启用),引导条在第一个技能被打开之后就不再出现。包内某个技能的开关状态读不出来时(账本里没有它的记录),状态行如实写「这一项的开关状态读不出来」,开关禁用 —— 不画一个拨下去必然失败的开关。
+ +
硬约束(不接受反转):①导入页不新增第六张卡,非插件目录的行为逐字不变;②每一条「不装」都必须带具名原因,没有「其他」这一档;③装完默认全关,界面不得「贴心地」自动打开任何一个;④移除失败必须显示失败,只有明确成功才算成功;⑤包内单个技能的移除请求 = 明确拒绝 + 指向整包移除,不是假成功;⑥取消 = 零写入,中止不了就如实说中止不了;⑦「读不出清单」不许显示成「没有安装」;⑧本地文件夹的内容没有经过审核这件事必须在按确认之前可见,不得写成「已验证 / 已检查 / 安全」;⑨引擎没重载成功不许宣称当场生效。
+
+
§8 · 与 v5 整包事实段(#396 已交付)的真源衔接 —— 无新帧:当前目录无已策展套件,不造数据