dsh-persona-forge 0.0.0-stage → 0.3.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/CHANGELOG.md +301 -0
- package/LICENSE +21 -0
- package/README.en.md +515 -0
- package/README.md +357 -2
- package/cards/muscle-crew.yml +100 -0
- package/cards/omnissiah.yml +98 -0
- package/cordis.patch.yml +11 -0
- package/lib/client.js +1994 -0
- package/lib/http.js +31 -0
- package/lib/index.js +182 -0
- package/lib/loopback.js +56 -0
- package/lib/preserve.js +138 -0
- package/lib/prompts.js +379 -0
- package/lib/rewrite.js +188 -0
- package/lib/routes.js +606 -0
- package/lib/shared/protocol.js +55 -0
- package/lib/store.js +618 -0
- package/lib/yaml.js +527 -0
- package/package.json +73 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2026-10-09
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **AI 代填(AI draft)**, in the character-card manager. Describe the character
|
|
14
|
+
in a sentence — "a grumpy but utterly reliable old lighthouse keeper" — and the
|
|
15
|
+
model writes the whole card: the style text, a name, an icon, a description and
|
|
16
|
+
the conversion examples for all four intensity levels.
|
|
17
|
+
|
|
18
|
+
The result **only prefills the form**. Nothing is written to the card directory
|
|
19
|
+
until the user reviews it and presses Save, so a generation the user dislikes
|
|
20
|
+
costs one reload — the same review discipline the rewrite panel enforces. The
|
|
21
|
+
generated card is also passed through the very same validator the save path
|
|
22
|
+
uses, so this route cannot hand the form a card that cannot be saved.
|
|
23
|
+
|
|
24
|
+
It sits **inside the new-card flow** (the form that "New character card" opens),
|
|
25
|
+
with a hint line on the settings page so it is discoverable without pressing a
|
|
26
|
+
button. It uses the **harness default model**, and says so: the
|
|
27
|
+
`settings.section` slot receives only `{ close }` from the shell, so it has no
|
|
28
|
+
session id to follow. The resolved model is displayed before and after
|
|
29
|
+
generating, rather than leaving the user to assume it matches the session.
|
|
30
|
+
|
|
31
|
+
- A **free, local preservation check** (`lib/preserve.js`), always on. It compares
|
|
32
|
+
the technical tokens of the draft against the rewrite — identifiers, paths,
|
|
33
|
+
versions, numbers, `--flags`, acronyms — and **names** what went missing. It
|
|
34
|
+
makes no model call, so it costs no tokens and adds no latency.
|
|
35
|
+
|
|
36
|
+
- **Per-message rewrite mode and fidelity** in the 🎭 menu, alongside intensity.
|
|
37
|
+
Mode falls back to the card's own when the card has no content for the chosen
|
|
38
|
+
mode. Fidelity may be **narrowed** (a `strategy` card asked to behave as
|
|
39
|
+
voice-only for one message) but never **widened**, and the server enforces the
|
|
40
|
+
same rule.
|
|
41
|
+
|
|
42
|
+
- A **model-check switch** in the 🎭 menu, remembered per session.
|
|
43
|
+
|
|
44
|
+
- The card form now edits the **per-intensity examples**. They were part of the
|
|
45
|
+
card but invisible in the UI, which is what made the bug below possible.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **The model-based preservation check is now off by default** and switchable
|
|
50
|
+
from the menu. It is the only thing that can see a requirement reworded weaker,
|
|
51
|
+
or a constraint added in prose — but it costs a second model call on **every**
|
|
52
|
+
send, and the new local check already proves the common and most damaging
|
|
53
|
+
failure for free. `factCheck` in the plugin config now sets that default, and
|
|
54
|
+
only an explicit `true` enables it.
|
|
55
|
+
|
|
56
|
+
- The Chinese README is the primary `README.md` and the English one is
|
|
57
|
+
`README.en.md`, so GitHub, npm and the repository landing page open in Chinese,
|
|
58
|
+
with a language switch at the top of each file. The npm package description is
|
|
59
|
+
Chinese for the same reason.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **Editing a card silently destroyed its per-intensity examples.** The form did
|
|
64
|
+
not carry `examplesByIntensity`, so pressing Save on any card rebuilt it
|
|
65
|
+
without the four demonstration sets and the card fell back to its flat
|
|
66
|
+
`examples` list. Renaming a bundled card was enough to lose all four intensity
|
|
67
|
+
sets. The form now shows and round-trips them.
|
|
68
|
+
|
|
69
|
+
- **Every bundled card was labelled 自定义 (custom).** Bundled cards are seeded
|
|
70
|
+
into the user card directory as editable starting points, so the tag was
|
|
71
|
+
derived from "does a user file exist for this id" — true for cards nobody had
|
|
72
|
+
ever touched. The row tag now reports real provenance, with a new **已修改
|
|
73
|
+
(edited)** state for a bundled card the user has changed, and it is computed
|
|
74
|
+
with the same semantic comparison the drift check uses, so the tag and the
|
|
75
|
+
check's verdict now agree **by construction** instead of contradicting each
|
|
76
|
+
other on the same screen.
|
|
77
|
+
|
|
78
|
+
- **Delete was offered where it could not take effect.** A card whose content
|
|
79
|
+
still matches the bundle has no user-owned file: removing it cleared a seeded
|
|
80
|
+
copy that the next start wrote straight back, so the button reported success
|
|
81
|
+
and changed nothing. Delete is now shown only where it does something, and the
|
|
82
|
+
confirmation for an edited bundled card says that its changes are discarded
|
|
83
|
+
and the bundled version returns.
|
|
84
|
+
|
|
85
|
+
- **A narrowed fidelity never reached the model.** The route computed the
|
|
86
|
+
narrowed value and reported it in the response, but `buildSystemPrompt` read
|
|
87
|
+
`card.fidelity` directly — so the setting appeared to work and did nothing. The
|
|
88
|
+
effective fidelity is now passed into the prompt builder, and a test asserts on
|
|
89
|
+
the prompt itself rather than only on the response.
|
|
90
|
+
|
|
91
|
+
- Three examples in the bundled 肌肉集团 · 硬邦邦 card dropped the word "bug"
|
|
92
|
+
from their input. The self-check now verifies the per-intensity examples too,
|
|
93
|
+
not just the flat list, which is what surfaced them.
|
|
94
|
+
|
|
95
|
+
## [0.2.1] - 2026-10-09
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **Check for changes**, in the character-card manager. It compares every user
|
|
100
|
+
card against the bundled card of the same id and reports only the ones that
|
|
101
|
+
actually differ; only a drifted card grows a **Restore bundled** action on its
|
|
102
|
+
row. The alternative — a restore button on every user card — turns a long list
|
|
103
|
+
into noise, and "restore" on a card that never changed is a no-op nobody
|
|
104
|
+
asked for.
|
|
105
|
+
- The drift report names the differing fields (`style`, `examples`, …).
|
|
106
|
+
|
|
107
|
+
### Fixed
|
|
108
|
+
|
|
109
|
+
- **The card row's description overlapped the buttons.** `.pf-card-desc` is a
|
|
110
|
+
`<span>`, and `overflow: hidden` / `text-overflow: ellipsis` do not apply to
|
|
111
|
+
inline elements, so a long description — such as the bundled 硬邦邦 card's —
|
|
112
|
+
spilled out of the card instead of being clipped. The body is now a flex
|
|
113
|
+
column, which blockifies its children, and the description is clamped to two
|
|
114
|
+
lines. The row's actions are `flex: none`, so the description yields rather
|
|
115
|
+
than the buttons.
|
|
116
|
+
|
|
117
|
+
### Changed
|
|
118
|
+
|
|
119
|
+
- The drift comparison is by **content, not bytes**. Bundled cards are
|
|
120
|
+
hand-written YAML carrying comments, while a saved card round-trips through
|
|
121
|
+
the store's serializer and loses them, so a byte comparison reported every
|
|
122
|
+
untouched seeded card as modified.
|
|
123
|
+
|
|
124
|
+
## [0.2.0] - 2026-10-09
|
|
125
|
+
|
|
126
|
+
### Added
|
|
127
|
+
|
|
128
|
+
- **The composer's own send gesture starts the rewrite.** Pressing the native
|
|
129
|
+
**Send** button, or **Enter**, rewrites the draft instead of sending it raw.
|
|
130
|
+
In review mode the comparison panel appears; in direct mode the rewrite is
|
|
131
|
+
sent. The menu's separate "Rewrite with this persona" button is gone, because
|
|
132
|
+
the send gesture replaced it.
|
|
133
|
+
- **Enter gets its own listener.** Enter is the primary send gesture and does
|
|
134
|
+
not go through the button at all — the editor's keymap calls the shell's
|
|
135
|
+
submit directly. Shift+Enter (newline), IME composition and the modifier
|
|
136
|
+
chords the shell ignores still fall through untouched.
|
|
137
|
+
- **A fourth intensity rung, 重 / Strong.** The ladder was light → medium →
|
|
138
|
+
zealot, which left no way to ask for a strong voice that is not yet full
|
|
139
|
+
ritual. Every bundled card now ships demonstrations for all four rungs.
|
|
140
|
+
- **Restore bundled** in the character-card manager, for a user card that
|
|
141
|
+
shadows a bundled one.
|
|
142
|
+
- Inline explanations for `intensity` and `fidelity` in the card editor.
|
|
143
|
+
|
|
144
|
+
### Changed
|
|
145
|
+
|
|
146
|
+
- The review-first description now describes what actually happens: the rewrite
|
|
147
|
+
appears in the comparison panel and the draft is left alone until you confirm.
|
|
148
|
+
- Segmented controls are wider, and the review panel is capped to the composer
|
|
149
|
+
card width instead of spanning the whole conversation.
|
|
150
|
+
|
|
151
|
+
### Fixed
|
|
152
|
+
|
|
153
|
+
- **A seeded card could freeze a bundled card at the version that shipped
|
|
154
|
+
first.** Seeding wrote a copy of each bundled card into the user directory,
|
|
155
|
+
and that copy shadows the bundled one — so an improved bundled card in a later
|
|
156
|
+
release could never reach anyone who had already run an earlier version. The
|
|
157
|
+
store now records what it seeded, together with a hash of the exact bytes: a
|
|
158
|
+
copy still matching that hash is untouched and is refreshed on upgrade, while
|
|
159
|
+
a copy the user edited is never written over. A file with no ledger entry is
|
|
160
|
+
treated as the user's own card and is left alone.
|
|
161
|
+
- **The seed ledger was loaded as a character card**, producing a spurious
|
|
162
|
+
"card has no name" diagnostic on every catalog read. The loader now skips
|
|
163
|
+
dot-files.
|
|
164
|
+
- **`check.mjs` read the developer's real card directory.** The store merges the
|
|
165
|
+
bundled cards with `$DSH_HOME/persona-cards`, so a stale seeded card silently
|
|
166
|
+
replaced the bundled one and the suite's results depended on whatever the
|
|
167
|
+
developer happened to have saved. The suite now isolates `DSH_HOME` to a
|
|
168
|
+
temporary directory before importing the store.
|
|
169
|
+
|
|
170
|
+
### Security
|
|
171
|
+
|
|
172
|
+
- Both send-intercept paths are fail-safe: the locale service being absent, the
|
|
173
|
+
composer card not being found, a label mismatch, the persona being off, an
|
|
174
|
+
empty draft, or a draft that was already rewritten all fall through to the
|
|
175
|
+
native send. Disabling the plugin unmounts the component and removes its
|
|
176
|
+
listeners entirely, so sending behaves exactly as if it were never installed.
|
|
177
|
+
|
|
178
|
+
## [0.1.2] - 2026-10-09
|
|
179
|
+
|
|
180
|
+
### Fixed
|
|
181
|
+
|
|
182
|
+
- **No route was ever registered, so the whole UI was inert.** The plugin
|
|
183
|
+
declares no service dependencies, so it applied *before* the web server
|
|
184
|
+
mounted; reading `ctx.get('webServer')` at that moment found nothing and the
|
|
185
|
+
route registration was silently skipped. Every request then fell through to
|
|
186
|
+
the SPA fallback — an empty `405`/`404` body, which is what made the character
|
|
187
|
+
list empty and "open the card directory" appear to fail. The entry now waits
|
|
188
|
+
for the service with `ctx.inject(['webServer'], …)` and still loads on a
|
|
189
|
+
composition that has no web server.
|
|
190
|
+
- **The menu let the conversation show through it.** `--dsw-menu-surface-fill`
|
|
191
|
+
is translucent by design (~58% alpha) and only reads correctly with the
|
|
192
|
+
host's `backdrop-filter` behind it; the plugin used the fill without the blur.
|
|
193
|
+
Both are now applied together, exactly as the host Menu does, with the opaque
|
|
194
|
+
`--dsw-alias-bg-overlay` as the fallback.
|
|
195
|
+
- **The card watcher could leak on unload.** The watcher is created
|
|
196
|
+
asynchronously, so a plugin unloaded in that window never closed it. A
|
|
197
|
+
disposed flag now closes a watcher created after teardown.
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
|
|
201
|
+
- `host-check.mjs` reproduces the mounting order that caused the missing
|
|
202
|
+
routes: its stub context only runs `inject` callbacks for mounted services,
|
|
203
|
+
and asserts that routes register through `inject` and that a
|
|
204
|
+
web-server-less composition still loads.
|
|
205
|
+
|
|
206
|
+
## [0.1.1] - 2026-10-09
|
|
207
|
+
|
|
208
|
+
Three defects found by using the plugin in a real session.
|
|
209
|
+
|
|
210
|
+
### Fixed
|
|
211
|
+
|
|
212
|
+
- **The rewrite could not be started from the UI.** The only trigger was a
|
|
213
|
+
right-click on the 🎭 control, which is undiscoverable — picking a character
|
|
214
|
+
and an intensity changed settings and then nothing happened. The menu now
|
|
215
|
+
opens with a full-width **"Rewrite with this persona"** action, disabled with
|
|
216
|
+
an explanatory hint until a character is selected.
|
|
217
|
+
- **Filled controls were invisible in the light theme.** The selected segment
|
|
218
|
+
and the primary action used `--dsw-alias-brand-primary` as a fill with a
|
|
219
|
+
hard-coded white label. That token is *inverted* — near-white in the dark
|
|
220
|
+
theme and near-black in the light theme — so the label vanished against it.
|
|
221
|
+
Filled controls now use `--dsw-alias-button-primary-fill` with
|
|
222
|
+
`--dsw-alias-label-primary-foreground`, exactly like the host Button, and the
|
|
223
|
+
segmented control uses the host's translucent-track + raised-pill pattern.
|
|
224
|
+
- **The card directory was empty.** The bundled cards live read-only inside the
|
|
225
|
+
package, so "open the card directory" showed an empty folder. The bundled
|
|
226
|
+
cards are now seeded into `~/.dsh/persona-cards/` on first mount as editable
|
|
227
|
+
files. Seeding uses an exclusive create (`wx`), so it can never overwrite a
|
|
228
|
+
card you wrote — including one created in the window between a stat and a
|
|
229
|
+
write.
|
|
230
|
+
- **A half-written card could silently replace a good one.** A single file save
|
|
231
|
+
emits several watcher events, and a reload could read the file mid-write and
|
|
232
|
+
parse a truncated card. Reloads are now debounced so they read settled files.
|
|
233
|
+
|
|
234
|
+
### Added
|
|
235
|
+
|
|
236
|
+
- **An explicit "No persona" state.** The control previously had no way back to
|
|
237
|
+
sending exactly what you typed; it is now a first-class, persisted choice
|
|
238
|
+
listed alongside the cards, and the state the control starts in.
|
|
239
|
+
- `scripts/host-check.mjs` covers the seeding behaviour, including that a user
|
|
240
|
+
edit is never overwritten.
|
|
241
|
+
- `scripts/check.mjs` asserts the UI contract that regressed here: the off
|
|
242
|
+
state exists, the rewrite is reachable from a visible menu action, and no
|
|
243
|
+
hidden gesture-only trigger remains.
|
|
244
|
+
|
|
245
|
+
## [0.1.0] - 2026-10-09
|
|
246
|
+
|
|
247
|
+
First public release.
|
|
248
|
+
|
|
249
|
+
### Added
|
|
250
|
+
|
|
251
|
+
- **Composer persona control** (`conversation.input.left`): pick a character,
|
|
252
|
+
set intensity (light / medium / zealot), and choose **review first** or
|
|
253
|
+
**send directly**. Right-clicking the control runs a rewrite immediately.
|
|
254
|
+
- **Review panel** (`conversation.input.dock`): original vs rewrite side by
|
|
255
|
+
side, with Send, Fill only, Restore original and Rewrite again. The original
|
|
256
|
+
draft is never modified without an explicit action.
|
|
257
|
+
- **Character-card manager** (`settings.section`): create, edit and delete
|
|
258
|
+
cards from the UI, or drop a `.yml` file into `~/.dsh/persona-cards/` — the
|
|
259
|
+
directory is watched, so a new card appears without a restart.
|
|
260
|
+
- **Rewrites follow the session's own model.** The rewrite runs on the exact
|
|
261
|
+
provider/model the session last used, falling back to the harness default
|
|
262
|
+
only before the session's first request. There is no separate model setting.
|
|
263
|
+
- **Technical-fact preservation check**: a second call on the same model
|
|
264
|
+
compares the rewrite against the original and reports whether substance was
|
|
265
|
+
lost or invented. Advisory, never blocking, and never reports approval when
|
|
266
|
+
the check itself failed.
|
|
267
|
+
- **Two fidelity modes**: `style` changes only the voice; `strategy` may also
|
|
268
|
+
add behavioural constraints that fit the character. Fidelity is a property of
|
|
269
|
+
the card, not a per-message toggle.
|
|
270
|
+
- **Template mode**: a card may skip the model entirely and substitute the
|
|
271
|
+
draft into a fixed template.
|
|
272
|
+
- Two bundled cards: **万机之神 · 欧姆弥赛亚** (`omnissiah`, style fidelity) and
|
|
273
|
+
**肌肉集团 · 硬邦邦** (`muscle-crew`, strategy fidelity).
|
|
274
|
+
- **Zero dependencies.** The plugin ships its own YAML subset reader
|
|
275
|
+
(`lib/yaml.js`) instead of importing a YAML library, because a locally
|
|
276
|
+
`link:`-ed plugin does not get its dependencies installed. The reader refuses
|
|
277
|
+
constructs it cannot read faithfully — flow collections, anchors, aliases,
|
|
278
|
+
tags, merge keys, multi-document streams — naming the offending line, rather
|
|
279
|
+
than silently misreading a card.
|
|
280
|
+
- **Zero build step.** `lib/` is plain ESM and the browser half is a plain-JS
|
|
281
|
+
module, so a git checkout installs and runs as-is.
|
|
282
|
+
- Two self-check suites: `scripts/check.mjs` (offline unit: parser, cards,
|
|
283
|
+
prompts, client syntax) and `scripts/host-check.mjs` (host integration: the
|
|
284
|
+
real plugin mounted over a real socket with a stubbed model).
|
|
285
|
+
|
|
286
|
+
### Security
|
|
287
|
+
|
|
288
|
+
- All HTTP routes are loopback-fenced: the socket must be loopback, the `Host`
|
|
289
|
+
header must name a loopback host (defeating DNS rebinding), and
|
|
290
|
+
proxy-forwarding headers are refused.
|
|
291
|
+
- The rewrite call is standalone: it creates no session turn, cannot call
|
|
292
|
+
tools, and never enters the session log. What the session records is the text
|
|
293
|
+
the user actually sent.
|
|
294
|
+
|
|
295
|
+
[Unreleased]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.3.0...HEAD
|
|
296
|
+
[0.3.0]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.2.1...v0.3.0
|
|
297
|
+
[0.2.1]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.2.0...v0.2.1
|
|
298
|
+
[0.2.0]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.1.2...v0.2.0
|
|
299
|
+
[0.1.2]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.1.1...v0.1.2
|
|
300
|
+
[0.1.1]: https://github.com/sugarmaster666/dsh-persona-forge/compare/v0.1.0...v0.1.1
|
|
301
|
+
[0.1.0]: https://github.com/sugarmaster666/dsh-persona-forge/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sugarmaster666
|
|
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.
|