@dr-co/dsh-assistants 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +196 -2
- package/cordis.patch.yml +16 -0
- package/defaults.json +28 -0
- package/index.mjs +839 -0
- package/lib/bots.mjs +151 -0
- package/lib/client.js +3096 -0
- package/lib/fullPrompt.mjs +110 -0
- package/lib/identity.mjs +217 -0
- package/lib/markdown.mjs +16 -0
- package/lib/memory.mjs +91 -0
- package/lib/model.mjs +356 -0
- package/lib/permission.mjs +95 -0
- package/lib/personaPrompt.mjs +54 -0
- package/lib/personaService.mjs +38 -0
- package/lib/prompt.mjs +400 -0
- package/lib/resolve.mjs +91 -0
- package/lib/routes.mjs +544 -0
- package/lib/rules.mjs +73 -0
- package/lib/services.mjs +113 -0
- package/lib/store.mjs +310 -0
- package/lib/summary.mjs +506 -0
- package/lib/summaryQueue.mjs +58 -0
- package/package.json +50 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UNera
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,197 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @dr-co/dsh-assistants
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A DSH bundle/plugin that gives every session a **who answers** and a **who is asking**,
|
|
4
|
+
and keeps the memory of that pair. It is useful when one DSH installation serves
|
|
5
|
+
several assistants, people, bots, or scheduled jobs and each session needs a
|
|
6
|
+
predictable persona without changing the underlying agent preset.
|
|
7
|
+
|
|
8
|
+
This package contains the host plugin and the browser client bundle. It does not
|
|
9
|
+
patch DSH core and it does not replace the host's preset, tool, delegation, or
|
|
10
|
+
transport implementation.
|
|
11
|
+
|
|
12
|
+
## Installation and bundle metadata
|
|
13
|
+
|
|
14
|
+
Install the package using the normal plugin/bundle mechanism of the DSH host. The
|
|
15
|
+
package is self-describing: `package.json` declares the `dsh.bundle.patch` metadata,
|
|
16
|
+
and `cordis.patch.yml` inserts the `dsh-assistants` row into the host's plugin list.
|
|
17
|
+
The same package metadata declares the browser entry as `dsh.client`; the host loads
|
|
18
|
+
`lib/client.js` when the Web client is available. No manual edit to `index.html` or
|
|
19
|
+
DSH core is required.
|
|
20
|
+
|
|
21
|
+
The bundle is intentionally opt-in through the host's plugin list. Its state is
|
|
22
|
+
kept separately from DSH core and can be disabled or removed by the host's ordinary
|
|
23
|
+
plugin controls. If a host does not implement the DSH bundle/plugin metadata
|
|
24
|
+
loader, this package cannot install itself there; use a host version that supports
|
|
25
|
+
that loader rather than applying the patch by hand.
|
|
26
|
+
|
|
27
|
+
## Compatibility and limitations
|
|
28
|
+
|
|
29
|
+
The plugin uses public host services and optional integrations only. It does not
|
|
30
|
+
promise an official DSH core patch API. In particular:
|
|
31
|
+
|
|
32
|
+
- identity lookup for IM sessions requires the host's `dshIm.describeConversation`;
|
|
33
|
+
without it, only the transport can be identified;
|
|
34
|
+
- cron identity requires a schedule entry with a matching `sessionId`;
|
|
35
|
+
- the `subagentPersona.resolve` composition hook is used only when the host exposes
|
|
36
|
+
`ctx.provide`; older hosts keep inherited assistant behaviour;
|
|
37
|
+
- unavailable browser slots are reported and skipped, so the settings card and
|
|
38
|
+
composer controls may be absent on older Web hosts;
|
|
39
|
+
- the client bundle is Web-only; the host part can still provide prompt, memory, and
|
|
40
|
+
HTTP functionality without the browser client.
|
|
41
|
+
|
|
42
|
+
The fallback behaviour is deliberate: missing optional services do not make the
|
|
43
|
+
plugin claim that DSH core supports a new API, and they do not prevent the rest of
|
|
44
|
+
the plugin from loading.
|
|
45
|
+
|
|
46
|
+
## What it models
|
|
47
|
+
|
|
48
|
+
Two independent axes:
|
|
49
|
+
|
|
50
|
+
- **assistant** — a persona with one block of text about itself;
|
|
51
|
+
- **virtual user** — a persona with a prompt, facts about the person, and the access
|
|
52
|
+
mode that session runs under.
|
|
53
|
+
|
|
54
|
+
Both are resolved independently: their own rules, their own default, their own log
|
|
55
|
+
line. A rule never assigns both axes.
|
|
56
|
+
|
|
57
|
+
The prompt the model sees is one section with four parts, each dropped when empty:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
# кто ты the assistant's own text
|
|
61
|
+
# с кем ты общаешься user prompt + facts
|
|
62
|
+
# о том, с кем ты общаешься… memory au:<assistant>:<user>
|
|
63
|
+
# дополнительные сведения о тебе… memory ua:<user>:<assistant>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Memory is keyed by the **pair**, never by the bot: the bot is transport. There are
|
|
67
|
+
two buckets and only two:
|
|
68
|
+
|
|
69
|
+
| key | meaning | written by |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `au:<assistant>:<user>` | what this assistant knows about this person | the model, via `memory_save` |
|
|
72
|
+
| `ua:<user>:<assistant>` | how this person wants this assistant to answer | the model, via `memory_save` |
|
|
73
|
+
|
|
74
|
+
Each bucket also has a **summary** — `rau:<assistant>:<user>` for `au:`,
|
|
75
|
+
`rua:<user>:<assistant>` for `ua:` — and the prompt prints the summary when it
|
|
76
|
+
exists, falling back to the raw rows. A summary records the row count it was built
|
|
77
|
+
from, so "stale" is a comparison. `POST /__as/summary` recomputes one through the
|
|
78
|
+
host's `llm` service; without that service it answers `503 llm-unavailable` and the
|
|
79
|
+
summary can be written by hand in the same field.
|
|
80
|
+
|
|
81
|
+
Everything about the assistant itself or about the person in general is a persona
|
|
82
|
+
field — a human edits it, the model does not.
|
|
83
|
+
|
|
84
|
+
## Matching
|
|
85
|
+
|
|
86
|
+
Rules are two ordered lists (assistants, users). A rule is a set of conditions plus
|
|
87
|
+
`assign`; every condition present must hold, and a condition the session cannot
|
|
88
|
+
answer makes the rule miss. First match wins. A rule with no conditions is the axis
|
|
89
|
+
default.
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
users:
|
|
93
|
+
- sender_id: "161161619" # this person, in any chat
|
|
94
|
+
assign: u-unera
|
|
95
|
+
- bot_id: telegram_fc1c… # everybody else who writes to this bot
|
|
96
|
+
assign: u-marish
|
|
97
|
+
- assign: u-unera # the axis default
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Conditions: `transport` (`web`, `im:telegram`, `im:discord`, `cron`), `bot_id`,
|
|
101
|
+
`group_id`, `chat_id`, `sender_id`, `regexp` (over the conversation key).
|
|
102
|
+
|
|
103
|
+
Identity comes from the session's creator:
|
|
104
|
+
|
|
105
|
+
| transport | where the identity comes from |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| im | `dshIm.describeConversation(sessionId)` |
|
|
108
|
+
| cron | the task in the schedule store whose `sessionId` matches |
|
|
109
|
+
| web | nothing but "web", until the person chooses in the form |
|
|
110
|
+
|
|
111
|
+
## Access mode
|
|
112
|
+
|
|
113
|
+
The mode belongs to the **virtual user**, not to the assistant: rights follow the
|
|
114
|
+
person who arrived, not the character who answers. One person arrives, one mode
|
|
115
|
+
applies, so there is nothing to reconcile.
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
- id: dsh-assistants
|
|
121
|
+
name: '@dr-co/dsh-assistants'
|
|
122
|
+
config:
|
|
123
|
+
promptLimit: 6000
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`promptLimit` caps the rendered section; over the cap the tail is dropped and the
|
|
127
|
+
loss is logged once. State lives in `$DSH_HOME/storages/dsh-assistants.json`, is
|
|
128
|
+
written atomically, watched, and applied without a restart. The package ships
|
|
129
|
+
`defaults.json`; it seeds that file once, on the first boot.
|
|
130
|
+
|
|
131
|
+
## HTTP
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
GET /__as/state personas, rules, defaults, overrides, memory
|
|
135
|
+
POST /__as/save replace personas / rules / defaults
|
|
136
|
+
POST /__as/session manual override for one session
|
|
137
|
+
POST /__as/memory add a row, or delete one by id
|
|
138
|
+
GET/POST /__as/persona_prompt one target persona id; returns its base prompt and pair memory
|
|
139
|
+
GET /__as/diag[?session=…] what this plugin thinks, and why
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Not here yet
|
|
143
|
+
|
|
144
|
+
The model-facing `persona_prompt` tool accepts one optional `target` persona id. An empty
|
|
145
|
+
id uses the current assistant id. Its result is the complete localized base prompt for
|
|
146
|
+
that persona, including both `au:` and `ua:` rows for the current user pair; it never
|
|
147
|
+
reads mneme. The parent may append the task section and pass one final prompt to the
|
|
148
|
+
ordinary `subagent_fork`; preset switching is intentionally unsupported. On hosts exposing
|
|
149
|
+
`ctx.provide`, the plugin also registers `subagentPersona.resolve(targetId, { parent, child })`
|
|
150
|
+
for the host subagent composition hook. Older hosts have no such hook and retain inherited
|
|
151
|
+
assistant behavior.
|
|
152
|
+
|
|
153
|
+
The client bundle (composer pickers, names and icons on messages, the admin card)
|
|
154
|
+
and the migration from `dsh-agent-profiles` are the next iteration.
|
|
155
|
+
|
|
156
|
+
## Client half
|
|
157
|
+
|
|
158
|
+
The browser side is a real client plugin (the same shape `@dr-co/dsh-skills` uses),
|
|
159
|
+
not a script injected into `index.html`: `package.json` declares `dsh.client`, the
|
|
160
|
+
host loads `lib/client.js` and calls `apply(ctx)`, and every contribution goes
|
|
161
|
+
through the public slot registry.
|
|
162
|
+
|
|
163
|
+
| slot | what it draws |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `settings.section` | the admin page: assistants, users, both rule lists, sessions |
|
|
166
|
+
| `conversation.input.left` | the two pickers in the composer row, scoped per session |
|
|
167
|
+
| `conversation.chat.node` | the assistant's name and icon above a turn (shadows the official `turn-process` node and renders it inside, unchanged) |
|
|
168
|
+
|
|
169
|
+
Strings live in `src/client/locales.js` (ru / en / zh); a test compares the key sets
|
|
170
|
+
so a translation cannot silently miss a key.
|
|
171
|
+
|
|
172
|
+
### Build and tests
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
npm install # esbuild only
|
|
176
|
+
npm run build # writes lib/client.js
|
|
177
|
+
npm test # full Node test suite, including the built bundle
|
|
178
|
+
npm pack --dry-run # inspect the public package file list
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The CI job runs the build before the tests and checks the package with
|
|
182
|
+
`npm pack --dry-run`. The package does not publish or install anything by itself.
|
|
183
|
+
|
|
184
|
+
The build is a single esbuild pass plus the `window.__ModuleLoader__` wrapper the
|
|
185
|
+
shell expects, so `lib/client.js` is reproducible from `src/client/`.
|
|
186
|
+
|
|
187
|
+
### Client beacon
|
|
188
|
+
|
|
189
|
+
At the end of `apply` the bundle posts `{ok, mounted, failed}` to
|
|
190
|
+
`POST /__as/client-report`; the host logs it as one line:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
[dsh-assistants] client mounted=settings.section,sidebar.panellist,main failed=conversation.input.dock: not mounted on this host
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Browser warnings never reach the server log, and "the page is not there" is exactly
|
|
197
|
+
the report that needs a line in the journal rather than a DevTools session.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Bundle layer for dsh-assistants.
|
|
2
|
+
#
|
|
3
|
+
# Installing the package mounts this row, so the plugin shows up in the settings
|
|
4
|
+
# plugin list and can be switched off or removed like any other.
|
|
5
|
+
#
|
|
6
|
+
# There is deliberately no content here: assistants, virtual users, their prompts
|
|
7
|
+
# and the matching rules are things a person edits in the card, they must apply
|
|
8
|
+
# without a restart, and they live in `storages/dsh-assistants.json`. On the first
|
|
9
|
+
# boot this plugin copies the `defaults.json` shipped with the package into that
|
|
10
|
+
# file; from then on the file is the truth, and an empty list of rules is a
|
|
11
|
+
# deliberate "no rules" rather than an invitation to re-read this patch.
|
|
12
|
+
- insert:
|
|
13
|
+
- id: dsh-assistants
|
|
14
|
+
name: '@dr-co/dsh-assistants'
|
|
15
|
+
config:
|
|
16
|
+
promptLimit: 6000
|
package/defaults.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"assistants": [],
|
|
4
|
+
"users": [],
|
|
5
|
+
"rules": {
|
|
6
|
+
"assistants": [],
|
|
7
|
+
"users": []
|
|
8
|
+
},
|
|
9
|
+
"defaults": {
|
|
10
|
+
"assistant": null,
|
|
11
|
+
"user": null
|
|
12
|
+
},
|
|
13
|
+
"sessions": {},
|
|
14
|
+
"memory": {},
|
|
15
|
+
"template": {
|
|
16
|
+
"assistant": {
|
|
17
|
+
"name": "Новый ассистент",
|
|
18
|
+
"icon": "🤖",
|
|
19
|
+
"prompt": ""
|
|
20
|
+
},
|
|
21
|
+
"user": {
|
|
22
|
+
"name": "Новый пользователь",
|
|
23
|
+
"prompt": "",
|
|
24
|
+
"facts": "",
|
|
25
|
+
"permission": "workspace-write"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|