@dr-co/dsh-assistants

中文文档
0.9.12发布于 2026/11/12MITES Module零依赖近 30 天下载 5256

DSH plugin: assistants, virtual users and pair memory bound to sessions (host part)

原始使用文档
文档目录14 节
  1. Why this is useful
  2. Installation and bundle metadata
  3. Compatibility and limitations
  4. What it models
  5. Matching
  6. Access mode
  7. Configuration
  8. Memory search (semantic, optional)
  9. HTTP
  10. Delegation
  11. Models
  12. Client half
  13. Build and tests
  14. Client beacon

@dr-co/dsh-assistants

A DSH bundle/plugin that gives every session a who answers and a who is asking, and keeps the memory of that pair. It is useful when one DSH installation serves several assistants, people, bots, or scheduled jobs and each session needs a predictable persona without changing the underlying agent preset.

Why this is useful

One DSH installation can be shared by several people while giving each person an independent assistant and pair-specific context. For setups that would otherwise need several separate installations of products such as OpenClaw, one DSH host can provide the same kind of separation through session routing and virtual users, without claiming to replace every feature of those products.

The administration UI is the place to create and manage assistant and virtual-user variants. This is also useful for subagent workflows: you can define roles such as architect, programmer, and reviewer, then ask for a sequence such as “let the architect design a plan, then let the programmer implement it”. Each subagent is resolved with the prompt configured for the selected role.

Every assistant has its own pair-scoped memory: what it knows about a user and how that user asks it to respond. The memory follows the assistant–user pair rather than the transport or bot.

This package contains the host plugin and the browser client bundle. It does not patch DSH core and it does not replace the host's preset, tool, delegation, or transport implementation.

Installation and bundle metadata

Install the package using the normal plugin/bundle mechanism of the DSH host. The package is self-describing: package.json declares the dsh.bundle.patch metadata, and cordis.patch.yml inserts the dsh-assistants row into the host's plugin list. The same package metadata declares the browser entry as dsh.client; the host loads lib/client.js when the Web client is available. No manual edit to index.html or DSH core is required.

The bundle is intentionally opt-in through the host's plugin list. Its state is kept separately from DSH core and can be disabled or removed by the host's ordinary plugin controls. If a host does not implement the DSH bundle/plugin metadata loader, this package cannot install itself there; use a host version that supports that loader rather than applying the patch by hand.

Compatibility and limitations

The plugin uses public host services and optional integrations only. It does not promise an official DSH core patch API. In particular:

  • identity lookup for IM sessions requires the host's dshIm.describeConversation; without it, only the transport can be identified;
  • cron identity requires a schedule entry with a matching sessionId;
  • the subagentPersona.resolve composition hook is used only when the host exposes ctx.provide; older hosts keep inherited assistant behaviour;
  • unavailable browser slots are reported and skipped, so the settings card and composer controls may be absent on older Web hosts;
  • the client bundle is Web-only; the host part can still provide prompt, memory, and HTTP functionality without the browser client.

The fallback behaviour is deliberate: missing optional services do not make the plugin claim that DSH core supports a new API, and they do not prevent the rest of the plugin from loading.

What it models

Two independent axes:

  • assistant — a persona with one block of text about itself;
  • virtual user — a persona with a prompt, facts about the person, and the access mode that session runs under.

Both are resolved independently: their own rules, their own default, their own log line. A rule never assigns both axes.

The prompt the model sees is one section with four parts, each dropped when empty:

# who you are                             the assistant's own text
# who you are talking to                  user prompt + facts
# about the person you are talking to      memory about-user
# additional information about you         memory for-this-user

Memory is keyed by the pair, never by the bot: the bot is transport. Four buckets, in the public vocabulary the tools and the prompt use (storage keys are short internally and never leak into prompts or tool descriptions):

bucketmeaningscopewritten by
about-userwhat this assistant knows about this personthe userthe model, via dsha_save
for-this-userhow this person wants this assistant to answerthe pairthe model, via dsha_save
memorylong-term searchable episode summariesthe userthe model, via dsha_save target=memory
assistant-notesthe assistant's own notes to itselfthe assistantthe model, via dsha_save target=assistant-notes

about-user and for-this-user are injected into the prompt. memory and assistant-notes are search-only: they are never injected, and are read only via dsha_search (whose results name the bucket). The two search-only banks are behind one flag — memoryBanks: false (or DSH_ASSISTANTS_MEMORY_BANKS=0) disables the write targets and their prompt rule blocks. dsha_search without bucket searches all four.

Each pair bucket also has a summary — rau:<assistant>:<user> for about-user, rua:<user>:<assistant> for for-this-user — and the prompt prints the summary when it exists, falling back to the raw rows. A summary records the row count it was built from, so "stale" is a comparison. POST /api/assistants/summary recomputes one through the host's llm service; without that service it answers 503 llm-unavailable and the summary can be written by hand in the same field.

Everything about the assistant itself or about the person in general is a persona field — a human edits it, the model does not.

Matching

Rules are two ordered lists (assistants, users). A rule is a set of conditions plus assign; every condition present must hold, and a condition the session cannot answer makes the rule miss. First match wins. A rule with no conditions is the axis default.

users:
  - sender_id: "161161619"        # this person, in any chat
    assign: u-unera
  - bot_id: telegram_fc1c…        # everybody else who writes to this bot
    assign: u-marish
  - assign: u-unera               # the axis default

Conditions: transport (web, im:telegram, im:discord, cron), bot_id, group_id, chat_id, sender_id, regexp (over the conversation key).

Identity comes from the session's creator:

transportwhere the identity comes from
imdshIm.describeConversation(sessionId)
cronthe task in the schedule store whose sessionId matches
webnothing but "web", until the person chooses in the form

Access mode

The mode belongs to the virtual user, not to the assistant: rights follow the person who arrived, not the character who answers. One person arrives, one mode applies, so there is nothing to reconcile.

Configuration

- id: dsh-assistants
  name: '@dr-co/dsh-assistants'
  config:
    promptLimit: 6000

promptLimit caps the rendered section; over the cap the tail is dropped and the loss is logged once. State lives in $DSH_HOME/storages/dsh-assistants.json, is written atomically, watched, and applied without a restart. The package ships defaults.json; it seeds that file once, on the first boot.

Memory search (semantic, optional)

dsha_search is hybrid: exact-token FTS first, then a semantic top-k over locally stored vectors, merged deterministically. The embedder is fully local ONNX inference through @huggingface/transformers (transformers.js) — no HTTP embedding API and no API key. The only network use is the one-time download of the ONNX weights, which happens lazily on first use, never at plugin boot and never on the memory write path.

  • Default model: Xenova/multilingual-e5-small (384 dimensions, handles Russian and English). BGE models use CLS pooling, everything else uses mean pooling, and vectors are L2-normalised; the pooling is chosen per model family.
  • Model cache directory: $DSH_HOME/dsh-assistants/models (configurable via embedCacheDir). New writes are indexed as they arrive; pre-existing rows are backfilled on demand from the admin card or POST /api/assistants/memory/embedding/reindex.
  • To disable it entirely, set embedDisabled: true (or DSH_ASSISTANTS_EMBED_DISABLED=1). Search then stays pure FTS and nothing is ever downloaded. The model id is configurable with embedModel (DSH_ASSISTANTS_EMBED_MODEL).

When the model or the dependency is unavailable, search degrades to FTS exactly as before and memory writes stay fast and offline. The embedding admin surface lives in the settings card ("Accumulated memory" tab): it shows whether the model is downloaded, the running download/reindex progress, and the vector count.

HTTP

The panel talks to the plugin over exact routes under /api/assistants, which the host admits only for an authenticated owner — the same gate /api/settings sits behind. Identifiers travel in the JSON body, never in the path.

routemethodbody
/api/assistants/stateGET—
/api/assistants/botsGET—
/api/assistants/modelsGET— (providers, models and their reasoning efforts)
/api/assistants/diagPOST{ session? }
/api/assistants/previewPOST{ assistant?, user? }
/api/assistants/promptPOST{ session }
/api/assistants/resolvePOST{ session? } or { transport, bot_id, … }
/api/assistants/session-infoPOST{ session }
/api/assistants/savePOST{ assistants?, users?, rules?, defaults?, models?, summaries?, memory? }
/api/assistants/sessionPOST{ session, assistant?, user?, source?, none?, force? }
/api/assistants/memoryPOST{ session?, assistant?, user?, text?, target?, action?, id? }
/api/assistants/client-reportPOSTthe client's mount report
/api/assistants/summaryPOSTdisabled (410 summary-disabled)
/api/assistants/memory/embeddingGET—
/api/assistants/memory/embedding/downloadPOST—
/api/assistants/memory/embedding/reindexPOST{ batch? }
/api/assistants/memory/rowsPOST{ bank, after?, user?, assistant?, limit? }

Delegation

The host's stock subagent / subagent_fork tools carry no persona (persona is now rejected outright, agentPreset is spawn-only), so a child started through them cannot be told which assistant it plays. The plugin registers its own two tools instead: dsha_subagent_fork (child inherits this conversation) and dsha_subagent (fresh child), both taking assistant plus the usual prompt, description, cwd and provider/model/reasoning_effort.

They do not go through the stock tools. They call the host's own seam, ctx.subagents.startActivation, with a child id the plugin generates, and write the (assistant, user, lineage) binding into the store under that id before the session exists. So the child's first prompt already resolves the right persona, no call-order queue is involved, two delegations can run at once, and a failed start drops the binding again. The only host surface relied on is the subagents service (startActivation, resolveMaxDepth), not a tool definition.

A fork renders the root's system prompt verbatim — that prefix is the prompt-cache key — and the child's own persona travels in the task text, and only when there is no inherited history to state it. A fork keeps the caller's persona by definition, so its schema offers no assistant; dsha_subagent takes an id, or null for a child with no persona at all (its binding records none and the prompt section renders nothing).

Models

The admin page has a Models tab — the single place where a model is assigned to an assistant. The policy says where the assignment comes from: default (the host decides), from the card (the assistant's own model field), or one of the two named slots applied to every assistant. Each slot is a provider + model + reasoning effort, picked from the host's own catalogue (GET /api/assistants/models), never a hardcoded id; a provider that advertises no models is not offered, unless a route already points at it. A route the panel assigns wins over provider/model in the tool call; an unadvertised route is logged and still used.

The assignment reaches two places. A delegated child gets the route in its start request, so it never runs a turn on the wrong model. A session gets it when the panel's own pickers write its pair: the plugin calls the host's sessionController.selectModel — the same lever the host's model picker uses — and only while the session is still blank; a conversation that started keeps its model. selectModel also persists the route as the default for NEW sessions, so the previous default is written back: this choice is per-session, not a new global default.

Client half

The browser side is a real client plugin (the same shape @dr-co/dsh-skills uses), not a script injected into index.html: package.json declares dsh.client, the host loads lib/client.js and calls apply(ctx), and every contribution goes through the public slot registry.

slotwhat it draws
settings.sectionthe admin page: assistants, users, models, both rule lists, sessions
conversation.input.leftthe two pickers in the composer row, scoped per session
conversation.chat.nodethe assistant's name and icon above a turn (shadows the official turn-process node and renders it inside, unchanged)

Strings live in src/client/locales.js (ru / en / zh); a test compares the key sets so a translation cannot silently miss a key.

Build and tests

npm install          # esbuild only
npm run build        # writes lib/client.js
npm test             # full Node test suite, including the built bundle
npm pack --dry-run    # inspect the public package file list

The CI job runs the build before the tests and checks the package with npm pack --dry-run. The package does not publish or install anything by itself.

The build is a single esbuild pass plus the window.__ModuleLoader__ wrapper the shell expects, so lib/client.js is reproducible from src/client/.

Client beacon

At the end of apply the bundle posts {ok, mounted, failed} to POST /api/assistants/client-report; the host logs it as one line:

[dsh-assistants] client mounted=settings.section,sidebar.panellist,main failed=conversation.input.dock: not mounted on this host

Browser warnings never reach the server log, and "the page is not there" is exactly the report that needs a line in the journal rather than a DevTools session.

版本与发布记录

@dr-co/dsh-assistants 共发布 35 个版本,首次发布于 2026/10/27;过去 12 个月发布了 35 个版本。

版本发布日期距今
0.9.12latest2026/11/12今天
0.9.112026/11/12今天
0.9.102026/11/12今天
0.9.92026/11/121 天前
0.9.82026/11/121 天前
0.8.92026/11/103 天前

关于 @dr-co/dsh-assistants 的常见问题

@dr-co/dsh-assistants 是什么?

DSH plugin: assistants, virtual users and pair memory bound to sessions (host part)

如何安装 @dr-co/dsh-assistants?

在项目目录运行 npm install @dr-co/dsh-assistants,或使用 pnpm add / yarn add / bun add。

@dr-co/dsh-assistants 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/dr-co__dsh-assistants 是否存在。

@dr-co/dsh-assistants 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@dr-co/dsh-assistants 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dr-co/dsh-assistants 可以商用吗?

@dr-co/dsh-assistants 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dr-co/dsh-assistants 的下载量有多少?

根据 npm 官方下载统计,@dr-co/dsh-assistants 在 2026/10/13 – 2026/11/11 这 30 天内被下载 5,256 次,日均约 175 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dr-co/dsh-assistants 还在维护吗?

最新版本 0.9.12 发布于 2026/11/12(今天);至今共发布 35 个版本,其中过去 12 个月发布了 35 个;首次发布于 2026/10/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。