# Skill Runtime V4 — Native DeepSeek Harness Skill Integration > 本文档是 Worker Skill 运行时的当前权威架构(supersede > [skill-runtime.md](skill-runtime.md) 的 V1 运行时与 [skill-runtime-v2-implementation.md](skill-runtime-v2-implementation.md) > 的 V2/V3 工具面;V2/V3 的 Agent Library / Bundle / Grant / Search 资产继续有效)。 ## 1. 目标与结果 V4 之前,Pentester Worker 用自研工具(`pentester_skill_search` / `pentester_skill_load`) "模仿" Harness Skill。V4 之后,Worker **就是** DeepSeek Harness Skill 生态的一员: ``` Skill Pack (builtin 961 / custom) │ ▼ Agent Library │ Agent Skill Grant(profile inherit + override) │ ▼ PTES Shared Capabilities(Settings V5:Global ∪ Stage,additive) │ resolveEffectiveCapabilities() ▼ Effective Worker Skills(agent ∪ global ∪ stage,dedup + sort) │ ▼ pentester_delegate │ ▼ reserve Child Session ID(spawn 前写 run.json) │ ▼ frozen immutable snapshot(bundles + manifest.json) │ ▼ child agent/created(创建窗口内) │ ▼ scoped Pentester SkillProvider(dsh-pentester-frozen) │ ▼ ctx.skills │ ▼ Harness tool-skill(官方) ┌──────┴──────┐ ▼ ▼ durable skill-catalog skill({name}) tool name+description on-demand │ │ ▼ ▼ Worker task ``` 与黄金 DSH 会话一致的链路:Catalog(durable `user/message` source.kind=skill-catalog, 只含 name+description)→ 模型路由决策 → `skill({name})` → `` → 任务动作。 ## 2. 概念 | 概念 | 定义 | 实现位置 | |---|---|---| | Assignment | Agent 被授予什么能力(Agent Profile grant / user override) | `src/agent-library/` | | Shared Grant | PTES Global / per-Stage 的 Worker 共享能力(Settings V5 `ptesCapabilities`;additive,无 remove) | `src/ptes-capabilities.ts` | | Frozen Grant | Delegation 创建时冻结的 effective allowedSkillIds(agent ∪ global ∪ stage)+ bundle 副本 + manifest | `src/delegations.ts` preflight/dispatch | | Native Catalog | Worker Session 通过 Harness Skill Registry 看到的 Skill summaries | 官方 tool-skill(durable) | | Native Load | 模型调用 `skill({name})`,由 Harness 完成 | 官方 tool-skill tool | | Search V2.3 | BM25/alias/relation/MMR 检索(curation/推荐/离线分析) | `src/skill-search.ts` + generated indexes | Search V2.3 不再是 Worker runtime 工具面的一部分;保留用于 Agent grant curation、 Settings 推荐、benchmark 与未来自动推荐。 ## 3. 组件 ### 3.1 Frozen Skill Manifest(`src/native-skill/manifest.ts`) Dispatch(spawn 之前)与 bundle 快照一起写 `manifest.json`: ```json { "schemaVersion": 1, "delegationId": "D-003", "agentId": "web", "skills": [ { "name": "jwt-testing", "description": "…", "revision": "", "relativeBundle": "jwt-testing.skill" } ] } ``` 不含 Host 绝对路径;provider mount 与 cold resume 都读它 —— Catalog 始终是 Delegation 创建时的 frozen 边界(Settings 后续改动只影响未来 Delegation)。 ### 3.2 Frozen SkillProvider(`src/native-skill/provider.ts`) Provider name `dsh-pentester-frozen`,注册在每个 child 的 scope layer: - `list()`:只读 manifest metadata(catalog 阶段 0 次 SKILL.md body 读取)。 entries `invocation = { modelInvocable: true, userInvocable: false }` —— 本轮审计目标是 durable skill tool call,用户 `/skill` 直调路径统一关闭。 - `get()`:仅在 `skill({name})` 时读取 frozen SKILL.md(一次一个文件),校验 frontmatter name 与 revision(不可变语义:body 变化即拒绝)。永不回读 live Agent Library / Skill Pack。 - **Capability isolation(global mask)**:DSH SkillRegistry 是 global + scope-chain 合并读取,官方没有 per-scope skill restriction API。Provider 利用两个官方公开语义: 1. nearest layer 同名 entry 直接胜出(child layer 遮蔽 global 同名项); 2. `invocation = {modelInvocable:false, userInvocable:false}` 使 entry 从 model catalog 与 user invocation 双面消失。 因此 `list()` 除 granted entries 外,还枚举 global-only 视图 (`snapshot({cwd})`,scope 省略)里非 granted 的名称,返回同名 disabled shadow candidates —— Worker native catalog 严格等于 frozen grant,不泄漏 user/project/bundled skills。mask 枚举失败 → observation incomplete(保留 last-good, 不静默泄漏)。 - **已知边界(agent-layer runtime 注入)**:同 profile 的第三方插件(如 browser-skill)可在 `agent/session-start` 经 `agent.ctx` 调官方 `ctx.skills.register()` 把 skill 注入 agent 自己的 layer runtime map—— nearest-layer 语义下它会出现在每个 agent 的 catalog(普通 DSH 会话同样如此)。 官方 runtime register 是同层 first-wins,pentester 侧无法在不 patch harness 的 前提下撤销它(我们注册 disabled runtime entry 的时机晚于 session-start 就已 first-wins 失效)。这是部署组合层的注入,不属于 frozen grant 边界失败; 其工具面(如 browser_*)是否对 Worker 可见由 preset 工具组合决定(实测 Worker header.tools 不含 browser_*,catalog 条目只是描述性文字)。 - **resourceBase omitted**:官方 tool-skill 会把 resourceBase 渲染给模型,因此本 provider 不提供它(模型看到 "Resources are managed by provider …")。Host-private 路径不泄漏。 - Path traversal 防线:relativeBundle allowlist(`.skill` + 已知 legacy 目录) + 解析后 containment 检查 + revision 匹配。 ### 3.3 Mount seam(`src/native-skill/mount.ts`) `agent/created`(创建窗口内、首个 pre-step 之前)同步挂载: 1. 以 agent 对象为 authority(`agent.id` / `agent.session.header.cwd`),解析 child session id → run.json → D-xxx → Host-private frozen snapshot。 dispatch 已在 spawn 前把 `delegation.sessionId` 写入 run.json(见 §3.4), 所以 `agent/created` 时映射必然存在;cold resume 同样经过 `agent/created` 重新 mount 同一 manifest。 2. `ctx.get('skills')`(官方 optional-service 读取)返回 traced registry; 经它调用 `registerProvider` 把注册落在 child agent 自己的 scope layer, 效果归 child fiber —— Activation dispose 自动注销,无 process 级泄漏。 3. manifest 缺失(V3 及更早 Delegation 冷启动):从 agent-profile.json grant + 快照目录推导 manifest 并写盘(一次性确定性迁移;legacy underscore id 经 `LEGACY_NATIVE_SKILL_ALIASES` canonical 化)。推导不出 skill 时写空 manifest —— 边界仍然显式。 4. 非 Pentester Delegation 的 subagent → skip;registry 缺失 → `worker_skill_catalog_invalid` fail loud(veto 创建)。 ### 3.4 Capability 组合与 Dispatch 事务(`src/delegations.ts` + `src/ptes-capabilities.ts`) **Frozen Skill Grant 不再只来源 AgentProfile**。effective capability = agent grant ∪ global grant ∪ stage grant(三层 additive;agent 的 skillsRemove 只作用于 Agent inheritance,不减除 Shared policy),然后冻结: - preflight 一次 resolve(`manager.resolveDelegationCapabilities(stage, profile)`;纯函数 `resolveEffectiveCapabilities`,Bundle 展开复用 `expandGrantRef` / `resolveBundleLeaves`)→ 冻结进 `DelegationPlan.capabilities`; - dispatch 只消费 plan —— 不重新读 Settings / Global / Stage capability (TOCTOU 收敛:spawn 期间用户改 Shared 配置不影响已过 preflight 的 Worker, 下一次 Delegation 才使用新配置); - Effective Skills(agent + global + stage)进入**同一个** frozen native catalog:同一个 manifest、同一个 child-scoped SkillProvider、同一个 `skill({name})` tool —— 对 Worker 来说就是自己的 capability catalog, 没有第二套 tool,也不按来源区分(Trace 无需特殊着色); - Skill availability gate 作用在 effective 层:引用不可解析 → preflight fail loud(pack missing → `builtin_skill_pack_required`,custom-only 不 误伤;pack 在位但悬空 → `ptes_skill_not_available`),绝不在 snapshot 阶段静默跳过让 Worker 少能力。 Dispatch 顺序:resolve profile + effective capabilities → validate Skill availability + MCP Mandatory Gate → allocate D-xxx → **reserve child session id**(`SessionId(randomUUID())`)→ 写 Delegation `{sessionId, status: starting}` → snapshot frozen skills + manifest + agent-profile.json(V5: skillGrant / mcpAssignment 记录 agent/global/stage 三层来源 + effective 冻结值)→ `startContinuable({ childId: reserved })`(官方 `ContinuableStartSpec.childId` caller-reserved 语义)→ `agent/created` mount + tool surface validate → 初始 prompt admitted → `status = active`。 childId 预留保证 `agent/created` 触发时 Host 已能 child id → D-xxx → frozen snapshot —— 没有"provider 晚于 catalog"的 race。spawn 失败时 sessionId 保留为 attempted child identity(delegation.failed 事件带 attemptedSessionId), 官方 AgentRegistry.enter() 是 id 冲突的权威拒绝边界。 ### 3.5 Preset 面(`presets/pentester/agent.cordis.yml` + `run-state.mjs`) - `agent.cordis.yml` 新增官方行 `tool-skill`(`@deepseek-ai/dsh-tool-skill`), 注册进本 preset 的 standing scope —— Worker 经 composeFrom 继承可见。 - `ROOT_DENY = ['pentester_container_exec', 'skill']`:Root 是纯 Orchestrator。 `tools.restrict` 的 deny 同时移除工具 schema 与 tool-skill 的 catalog 注入 (官方 tool-visible → catalog 契约:restriction 后 `ctx.tools.get('skill', agent) !== skillTool`,catalog 不注入)。 - `WORKER_REQUIRED_TOOLS = ['pentester_container_exec', 'skill']`:worker 创建窗口校验(缺任一 → `worker_tool_surface_invalid` veto)。 - `agent/created` worker 分支顺序:mount provider(fail loud)→ validate tool surface。 ### 3.6 Native Audit Projection(`src/native-skill/audit.ts` + `ui-view/snapshot.ts`) **Source of truth = DSH child Session durable events**: - `tool/call` name=`skill`,arguments JSON 的 `name` 字段是权威 skill 名 (不靠解析 `` 标记)。 - 配对 `tool/result`:`error` 字段或 block `isError` → failed attempt; 成功才计入 `loadedSkillNames`(unique)。 - 重复 load:timeline 记录每次 call;Loaded Skills 计数按 unique successful names。 `preloadSkillActivity`(snapshot)按 delegation 读 child session events 投影为 `DelegationSkillActivity`(native 优先;V3 及更早回退只读 legacy skill-state.json)。成功读取后 content-diff + atomic 刷新 `/loaded_skills.md`(人类可读投影;由 snapshot single-flight 保护, 多 tab 安全)。`skill-state.json` 不再是 V4 authoritative state。 ### 3.7 Transcript / Trace / Output - 新 native `skill` 调用:完全由官方 `@deepseek-ai/dsh-client-ui-skill` SkillRow 渲染(本插件不注册 `skill` key,不复制官方 UI)。 - 历史 `pentester_skill_search` / `pentester_skill_load` 调用: `LegacyPentesterSkillToolRow`(read-only 回放)。 - Trace Agent node:`Skills N loaded`(unique successful native loads); topology 上每个成功加载的 skill 渲染为 agent 分支外侧的一个芯片元素 (chip),由 agent 边缘经短总线用线连接 —— skill 栈不与 delivery 行冲突 (外侧 vs 下方);timeline:`Skill `(native load 事件)。legacy timeline 显示为 legacy 语义。 - Output:`D-xxx/loaded_skills.md`(native audit 投影渲染)。 ## 4. Skill name 语法与 legacy 迁移 Harness native name grammar:`^[a-z0-9]+(?:-[a-z0-9]+)*$`(kebab-case)。 Builtin pack 1.0.0 有两个 underscore identity: | legacy id | canonical name | 迁移 | |---|---|---| | `security_reporter` | `security-reporter` | pack 1.0.1:frontmatter + agents/reporting grant | | `hello_js_reverse_skill` | `hello-js-reverse` | pack 1.0.1:目录名 + frontmatter + agents/web grant | `LEGACY_NATIVE_SKILL_ALIASES`(`src/native-skill/names.ts`)只覆盖这两个已知 id: 确定性查表、collision-checked(模块自检 + 测试)、UI diagnostic 明确标注。 禁止通用 `replaceAll('_', '-')`。旧 grant / user override / 旧 run 记录在 resolve 阶段安全迁移(`canonicalizeSkillId`),能力不静默消失。 Canonical namespace(单一 ID 系统): ``` Agent Library Skill ID = SKILL.md frontmatter.name = Harness native Skill name = Trace skill name = tool call name ``` `pnpm run validate:skills` 增加 native name 校验(961/961 必须通过)。 ## 4.1 添加自定义 Skill **Skill 就是一个文件夹** —— 你可以在自己的 skills 目录里随手加一个, 不需要发布 Skill Pack: ``` $DSH_HOME/dsh-pentester/skills/ # 默认 ~/.dsh/dsh-pentester/skills/ └── my-reverse-proxy-skill.skill/ # 一个文件夹 = 一个 skill └── SKILL.md # 唯一必需的文件 ``` `SKILL.md` 只需要 YAML frontmatter 的 `name` + `description`,其余是正文: ```markdown --- name: my-reverse-proxy-skill description: > Nginx 反向代理配置审计与绕过技巧。当目标存在反代层、需要判断 后端真实服务或测试路径改写绕过时使用。 --- # 反向代理审计 1. 指纹识别:… 2. 路径改写测试:… ``` 规则与行为: - **name = 文件夹名(去 `.skill` 后缀)= 一切 ID**(catalog、`skill({name})`、 Trace、loaded_skills.md 共用一个名字,见 §4 的 canonical namespace)。 name 必须是 kebab-case(`^[a-z0-9]+(?:-[a-z0-9]+)*$`),否则不进入新 Worker 的 native catalog(加载时产生 diagnostic)。 - **description 是模型的路由依据** —— Worker 看不到 SKILL.md 正文,只看 catalog 里这一行描述来决定要不要 `skill({name})`。写得具体(何时用、 覆盖什么场景),模型才会正确选中它。 - 目录 `fs.watch` 热更新(200ms debounce + 轮询兜底):放进新文件夹约 1–2 秒后 Agent Library 自动看到它,Settings → Pentester → Agent Library → Skills 里出现,无需重启 DSH。 - 授权即生效路径:在 Settings 里把它 grant 给某个 Agent(或建 Custom Agent extends Builtin + `skills: [{type: skill, id: my-reverse-proxy-skill}]`)→ 之后新建的 Delegation 会把它冻结进 catalog。**已存在的 Delegation 不变** (frozen grant 硬性不变量)。 - Custom skill 与 Builtin 共享全局 namespace:不得与 builtin 重名(重名 产生 `skill_duplicate_name` diagnostic,双方都不可用);单 bundle 限制 10 MiB(builtin 32 MiB),含 symlink 拒绝加载。 - 验证:`pnpm run validate:skills`(也可对单目录跑 `DSH_PENTESTER_BUILTIN_SKILLS_DIR=<目录> pnpm run validate:skills`)。 调好的 custom skills 想分享给他人时,可发布为外部 Skill Pack (见 [external-skill-pack-implementation.md](external-skill-pack-implementation.md))。 ## 5. 兼容矩阵 | 场景 | 行为 | |---|---| | 新 Delegation(V4) | frozen snapshot + manifest + native provider + skill tool | | V3 Delegation cold resume | deriveLegacyManifest(一次性迁移)→ native provider | | V2 Delegation(input/skills) | 同上(read-only 回退读取 input/skills) | | 旧 Session 回放 | LegacyPentesterSkillToolRow(durable events,不依赖 runtime) | | 旧 loaded_skills.md / skill-state.json | 只读保留;新内容来自 native 投影 | | Empty grant Delegation | 空 manifest + provider 仍挂载(mask-only 边界);官方 catalog 不注入条目 | | Builtin Skill Pack missing + **unresolvable** grant(builtin skill) | `pentester_delegate` preflight 整批 fail `builtin_skill_pack_required`(0 side effects,不假装空 catalog) | | Builtin Skill Pack missing + custom-only grant | 正常 delegate(custom skill 不依赖 pack,gate 不误伤) | ## 6. 测试 - `test/native-skill.test.ts`:name grammar / alias 迁移 / manifest 读写与 fail-loud / provider progressive disclosure(list 0 次 body、get 1 次)/ frozen revision / capability isolation mask / path traversal / native audit golden 序列 / loaded_skills.md 渲染 / legacy bundle 兼容。 - `test/native-skill-mount.test.ts`:agent/created 同步挂载 / V3 manifest 推导迁移 / legacy id canonical 化 / skip 语义 / fail-loud / 空 grant。 - `test/delegations.test.ts`:childId 预留(UUID)+ ContinuableStartSpec.childId 一致性 + spawn 前写入 run.json。 - `test/run-state.test.ts` / `test/tools.test.ts`:ROOT_DENY / mount 调用顺序 / worker 工具面契约。 - E2E:真实 DSH web profile 下验证 durable skill-catalog、native skill call、 官方 Skill Row、Trace / loaded_skills.md、冷启动 frozen 语义。