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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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` |
|
||||
|
||||
该能力位于核心控制主干之外,可以使用本地、嵌入式或远程提供方,而无需更改面向模型的约定。
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
{ "path": "../../../vendor/cosmokit" },
|
||||
{ "path": "../../../vendor/cordis" },
|
||||
{ "path": "../skill" },
|
||||
{ "path": "../../support/invariants" }
|
||||
{ "path": "../../runtime-diagnostics/invariants" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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. |
|
||||
@@ -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 可能发生变化时使本地提供方失效。 |
|
||||
@@ -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:^"
|
||||
}
|
||||
@@ -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`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"path": "../../llm/llm"
|
||||
},
|
||||
{
|
||||
"path": "../../support/invariants"
|
||||
"path": "../../runtime-diagnostics/invariants"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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:^"
|
||||
}
|
||||
|
||||
@@ -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.',
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"path": "../../core/tools"
|
||||
},
|
||||
{
|
||||
"path": "../../support/invariants"
|
||||
"path": "../../runtime-diagnostics/invariants"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user