refactor: apply repository naming contract

Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
Tianyi Cui
2026-08-13 00:36:22 +08:00
parent 101df7cf58
commit a2d0f7f411
3281 changed files with 21730 additions and 21592 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/README.md
README.md: cd606e778335249bf8ecc876d3b1a4e5feb79448
README.zh.md: ec43a531033799e5e5e920c1b2212f39a5699e81
README.md: a9fc04cbd4e81ddcf4b9cfebb4a4d8b9e8888227
README.zh.md: 84bfa69e133962e27ecb792d6b989ca446eee787

View File

@@ -8,7 +8,7 @@ This family discovers reusable agent instructions and exposes them to the model
|---|---|---|
| [`skill/`](skill/README.md) | Defines skill provider registration and lookup | `ctx.skills` |
| [`skill-badge/`](skill-badge/README.md) | Contributes the optional bundled dsh badge skill | registers on `ctx.skills` |
| [`skill-local/`](skill-local/README.md) | Discovers skills from local filesystems | registers on `ctx.skills` |
| [`skill-filesystem/`](skill-filesystem/README.md) | Discovers skills from local filesystems | registers on `ctx.skills` |
| [`tool-skill/`](tool-skill/README.md) | Publishes the skill catalog and model-facing loader | registers on `ctx.tools` |
This capability remains outside the core control spine and can use local, embedded, or remote providers without changing the model-facing contract.

View File

@@ -8,7 +8,7 @@
|---|---|---|
| [`skill/`](skill/README.md) | 定义 skill 提供方注册和查找 | `ctx.skills` |
| [`skill-badge/`](skill-badge/README.md) | 贡献可选的内置 dsh 徽章 skill | 注册到 `ctx.skills` |
| [`skill-local/`](skill-local/README.md) | 从本地文件系统发现 skill | 注册到 `ctx.skills` |
| [`skill-filesystem/`](skill-filesystem/README.md) | 从本地文件系统发现 skill | 注册到 `ctx.skills` |
| [`tool-skill/`](tool-skill/README.md) | 发布 skill 目录和面向模型的 loader | 注册到 `ctx.tools` |
该能力位于核心控制主干之外,可以使用本地、嵌入式或远程提供方,而无需更改面向模型的约定。

View File

@@ -3,13 +3,13 @@ import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import SkillService from '@deepseek-ai/dsh-skill'
import SkillRegistry from '@deepseek-ai/dsh-skill'
import * as SkillBadge from '@deepseek-ai/dsh-skill-badge'
describe('dsh-skill-badge', () => {
it('registers and disposes the bundled badge skill', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillBadge)
const resourcePath = fileURLToPath(new URL('../assets/', import.meta.url))

View File

@@ -9,6 +9,6 @@
{ "path": "../../../vendor/cosmokit" },
{ "path": "../../../vendor/cordis" },
{ "path": "../skill" },
{ "path": "../../support/invariants" }
{ "path": "../../runtime-diagnostics/invariants" }
]
}

View File

@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# 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: 877b784353998a191a2d1a377aaf5406e1a97965
README.zh.md: 0cd67ae9d0540993a831822352753eae257300dc
# pnpm run verify-translation-pairing --write packages/skill/skill-filesystem/README.md
README.md: 33fb550a9ca804c4f0f380232c2bfd94cc58a67e
README.zh.md: 4c09111fc2d31bf5467b57f90f65cf68e298da09

View File

@@ -1,4 +1,4 @@
# @deepseek-ai/dsh-skill-local
# @deepseek-ai/dsh-skill-filesystem
English | [中文](README.zh.md)
@@ -14,9 +14,9 @@ Requires `ctx.skills` (`inject: ['skills']`).
| Field | Default | Meaning |
|---|---|---|
| `providerName` | `local` | Unique name used to register this provider on `ctx.skills`. |
| `providerName` | `filesystem` | 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. |
| `dshHome` | `$DSH_HOME` or `~/.dsh` | DeepSeek Harness config root resolved by [`@deepseek-ai/dsh-home-paths`](../../util/home-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. |
| `watch` | `true` | Watch host-local roots and invalidate the local provider when catalog membership or frontmatter may have changed. |

View File

@@ -1,4 +1,4 @@
# @deepseek-ai/dsh-skill-local
# @deepseek-ai/dsh-skill-filesystem
[English](README.md) | 中文
@@ -14,9 +14,9 @@
| 字段 | 默认值 | 含义 |
|---|---|---|
| `providerName` | `local` | 在 `ctx.skills` 上注册该提供方时使用的唯一名称。 |
| `providerName` | `filesystem` | 在 `ctx.skills` 上注册该提供方时使用的唯一名称。 |
| `includeDefaultRoots` | `true` | 在 `customSkillDirs` 周围包含项目根和用户根;设为 false 时仅使用隔离的自定义根。 |
| `dshHome` | `$DSH_HOME``~/.dsh` | 由 [`@deepseek-ai/dsh-paths`](../../util/paths/README.md) 解析的 DeepSeek Harness 配置根目录;扫描该目录下的 `skills`。 |
| `dshHome` | `$DSH_HOME``~/.dsh` | 由 [`@deepseek-ai/dsh-home-paths`](../../util/home-paths/README.md) 解析的 DeepSeek Harness 配置根目录;扫描该目录下的 `skills`。 |
| `agentsHome` | `$DSH_AGENTS_HOME``~/.agents` | 为兼容 skill 扫描的共享 agent智能体配置根目录。 |
| `customSkillDirs` | `[]` | 在项目根目录之后、用户根目录之前扫描的其他本地 skill 根目录。 |
| `watch` | `true` | 监视宿主本地根,并在目录成员或 frontmatter 可能发生变化时使本地提供方失效。 |

View File

@@ -1,5 +1,5 @@
{
"name": "@deepseek-ai/dsh-skill-local",
"name": "@deepseek-ai/dsh-skill-filesystem",
"description": "Local filesystem skill provider for the DeepSeek Harness",
"version": "0.0.1-rc.2",
"publishConfig": {
@@ -8,7 +8,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
"directory": "packages/skill/skill-local"
"directory": "packages/skill/skill-filesystem"
},
"type": "module",
"main": "lib/index.js",
@@ -34,7 +34,7 @@
"peerDependencies": {
"@deepseek-ai/dsh-fs": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-home-paths": "workspace:^",
"@deepseek-ai/dsh-skill": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
},
@@ -46,7 +46,7 @@
"devDependencies": {
"@deepseek-ai/dsh-fs": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-home-paths": "workspace:^",
"@deepseek-ai/dsh-skill": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
}

View File

@@ -6,7 +6,7 @@
* user roots, parses YAML frontmatter, and loads bodies through `ctx.fs` when a
* filesystem service is present.
*
* @module @deepseek-ai/dsh-skill-local
* @module @deepseek-ai/dsh-skill-filesystem
*/
import { access, lstat, readdir, readFile, stat } from 'node:fs/promises'
@@ -19,7 +19,7 @@ import z from '@deepseek-ai/schemastery'
import type Schema from '@deepseek-ai/schemastery'
import { parse as parseYaml } from 'yaml'
import type { FileSystem, FsDirEntry, FsTarget } from '@deepseek-ai/dsh-fs'
import { canonicalizeWatchPath, resolveDshHome } from '@deepseek-ai/dsh-paths'
import { canonicalizeWatchPath, resolveDshHome } from '@deepseek-ai/dsh-home-paths'
import {
BUNDLED_SKILL_RANK,
isSkillName,
@@ -42,7 +42,7 @@ const DEFAULT_WATCH_STABILITY_THRESHOLD_MS = 200
const DEFAULT_WATCH_POLL_INTERVAL_MS = 100
const DEFAULT_WATCH_MAX_PROJECTS = 128
export const name = 'skill-local'
export const name = 'skill-filesystem'
export const inject = ['skills']
/** Local filesystem skill provider configuration. */
@@ -74,7 +74,7 @@ export interface Config {
}
export const Config: Schema<Config> = z.object({
providerName: z.string().min(1).default('local'),
providerName: z.string().min(1).default('filesystem'),
includeDefaultRoots: z.boolean().default(true),
dshHome: z.string(),
agentsHome: z.string(),
@@ -128,14 +128,14 @@ interface ResolvedWatchConfig {
/** Register the local filesystem skill provider on `ctx.skills`. */
export function apply(ctx: Context, config: Config = {}): void {
let provider!: LocalSkillProvider
let provider!: FileSystemSkillProvider
ctx.skills.registerProvider((control) => {
provider = new LocalSkillProvider(ctx, control, config)
provider = new FileSystemSkillProvider(ctx, control, config)
return provider
})
ctx.effect(function* () {
yield async () => { await provider.dispose() }
}, 'skill-local watcher')
}, 'skill-filesystem watcher')
ctx.on('fs/observed', (target, _observation, actor) => {
if (mutationToolName(actor) === undefined) return
provider.observeHostMutation(target.displayPath)
@@ -143,7 +143,7 @@ export function apply(ctx: Context, config: Config = {}): void {
}
/** Provider that maps local project/user skill roots into `ctx.skills`. */
export class LocalSkillProvider implements SkillProvider {
export class FileSystemSkillProvider implements SkillProvider {
readonly name: string
private readonly includeDefaultRoots: boolean
private readonly dshHome: string
@@ -158,7 +158,7 @@ export class LocalSkillProvider implements SkillProvider {
control: SkillProviderControl,
config: Config = {},
) {
this.name = config.providerName ?? 'local'
this.name = config.providerName ?? 'filesystem'
this.includeDefaultRoots = config.includeDefaultRoots ?? true
this.dshHome = resolveDshHome(config.dshHome)
this.agentsHome = resolve(config.agentsHome ?? process.env.DSH_AGENTS_HOME ?? join(homedir(), '.agents'))
@@ -338,7 +338,7 @@ class SkillWatchManager {
async dispose(): Promise<void> {
this.closing = true
this.lifecycle.abort(new Error('skill-local watcher disposed'))
this.lifecycle.abort(new Error('skill-filesystem watcher disposed'))
const states = [...this.roots.values()]
this.roots.clear()
this.projects.clear()
@@ -425,7 +425,7 @@ class SkillWatchManager {
// oxlint-disable-next-line typescript/no-unnecessary-condition -- teardown can race awaited watcher startup
if (!this.closing) {
state.unhealthy = true
this.ctx.logger.warn(`skill-local: failed to watch ${state.root.path}: ${errorMessage(error)}`)
this.ctx.logger.warn(`skill-filesystem: failed to watch ${state.root.path}: ${errorMessage(error)}`)
}
throw error
}
@@ -556,7 +556,7 @@ class SkillWatchManager {
private handleWatcherError(state: RootWatchState, error: unknown): void {
if (this.closing) return
this.ctx.logger.warn(`skill-local: watcher for ${state.root.path} failed: ${errorMessage(error)}`)
this.ctx.logger.warn(`skill-filesystem: watcher for ${state.root.path} failed: ${errorMessage(error)}`)
state.unhealthy = true
this.queueInvalidation()
this.scheduleRewatch(state)
@@ -591,7 +591,7 @@ class SkillWatchManager {
try {
await watcher.close()
} catch (error) {
this.ctx.logger.warn(`skill-local: failed to close watcher: ${errorMessage(error)}`)
this.ctx.logger.warn(`skill-filesystem: failed to close watcher: ${errorMessage(error)}`)
}
}
}
@@ -698,7 +698,7 @@ function mutationToolName(actor: object | undefined): 'edit' | 'write' | undefin
function assertPositiveInteger(field: string, value: number): void {
if (!Number.isInteger(value) || value < 1) {
throw new TypeError(`skill-local: ${field} must be a positive integer`)
throw new TypeError(`skill-filesystem: ${field} must be a positive integer`)
}
}

View File

@@ -1,16 +1,16 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-skill-local`.
* @module @deepseek-ai/dsh-skill-local/invariant
* Package-owned invariant companion for `@deepseek-ai/dsh-skill-filesystem`.
* @module @deepseek-ai/dsh-skill-filesystem/invariant
*/
/* jscpd:ignore-start */
import type { Context } from '@deepseek-ai/cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-skill-local'
const PACKAGE_NAME = '@deepseek-ai/dsh-skill-filesystem'
/** Cordis companion plugin name. */
export const name = 'skill-local-invariant'
export const name = 'skill-filesystem-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']

View File

@@ -5,7 +5,7 @@ import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import SkillService from '@deepseek-ai/dsh-skill'
import SkillRegistry from '@deepseek-ai/dsh-skill'
interface FakeWatcherControl {
emitter: EventEmitter
@@ -89,7 +89,7 @@ vi.mock('chokidar', () => ({
},
}))
const SkillLocal = await import('../src/index.ts')
const SkillFileSystem = await import('../src/index.ts')
async function tempDir(name: string): Promise<string> {
return await import('node:fs/promises').then(fs => fs.mkdtemp(join(tmpdir(), `dsh-${name}-`)))
@@ -114,7 +114,7 @@ beforeEach(() => {
watcherHarness.statGates.length = 0
})
describe('skill-local watcher failures', () => {
describe('skill-filesystem watcher failures', () => {
it('canonicalizes an existing root before opening its native watcher', async () => {
const target = await tempDir('skill-watch-canonical-target')
const aliasParent = await tempDir('skill-watch-canonical-alias')
@@ -123,8 +123,8 @@ describe('skill-local watcher failures', () => {
const root = join(alias, '.dsh/skills')
await writeSkill(root, 'canonical-skill')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(alias, '.dsh'),
agentsHome: join(alias, '.agents'),
watch: true,
@@ -143,8 +143,8 @@ describe('skill-local watcher failures', () => {
await writeSkill(target, 'linked-skill')
await symlink(target, alias, process.platform === 'win32' ? 'junction' : 'dir')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
includeDefaultRoots: false,
customSkillDirs: [alias],
watch: true,
@@ -165,8 +165,8 @@ describe('skill-local watcher failures', () => {
it('ignores missing-path probes until the observed path actually changes', async () => {
const home = await tempDir('skill-watch-missing-stable')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -198,8 +198,8 @@ describe('skill-local watcher failures', () => {
)
watcherHarness.closeErrors = 1
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -239,8 +239,8 @@ describe('skill-local watcher failures', () => {
const root = join(home, '.dsh/skills')
await writeSkill(root, 'watched-skill')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -286,8 +286,8 @@ describe('skill-local watcher failures', () => {
const root = join(home, '.dsh/skills')
await writeSkill(root, 'removed-skill')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -314,8 +314,8 @@ describe('skill-local watcher failures', () => {
const root = join(home, '.dsh/skills')
await writeSkill(root, 'old-skill')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -349,10 +349,10 @@ describe('skill-local watcher failures', () => {
await writeSkill(root, 'racing-skill')
watcherHarness.deferredReady = 1
const ctx = new Context()
await ctx.plugin(SkillService)
let provider!: InstanceType<typeof SkillLocal.LocalSkillProvider>
await ctx.plugin(SkillRegistry)
let provider!: InstanceType<typeof SkillFileSystem.FileSystemSkillProvider>
const disposeProvider = ctx.skills.registerProvider((control) => {
provider = new SkillLocal.LocalSkillProvider(ctx, control, {
provider = new SkillFileSystem.FileSystemSkillProvider(ctx, control, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -368,7 +368,7 @@ describe('skill-local watcher failures', () => {
if (first === undefined) throw new Error('expected an opening root watcher')
const disposal = provider.dispose()
await expect(discovery).rejects.toThrow('skill-local watcher disposed')
await expect(discovery).rejects.toThrow('skill-filesystem watcher disposed')
await disposal
disposeProvider()
await settle()
@@ -386,10 +386,10 @@ describe('skill-local watcher failures', () => {
}
watcherHarness.statGates.push(statGate)
const ctx = new Context()
await ctx.plugin(SkillService)
let provider!: InstanceType<typeof SkillLocal.LocalSkillProvider>
await ctx.plugin(SkillRegistry)
let provider!: InstanceType<typeof SkillFileSystem.FileSystemSkillProvider>
const disposeProvider = ctx.skills.registerProvider((control) => {
provider = new SkillLocal.LocalSkillProvider(ctx, control, {
provider = new SkillFileSystem.FileSystemSkillProvider(ctx, control, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -404,7 +404,7 @@ describe('skill-local watcher failures', () => {
const disposal = provider.dispose()
statGate.release.resolve(undefined)
await expect(discovery).rejects.toThrow('skill-local watcher disposed')
await expect(discovery).rejects.toThrow('skill-filesystem watcher disposed')
await disposal
expect(watcherHarness.watchers).toHaveLength(1)
expect(watcherHarness.watchers[0]?.closeCalls).toBeGreaterThan(0)
@@ -417,10 +417,10 @@ describe('skill-local watcher failures', () => {
await writeSkill(root, 'rejected-skill')
watcherHarness.deferredReady = 1
const ctx = new Context()
await ctx.plugin(SkillService)
let provider!: InstanceType<typeof SkillLocal.LocalSkillProvider>
await ctx.plugin(SkillRegistry)
let provider!: InstanceType<typeof SkillFileSystem.FileSystemSkillProvider>
const disposeProvider = ctx.skills.registerProvider((control) => {
provider = new SkillLocal.LocalSkillProvider(ctx, control, {
provider = new SkillFileSystem.FileSystemSkillProvider(ctx, control, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,

View File

@@ -3,9 +3,9 @@ import { mkdir, readdir, readFile, rename, rm, stat, symlink, writeFile } from '
import { dirname, join } from 'node:path'
import { tmpdir } from 'node:os'
import { Context } from '@deepseek-ai/cordis'
import SkillService from '@deepseek-ai/dsh-skill'
import SkillRegistry from '@deepseek-ai/dsh-skill'
import { FileSystem, FsError, FsVersion, type FsDirEntry, type FsEditOutcome, type FsEditRequest, type FsInfo, type FsPathInfo, type FsTarget, type FsWriteOutcome } from '@deepseek-ai/dsh-fs'
import * as SkillLocal from '../src/index.ts'
import * as SkillFileSystem from '../src/index.ts'
async function tempDir(name: string): Promise<string> {
return await import('node:fs/promises').then(fs => fs.mkdtemp(join(tmpdir(), `dsh-${name}-`)))
@@ -138,10 +138,10 @@ class TestFileSystem extends FileSystem {
}
}
async function setupLocal(home: string, config: Partial<SkillLocal.Config> = {}): Promise<Context> {
async function setupLocal(home: string, config: Partial<SkillFileSystem.Config> = {}): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: false,
@@ -160,14 +160,14 @@ async function waitFor<T>(read: () => Promise<T>, accept: (value: T) => boolean)
}
}
describe('dsh-skill-local plugin exports', () => {
describe('dsh-skill-filesystem plugin exports', () => {
it('declares stable plugin metadata', () => {
expect(SkillLocal.name).toBe('skill-local')
expect(SkillLocal.inject).toEqual(['skills'])
expect(SkillFileSystem.name).toBe('skill-filesystem')
expect(SkillFileSystem.inject).toEqual(['skills'])
})
})
describe('LocalSkillProvider', () => {
describe('FileSystemSkillProvider', () => {
it('discovers project, custom, user, and agents skill roots in priority order', async () => {
const home = await tempDir('skill-home')
const project = await tempDir('skill-project')
@@ -452,8 +452,8 @@ describe('LocalSkillProvider', () => {
type: 'directory',
size: 0,
})
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
expect((await ctx.skills.list({ cwd: nestedCwd })).map(skill => [skill.name, skill.source])).toEqual([
['backend-root', 'project-agents'],
@@ -468,8 +468,8 @@ describe('LocalSkillProvider', () => {
await bundledCtx.plugin(TestFileSystem)
const bundledFs = bundledCtx.fs as TestFileSystem
bundledFs.failResolvePaths.add(bundled)
await bundledCtx.plugin(SkillService)
await bundledCtx.plugin(SkillLocal, {
await bundledCtx.plugin(SkillRegistry)
await bundledCtx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
bundledSkillDir: bundled,
@@ -484,8 +484,8 @@ describe('LocalSkillProvider', () => {
const ctx = new Context()
await ctx.plugin(TestFileSystem)
const fs = ctx.fs as TestFileSystem
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: false,
@@ -520,8 +520,8 @@ describe('LocalSkillProvider', () => {
const ctx = new Context()
await ctx.plugin(TestFileSystem)
const fs = ctx.fs as TestFileSystem
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: false,
@@ -570,8 +570,8 @@ describe('LocalSkillProvider', () => {
const ctx = new Context()
await ctx.plugin(TestFileSystem)
const fs = ctx.fs as TestFileSystem
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(['abortable-skill'])
fs.statSignals = []
@@ -602,8 +602,8 @@ describe('LocalSkillProvider', () => {
const home = await tempDir('skill-watch-home')
const agentsRoot = join(home, '.agents/skills')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -710,8 +710,8 @@ describe('LocalSkillProvider', () => {
await writeSkill(join(first, '.agents/skills'), 'first-project', 'First project')
await writeSkill(join(second, '.agents/skills'), 'second-project', 'Second project')
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
customSkillDirs: [join(first, '.agents/skills')],
@@ -732,8 +732,8 @@ describe('LocalSkillProvider', () => {
}
const noWatch = new Context()
await noWatch.plugin(SkillService)
await noWatch.plugin(SkillLocal, {
await noWatch.plugin(SkillRegistry)
await noWatch.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: false,
@@ -749,10 +749,10 @@ describe('LocalSkillProvider', () => {
await writeFile(nonDirectoryRoot, 'not a skill root')
await writeSkill(join(home, '.agents/skills'), 'disposed-skill', 'Disposed skill')
const ctx = new Context()
await ctx.plugin(SkillService)
let provider!: SkillLocal.LocalSkillProvider
await ctx.plugin(SkillRegistry)
let provider!: SkillFileSystem.FileSystemSkillProvider
const disposeProvider = ctx.skills.registerProvider((control) => {
provider = new SkillLocal.LocalSkillProvider(ctx, control, {
provider = new SkillFileSystem.FileSystemSkillProvider(ctx, control, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
customSkillDirs: [nonDirectoryRoot],
@@ -784,8 +784,8 @@ describe('LocalSkillProvider', () => {
await mkdir(root, { recursive: true })
await symlink(join(external, 'linked-skill'), join(root, 'linked-skill'))
const ctx = new Context()
await ctx.plugin(SkillService)
const fiber = await ctx.plugin(SkillLocal, {
await ctx.plugin(SkillRegistry)
const fiber = await ctx.plugin(SkillFileSystem, {
dshHome: join(home, '.dsh'),
agentsHome: join(home, '.agents'),
watch: true,
@@ -808,11 +808,11 @@ describe('LocalSkillProvider', () => {
it('validates watcher tunables at plugin load', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
await expect(ctx.plugin(SkillLocal, { watchMaxProjects: 0 })).rejects.toThrow('watchMaxProjects')
await expect(ctx.plugin(SkillLocal, { watchPollIntervalMs: 1.5 })).rejects.toThrow('watchPollIntervalMs')
await expect(ctx.plugin(SkillLocal, { watchStabilityThresholdMs: 0 })).rejects.toThrow('watchStabilityThresholdMs')
await expect(ctx.plugin(SkillFileSystem, { watchMaxProjects: 0 })).rejects.toThrow('watchMaxProjects')
await expect(ctx.plugin(SkillFileSystem, { watchPollIntervalMs: 1.5 })).rejects.toThrow('watchPollIntervalMs')
await expect(ctx.plugin(SkillFileSystem, { watchStabilityThresholdMs: 0 })).rejects.toThrow('watchStabilityThresholdMs')
})
it('uses default home root resolution without exposing builtin skills', async () => {
@@ -828,18 +828,18 @@ describe('LocalSkillProvider', () => {
await writeSkill(join(envHome, '.dsh/skills'), 'env-skill', 'Env skill')
await writeSkill(bundled, 'env-bundled-skill', 'Env bundled skill')
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, { watch: false })
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, { watch: false })
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(['env-bundled-skill', 'env-skill'])
// Isolated providers see only their explicit roots: the environment
// bundled root is a default root, so includeDefaultRoots: false must
// drop it — isolated providers never re-claim the app's builtins.
const isolated = new Context()
await isolated.plugin(SkillService)
await isolated.plugin(SkillRegistry)
const customOnly = join(envHome, 'custom-only')
await writeSkill(customOnly, 'custom-isolated-skill', 'Custom isolated skill')
await isolated.plugin(SkillLocal, {
await isolated.plugin(SkillFileSystem, {
providerName: 'isolated',
includeDefaultRoots: false,
customSkillDirs: [customOnly],
@@ -852,15 +852,15 @@ describe('LocalSkillProvider', () => {
delete process.env.DSH_BUNDLED_SKILL_DIR
process.env.DSH_AGENTS_HOME = join(envHome, 'empty-agents')
const empty = new Context()
await empty.plugin(SkillService)
SkillLocal.apply(empty, { watch: false })
await empty.plugin(SkillRegistry)
SkillFileSystem.apply(empty, { watch: false })
expect(await empty.skills.list()).toEqual([])
delete process.env.DSH_AGENTS_HOME
expect(new SkillLocal.LocalSkillProvider(empty, {
expect(new SkillFileSystem.FileSystemSkillProvider(empty, {
signal: new AbortController().signal,
invalidate() {},
}, { dshHome: join(envHome, 'empty-dsh') }).name).toBe('local')
}, { dshHome: join(envHome, 'empty-dsh') }).name).toBe('filesystem')
} finally {
if (previousDshHome === undefined) {
delete process.env.DSH_HOME

View File

@@ -10,8 +10,8 @@
{ "path": "../../../vendor/cordis" },
{ "path": "../../../vendor/schemastery" },
{ "path": "../../fs/fs" },
{ "path": "../../util/paths" },
{ "path": "../../util/home-paths" },
{ "path": "../skill" },
{ "path": "../../support/invariants" }
{ "path": "../../runtime-diagnostics/invariants" }
]
}

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/README.md
README.md: a728b05fa5f1f2ad10ec7c4ac12f0ac66ed48e7e
README.zh.md: f8d49e371c6109e66b6ec03b49ba83ad1fa8eb3f
README.md: ae8812aeb3cc6583677d22bd5c35639318d15237
README.zh.md: 5eae1d5aeb9eb87072d4b33c26f6c9a6e20cffdf

View File

@@ -4,11 +4,11 @@ English | [中文](README.zh.md)
Pure agent skill provider registry.
This package owns the `ctx.skills` interface. It does not know whether skills come from local files, embedded plugin data, HTTP, or another backend; providers register those sources with `ctx.skills.registerProvider(...)`. The shipped local implementation is [`@deepseek-ai/dsh-skill-local`](../skill-local).
This package owns the `ctx.skills` interface. It does not know whether skills come from local files, embedded plugin data, HTTP, or another backend; providers register those sources with `ctx.skills.registerProvider(...)`. The shipped local implementation is [`@deepseek-ai/dsh-skill-filesystem`](../skill-filesystem).
The registry is host+per-scope layered over [`@deepseek-ai/dsh-scope`](../../core/scope), the shape the tools registry established: a registration files into the layer of its calling context's scope — host rows and repository plugins land in the global layer, a plugin mounted by an agent preset's standing composition lands in that preset's layer — and a read merges the global layer with the viewing scope's chain, the nearest layer winning a duplicate name outright while rank decides duplicates only within one layer.
## Service: `SkillService` (ctx key: `skills`)
## Service: `SkillRegistry` (ctx key: `skills`)
### Public API

View File

@@ -4,11 +4,11 @@
纯 agent skill智能体技能提供方注册表。
该包负责 `ctx.skills` 接口。它不知道 skill 来自本地文件、嵌入式插件数据、HTTP 还是其他后端;提供方通过 `ctx.skills.registerProvider(...)` 注册这些来源。已发布的本地实现是 [`@deepseek-ai/dsh-skill-local`](../skill-local)。
该包负责 `ctx.skills` 接口。它不知道 skill 来自本地文件、嵌入式插件数据、HTTP 还是其他后端;提供方通过 `ctx.skills.registerProvider(...)` 注册这些来源。已发布的本地实现是 [`@deepseek-ai/dsh-skill-filesystem`](../skill-filesystem)。
注册表基于 [`@deepseek-ai/dsh-scope`](../../core/scope) 采用宿主 + 按 scope 的分层结构,即工具注册表确立的形态:注册落入调用方上下文 scope 对应的层——宿主行与 repository 插件落入全局层,由 agent preset 常驻组合挂载的插件落入该 preset 的层——读取时将全局层与观察 scope 的链合并最近层直接赢得重名rank 只在单层内裁决重名。
## 服务:`SkillService`ctx 键:`skills`
## 服务:`SkillRegistry`ctx 键:`skills`
### 公开 API

View File

@@ -3,7 +3,7 @@
*
* This package owns the Service Definition role of the skill capability seam.
* Concrete
* providers such as `@deepseek-ai/dsh-skill-local` decide where skills come
* providers such as `@deepseek-ai/dsh-skill-filesystem` decide where skills come
* from; this service only merges provider catalogs, resolves the winning skill
* for a name, and exposes the winning summaries and definitions to consumers.
*
@@ -283,7 +283,7 @@ export interface Config {
declare module '@deepseek-ai/cordis' {
interface Context {
skills: SkillService
skills: SkillRegistry
}
interface Events {
@@ -354,7 +354,7 @@ class SkillLayer implements ScopeLayer {
* It exposes sorted invocation-neutral summaries and loads full skill bodies
* on demand.
*/
export class SkillService extends Service {
export class SkillRegistry extends Service {
static Config: Schema<Config> = z.object({
collectCacheMaxEntries: z.number().default(DEFAULT_COLLECT_CACHE_ENTRIES),
})
@@ -865,4 +865,4 @@ function errorMessage(error: unknown): string {
}
}
export default SkillService
export default SkillRegistry

View File

@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { bindScopeParent, createScope, scopeOf } from '@deepseek-ai/dsh-scope'
import SkillService, {
import SkillRegistry, {
isModelInvocable,
isUserInvocable,
renderSkillContent,
@@ -51,16 +51,16 @@ function registerProvider(ctx: Context, provider: SkillProvider): () => void {
}
/** The skills service as a scoped caller resolves it (scope contexts declare no inject). */
function scopedSkills(ctx: Context): SkillService {
function scopedSkills(ctx: Context): SkillRegistry {
const skills = ctx.get('skills')
if (skills === undefined) throw new Error('skills service missing')
return skills
}
describe('SkillService registry', () => {
describe('SkillRegistry registry', () => {
it('registers providers, resolves duplicates first-wins, and disposes providers', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const provider = new MemoryProvider([
memorySkill('z-skill', 'Z skill', 20),
memorySkill('a-skill', 'A skill', 10),
@@ -142,7 +142,7 @@ describe('SkillService registry', () => {
expect(failedSignal?.reason).toBe(factoryFailure)
const effectContext = new Context()
const effectService = new SkillService(effectContext)
const effectService = new SkillRegistry(effectContext)
const effectFailure = new Error('effect registration failed')
vi.spyOn(effectContext, 'effect').mockImplementation(() => { throw effectFailure })
let effectSignal: AbortSignal | undefined
@@ -162,7 +162,7 @@ describe('SkillService registry', () => {
it('returns an invocation-neutral catalog and resolves model and user policy independently', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const registrations = [
{ name: 'both', invocation: undefined },
{ name: 'model-only', invocation: { modelInvocable: true, userInvocable: false } },
@@ -190,7 +190,7 @@ describe('SkillService registry', () => {
it('validates parsed candidate fields', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const badDescription = { value: 'object-description' }
registerProvider(ctx, {
name: 'bad-candidate',
@@ -205,7 +205,7 @@ describe('SkillService registry', () => {
await expect(ctx.skills.list()).rejects.toThrow('non-string description')
const badBoolean = new Context()
await badBoolean.plugin(SkillService)
await badBoolean.plugin(SkillRegistry)
registerProvider(badBoolean, {
name: 'bad-boolean',
list: () => Promise.resolve([{
@@ -222,7 +222,7 @@ describe('SkillService registry', () => {
const malformedOutputs: unknown[] = [null, 1, {}, { candidates: [], complete: 'yes' }]
for (const [index, output] of malformedOutputs.entries()) {
const badList = new Context()
await badList.plugin(SkillService)
await badList.plugin(SkillRegistry)
registerProvider(badList, {
name: `malformed-list-${index}`,
list: () => Promise.resolve(output as readonly SkillCandidate[] | SkillProviderObservation),
@@ -241,7 +241,7 @@ describe('SkillService registry', () => {
]
for (const [index, { patch, expected }] of cases.entries()) {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const providerName = `candidate-provider-${index}`
const candidate = {
name: `candidate-${index}`,
@@ -267,7 +267,7 @@ describe('SkillService registry', () => {
it('borrows the exact lookup options through discovery and loading', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const options: SkillLookupOptions = { cwd: '/workspace/a' }
let listedWith: SkillLookupOptions | undefined
let loadedWith: SkillLookupOptions | undefined
@@ -301,7 +301,7 @@ describe('SkillService registry', () => {
it('rechecks cancellation after cached discovery before provider loading', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let getCalls = 0
registerProvider(ctx, {
name: 'cached',
@@ -337,7 +337,7 @@ describe('SkillService registry', () => {
it('stops waiting for cached provider loading when a hostile abort reason fires', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let markStarted: (() => void) | undefined
let release: (() => void) | undefined
let seenSignal: AbortSignal | undefined
@@ -406,7 +406,7 @@ describe('SkillService registry', () => {
it('borrows cached candidates and loaded definitions from the provider', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const locator = { id: 'provider-owned' }
const invocation = { modelInvocable: true, userInvocable: true }
const candidate: SkillCandidate = {
@@ -466,7 +466,7 @@ describe('SkillService registry', () => {
it('preserves readonly runtime resource identities while adding the default provider', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const resourceBase = { kind: 'opaque' as const, description: 'runtime resources' }
const metadata = { owner: 'runtime' }
const invocation = { modelInvocable: true, userInvocable: true }
@@ -527,7 +527,7 @@ describe('SkillService registry', () => {
]
for (const [index, { patch, expected }] of cases.entries()) {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const providerName = `definition-provider-${index}`
const skillName = `definition-${index}`
registerProvider(ctx, {
@@ -559,11 +559,11 @@ describe('SkillService registry', () => {
})
it('validates provider candidates and invalid registry caps', async () => {
const defaultedService = new SkillService(new Context())
const defaultedService = new SkillRegistry(new Context())
expect(await defaultedService.list()).toEqual([])
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
registerProvider(ctx, {
name: 'bad',
async list() {
@@ -582,7 +582,7 @@ describe('SkillService registry', () => {
]
for (const candidate of invalidCandidates) {
const invalid = new Context()
await invalid.plugin(SkillService)
await invalid.plugin(SkillRegistry)
registerProvider(invalid, {
name: candidate.name,
async list() {
@@ -595,12 +595,12 @@ describe('SkillService registry', () => {
await expect(invalid.skills.list()).rejects.toThrow('skill provider')
}
await expect(new Context().plugin(SkillService, { collectCacheMaxEntries: 1.5 })).rejects.toThrow('collectCacheMaxEntries')
await expect(new Context().plugin(SkillRegistry, { collectCacheMaxEntries: 1.5 })).rejects.toThrow('collectCacheMaxEntries')
})
it('sorts model-visible summaries without locale-sensitive collation', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
registerProvider(ctx, new MemoryProvider([
memorySkill('z-skill', 'Z skill', 10),
memorySkill('a-skill', 'A skill', 10),
@@ -624,7 +624,7 @@ describe('SkillService registry', () => {
it('caches provider discovery, skips failing providers, and invalidates on runtime skills', async () => {
const ctx = new Context()
await ctx.plugin(SkillService, { collectCacheMaxEntries: 1 })
await ctx.plugin(SkillRegistry, { collectCacheMaxEntries: 1 })
const provider = new MemoryProvider([memorySkill('first-skill', 'First', 10)])
registerProvider(ctx, provider)
@@ -679,7 +679,7 @@ describe('SkillService registry', () => {
it('keeps candidates from incomplete provider observations loadable without caching them', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let listCalls = 0
registerProvider(ctx, {
name: 'incomplete-candidates',
@@ -706,7 +706,7 @@ describe('SkillService registry', () => {
it('invalidates only the exact registered provider and ignores its late callbacks', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const provider = new MemoryProvider([memorySkill('first-skill', 'First', 10)])
let invalidate = (): void => {}
let signal: AbortSignal | undefined
@@ -735,7 +735,7 @@ describe('SkillService registry', () => {
it('emits catalog invalidations for live provider and runtime mutations', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const provider = new MemoryProvider([memorySkill('provider-skill', 'Provider', 10)])
let changes = 0
ctx.on('skills/change', () => { changes += 1 })
@@ -766,7 +766,7 @@ describe('SkillService registry', () => {
it('contains synchronous and asynchronous catalog observer failures', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const warnings: string[] = []
ctx.logger.warn = ((message: unknown) => { warnings.push(String(message)) }) as typeof ctx.logger.warn
const disposeThrowing = ctx.on('skills/change', () => { throw new Error('observer threw') })
@@ -791,7 +791,7 @@ describe('SkillService registry', () => {
it('retries an in-flight catalog invalidated by its provider', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let release: (() => void) | undefined
const started = Promise.withResolvers<undefined>()
const gate = new Promise<void>((resolve) => { release = resolve })
@@ -824,7 +824,7 @@ describe('SkillService registry', () => {
it('bounds repeated in-flight invalidation and leaves the result uncached', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let listCalls = 0
ctx.skills.registerProvider(control => ({
name: 'self-invalidating',
@@ -859,7 +859,7 @@ describe('SkillService registry', () => {
it('invalidates a provider whose loaded definition changed identity', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let listCalls = 0
const provider: SkillProvider = {
name: 'renamed',
@@ -888,7 +888,7 @@ describe('SkillService registry', () => {
it('returns undefined when a discovered candidate disappears before loading', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
registerProvider(ctx, {
name: 'vanished-body',
async list() {
@@ -904,7 +904,7 @@ describe('SkillService registry', () => {
it('propagates a load failure raced against an armed abort signal', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
registerProvider(ctx, {
name: 'failing-loader',
list: () => Promise.resolve([{
@@ -924,7 +924,7 @@ describe('SkillService registry', () => {
it('contains a provider rejection whose string coercion throws', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const warnings: string[] = []
ctx.logger.warn = ((message: unknown) => { warnings.push(String(message)) }) as typeof ctx.logger.warn
const hostileFailure = {
@@ -952,7 +952,7 @@ describe('SkillService registry', () => {
it('abandons an in-flight catalog when provider registrations change', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let markStarted: (() => void) | undefined
let release: (() => void) | undefined
const started = new Promise<void>((resolve) => { markStarted = resolve })
@@ -979,7 +979,7 @@ describe('SkillService registry', () => {
it('stops waiting for discovery when its lookup signal aborts', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
let markStarted: (() => void) | undefined
let release: (() => void) | undefined
let seenSignal: AbortSignal | undefined
@@ -1021,7 +1021,7 @@ describe('SkillService registry', () => {
it('rejects invalid runtime skill registrations and ignores duplicates', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
expect(() => ctx.skills.register({ name: 'Bad_Name', description: 'Bad', source: 'runtime', content: 'bad' })).toThrow('invalid skill name')
expect(() => ctx.skills.register({ name: 'no-description', description: '', source: 'runtime', content: 'bad' })).toThrow('requires a description')
expect(() => ctx.skills.register({
@@ -1105,10 +1105,10 @@ describe('renderSkillContent', () => {
})
})
describe('SkillService scoped layers', () => {
describe('SkillRegistry scoped layers', () => {
it('files a scoped provider into its layer and merges it into that scope view only', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
registerProvider(ctx, new MemoryProvider([memorySkill('global-skill', 'Global', 100)]))
const preset = createScope(ctx, { preset: 'a' })
const presetProvider: SkillProvider = {
@@ -1140,7 +1140,7 @@ describe('SkillService scoped layers', () => {
it('lets the nearest layer win a duplicate name regardless of rank', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
registerProvider(ctx, new MemoryProvider([memorySkill('shared-name', 'Global wins ranks', 10)]))
const preset = createScope(ctx, { preset: 'shadow' })
scopedSkills(preset.ctx).registerProvider(() => ({
@@ -1171,7 +1171,7 @@ describe('SkillService scoped layers', () => {
it('resolves the scope chain so an agent key inherits its preset layer and recompose follows the new parent', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const presetA = createScope(ctx, { preset: 'a' })
const presetB = createScope(ctx, { preset: 'b' })
for (const [scope, label] of [[presetA, 'a'], [presetB, 'b']] as const) {
@@ -1195,7 +1195,7 @@ describe('SkillService scoped layers', () => {
it('scopes provider-name uniqueness per layer and reports scoped duplicates distinctly', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
registerProvider(ctx, new MemoryProvider([]))
const presetA = createScope(ctx, { preset: 'a' })
const presetB = createScope(ctx, { preset: 'b' })
@@ -1211,7 +1211,7 @@ describe('SkillService scoped layers', () => {
it('keeps runtime duplicate handling per layer and shadows a global runtime name', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const warn = vi.fn()
ctx.logger.warn = warn as never
ctx.skills.register({ name: 'told-twice', description: 'Global runtime', source: 'runtime', content: 'Global body.' })
@@ -1234,7 +1234,7 @@ describe('SkillService scoped layers', () => {
it('drops a disposed scoped registration from its scope view and notifies change', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const changes = vi.fn()
ctx.on('skills/change', changes)
const preset = createScope(ctx, { preset: 'hmr' })
@@ -1249,7 +1249,7 @@ describe('SkillService scoped layers', () => {
it('invalidates through a scoped provider control only while its exact registration is live', async () => {
const ctx = new Context()
await ctx.plugin(SkillService)
await ctx.plugin(SkillRegistry)
const preset = createScope(ctx, { preset: 'invalidate' })
const provider = new MemoryProvider([memorySkill('watched', 'Watched', 100)])
let control: { invalidate: () => void } | undefined

View File

@@ -22,7 +22,7 @@
"path": "../../llm/llm"
},
{
"path": "../../support/invariants"
"path": "../../runtime-diagnostics/invariants"
}
]
}

View File

@@ -49,7 +49,7 @@
"@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-skill": "workspace:^",
"@deepseek-ai/dsh-skill-local": "workspace:^",
"@deepseek-ai/dsh-skill-filesystem": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
}

View File

@@ -7,10 +7,10 @@ import { createUserMessage, CallId, type Message } from '@deepseek-ai/dsh-llm'
import { createScope, type Scope } from '@deepseek-ai/dsh-scope'
import { Session, SessionId, type SessionEvent, type UserMessage } from '@deepseek-ai/dsh-session'
import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import ToolRuntime, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import AgentRegistry, { agentEvents, Inbox, type Agent, type PreStepDecision } from '@deepseek-ai/dsh-agent'
import SkillService from '@deepseek-ai/dsh-skill'
import * as SkillLocal from '@deepseek-ai/dsh-skill-local'
import SkillRegistry from '@deepseek-ai/dsh-skill'
import * as SkillFileSystem from '@deepseek-ai/dsh-skill-filesystem'
import * as toolSkill from '@deepseek-ai/dsh-tool-skill'
const testToolSignal = new AbortController().signal
@@ -28,10 +28,10 @@ async function writeSkill(root: string, name: string, description: string, body:
async function setup(home: string, config: toolSkill.Config = {}): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(ToolRuntime)
await ctx.plugin(AgentRegistry)
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
await ctx.plugin(toolSkill, config)
return ctx
}
@@ -160,11 +160,11 @@ describe('dsh-tool-skill', () => {
it('registers the skill tool schema and removes it on dispose', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(ToolRuntime)
await ctx.plugin(AgentRegistry)
const home = await tempDir('tool-schema')
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
ctx.skills.register({ name: 'lifecycle-skill', description: 'Lifecycle', source: 'runtime', content: 'body' })
const fiber = await ctx.plugin(toolSkill)
@@ -749,10 +749,10 @@ describe('dsh-tool-skill', () => {
const home = await tempDir('tool-invalid-catalog-cap')
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(ToolRuntime)
await ctx.plugin(AgentRegistry)
await ctx.plugin(SkillService)
await ctx.plugin(SkillLocal, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
await ctx.plugin(SkillRegistry)
await ctx.plugin(SkillFileSystem, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), watch: false })
await expect(ctx.plugin(toolSkill, { catalogDescriptionMaxLength: 2 })).rejects.toThrow('greater than or equal to 3')
})
@@ -776,7 +776,7 @@ describe('dsh-tool-skill', () => {
if (result.isError) throw new Error('expected skill success')
expect(result.value).toEqual({
name: 'project-skill',
provider: 'local',
provider: 'filesystem',
resourceBase: { kind: 'directory', path: join(project, '.dsh/skills/project-skill') },
content: 'Project instructions.',
})

View File

@@ -31,7 +31,7 @@
"path": "../../core/tools"
},
{
"path": "../../support/invariants"
"path": "../../runtime-diagnostics/invariants"
}
]
}