* feat(release): publish package source, declaration maps, and documentation Every release member publishes its TypeScript source and the declaration maps that resolve into it, and @deepseek-ai/dsh publishes the repository Markdown documentation staged from docs/ at prepack time. The ./src/* export most members already declared now resolves for an installed consumer, and a consumer editor follows a declaration to the implementation. Bundled JavaScript maps stay unpublished; the vendored family and the Desktop runtime file policy are unchanged. Rationale: .agents/notes/implemented/process/2026-10-08-published-source-and-docs.md * feat(release): publish JavaScript maps and compress bundles Every emitted JavaScript map ships beside the bundle it describes, and the workspace build and the client preset compress bundles without mangling identifiers. The documentation payload now mirrors the website publication manifest and is removed again by `postpack`; Desktop and the Python executable exclude the published source, documentation, and maps; `clean` removes a staged copy. Typert's publication contract gains the Remote declaration map. * fix(release): keep node bundles readable for the worker image The browser worker image lowers `yield*` only as a whole statement and rewrites `for await` loops by their source layout, so compressed node-side entries either fail that lowering or lower to unparseable code. Only the browser bundles keep compression, which removes about a third of their text. The bundle-purity assertion now matches the module-loader call regardless of the quote style compression picks, and the release-pack fixture exercises the installed package's new documentation requirement. * feat(release): publish the whole documentation tree `@deepseek-ai/dsh` copies `docs/` in full instead of the website's page set: the maintainer documents, the generated migration records, the translation guidelines, and the `*.i18n.yaml` consistency records ship with the installed package, so the documentation's own links resolve without a checkout. * feat(release): publish the upgrade guides with the documentation The staged documentation keeps the website's page set and adds every `docs/upgrade-guide/` document, which is what an installed reader consults when a released change breaks something. The rest of the maintainer tree stays out. * test(release): expect source and maps in master's new payload cases * feat(release): keep installed docs and source readable in Desktop Desktop keeps the published src/ and docs/ trees inside app.asar, and every model shell call receives DSH_INSTALL_DIR and DSH_NODE_EXECUTABLE so an agent can read them through Electron's Node mode. Staging also generates docs/upgrade-guide/README.md, an index of the upgrade guides grouped by the release they upgrade from. Minified client bundles print string literals with backticks: the worker image packer now recognizes those chunk requests, client license banners are prepended after minification so their text survives, and the affected artifact tests accept either quote. * test(desktop): read archived docs through the Windows PowerShell form Regenerate the config catalog for the shifted shell-env source line. * test(desktop): wait for the GUI-subsystem Electron process from PowerShell * build(release): publish source and maps from the message-quotes package * build(release): publish source and maps from packages new on master * build(release): publish source and maps from the tool-office package
description, kind
| description | kind |
|---|---|
| The scoped-registration library for plugin authors and maintainers building registries or event surfaces that isolate contributions per agent or per group. | package-library |
@deepseek-ai/dsh-scope
English | 中文
Summary
dsh-scope lets plugin authors give each agent or group an isolated contribution set with a shared lifetime. Child scopes inherit ancestor contributions, with the nearest definition taking precedence, while ancestor scopes can observe descendant activity; neither relationship works in reverse. Disposing a scope removes everything owned by it. Use this dependency-free library when per-agent or per-group isolation must work without depending on the agent loop or presets.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Known Limitations and Deferred Work
- Dev Note
Use this package
Plugin authors use dsh-scope to give one agent (or one group) its own registration world. The registries in the core group build on it — a tool registered through agent.ctx is visible only to that agent — and the same primitive serves any custom registry or scope-filtered event.
Mint a scope
createScope(ctx, key) creates a scope under ctx's fiber: its ctx carries the scope tag, and everything registered through it is both scope-visible and scope-lifetime. dispose() unwinds every registration through the scope; rawDispose is the exact Cordis disposer for nesting the teardown in an ordered composite effect.
const scope = createScope(ctx, agent)
scope.ctx.on('agent/status', ({ agent, status }) => track(agent, status))
// later:
await scope.dispose() // unwinds every registration made through scope.ctx
Route scoped events
scopeTarget(base, key) builds the opaque carrier a scope-filtered event dispatches with. Untagged listeners stay global; a listener tagged with key receives events for that key and its descendants. The carrier carries routing state only — the real subject travels in the event arguments.
Build a scoped registry layer
Registry authors use ScopedLayers, NamedEntries, and AnonymousEntries to hold one eager global layer plus lazily created exact-scope layers: reads never create layers, merge() materializes insertion-ordered named shadows along the scope chain, and effect() derives visibility and ownership from the same context. A scoped layer is reclaimed only when its whole aggregate is empty.
Understand the implementation
Implementation internals — click to expand
This section explains how the package realizes the behavior above; the observable contract is covered in Use this package.
Design concept
The registration context determines both visibility and ownership: a registration made through a scoped context is visible in that scope and disposed with it, preventing a contribution from being visible in one scope but torn down with another. The primitive routes trusted same-process plugins; it is not a sandbox or an authority boundary. Handing out a scoped context also hands out the minting plugin's service-resolution API (resolution walks the minting fiber's dependency chain), so a scope is minted from the plugin whose dependencies the scoped registrations need.
Source map
| File | Role |
|---|---|
src/index.ts |
createScope, scopeOf, scopeTarget, bindScopeParent/scopeParentOf/scopeChainOf, carrier marks |
src/store.ts |
ScopedLayers, NamedEntries, AnonymousEntries, ScopeLayer |
The parent chain
One relation powers both directions: registration views inherit DOWN the chain (a child scope sees its ancestors' layers), while event admission extends UP it (a listener tagged with an ancestor receives events dispatched to a descendant key). Binding is once — a key that already has a parent throws, and only the returned binding may re-link it — and every link rejects a cycle. scopeChainOf returns [key, parent, …] nearest-first.
Event filtering
scopeTarget composes the base's existing Context.filter with the scope predicate: an untagged listener is admitted; a tagged listener is admitted iff its tag is the dispatch key or an ancestor of it; key === undefined admits untagged listeners only. { global: true } listeners bypass filtering. The Scoped<T> brand demands the carrier as the this type of a scope-filtered event, so dispatching with a bare subject is a compile error.
Further Exploration
The package-level contract is enough for most consumers; read these when you need the surrounding domain and the design rationale.
- Scoped registration subsystem — the identity, carrier, and layer types.
- Agent-scope runtime design reference — how the loop builds per-agent scopes.
- Core group map — how the core packages compose.
Known Limitations and Deferred Work
These limits define when the primitive needs special care. They are current package constraints, not a task backlog.
- Only scope-aware APIs isolate state — registries must file by
scopeOf()and events must dispatch throughscopeTarget(); an arbitrary Cordis service remains context-global merely because it is called through a scoped context. - A context carries one nearest scope key — the hierarchy lives in the key-level parent relation, not in context tags; nested scope contexts still shadow to a single tag, and multi-membership policy sets remain unsupported.
- Service reachability comes from the scope minter — handing out
Scope.ctxalso hands out the minting plugin's injected services, so a broader minter cannot later be narrowed by the holder.
Dev Note
Working context for maintainers — click to expand
None.