fix: focus built-in skill defaults
The Skills catalog exposed nine product-marketing recipes as independent capabilities and enabled every marketing package for every Runtime by default. It now keeps five top-level Skills, loads the nine recipes on demand through the product-marketing package, and leaves the three marketing Skills disabled until users enable them. Existing saved Skill settings remain authoritative. Release note: 内置 Skills 已从 14 个顶层入口精简为 5 个;去 AI 味和长文工具默认启用,产品营销类默认停用,已保存的用户设置保持不变。
This commit is contained in:
@@ -78,6 +78,16 @@ stdio MCP 配置的绝对 command 表达用户对该 MCP 的明确覆盖,原
|
||||
分阶段交付期间,未实现的“工具执行环境”不进入 Tab 状态集合。第一阶段状态集合只有
|
||||
`skills | mcp`,后续实现完成后才扩展为 `skills | mcp | tool-environment`。
|
||||
|
||||
### INV-11 内置 Skill 默认值不覆盖用户状态
|
||||
|
||||
只有状态存储中没有对应 Skill 记录时才使用产品默认值。通用文档工具
|
||||
`deai-writing`、`longdoc-docx` 默认启用;产品营销类 `product-evidence`、
|
||||
`product-marketing`、`product-presentation` 默认停用。用户显式保存的启停状态和
|
||||
Runtime 分配优先,升级不得重置。
|
||||
|
||||
九种产品营销文档配方属于 `product-marketing` 包内工作流,不是独立能力状态维度,
|
||||
不进入顶层 Skill 目录,也不单独注入 Runtime。
|
||||
|
||||
## 3. 状态维度
|
||||
|
||||
### 3.1 来源选择
|
||||
|
||||
@@ -179,6 +179,13 @@ Python、虚拟环境或 Conda 环境时,也不能被 GoodBuddy 静默替换
|
||||
`Skills`、`MCP`、`工具执行环境` 三个水平 Tab。始终默认打开 `Skills`。
|
||||
- 三个 Tab 使用共享 `PageTabs`,保留 `tablist`、`tab`、`tabpanel`、方向键和可见焦点。
|
||||
- `Skills` 继续承载现有 Skill 启停、导入、状态和 Runtime 分配。
|
||||
- 内置 Skill 顶层目录只展示五个独立能力:`deai-writing`、`longdoc-docx`、
|
||||
`product-evidence`、`product-marketing`、`product-presentation`。产品营销的功能目录、
|
||||
一页纸、技术方案、白皮书、招标、演示套件、客户案例和竞品定位作为
|
||||
`product-marketing` 内部工作流,不重复显示为独立 Skill。
|
||||
- `deai-writing` 和 `longdoc-docx` 默认启用;`product-evidence`、
|
||||
`product-marketing`、`product-presentation` 默认停用。已保存的用户启停和 Runtime
|
||||
分配始终优先于默认值;导入 Skill 的默认行为保持不变。
|
||||
- `MCP` 继续承载现有 MCP 配置、凭据、状态和 Runtime 分配;其现有内部分组页签保持不变。
|
||||
- `工具执行环境` 承载工具下载源、Node.js、Python、诊断和生效范围。
|
||||
- 合并导航不改写 Skills/MCP 业务数据;未来新增定向入口时必须进入对应水平 Tab。
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
- [x] 定义设置页结构、状态、文案和无障碍要求。
|
||||
- [x] 定义 Main 服务、shim、探测、诊断、PATH 注入和 Python 生命周期。
|
||||
- [x] 远程主机设置副标题已说明本机 Skills 和 MCP 不作用于远程主机。
|
||||
- [x] 将内置 Skill 顶层目录从 14 个收敛为 5 个,九种产品营销配方改为
|
||||
`product-marketing` 包内按需工作流。
|
||||
- [x] 通用文档工具默认启用,三个产品营销 Skill 默认停用,同时保留用户显式保存状态。
|
||||
|
||||
## 待实施
|
||||
|
||||
@@ -83,9 +86,10 @@
|
||||
|
||||
- `npm run typecheck` 通过。
|
||||
- `npm run lint` 通过。
|
||||
- `npm run build` 通过。
|
||||
- `npm test` 通过:`3412` 通过、`50` 跳过。
|
||||
- Skill 聚焦测试通过:Capability/Bundled Skills `39/39`,产品营销路由 `10/10`。
|
||||
- `npm test -- src/renderer/src/SettingsPanel.test.tsx` 通过:`80/80`。
|
||||
- 最近一次 `npm test` 为 `3410` 通过、`50` 跳过、`1` 失败;失败是并行修改中的浏览器
|
||||
控制用例超时,该用例随后单独复跑通过。本功能聚焦测试全部通过。
|
||||
- 设置页测试确认左侧不再显示独立 Skills/MCP,统一入口默认打开 Skills,MCP 原内层页签
|
||||
保持可用,且不显示工具执行环境占位。
|
||||
- 修复能力与工具 Tab 包装层在可滚动设置布局中被 Flexbox 压缩到零高度的问题,并增加与
|
||||
|
||||
@@ -232,6 +232,17 @@ URL 只存在于 Main/构建目录,不进入 Renderer。两个 Target 共用
|
||||
|
||||
SKILL 稳定调用命令名,不接收真实安装路径。
|
||||
|
||||
- `resources/skills` 顶层只保留五个带 `SKILL.md` 的独立包:
|
||||
`deai-writing`、`longdoc-docx`、`product-evidence`、`product-marketing`、
|
||||
`product-presentation`。
|
||||
- 九种纯 Markdown 产品营销配方位于 `product-marketing/workflows/<route-id>/`,
|
||||
使用 `WORKFLOW.md` 与同目录 `templates/`,不参与顶层 Skill 发现。
|
||||
- `CapabilityService` 仅在状态中没有该 ID 时应用默认值。三个产品营销 Skill 默认
|
||||
`enabled: false`;两个通用文档工具和导入 Skill 维持默认启用。显式保存状态不迁移、
|
||||
不覆盖。
|
||||
- 默认停用的 Skill 不进入 Runtime 指令和原生 Skill 包列表;用户启用后沿用现有四个
|
||||
Runtime 分配。
|
||||
|
||||
stdio MCP:
|
||||
|
||||
- 裸 `node`、`npm`、`npx`、`python`、`python3`、`pip` 使用工具 bin。
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
|
||||
Skills 面板
|
||||
现有 Skill 启停、导入、状态和 Runtime 分配
|
||||
五个内置 Skill;产品营销三个默认停用,通用文档工具两个默认启用
|
||||
|
||||
MCP 面板
|
||||
现有 MCP 配置、凭据、状态和 Runtime 分配
|
||||
@@ -80,6 +81,10 @@ MCP 面板
|
||||
测试环境
|
||||
```
|
||||
|
||||
产品营销的九种文档配方不单独占用 Skill 卡片。用户启用 `product-marketing` 后,
|
||||
由该 Skill 根据任务按需读取内部工作流。界面只展示能够独立启停和分配 Runtime 的
|
||||
五个顶层内置 Skill;已保存状态覆盖产品默认值。
|
||||
|
||||
工具执行环境面板内的区块顺序固定为工具下载源、Node.js、Python,不再增加页签。
|
||||
|
||||
## 3. 三个水平 Tab
|
||||
|
||||
@@ -308,3 +308,16 @@
|
||||
- 切换三个外层 Tab 后返回时保留本次设置面板会话中的编辑状态
|
||||
- 关闭并重新打开设置时,普通入口仍默认显示 Skills,不持久化上次外层 Tab
|
||||
- 未来新增 Skills/MCP 定向入口时打开对应水平 Tab
|
||||
|
||||
### US-F3 聚焦默认内置能力
|
||||
|
||||
作为普通用户,我希望默认只加载通用文档工具,不让低频产品营销说明占用每次会话的上下文。
|
||||
|
||||
验收:
|
||||
|
||||
- Skills 列表只把五个具备独立入口或执行器的内置能力显示为顶层 Skill
|
||||
- 产品营销的九种文档配方从 `product-marketing` 内部按需读取
|
||||
- `deai-writing` 和 `longdoc-docx` 在没有保存状态时默认启用
|
||||
- 三个产品营销 Skill 在没有保存状态时默认停用
|
||||
- 用户显式保存的启停状态和 Runtime 分配不因版本升级而重置
|
||||
- 用户导入的 Skill 继续使用现有默认启用和 Runtime 分配规则
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
---
|
||||
name: product-marketing
|
||||
version: 1.0.0
|
||||
version: 1.1.0
|
||||
description: |
|
||||
编排产品事实、功能列表、招标参数、产品 PPT、技术方案、一页纸、白皮书、招标响应、
|
||||
演示套件、客户案例和竞品定位等多个独立技能。用于一次请求需要选择或组合多种
|
||||
产品市场产物,并确保它们共享同一事实版本、术语和承诺边界。
|
||||
演示套件、客户案例和竞品定位。用于一次请求需要选择或组合多种产品市场产物,
|
||||
并确保它们共享同一事实版本、术语和承诺边界。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
- Execute
|
||||
compatibility: 可独立规划;执行节点需要对应子技能可用
|
||||
compatibility: 内置九种文档工作流;事实校验和 PPT 构建需要对应独立 Skill
|
||||
---
|
||||
|
||||
# 产品市场总编排
|
||||
|
||||
本技能只负责需求澄清、路由、依赖、门禁和跨产物一致性。各产物的方法和模板由
|
||||
对应独立技能负责,不能在总技能中重新实现一份简化版本。
|
||||
本技能负责需求澄清、路由、依赖、门禁和跨产物一致性。九种文档产物的方法与模板
|
||||
内置在 `workflows/`;需要哪种产物才读取对应 `WORKFLOW.md`,不要一次加载全部。
|
||||
|
||||
## 子技能
|
||||
## 内置工作流
|
||||
|
||||
| 技能 | 唯一职责 |
|
||||
|---|---|
|
||||
| `product-evidence` | 冻结产品事实、状态、证据和限制 |
|
||||
| `product-feature-catalog` | 功能目录和版本矩阵 |
|
||||
| `tender-technical-spec` | 可采购、可验收的招标技术规格 |
|
||||
| `product-presentation` | 产品介绍 PPT 与讲稿 |
|
||||
| `technical-proposal` | 客户或投标技术方案 |
|
||||
| `product-one-pager` | 一页纸产品概览和彩页文案 |
|
||||
| `solution-whitepaper` | 原理、架构、证据和边界白皮书 |
|
||||
| `tender-response-matrix` | 招标要求逐条响应、缺口和偏离 |
|
||||
| `sales-demo-kit` | 可执行演示套件、回退和演练材料 |
|
||||
| `customer-case-study` | 经授权且可复核的客户案例 |
|
||||
| `competitive-positioning` | 有来源的竞品矩阵和定位 |
|
||||
| 路由标识 | 工作流文件 | 唯一职责 |
|
||||
|---|---|---|
|
||||
| `product-feature-catalog` | `workflows/product-feature-catalog/WORKFLOW.md` | 功能目录和版本矩阵 |
|
||||
| `tender-technical-spec` | `workflows/tender-technical-spec/WORKFLOW.md` | 可采购、可验收的招标技术规格 |
|
||||
| `technical-proposal` | `workflows/technical-proposal/WORKFLOW.md` | 客户或投标技术方案 |
|
||||
| `product-one-pager` | `workflows/product-one-pager/WORKFLOW.md` | 一页纸产品概览和彩页文案 |
|
||||
| `solution-whitepaper` | `workflows/solution-whitepaper/WORKFLOW.md` | 原理、架构、证据和边界白皮书 |
|
||||
| `tender-response-matrix` | `workflows/tender-response-matrix/WORKFLOW.md` | 招标要求逐条响应、缺口和偏离 |
|
||||
| `sales-demo-kit` | `workflows/sales-demo-kit/WORKFLOW.md` | 可执行演示套件、回退和演练材料 |
|
||||
| `customer-case-study` | `workflows/customer-case-study/WORKFLOW.md` | 经授权且可复核的客户案例 |
|
||||
| `competitive-positioning` | `workflows/competitive-positioning/WORKFLOW.md` | 有来源的竞品矩阵和定位 |
|
||||
|
||||
`deai-writing` 和 `longdoc-docx` 是可选质量与导出技能,不承担产品事实判断。
|
||||
`product-evidence` 负责冻结产品事实、状态、证据和限制;`product-presentation`
|
||||
负责产品 PPT 与讲稿构建。两者保留为独立 Skill,使用前必须确认已启用。
|
||||
`deai-writing` 和 `longdoc-docx` 也是独立的质量与导出 Skill,不承担产品事实判断。
|
||||
|
||||
## 第一步:形成任务简报
|
||||
|
||||
@@ -60,8 +60,9 @@ compatibility: 可独立规划;执行节点需要对应子技能可用
|
||||
<python> "<skill-dir>/scripts/validate_route_plan.py" ./route-plan.json
|
||||
```
|
||||
|
||||
所有产物必须直接或间接依赖唯一的 `product-evidence` 节点。节点缺少对应技能时
|
||||
明确报告缺失,不允许由总技能静默生成低质量替代物。
|
||||
所有产物必须直接或间接依赖唯一的 `product-evidence` 节点。路由计划 v1 继续用
|
||||
`skill` 字段记录工作流或独立 Skill 标识,避免破坏已有计划。缺少或未启用
|
||||
`product-evidence`、`product-presentation` 时明确报告,不允许静默生成替代物。
|
||||
|
||||
`depends_on` 表示硬依赖:被依赖节点未 `completed` 时,本节点不能进入 `running`
|
||||
或 `completed`。可选输入(例如尚无授权的客户案例)不要写进 `depends_on`,而是
|
||||
@@ -179,7 +180,7 @@ deliverables/
|
||||
internal/ # 仅内部
|
||||
```
|
||||
|
||||
各子技能的 `output` 一律指向 `deliverables/<密级>/`,核验产物一律写入
|
||||
各工作流的 `output` 一律指向 `deliverables/<密级>/`,核验产物一律写入
|
||||
`build/check/`。交付目录内只允许出现 DOCX 与 PPTX。
|
||||
|
||||
单文件产物直接放 `build/<产物名>.md`。技术方案、白皮书等需要分章节的长文,改用
|
||||
|
||||
+3
-13
@@ -1,18 +1,8 @@
|
||||
---
|
||||
name: competitive-positioning
|
||||
version: 1.0.0
|
||||
description: |
|
||||
基于公开、可定位且有日期的来源生成竞品矩阵、差异化定位和销售边界。用于市场
|
||||
分析、产品定位和受控销售准备;不允许推断竞品“不支持”或生成无证据攻击性话术。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
compatibility: Markdown/JSON;需要提供或获准获取竞品公开来源
|
||||
---
|
||||
|
||||
# 竞品与定位分析
|
||||
|
||||
基于公开、可定位且有日期的来源生成竞品矩阵、差异化定位和销售边界。用于市场
|
||||
分析、产品定位和受控销售准备;不允许推断竞品“不支持”或生成无证据攻击性话术。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- 本产品事实与证据。
|
||||
+3
-13
@@ -1,18 +1,8 @@
|
||||
---
|
||||
name: customer-case-study
|
||||
version: 1.0.0
|
||||
description: |
|
||||
基于客户授权、实施记录和可复核指标生成客户案例、成功故事和案例摘要。用于公开
|
||||
宣传、销售材料或受控投标引用;没有披露授权或历史结果时不得生成可发布案例。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
compatibility: Markdown;建议配合 product-evidence
|
||||
---
|
||||
|
||||
# 客户案例
|
||||
|
||||
基于客户授权、实施记录和可复核指标生成客户案例、成功故事和案例摘要。用于公开
|
||||
宣传、销售材料或受控投标引用;没有披露授权或历史结果时不得生成可发布案例。
|
||||
|
||||
## 发布前硬门禁
|
||||
|
||||
必须具备:
|
||||
+4
-14
@@ -1,19 +1,9 @@
|
||||
---
|
||||
name: product-feature-catalog
|
||||
version: 1.0.0
|
||||
description: |
|
||||
基于产品事实与证据生成结构化功能列表、模块树和版本能力矩阵。用于产品规划、
|
||||
售前交流、招标附件或交付范围梳理;重点回答产品有什么功能、谁使用、产生什么
|
||||
结果,不负责撰写技术参数或长篇方案。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
compatibility: Markdown;建议配合 product-evidence
|
||||
---
|
||||
|
||||
# 产品功能列表
|
||||
|
||||
基于产品事实与证据生成结构化功能列表、模块树和版本能力矩阵。用于产品规划、
|
||||
售前交流、招标附件或交付范围梳理;重点回答产品有什么功能、谁使用、产生什么
|
||||
结果,不负责撰写技术参数或长篇方案。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- `product-evidence.json`,至少包含 `product`、`features`、`limitations`。
|
||||
+3
-13
@@ -1,18 +1,8 @@
|
||||
---
|
||||
name: product-one-pager
|
||||
version: 1.0.0
|
||||
description: |
|
||||
将已核验产品信息压缩为一页纸产品概览、宣传彩页或官网下载页文案。用于首次触达、
|
||||
展会资料和销售跟进;不替代完整功能清单、技术规格或技术方案。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
compatibility: Markdown/HTML;建议配合 product-evidence
|
||||
---
|
||||
|
||||
# 产品一页纸
|
||||
|
||||
将已核验产品信息压缩为一页纸产品概览、宣传彩页或官网下载页文案。用于首次触达、
|
||||
展会资料和销售跟进;不替代完整功能清单、技术规格或技术方案。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- 产品定位、目标受众和本次一页纸唯一目标。
|
||||
+3
-14
@@ -1,19 +1,8 @@
|
||||
---
|
||||
name: sales-demo-kit
|
||||
version: 1.0.0
|
||||
description: |
|
||||
生成可执行的产品演示故事线、环境清单、操作脚本、讲解词、失败回退和演练检查表。
|
||||
用于售前 Demo、POC 汇报或验收演示;不负责制作通用产品介绍 PPT。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
- Execute
|
||||
compatibility: Markdown/JSON;演示自动化需另行使用可用的控制工具
|
||||
---
|
||||
|
||||
# 售前演示套件
|
||||
|
||||
生成可执行的产品演示故事线、环境清单、操作脚本、讲解词、失败回退和演练检查表。
|
||||
用于售前 Demo、POC 汇报或验收演示;不负责制作通用产品介绍 PPT。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- 演示目标、受众、时长和希望获得的下一步。
|
||||
+4
-15
@@ -1,20 +1,9 @@
|
||||
---
|
||||
name: solution-whitepaper
|
||||
version: 1.0.0
|
||||
description: |
|
||||
编写解释行业问题、技术原理、参考架构、实现方法、测试证据和适用边界的产品或
|
||||
解决方案白皮书。用于技术传播和决策评估;不编制客户项目计划,也不把宣传口号
|
||||
当作技术论证。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
- Execute
|
||||
compatibility: Markdown;可配合 longdoc-docx 导出 Word(PDF 仅用于核验)
|
||||
---
|
||||
|
||||
# 解决方案白皮书
|
||||
|
||||
编写解释行业问题、技术原理、参考架构、实现方法、测试证据和适用边界的产品或
|
||||
解决方案白皮书。用于技术传播和决策评估;不编制客户项目计划,也不把宣传口号
|
||||
当作技术论证。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- 白皮书主题、目标读者、研究问题和发布范围。
|
||||
+4
-15
@@ -1,20 +1,9 @@
|
||||
---
|
||||
name: technical-proposal
|
||||
version: 1.0.0
|
||||
description: |
|
||||
基于客户需求、产品事实和项目约束编制完整技术方案,覆盖需求分析、总体架构、
|
||||
详细设计、实施、交付、质量、安全、风险和验收。用于投标技术方案或客户解决
|
||||
方案;不负责制定招标参数,也不替代逐条招标响应矩阵。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
- Execute
|
||||
compatibility: Markdown;可配合 longdoc-docx 导出 Word(PDF 仅用于核验)
|
||||
---
|
||||
|
||||
# 技术方案
|
||||
|
||||
基于客户需求、产品事实和项目约束编制完整技术方案,覆盖需求分析、总体架构、
|
||||
详细设计、实施、交付、质量、安全、风险和验收。用于投标技术方案或客户解决
|
||||
方案;不负责制定招标参数,也不替代逐条招标响应矩阵。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- 客户需求原文及编号、评分点或验收目标。
|
||||
+3
-13
@@ -1,18 +1,8 @@
|
||||
---
|
||||
name: tender-response-matrix
|
||||
version: 1.0.0
|
||||
description: |
|
||||
将招标文件技术要求逐条拆解并生成符合性响应矩阵、缺口清单和方案章节映射。用于
|
||||
投标前要求解析、响应检查和技术偏离管理;不制定采购参数,也不代写整篇方案。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
compatibility: Markdown/CSV;建议配合 product-evidence
|
||||
---
|
||||
|
||||
# 招标响应矩阵
|
||||
|
||||
将招标文件技术要求逐条拆解并生成符合性响应矩阵、缺口清单和方案章节映射。用于
|
||||
投标前要求解析、响应检查和技术偏离管理;不制定采购参数,也不代写整篇方案。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- 招标文件原文及可定位的章节、页码或条款编号。
|
||||
+4
-14
@@ -1,19 +1,9 @@
|
||||
---
|
||||
name: tender-technical-spec
|
||||
version: 1.0.0
|
||||
description: |
|
||||
将已核验产品能力转写为可采购、可测试、可验收的招标技术规格和参数表。用于编制
|
||||
招标文件技术要求、采购参数或技术规格书;不负责判断投标方是否符合,也不负责
|
||||
撰写整篇投标技术方案。
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Grep
|
||||
- Glob
|
||||
compatibility: Markdown;建议配合 product-evidence
|
||||
---
|
||||
|
||||
# 招标技术参数
|
||||
|
||||
将已核验产品能力转写为可采购、可测试、可验收的招标技术规格和参数表。用于编制
|
||||
招标文件技术要求、采购参数或技术规格书;不负责判断投标方是否符合,也不负责
|
||||
撰写整篇投标技术方案。
|
||||
|
||||
## 必要输入
|
||||
|
||||
- 产品事实与证据清单,尤其是功能、参数、限制、部署和兼容性。
|
||||
@@ -56,28 +56,55 @@ describe('bundled skills', () => {
|
||||
it('parses every bundled SKILL.md', async () => {
|
||||
const snapshot = await (await createService()).getSnapshot()
|
||||
|
||||
expect(snapshot.skills.length).toBeGreaterThan(0)
|
||||
expect(snapshot.skills.map((skill) => skill.id)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'deai-writing',
|
||||
'longdoc-docx',
|
||||
'product-evidence',
|
||||
'product-marketing',
|
||||
'product-presentation'
|
||||
])
|
||||
)
|
||||
expect(snapshot.skills).toHaveLength(5)
|
||||
expect(snapshot.skills.every((skill) => skill.source === 'builtin')).toBe(
|
||||
true
|
||||
)
|
||||
expect(snapshot.skills.map((skill) => skill.id)).toContain(
|
||||
'product-marketing'
|
||||
expect(
|
||||
snapshot.skills
|
||||
.filter((skill) => skill.enabled)
|
||||
.map((skill) => skill.id)
|
||||
).toEqual(
|
||||
expect.arrayContaining(['deai-writing', 'longdoc-docx'])
|
||||
)
|
||||
expect(
|
||||
snapshot.skills
|
||||
.filter((skill) => !skill.enabled)
|
||||
.map((skill) => skill.id)
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
'product-evidence',
|
||||
'product-marketing',
|
||||
'product-presentation'
|
||||
])
|
||||
)
|
||||
expect(snapshot.skills.map((skill) => skill.id)).not.toContain(
|
||||
'web-3d-game'
|
||||
)
|
||||
})
|
||||
|
||||
it('injects every enabled bundled skill with its resolved directory', async () => {
|
||||
it('injects enabled tools without loading disabled marketing skills', async () => {
|
||||
const service = await createService()
|
||||
const snapshot = await service.getSnapshot()
|
||||
|
||||
const instructions = await service.getSkillInstructions('continue')
|
||||
|
||||
expect(instructions).not.toContain('因超出注入上限未加载')
|
||||
for (const skill of snapshot.skills) {
|
||||
for (const skill of snapshot.skills.filter((item) => item.enabled)) {
|
||||
expect(instructions).toContain(join(builtinSkillsRoot, skill.id))
|
||||
}
|
||||
for (const skill of snapshot.skills.filter((item) => !item.enabled)) {
|
||||
expect(instructions).not.toContain(join(builtinSkillsRoot, skill.id))
|
||||
}
|
||||
})
|
||||
|
||||
it('exposes bundled Skills as native Harness packages', async () => {
|
||||
@@ -88,8 +115,12 @@ describe('bundled skills', () => {
|
||||
).resolves.toMatchObject({
|
||||
packages: expect.arrayContaining([
|
||||
{
|
||||
id: 'product-marketing',
|
||||
directory: join(builtinSkillsRoot, 'product-marketing')
|
||||
id: 'deai-writing',
|
||||
directory: join(builtinSkillsRoot, 'deai-writing')
|
||||
},
|
||||
{
|
||||
id: 'longdoc-docx',
|
||||
directory: join(builtinSkillsRoot, 'longdoc-docx')
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
@@ -358,6 +358,54 @@ describe('CapabilityService', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('disables product marketing skills by default and preserves explicit enablement', async () => {
|
||||
const { filePath, builtinRoot, importedRoot, service } =
|
||||
await createService()
|
||||
await Promise.all([
|
||||
writeSkill(builtinRoot, 'product-marketing', '产品市场'),
|
||||
writeSkill(builtinRoot, 'product-evidence', '产品证据'),
|
||||
writeSkill(builtinRoot, 'product-presentation', '产品演示'),
|
||||
writeSkill(builtinRoot, 'deai-writing', '中文审校'),
|
||||
writeSkill(builtinRoot, 'longdoc-docx', '长文导出')
|
||||
])
|
||||
|
||||
const snapshot = await service.getSnapshot()
|
||||
expect(snapshot.skills).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'product-marketing',
|
||||
enabled: false
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'product-evidence',
|
||||
enabled: false
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'product-presentation',
|
||||
enabled: false
|
||||
}),
|
||||
expect.objectContaining({ id: 'deai-writing', enabled: true }),
|
||||
expect.objectContaining({ id: 'longdoc-docx', enabled: true })
|
||||
])
|
||||
)
|
||||
|
||||
await service.setSkillEnabled('product-marketing', true)
|
||||
const reloaded = new CapabilityService(
|
||||
filePath,
|
||||
builtinRoot,
|
||||
importedRoot,
|
||||
cipher
|
||||
)
|
||||
await expect(reloaded.getSnapshot()).resolves.toMatchObject({
|
||||
skills: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'product-marketing',
|
||||
enabled: true
|
||||
})
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('imports and removes a managed SKILL.md package', async () => {
|
||||
const { directory, service } = await createService()
|
||||
const packageRoot = join(directory, 'source-skill')
|
||||
|
||||
@@ -78,6 +78,11 @@ const MAX_SKILL_PACKAGE_BYTES = 10 * 1024 * 1024
|
||||
const MAX_SKILL_PACKAGE_FILES = 128
|
||||
const MAX_SKILL_DEPTH = 6
|
||||
const MAX_SKILL_DISCOVERY_DEPTH = 4
|
||||
const DEFAULT_DISABLED_BUILTIN_SKILL_IDS = new Set([
|
||||
'product-marketing',
|
||||
'product-evidence',
|
||||
'product-presentation'
|
||||
])
|
||||
const MAX_SKILL_DISCOVERY_RESULTS = 64
|
||||
const MAX_SKILL_INSTRUCTION_CHARACTERS = 262_144
|
||||
const SKILL_DISCOVERY_IGNORED_DIRECTORIES = new Set([
|
||||
@@ -304,9 +309,13 @@ function emptyStoredCapabilities(
|
||||
}
|
||||
}
|
||||
|
||||
function defaultSkillState(): z.infer<typeof skillStateSchema> {
|
||||
function defaultSkillState(
|
||||
skill?: Pick<SkillSummary, 'id' | 'source'>
|
||||
): z.infer<typeof skillStateSchema> {
|
||||
return {
|
||||
enabled: true,
|
||||
enabled:
|
||||
skill?.source !== 'builtin' ||
|
||||
!DEFAULT_DISABLED_BUILTIN_SKILL_IDS.has(skill.id),
|
||||
assignments: [
|
||||
'model',
|
||||
'opencode',
|
||||
@@ -932,7 +941,7 @@ export class CapabilityService {
|
||||
skills: catalog
|
||||
.map((skill) => ({
|
||||
...skill,
|
||||
...(state.skills[skill.id] ?? defaultSkillState())
|
||||
...(state.skills[skill.id] ?? defaultSkillState(skill))
|
||||
}))
|
||||
.sort((left, right) =>
|
||||
left.source === right.source
|
||||
@@ -1664,7 +1673,8 @@ export class CapabilityService {
|
||||
return this.queue(async () => {
|
||||
const id = skillIdSchema.parse(skillId)
|
||||
const catalog = await this.getSkillCatalog()
|
||||
if (!catalog.some((skill) => skill.id === id)) {
|
||||
const skill = catalog.find((item) => item.id === id)
|
||||
if (!skill) {
|
||||
throw new Error('Skill 不存在')
|
||||
}
|
||||
const state = await this.load()
|
||||
@@ -1673,7 +1683,7 @@ export class CapabilityService {
|
||||
skills: {
|
||||
...state.skills,
|
||||
[id]: {
|
||||
...(state.skills[id] ?? defaultSkillState()),
|
||||
...(state.skills[id] ?? defaultSkillState(skill)),
|
||||
...update
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user