Merge remote-tracking branch 'origin/master' into worktree/pr823-retarget-latest-20260729

# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/skills.i18n.yaml
#	docs/core-data-structures/skills.md
#	docs/core-data-structures/skills.zh.md
#	packages/host/apiproxy/README.i18n.yaml
#	packages/skill/skill-local/README.i18n.yaml
#	packages/skill/skill/README.i18n.yaml
#	packages/skill/skill/README.md
#	packages/skill/skill/README.zh.md
#	packages/skill/skill/src/index.ts
#	packages/skill/skill/tests/skill.spec.ts
#	packages/skill/tool-skill/README.i18n.yaml
#	packages/skill/tool-skill/src/index.ts
#	packages/ui/tui/README.i18n.yaml
#	packages/ui/tui/README.md
#	packages/ui/tui/README.zh.md
#	packages/ui/tui/src/index.ts
#	packages/ui/tui/tests/tui.spec.ts
This commit is contained in:
Tianyi Cui
2026-07-29 23:36:49 +08:00
205 changed files with 5429 additions and 840 deletions

View File

@@ -10,11 +10,16 @@ This package owns the `ctx.skills` interface. It does not know whether skills co
### Public API
- `ctx.skills.registerProvider(provider): () => void` Registers a readonly provider by unique `provider.name`. Duplicate provider names throw, and `runtime` is reserved for `ctx.skills.register(...)`. The registry borrows the provider object and invokes its methods directly. The registration is effect-scoped and HMR-safe, and the exact Cordis disposer supports ordered composite teardown.
- `ctx.skills.list({ cwd?, signal? })` Borrows the readonly lookup options, then returns every winning summary for the current workspace, merged across providers and sorted by name. The result is invocation-neutral; consumers apply `isModelInvocable(skill)` or `isUserInvocable(skill)` at their own boundary.
- `ctx.skills.registerProvider(create): () => void` Calls a synchronous provider factory with `{ signal, invalidate }`, then registers its readonly result by unique `provider.name`. Duplicate names throw, `runtime` is reserved, and failed registration aborts the signal. The exact Cordis disposer unregisters the provider, aborts the signal, and preserves ordered composite teardown.
- `ctx.skills.snapshot({ cwd?, signal? })` Returns the invocation-neutral `{ skills, complete }` observation. `complete` is false when any provider rejects or explicitly reports incomplete discovery, or when a second catalog revision races the bounded retry; candidates supplied by that observation remain in this result, which is never cached.
- `ctx.skills.list({ cwd?, signal? })` Borrows the readonly lookup options, then returns every winning summary for the current workspace, merged across providers and sorted by name. Consumers apply `isModelInvocable(skill)` or `isUserInvocable(skill)` at their own boundary.
- `ctx.skills.get(name, { cwd?, signal? })` Uses the same readonly options and winning candidate for discovery and loading, rechecks cancellation after discovery or a cache hit, races provider loading against the signal, validates the loaded definition, then returns it regardless of invocation policy.
- `ctx.skills.register(skill): () => void` Registers a readonly runtime embedded skill, adding the all-invocable policy and `provider: "runtime"` when omitted. Same-name runtime registrations are first-wins: a duplicate logs a warning and gets a no-op disposer. Successful registrations return the exact Cordis disposer for ordered composite teardown.
### Events
- `skills/change` is an unfiltered invalidation notification emitted after a provider or runtime contribution is registered or disposed and after an active provider's registration control invalidates. It carries no catalog or diff: each consumer refetches `snapshot()` with its own lookup options. Listener throws and rejected promises are logged and cannot veto the registry mutation or starve later listeners.
### Config
| Field | Default | Meaning |
@@ -36,11 +41,13 @@ This package owns the `ctx.skills` interface. It does not know whether skills co
## Provider Contract
A provider registers synchronously and performs remote setup, authentication, and discovery in its awaited `list(options)` call. Provider objects, lookup options, candidates, and definitions are borrowed readonly rather than cloned or rebound. Providers should honor `options.signal`; the registry also stops awaiting uncooperative discovery or loading after cancellation.
A provider factory runs synchronously and receives one registration-scoped control. `control.signal` aborts when registration fails or is disposed; `control.invalidate()` clears completed catalogs only while that exact registration remains active, so late callbacks cannot affect a replacement with the same name. Immutable providers may ignore the control. Remote setup, authentication, and discovery belong in the provider's awaited `list(options)` call. An array return is shorthand for complete discovery; a provider that collected usable candidates but could not establish an authoritative observation returns `{ candidates, complete: false }`. Provider objects, lookup options, candidates, and definitions are borrowed readonly rather than cloned or rebound. Providers should honor `options.signal`; the registry also stops awaiting uncooperative discovery or loading after cancellation.
The registry validates candidates before caching and definitions before returning them. The winning provider receives the same candidate and opaque `locator` it returned from `list()`, allowing backend-specific file, URL, id, or version handles. Callers and providers must preserve the readonly contract.
Contract violations fail fast. A rejected `list()` is treated as a transient source failure: it is logged, skipped, and not cached. Only completed catalogs are cached; a provider or runtime revision change discards an in-flight result and retries. Duplicate names resolve by rank, provider registration order, then provider-local order. Summaries are sorted by skill name.
Contract violations fail fast. A rejected provider `list()` is treated as a transient source failure and omitted. An explicit incomplete observation still contributes its candidates for `list()` and `get()`, but makes the aggregate snapshot incomplete and uncacheable. A provider or runtime revision change discards an in-flight result and retries once. If the retry is also superseded, its candidates are returned incomplete and uncached so a continuously invalidating provider cannot monopolize the caller. Duplicate names resolve by rank, provider registration order, then provider-local order. Summaries are sorted by skill name.
Definitions remain progressively loaded. `get()` asks the winning provider for the body on every call rather than caching it in this registry. If the returned definition has a different name from the selected candidate, the stale selection is rejected and the registry internally invalidates that exact provider so the next snapshot rediscovers its catalog.
## Runtime Skills
@@ -48,19 +55,19 @@ Contract violations fail fast. A rejected `list()` is treated as a transient sou
## Consumer boundary
The registry does not render model guidance or register model-facing tools. [`@deepseek-ai/dsh-tool-skill`](../tool-skill) consumes `ctx.skills` to provide the session-prefix catalog and `skill` tool, so providers remain independent of the model surface.
The registry does not render model guidance or register model-facing tools. [`@deepseek-ai/dsh-tool-skill`](../tool-skill) consumes `ctx.skills` to provide durable session catalogs and the `skill` tool, so providers remain independent of the model surface.
## Model Experience
Indirectly, through `dsh-tool-skill`, which renders provider summaries into the session prefix and loaded instructions into retained tool results.
Indirectly, through `dsh-tool-skill`, which renders provider summaries into durable initial or replacement catalog messages and loaded instructions into retained tool results.
#### KV Cache effect
No direct invalidation; the named consumer owns any request-prefix changes.
No direct prompt effect. The named consumer owns the durable initial catalog and append-only replacements after invalidation.
## Known Limitations and Deferred Work
- **Completed catalogs have no TTL or watcher invalidation** — a provider's underlying files or remote data can change without a registration revision, so a cached cwd stays stale until eviction or provider/runtime reload.
- **Invalidation is provider-driven** — the registry has no TTL and cannot infer that an arbitrary remote source changed; each mutable provider must retain and call its registration-scoped `invalidate()` capability from its own observation mechanism.
- **Providers are queried sequentially** — one slow cooperative provider delays every provider registered after it; cancellation stops the caller's wait but cannot terminate work an uncooperative provider keeps running.
- **A provider-list failure removes that whole source for the request** — the registry logs and skips it, with no model-visible diagnostic or partial-catalog recovery contract.
- **Incomplete observations are not retained** — rejected providers are omitted and explicitly supplied candidates remain available only to the current lookup; the registry owns neither a last-good catalog nor per-provider diagnostics.
- **Duplicate resolution is first-wins** — later lower-priority candidates are logged and hidden; there is no API to inspect all shadowed definitions.