feat: add static repository plugin format

This commit is contained in:
Tianyi Cui
2026-07-30 04:15:07 +08:00
parent fd4d369907
commit fa7051a9d1
35 changed files with 1310 additions and 27 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/skill/skill-local/README.md
README.md: 2077cf852fe90f7a0fec4e9bda1e9ff68fc56453
README.zh.md: ba1c71f1bc1916daad82d872ae6658bb203133c9
README.md: 836a2a631e9e6e452a11e3cffc102de355f1c5d9
README.zh.md: 2e2cc45ad80f760e04f813b7ee85932b51b1df05

View File

@@ -14,6 +14,8 @@ Requires `ctx.skills` (`inject: ['skills']`).
| Field | Default | Meaning |
|---|---|---|
| `providerName` | `local` | Unique name used to register this provider on `ctx.skills`. |
| `includeDefaultRoots` | `true` | Include project and user roots around `customSkillDirs`; set false for an isolated custom-root provider. |
| `dshHome` | `$DSH_HOME` or `~/.dsh` | DeepSeek Harness config root resolved by [`@deepseek-ai/dsh-paths`](../../util/paths/README.md); scans `skills` under this directory. |
| `agentsHome` | `$DSH_AGENTS_HOME` or `~/.agents` | Shared agent config root scanned for compatible skills. |
| `customSkillDirs` | `[]` | Additional local skill roots scanned after project roots and before user roots. |
@@ -36,7 +38,7 @@ Default roots are resolved in this provider's rank order:
| 400 | `user-dsh` | `<dshHome>/skills` |
| 500 | `user-agents` | `<agentsHome>/skills` |
The project root is the nearest ancestor containing `.git`; without one, the current cwd is used. The user DSH root skips its `.system` child so system-owned directories are not treated as normal user skills. This provider supplies project and user skills; another provider may supply built-in system skills.
The project root is the nearest ancestor containing `.git`; without one, the current cwd is used. The user DSH root skips its `.system` child so system-owned directories are not treated as normal user skills. `includeDefaultRoots: false` omits both project and user rows while retaining explicitly configured custom and bundled roots, allowing several uniquely named isolated providers such as immutable repository Plugins. This provider supplies project and user skills; another provider may supply built-in system skills.
When `ctx.fs` is available, discovery lists roots through `ctx.fs.listDir`, reads skill files through `ctx.fs.readText`, and probes `.git` through the filesystem service. Full skill loads forward the lookup abort signal to filesystem metadata and content reads. Without a filesystem service, the provider falls back to abortable Node filesystem I/O so minimal local contexts can still load skills. Confirmed missing paths are valid empty state, malformed or non-text entries warn and skip, and unexpected discovery/read failures make the registry snapshot incomplete rather than replacing a last-good model catalog with a misleading deletion.

View File

@@ -14,6 +14,8 @@
| 字段 | 默认值 | 含义 |
|---|---|---|
| `providerName` | `local` | 在 `ctx.skills` 上注册该提供方时使用的唯一名称。 |
| `includeDefaultRoots` | `true` | 在 `customSkillDirs` 周围包含项目根和用户根;设为 false 时仅使用隔离的自定义根。 |
| `dshHome` | `$DSH_HOME``~/.dsh` | 由 [`@deepseek-ai/dsh-paths`](../../util/paths/README.md) 解析的 DeepSeek Harness 配置根目录;扫描该目录下的 `skills`。 |
| `agentsHome` | `$DSH_AGENTS_HOME``~/.agents` | 为兼容 skill 扫描的共享 agent智能体配置根目录。 |
| `customSkillDirs` | `[]` | 在项目根目录之后、用户根目录之前扫描的其他本地 skill 根目录。 |
@@ -36,7 +38,7 @@
| 400 | `user-dsh` | `<dshHome>/skills` |
| 500 | `user-agents` | `<agentsHome>/skills` |
项目根目录是包含 `.git` 的最近祖先目录;如果不存在,则使用当前 cwd。用户 DSH 根目录会跳过其 `.system` 子目录,因此归系统所有的目录不会被当作普通用户 skill。该提供方提供项目和用户 skill其他提供方可提供内置系统 skill。
项目根目录是包含 `.git` 的最近祖先目录;如果不存在,则使用当前 cwd。用户 DSH 根目录会跳过其 `.system` 子目录,因此归系统所有的目录不会被当作普通用户 skill。`includeDefaultRoots: false` 会省略项目和用户两类根,同时保留显式配置的自定义根与 bundled 根,因此可以挂载多个唯一命名的隔离提供方,例如不可变 repository Plugin。该提供方提供项目和用户 skill其他提供方可提供内置系统 skill。
`ctx.fs` 可用时,发现通过 `ctx.fs.listDir` 列出根,通过 `ctx.fs.readText` 读取 skill 文件,并通过文件系统服务探测 `.git`。完整 skill 加载会将查找中止信号转发给文件系统元数据和内容读取。如果没有文件系统服务,提供方回退到可中止的 Node 文件系统 I/O使最小本地上下文仍能加载 skill。已确认缺失的路径属于有效空状态格式错误或非文本条目会警告并跳过意外的发现或读取失败会使注册表快照不完整系统不会因此用看似发生删除的结果替换上一份可用模型目录。

View File

@@ -47,6 +47,10 @@ export const inject = ['skills']
/** Local filesystem skill provider configuration. */
export interface Config {
/** Unique provider name. Defaults to `local`. */
providerName?: string
/** Whether project and user roots are included around custom roots. */
includeDefaultRoots?: boolean
/** DeepSeek Harness config root. Defaults to `$DSH_HOME` or `~/.dsh`. */
dshHome?: string
/** Shared agent config root. Defaults to `$DSH_AGENTS_HOME` or `~/.agents`. */
@@ -70,6 +74,8 @@ export interface Config {
}
export const Config: Schema<Config> = z.object({
providerName: z.string().min(1).default('local'),
includeDefaultRoots: z.boolean().default(true),
dshHome: z.string(),
agentsHome: z.string(),
customSkillDirs: z.array(z.string()).default([]),
@@ -138,7 +144,8 @@ export function apply(ctx: Context, config: Config = {}): void {
/** Provider that maps local project/user skill roots into `ctx.skills`. */
export class LocalSkillProvider implements SkillProvider {
readonly name = 'local'
readonly name: string
private readonly includeDefaultRoots: boolean
private readonly dshHome: string
private readonly agentsHome: string
private readonly customSkillDirs: string[]
@@ -151,6 +158,8 @@ export class LocalSkillProvider implements SkillProvider {
control: SkillProviderControl,
config: Config = {},
) {
this.name = config.providerName ?? 'local'
this.includeDefaultRoots = config.includeDefaultRoots ?? true
this.dshHome = resolveDshHome(config.dshHome)
this.agentsHome = resolve(config.agentsHome ?? process.env.DSH_AGENTS_HOME ?? join(homedir(), '.agents'))
this.customSkillDirs = (config.customSkillDirs ?? []).map(root => resolve(root))
@@ -177,7 +186,7 @@ export class LocalSkillProvider implements SkillProvider {
}
const candidates: SkillCandidate[] = []
for (const root of roots) {
for (const skill of await discoverRoot(root, this.ctx)) {
for (const skill of await discoverRoot(root, this.ctx, this.name)) {
candidates.push(skill)
}
}
@@ -227,21 +236,23 @@ export class LocalSkillProvider implements SkillProvider {
private async roots(cwd: string | undefined): Promise<SkillRoot[]> {
const roots: SkillRoot[] = []
if (cwd !== undefined) {
if (this.includeDefaultRoots && cwd !== undefined) {
const projectRoot = await findProjectRoot(resolve(cwd), optionalFileSystem(this.ctx))
roots.push(
{ path: join(projectRoot, '.dsh/skills'), source: 'project-dsh', rank: PROJECT_DSH_RANK, projectRoot },
{ path: join(projectRoot, '.agents/skills'), source: 'project-agents', rank: PROJECT_AGENTS_RANK, projectRoot },
)
}
roots.push(
...this.customSkillDirs.map(path => ({ path, source: 'custom' as const, rank: CUSTOM_RANK })),
{ path: join(this.dshHome, 'skills'), source: 'user-dsh', rank: USER_DSH_RANK, skipSystem: true },
{ path: join(this.agentsHome, 'skills'), source: 'user-agents', rank: USER_AGENTS_RANK },
...this.bundledSkillDir === undefined
? []
: [{ path: this.bundledSkillDir, source: 'bundled' as const, rank: BUNDLED_RANK, trustedHost: true }],
)
roots.push(...this.customSkillDirs.map(path => ({ path, source: 'custom' as const, rank: CUSTOM_RANK })))
if (this.includeDefaultRoots) {
roots.push(
{ path: join(this.dshHome, 'skills'), source: 'user-dsh', rank: USER_DSH_RANK, skipSystem: true },
{ path: join(this.agentsHome, 'skills'), source: 'user-agents', rank: USER_AGENTS_RANK },
)
}
if (this.bundledSkillDir !== undefined) {
roots.push({ path: this.bundledSkillDir, source: 'bundled', rank: BUNDLED_RANK, trustedHost: true })
}
return roots
}
}
@@ -693,7 +704,7 @@ function hasErrorCode(error: unknown, code: string): boolean {
return typeof error === 'object' && error !== null && 'code' in error && error.code === code
}
async function discoverRoot(root: SkillRoot, ctx: Context): Promise<SkillCandidate[]> {
async function discoverRoot(root: SkillRoot, ctx: Context, provider: string): Promise<SkillCandidate[]> {
const skills: SkillCandidate[] = []
const entries = await listSkillRootEntries(root, ctx)
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
@@ -711,7 +722,7 @@ async function discoverRoot(root: SkillRoot, ctx: Context): Promise<SkillCandida
description: parsed.description,
...parsed.whenToUse !== undefined ? { whenToUse: parsed.whenToUse } : {},
invocation: parsed.invocation,
provider: 'local',
provider,
source: root.source,
rank: root.rank,
locator,