refactor(skill): scope provider invalidation

This commit is contained in:
Tianyi Cui
2026-07-29 02:06:07 +08:00
parent def1b5c55a
commit 979fa8ab33
24 changed files with 295 additions and 181 deletions

View File

@@ -1187,7 +1187,7 @@ export interface Config {
}
```
Source: [`packages/skill/skill/src/index.ts:121`](../packages/skill/skill/src/index.ts)
Source: [`packages/skill/skill/src/index.ts:129`](../packages/skill/skill/src/index.ts)
## `@deepseek-ai/dsh-skill-local`
@@ -1219,7 +1219,7 @@ export interface Config {
}
```
Source: [`packages/skill/skill-local/src/index.ts:46`](../packages/skill/skill-local/src/index.ts)
Source: [`packages/skill/skill-local/src/index.ts:47`](../packages/skill/skill-local/src/index.ts)
## `@deepseek-ai/dsh-spill-local`

View File

@@ -657,7 +657,7 @@ A skill provider, runtime contribution, or provider-backed catalog may have chan
'skills/change'(): void
```
Source: [`packages/skill/skill/src/index.ts:139`](../../packages/skill/skill/src/index.ts)
Source: [`packages/skill/skill/src/index.ts:147`](../../packages/skill/skill/src/index.ts)
## `slash/*`

View File

@@ -1397,19 +1397,11 @@ Registry of skill providers. It merges provider catalogs with stable first-wins
* Register a borrowed same-process provider synchronously during plugin apply. Duplicate and
* reserved names throw; remote initialization belongs in `list()`. Fiber disposal unregisters
* the provider and invalidates catalog caches.
* @param provider - the provider to register by `provider.name`.
* @param create - synchronous factory receiving this registration's lifecycle and invalidation control.
* @returns the exact Cordis effect disposer that unregisters this provider;
* composite effects may yield it directly to preserve teardown ordering.
*/
registerProvider(provider: SkillProvider): () => void
/**
* Invalidate catalogs contributed by one currently registered provider. Exact object identity
* prevents a late callback from an old provider instance from invalidating its replacement.
* Calls for an already-unregistered provider are harmless.
* @param provider - exact provider instance whose external source changed.
*/
invalidateProvider(provider: SkillProvider): void
registerProvider(create: (control: SkillProviderControl) => SkillProvider): () => void
/**
* Register a borrowed readonly runtime skill. Project entries outrank runtime entries, which
@@ -1449,9 +1441,9 @@ async snapshot(options: SkillLookupOptions = {}): Promise<SkillCatalogSnapshot>
async get(name: string, options: SkillLookupOptions = {}): Promise<SkillDefinition | undefined>
```
Types: [SkillCatalogSnapshot](../core-data-structures/skills.md) · [SkillDefinition](../core-data-structures/skills.md) · [SkillLookupOptions](../core-data-structures/skills.md) · [SkillProvider](../core-data-structures/skills.md) · [SkillRegistration](../core-data-structures/skills.md) · [SkillSummary](../core-data-structures/skills.md)
Types: [SkillCatalogSnapshot](../core-data-structures/skills.md) · [SkillDefinition](../core-data-structures/skills.md) · [SkillLookupOptions](../core-data-structures/skills.md) · [SkillProvider](../core-data-structures/skills.md) · [SkillProviderControl](../core-data-structures/skills.md) · [SkillRegistration](../core-data-structures/skills.md) · [SkillSummary](../core-data-structures/skills.md)
Source: [`packages/skill/skill/src/index.ts:160`](../../packages/skill/skill/src/index.ts)
Source: [`packages/skill/skill/src/index.ts:168`](../../packages/skill/skill/src/index.ts)
## `ctx.spillStore` — `SpillStore` (abstract seam)

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 docs/core-data-structures/skills.md
skills.md: e1b2bfd5336c3cbce7f3c85bf5e440519efef36b
skills.zh.md: 3a80b6a93d37e945b7a2ad7ed4bccb45724d6b15
skills.md: 247d8d71890a1624225091a7d53dd6cee977134a
skills.zh.md: 7d5c61dcf3efc3cc90cae2d5bf9f16e4df74bd79

View File

@@ -10,7 +10,7 @@ Source: [`packages/skill/skill/src/index.ts`](../../packages/skill/skill/src/ind
`ctx.skills` combines local, embedded, remote, or other providers. Registration is synchronous; remote initialization and discovery belong in awaited `list()`. Provider objects, options, and candidates are borrowed readonly, while semantic fields are validated.
Duplicate names resolve by rank, provider order, then local order; summaries sort by name. A rejected `list()` is logged and omitted from an incomplete observation without caching it, while malformed candidates fail fast. `invalidateProvider()` clears completed catalogs only for the exact live provider object, and an in-flight discovery retries when its provider generation changes. Provider and runtime membership mutations emit the unfiltered `skills/change` invalidation event; it carries no diff, so consumers refetch `snapshot()` with their own lookup options.
Duplicate names resolve by rank, provider order, then local order; summaries sort by name. A rejected `list()` is logged and omitted from an incomplete observation without caching it, while malformed candidates fail fast. Each provider factory receives a registration-scoped control whose `invalidate()` clears completed catalogs only while that exact registration remains active and whose signal aborts on failed registration or disposal. An in-flight discovery retries when its provider generation changes. Provider and runtime mutations emit the unfiltered `skills/change` invalidation event; it carries no diff, so consumers refetch `snapshot()` with their own lookup options.
```ts type-equiv
/** Provider interface for one source of skills, such as local directories or a remote registry. */
@@ -36,6 +36,16 @@ interface SkillProvider {
}
```
```ts type-equiv
/** Registration-scoped lifecycle and invalidation capability borrowed by one provider. */
interface SkillProviderControl {
/** Aborts if registration fails or when the exact provider registration is disposed. */
readonly signal: AbortSignal
/** Invalidate completed catalogs and notify consumers only while the exact registration remains active. */
readonly invalidate: () => void
}
```
## Local discovery priority
The shipped local provider scans roots in rank order:

View File

@@ -10,7 +10,7 @@
`ctx.skills` 组合本地、内嵌、远程或其他提供方。注册是同步的;远程初始化与发现属于 `list()` 的 await 阶段。提供方对象、选项与候选项以只读方式借用,语义字段会被校验。
重名按 rank、提供方顺序、本地顺序依次解决摘要按名称排序。`list()` 拒绝时会记录日志并从不完整观测中省略,且该观测不会缓存;格式错误的候选项快速失败。`invalidateProvider()` 只针对传入的活动提供方对象清除已完成目录;若提供方代次在发现进行期间发生变化,该发现会重试。提供方和运行时的成员关系变更会发出不带过滤条件的 `skills/change` 失效事件;该事件不携带 diff因此消费方会使用自身的查找选项重新获取 `snapshot()`
重名按 rank、提供方顺序、本地顺序依次解决摘要按名称排序。`list()` 拒绝时会记录日志并从不完整观测中省略,且该观测不会缓存;格式错误的候选项快速失败。每个提供方工厂都会接收一项注册作用域内的控制能力;仅当该精确注册仍处于活动状态时,其 `invalidate()` 才会清除已完成目录;注册失败或释放时,其信号会中止。若提供方代次在发现进行期间发生变化,该发现会重试。提供方和运行时变更会发出不带过滤条件的 `skills/change` 失效事件;该事件不携带 diff因此消费方会使用自身的查找选项重新获取 `snapshot()`
```ts type-equiv
/** Provider interface for one source of skills, such as local directories or a remote registry. */
@@ -36,6 +36,16 @@ interface SkillProvider {
}
```
```ts type-equiv
/** Registration-scoped lifecycle and invalidation capability borrowed by one provider. */
interface SkillProviderControl {
/** Aborts if registration fails or when the exact provider registration is disposed. */
readonly signal: AbortSignal
/** Invalidate completed catalogs and notify consumers only while the exact registration remains active. */
readonly invalidate: () => void
}
```
## 本地发现优先级
内置的本地提供方按 rank 顺序扫描各根目录:

View File

@@ -35,7 +35,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:80`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-title`](../packages/session-title/session-title) |
| `session/event` | `emit` | [`packages/core/session/src/index.ts:92`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), `apiproxy`, [`cli-demo`](../packages/examples/cli-demo), [`compact`](../packages/compact/compact), [`compact-basic`](../packages/compact/compact-basic), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`plan-mode`](../packages/plan/plan-mode), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-title`](../packages/session-title/session-title), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-approval`](../packages/ui/user-approval), [`workspace-context`](../packages/context/workspace-context) |
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:102`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry) |
| `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:139`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | [`tui`](../packages/ui/tui) |
| `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:147`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | [`tui`](../packages/ui/tui) |
| `slash/input-begin-command` | `bail` | [`packages/client/ui-slash/src/types.ts:230`](../packages/client/ui-slash/src/types.ts) | - | `ui-conversation` |
| `slash/input-consume-token` | `bail` | [`packages/client/ui-slash/src/types.ts:244`](../packages/client/ui-slash/src/types.ts) | - | `ui-conversation` |
| `slash/input-insert-reference` | `bail` | [`packages/client/ui-slash/src/types.ts:237`](../packages/client/ui-slash/src/types.ts) | - | `ui-conversation` |