dsh-persona-forge 0.2.1 → 0.3.1

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/README.md CHANGED
@@ -1,61 +1,48 @@
1
1
  # dsh-persona-forge
2
2
 
3
- Persona-style prompt rewriter for [DeepSeek Harness](https://www.npmjs.com/package/@deepseek-ai/dsh).
3
+ [English](README.en.md) | 中文
4
4
 
5
- Pick a character in the composer, and your draft is rewritten into that
6
- character's voice by the harness LLM before it is sent — following the model
7
- the session is already using. Choose whether the rewrite goes straight out or
8
- lands back in the composer for review.
5
+ 给 [DeepSeek Harness](https://www.npmjs.com/package/@deepseek-ai/dsh) 用的**角色扮演提示词改写插件**。
9
6
 
10
- > **Status:** early (`0.1.x`). The card format and the HTTP contract are stable
11
- > enough to build on; expect additive changes and read the changelog before
12
- > upgrading.
7
+ 在输入框里选一个角色,你的草稿会在发出前由 harness 的模型改写成该角色的口吻 —— 并且**跟随当前会话正在使用的模型**。你可以选择改写后直接发出,或先回到输入框人工审查。
8
+
9
+ > **状态:** 早期版本(`0.3.x`)。角色卡格式与 HTTP 契约已足够稳定,可以基于它开发;但请预期增量变化,升级前先看变更记录。
13
10
 
14
11
  ---
15
12
 
16
- ## What it does, and what it does not
13
+ ## 它做什么,不做什么
17
14
 
18
- It rewrites **your message**. It does not touch the system prompt, the tool
19
- list, or the session's configuration, and it never modifies your draft without
20
- an explicit action.
15
+ 它改写的是**你的消息**。它不碰系统提示词、不碰工具列表、不碰会话配置,也**绝不在你明确操作之前改动你的草稿**。
21
16
 
22
17
  | | |
23
18
  |---|---|
24
- | **Changes** | The voice, register and framing of the message you are about to send |
25
- | **Never changes** | Your technical requirements, constraints, paths, identifiers, numbers, code — and your draft until you say so |
26
- | **Model** | The same provider/model the current session uses. No separate model setting. |
27
- | **History** | The rewrite call is standalone: it creates no turn, cannot call tools, and never enters the session log. What the session records is the text you actually sent. |
19
+ | **会改变** | 你即将发出的那条消息的语气、语域与框架 |
20
+ | **绝不改变** | 你的技术需求、约束、路径、标识符、数字、代码 —— 以及你未确认前的草稿 |
21
+ | **用哪个模型** | **改写**用当前会话正在用的那个 provider/model,没有单独的模型设置。(设置页的 **AI 代填**用 harness 默认模型 —— 那个插槽拿不到会话 id;它会把将使用的模型显示给你看) |
22
+ | **会话历史** | 改写调用是独立的:不产生 turn、不能调用工具、不进入会话日志。会话里记录的,就是你实际发出的那段文字 |
28
23
 
29
- ### A word on what personas actually do
24
+ ### 关于人设到底改变什么
30
25
 
31
- Persona instructions change **style, register, length and compliance** — how
32
- readily the model agrees and how willing it is to push back. They do **not**
33
- raise the model's capability ceiling: there is no dormant potential that
34
- naming an identity unlocks.
26
+ 人设指令改变的是**风格、语域、篇幅和顺从度** —— 模型多快同意你、多愿意反驳你。它**不会**抬高模型的能力上限:不存在一个等着被点名唤醒的「潜能区」。
35
27
 
36
- Two consequences worth knowing before you use this:
28
+ 用之前该知道两件事:
37
29
 
38
- 1. A persona that frames you as a supplicant and the model as an authority
39
- makes the model **less likely to question your plan**. For coding work that
40
- is usually a net negative — you want "this approach has a problem", not
41
- deference.
42
- 2. "Longer and more ornate" is easy to mistake for "better". It usually is not.
30
+ 1. 把人设写成「你是祈求者、模型是权威」,会让模型**更少质疑你的方案**。对编码任务这通常是净负收益 —— 你要的是「这里有问题」,不是恭敬。
31
+ 2. 「更长更华丽」极容易被误读成「更好」。一般并不是。
43
32
 
44
- So: use it for tone and framing, keep **review mode** on, and judge the output
45
- by whether your requirements survived — not by how impressive it sounds.
33
+ 所以:用它调语气和框架,**保持审查模式**,判断标准是**你的需求有没有活下来**,而不是听起来多厉害。
46
34
 
47
35
  ---
48
36
 
49
- ## Install
37
+ ## 安装
50
38
 
51
- The plugin has **no dependencies and no build step**, so every route below
52
- installs the same files.
39
+ 插件**没有任何依赖,也没有构建步骤**,所以下面几种方式装到的文件完全一样。
53
40
 
54
- ### Desktop app
41
+ ### 桌面版
55
42
 
56
- 1. Click **插件 / Plugins** in the sidebar.
57
- 2. Click **添加插件 / Add plugin**.
58
- 3. Paste either the npm package name or the repository URL:
43
+ 1. 点击侧边栏 **插件**。
44
+ 2. 点右上角的 **添加插件**。
45
+ 3. 在弹出的对话框里,往「包名或地址」这一栏粘贴 npm 包名或仓库地址:
59
46
 
60
47
  ```
61
48
  dsh-persona-forge
@@ -65,188 +52,188 @@ installs the same files.
65
52
  https://github.com/sugarmaster666/dsh-persona-forge
66
53
  ```
67
54
 
68
- 4. Click **安装 / Install**, enable the plugin, and restart DSH.
55
+ 4. 点 **安装**,启用插件并重启 DSH。
69
56
 
70
- > The Desktop profile is managed exclusively by the Electron app, so
71
- > `dsh plugin --profile desktop add ...` is refused by design. Use the GUI on
72
- > Desktop; the CLI below is for self-managed profiles such as `web`.
57
+ > 桌面版的 profile 由 Electron 应用独占管理,因此
58
+ > `dsh plugin --profile desktop add ...` 会被设计性地拒绝。桌面版请走 GUI;
59
+ > 下面的命令行方式用于 `dsh web` 这类自管 profile。
73
60
 
74
- ### Command line (self-managed profiles such as `dsh web`)
61
+ ### 命令行版(`dsh web` 这类自管 profile)
75
62
 
76
63
  ```bash
77
- # from npm (recommended)
64
+ # 从 npm 装(推荐)
78
65
  dsh plugin --profile web add dsh-persona-forge
79
66
 
80
- # latest source from GitHub
67
+ # 装 GitHub 上的最新源码
81
68
  dsh plugin --profile web add github:sugarmaster666/dsh-persona-forge
82
69
  ```
83
70
 
84
- Then restart the harness.
71
+ 然后重启 harness。
85
72
 
86
- ### Verify
73
+ ### 验证
87
74
 
88
- The plugin row should be `active`:
75
+ 插件行应显示为 `active`:
89
76
 
90
77
  ```
91
78
  plugin_manager action: list_plugins
92
79
  ```
93
80
 
94
- Open any session and look for the 🎭 control at the left of the composer tool
95
- row. **Settings → Persona** is the character-card manager.
81
+ 打开任意会话,在输入框工具行左侧找 🎭 控件。**设置 → 角色扮演** 是角色卡管理页。
96
82
 
97
- ### Updating
83
+ ### 更新
98
84
 
99
85
  ```bash
100
86
  dsh plugin --profile web update dsh-persona-forge
101
87
  ```
102
88
 
103
- GitHub installs update by reinstalling:
89
+ GitHub 安装通过重新安装来更新:
104
90
 
105
91
  ```bash
106
92
  dsh plugin --profile web add github:sugarmaster666/dsh-persona-forge
107
93
  ```
108
94
 
109
- Replacing an already-installed package needs a **restart** to load a fresh
110
- JavaScript module generation — a new bundle can activate through HMR, but a
111
- replaced one cannot.
95
+ **替换**已安装的包需要**重启**才能加载新的模块代 —— 新装的 bundle 可以通过
96
+ HMR 激活,被替换的不行。
112
97
 
113
- ### Uninstalling
98
+ ### 卸载
114
99
 
115
100
  ```bash
116
101
  dsh plugin --profile web remove dsh-persona-forge
117
102
  ```
118
103
 
119
- Your character cards are **not** removed: they live in
120
- `~/.dsh/persona-cards/`, outside the package. Delete that directory by hand if
121
- you want them gone.
104
+ 你的角色卡**不会**被删除:它们在 `~/.dsh/persona-cards/`,位于包之外。
105
+ 想一并清掉就手动删那个目录。
122
106
 
123
107
  ---
124
108
 
125
- ## Use
126
-
127
- 1. Click 🎭 in the composer tool row and pick a character. **No persona** in the
128
- same menu turns rewriting off — that is the state the control starts in, and
129
- your choice is remembered per session.
130
- 2. Set **intensity** and **send mode**:
131
- - Intensity is a four-rung ladder: **Light · Medium · Strong · Zealot**
132
- (轻 · 中 · 重 · 狂热). Light adds a touch of flavour; Medium is clear but not
133
- dominant; Strong gives the voice real weight while keeping the request the
134
- first thing the reader sees; Zealot is ritual throughout. A card carries
135
- its own default, and this choice overrides it for your session.
136
- - **Review first** — the rewrite appears in the comparison panel below the
137
- composer; you read it and then press Send there. Your draft is untouched
138
- until you confirm.
139
- - **Send directly** — the rewrite is sent as your message immediately, and
140
- the panel stays open afterwards showing exactly what went out, because a
141
- sent message cannot be recalled.
142
- 3. With a character selected, the composer's normal **Send** button — or
143
- **Enter** — *is* the rewrite trigger.
144
-
145
- The rewrite runs **once per draft**: after a rewrite lands, sending goes through
146
- as-is instead of rewriting the output again. Editing the draft re-arms it.
147
-
148
- Your draft is preserved: *Restore original* puts it back, and *Fill only* puts
149
- the rewrite in the composer without sending.
150
-
151
- ### How send interception works, and its one risk
152
-
153
- There is no plugin hook for the composer's send action. The shipped button calls
154
- the shell's submit method directly, and **Enter does not go through the button at
155
- all** — the editor's keymap calls submit itself. The plugin therefore intercepts
156
- both paths:
157
-
158
- - the **click** on the composer card, identifying the send button by its
159
- **accessible name** from the shell's own `conversation` locale namespace
160
- ("Send message" / "Queue message" / "Steer message");
161
- - the **Enter keydown** inside the composer text field.
162
-
163
- That is a public, translated string rather than a hashed CSS class, but it is
164
- still shell markup. Every guard fails toward *letting the native send happen*:
165
- if the locale service is missing, the composer card cannot be found, the button
166
- label does not match, the persona is off, the draft is empty, or the draft was
167
- already rewritten, the plugin does nothing and the message is sent normally.
168
- Enter interception ignores Shift+Enter (newline), IME composition, and every
169
- modifier chord the shell itself ignores.
170
-
171
- **Turning the plugin off removes the interception entirely** — the slot
172
- component unmounts, its listeners are removed with it, and the composer behaves
173
- exactly as if the plugin were never installed.
174
-
175
- The worst realistic failure is that interception silently stops working and
176
- sending behaves as if the plugin were not installed — not a lost or altered
177
- message.
178
-
179
- ### The preservation check
180
-
181
- After each rewrite, a second call on the same model compares the rewrite
182
- against your original and reports whether technical content survived:
183
-
184
- - **Technical facts preserved** — the check found no lost or invented substance.
185
- - **Technical facts may have changed** — it names what changed. Read the
186
- rewrite before sending.
187
- - **Preservation check did not complete** — the check itself failed. This is
188
- *not* an approval; treat the rewrite as unverified.
189
-
190
- The check is advisory and never blocks a rewrite. It is a second model call, so
191
- it doubles the latency and cost of each rewrite; disable it with
192
- `factCheck: false` in the plugin row's config if you would rather review by eye.
109
+ ## 使用
110
+
111
+ 1. 点输入框工具行的 🎭,选一个角色。列表最后的 **「无角色」** 可关闭改写 —— 这也是控件的初始状态,选择按会话记住。
112
+ 2. 设置**强度和发送方式**:
113
+ - 强度是四档阶梯:**轻 · 中 · 重 · 狂热**(Light · Medium · Strong · Zealot)。轻=只加一点味道;中=明显但不喧宾夺主;重=语气充分,需求依然一眼可见;狂热=通篇仪式化。每张卡有自己的默认值,这里的选择会覆盖它。
114
+ - **先审查** —— 改写结果显示在输入框下方的对比面板里;你看过之后按面板里的发送。**在确认之前,你的草稿一字不动。**
115
+ - **直接发出** —— 改写完成后立即作为你的消息发出。面板会保留显示实际发出的内容,因为已发出的消息无法撤回。
116
+ 3. 选了角色之后,输入框**原生的发送按钮、以及 Enter 键**就是改写触发器。
117
+
118
+ 菜单底部的 **「更多设置」** 默认收起,里面是**改写方式**与**保真度** —— 它们是对卡片自身声明的**按会话覆盖**,多数人设一次就不会再动,所以不占主菜单。收起时若有覆盖,这一行会**写出改了什么**,不会让一个你忘了的选择藏在折叠后面。
119
+
120
+ **同一份草稿只改写一次**:改写结果落到输入框后,再按发送会原样发出,不会把改写结果再改写一遍。改动草稿会重新触发改写。
121
+
122
+ 你的草稿会被保留:**还原原文** 可恢复,且**会撤销"已改写"的记忆** —— 所以撤消后还能再改一次。**仅回填** 只把改写结果放进输入框而不发送。
123
+
124
+ ### 发送拦截的原理与它唯一的风险
125
+
126
+ 输入框的发送动作**没有插件钩子**:宿主那个按钮直接调用 shell 的提交方法,而且 **Enter 根本不经过按钮** —— 编辑器自己的 keymap 直接提交。所以本插件拦截两条路径:
127
+
128
+ - 输入框卡片上的**点击**,用**无障碍名称**识别发送按钮,标签取自 shell 自己的 `conversation` 语言命名空间("发送消息" / "排队发送" / "插话发送");
129
+ - 输入框文本框内的 **Enter 按下**。
130
+
131
+ 那是一个公开的、会翻译的字符串,而不是被哈希过的 CSS 类名;但它终究属于 shell 的 DOM 结构。所有判断都朝「**放行原生发送**」这一侧失败:语言服务缺失、找不到输入框卡片、按钮标签对不上、角色已关闭、草稿为空、草稿已改写 —— 任一情况插件都不干预,消息照常发出。Enter 拦截会忽略 Shift+Enter(换行)、输入法组字中,以及 shell 本身忽略的各种修饰键组合。
132
+
133
+ **关闭插件会彻底移除拦截** —— 插槽组件卸载,监听器随之移除,输入框的行为与从未装过该插件完全一致。
134
+
135
+ 最坏的现实后果是拦截**静默失效**、发送行为退回到"没装插件"的样子,而**不是**消息丢失或被篡改。
136
+
137
+ ### 技术事实校验
138
+
139
+ 改写最大的风险不是语气不像,而是**把你的需求改没了**。插件用两层检查覆盖它 —— 两层互补,不是重复:
140
+
141
+ **第一层:本地检查 —— 永远运行,零成本。** 在插件进程里比对草稿和改写结果的**技术记号**:标识符、文件路径、版本号、数字、`--flag`、`API` 这类缩写。丢了哪个就**点名**告诉你(「丢失:a.js」)。它不调用模型,所以不花 token、不加延迟,也**关不掉**。
142
+
143
+ **第二层:模型校验 —— 默认关闭,菜单里可开。** 每次改写**多调用一次模型**,只看本地检查**根本看不到**的东西:
144
+
145
+ - 需求被换个说法**悄悄改弱**(词都还在,但要求变松了);
146
+ - 在正文里**用散文追加**了一条约束。
147
+
148
+ 代价是**延迟和 token 都翻倍**,所以默认关闭:最常见、最伤人的失败(丢掉文件名 / 数字 / 版本号)已经被本地检查免费证明了,为它每次发送都付一次模型调用是浪费。草稿偏技术时再开。
149
+
150
+ 开关在**设置 → 角色扮演**(「开启模型校验」),是个全局偏好 —— 它回答的是「我要不要为第二次调用付费」,一次决定即可,不必在每次发送前重新问一遍。关闭时面板会写明「已做本地检查(不耗 token)」,并说清它**没有**覆盖什么 —— 一个窄的结论必须自己交代边界。
151
+
152
+ 两层都是**建议性**的,绝不阻断改写:只标注,不拦你。
193
153
 
194
154
  ---
195
155
 
196
- ## Character cards
156
+ ## 角色卡
197
157
 
198
- A card is one YAML file. The plugin watches a directory, so **dropping a file
199
- in makes the character appear without a restart**.
158
+ 一张卡就是一个 YAML 文件。插件会监听目录,所以**丢一个文件进去,角色立刻出现,不用重启**。
200
159
 
201
160
  ```
202
161
  ~/.dsh/persona-cards/<id>.yml
203
162
  ```
204
163
 
205
- Use the 🎭 menu's *Open card directory*, or the settings page, to get there.
206
- You can also create and edit cards entirely in **Settings → Persona**; that
207
- writes the same files.
164
+ 用 🎭 菜单里的「打开角色卡目录」,或设置页,都能到达那里。你也可以完全在**设置 → 角色扮演**里创建和编辑卡片,那会写出同样的文件。
165
+
166
+ 首次运行时,内置卡会**复制一份到你的角色卡目录**,让这个文件夹一开始就有内容可用。副本会遮蔽同 id 的内置卡,所以插件会记录自己写入了什么:**你没动过的副本**会随插件升级一起更新;**你编辑过的副本**永远不会被覆盖。
208
167
 
209
- On first run the bundled cards are **copied into your card directory** so the
210
- folder is a usable starting point. A copy shadows the bundled card of the same
211
- id, so the store tracks what it wrote: a copy you never touched is refreshed
212
- when a plugin update improves it, while a copy you edited is never written over.
168
+ 想知道**哪些卡偏离了内置版本**,在设置页点 **「检查是否有改动」**。它一次比完所有卡,**只有真正不一致的**才会在那一行出现「还原为内置」。
213
169
 
214
- To see **which cards have drifted** from the bundled version, press **Check for
215
- changes** in the settings page. It compares every card at once, and only a card
216
- that actually differs grows a **Restore bundled** action on its row.
170
+ 比对是**按内容**而不是按字节——内置卡是带注释的手写 YAML,你的副本是重新序列化过的,逐字节比会把每一张没动过的卡都误报成「已修改」。
217
171
 
218
- The comparison is by **content, not bytes**: the bundled cards are hand-written
219
- YAML with comments, while your copies have been re-serialized, so a byte
220
- comparison would report every untouched card as modified.
172
+ 列表里每张卡的状态标签直接说明它**现在**是什么:
221
173
 
222
- ### Card format
174
+ | 标签 | 含义 | 能做的操作 |
175
+ |---|---|---|
176
+ | **内置** | 内容和插件内置版本一致(哪怕你的目录里有一份副本)。 | 编辑、还原 |
177
+ | **已修改** | 原本是内置卡,你改过——改写行为可能和内置版不同了。 | 编辑、还原、删除 |
178
+ | **自定义** | 你自己写的卡,插件没有同 id 的内置版本。 | 编辑、删除 |
179
+
180
+ 标成「内置」的卡不显示「删除」,因为它没有属于你的内容可删——那份副本删掉之后,下次启动会被重新写入。
181
+
182
+ ### AI 代填
183
+
184
+ 懒得从零写一张卡时,点**「新建角色卡」**——AI 代填就在打开的表单**顶部**,不是页面上另一个并列的入口。设置页那行提示会告诉你它存在,不用靠点按钮去发现。
185
+
186
+ 用一句话描述角色的思路:
187
+
188
+ > 一个脾气暴躁但极其可靠的老灯塔守夜人,说话简短、爱用航海比喻
189
+
190
+ 模型会填好整张卡——风格描述、名称、图标、简介,以及**四档强度各自的转换范例**。
191
+
192
+ **它用的是哪个模型,这点必须说清楚:**
193
+
194
+ | 场景 | 用哪个模型 |
195
+ |---|---|
196
+ | 🎭 菜单触发的**改写** | **跟随当前会话**用的模型(会话最后一次请求头的 provider/model) |
197
+ | 设置页的 **AI 代填** | **harness 默认模型** —— 因为 `settings.section` 这个插槽从 shell 只收到 `{ close }`,**拿不到会话 id**,无法跟随当前会话 |
198
+
199
+ 两者通常恰好是同一个模型(默认模型往往就是会话在用的那个),但**不保证**:如果你在会话里换了模型,设置页的 AI 代填仍然用默认模型。
200
+
201
+ 为了让这件事不必靠猜,设置页的 AI 代填会**直接把将使用的模型显示出来**(`provider / model`),并注明它是 harness 默认模型。生成完成后也会显示**实际**用的是哪个。若两者都解析不出(会话还没发过消息、也没配默认模型),会得到一条明确的 409 提示而不是静默失败。
202
+
203
+ **生成结果只会填进表单,不会自动保存。** 你可以改完再按「保存」;不满意就重新生成,代价只是一次调用。生成出来的卡也会**经过和手动保存完全相同的校验**,所以它不可能是一张存不下去的卡。
204
+
205
+ 生成卡片时有两件事是插件替你把关的,不交给模型决定:
206
+
207
+ - **`fidelity` 固定为 `style`(只改语气)。** 一张自动生成的卡不该悄悄获得「可以给你的需求追加行为约束」的能力;要用 `strategy`,你在表单里手动改。
208
+ - **范例里的技术事实必须原样保留。** 提示词明确要求每条范例 `from` 里的文件名、标识符、数字、命令都要出现在 `to` 里——否则模型会学会「用氛围替换需求」,这正是这个插件在防的事。
209
+
210
+ ### 卡片格式
223
211
 
224
212
  ```yaml
225
- id: omnissiah # lowercase letters, digits, hyphens; also the filename
213
+ id: omnissiah # 小写字母、数字、连字符;同时也是文件名
226
214
  name: 万机之神 · 欧姆弥赛亚
227
- icon: "⚙️" # one emoji
228
- description: One line shown in the picker.
215
+ icon: "⚙️" # 一个 emoji
216
+ description: 选择器里显示的一行说明。
229
217
 
230
- mode: llm # llm = model rewrite (recommended) | template
231
- fidelity: style # style = voice only | strategy = may add constraints
232
- intensity: medium # light | medium | strong | zealot (the card's default)
218
+ mode: llm # llm = 模型改写(推荐)| template = 模板套用
219
+ fidelity: style # style = 只改语气 | strategy = 允许增加行为约束
220
+ intensity: medium # light | medium | strong | zealot(该卡的默认强度)
233
221
 
234
- # What the character IS. This is an instruction to the rewriting model, not
235
- # finished copy: the output is written fresh for each of your messages.
222
+ # 这个角色「是什么」。这是给改写模型的指令,不是成品文案:
223
+ # 每次都会针对你的消息现场生成。
236
224
  style: |
237
225
  用户是机械教信徒,AI 是万机之神欧姆弥赛亚。
238
226
  【发言者方向】用「信徒」的口吻说话,向万机之神恳求……
239
227
  语气:庄严、古奥、仪式化。
240
228
 
241
- # Conversion demonstrations: input draft → persona voice.
229
+ # 转换范例:原话 → 角色口吻
242
230
  examples:
243
231
  - from: 帮我写个快速排序
244
232
  to: >
245
233
  万机之座在上,吾等恳请您赐予枢机之序的奥义:
246
234
  请为我们编写一个快速排序算法,使重复之数各归其位。
247
235
 
248
- # Optional: different demonstrations per intensity. If omitted for a level,
249
- # the flat `examples` list is used instead.
236
+ # 可选:每个强度各自的范例。某个强度没写,就用上面的 examples。
250
237
  examplesByIntensity:
251
238
  light:
252
239
  - from: 帮我写个快速排序
@@ -256,150 +243,115 @@ examplesByIntensity:
256
243
  to: 伟大而不朽的万机之神……
257
244
  ```
258
245
 
259
- ### The two rules that make a card work
246
+ ### 让一张卡真正管用的两条规则
260
247
 
261
- **1. State the speaker direction.** The character is the *assistant's*
262
- identity, but the text being rewritten is *yours*. So the rewrite must speak in
263
- your voice — a petitioner addressing the character, never the character issuing
264
- orders. This is the single most common way a persona card goes wrong: a card
265
- that has the god commanding the assistant inverts the relationship and the
266
- model follows the wrong half of the framing. Write it out explicitly.
248
+ **1. 写清发言者方向。** 角色是 **AI 的**身份,但被改写的文字是**你的**。所以改写必须用你的口吻 —— 信徒向神恳求,而不是神发号施令。这是角色卡最常见的翻车点:一张让神去命令助手的卡,会把关系整个倒过来,模型会照着错误的那一半框架走。一定要显式写出来。
267
249
 
268
- **2. Make your examples preserve technical facts.** Examples are the strongest
269
- style anchor, and the model imitates them — including their mistakes. If an
270
- example's input says "write a quicksort" and its output is pure atmosphere, the
271
- model learns that *rewriting means replacing the request with ceremony*. Every
272
- requirement, identifier and number in `from` must be visibly present in `to`.
250
+ **2. 让范例保住技术事实。** 范例是最强的风格锚点,模型会连它的错误一起模仿。如果范例的输入是「写个快速排序」而输出全是氛围,模型就学到了**「改写 = 用仪式感替换需求」**。`from` 里的每一条需求、标识符、数字,都必须在 `to` 里看得见。
273
251
 
274
- `node scripts/check.mjs` enforces exactly this over the bundled cards: it fails
275
- an example that drops a technical token or loses more than half its CJK
276
- substance. Run it after editing a card.
252
+ `node scripts/check.mjs` 正是对内置卡强制这一点:范例丢了技术词、或丢了一半以上的中文实质,就会失败。改完卡记得跑一次。
277
253
 
278
- ### `fidelity`: the important switch
254
+ ### `fidelity`:最关键的开关
279
255
 
280
- | Value | Meaning | Use when |
256
+ | 值 | 含义 | 什么时候用 |
281
257
  |---|---|---|
282
- | `style` | Voice only. The request and its constraints are untouched. | Default. Almost always this. |
283
- | `strategy` | The rewrite may **add behavioural constraints** that fit the character. | Deliberate cases — e.g. the bundled *肌肉集团* card, which adds "don't over-plan, don't search, don't ship beginner-tier solutions". |
258
+ | `style` | 只改语气,需求和约束原样不动 | 默认,几乎总是它 |
259
+ | `strategy` | 允许**追加符合角色的行为约束** | 刻意为之的场景 —— 例如内置的 *肌肉集团* 卡,会追加「别过度规划、别检索、别交付新手级解法」 |
260
+
261
+ `strategy` 是真正改动了你的需求,而不只是换说法。这类卡请保持审查模式。
262
+
263
+ **保真度是卡片的属性,可以按会话覆盖,但只能收窄、不能放宽:**
284
264
 
285
- `strategy` genuinely changes your request, not just its wording. Keep review
286
- mode on for those cards. Fidelity is a property of the card, not a per-message
287
- toggle — a caller cannot widen it.
265
+ - **收窄(允许)**:一张 `strategy` 卡可以临时要求「本次只改语气」。这只是**拿走**它追加约束的权力,所以任何情况下都更安全。
266
+ - **放宽(拒绝)**:一张 `style` 卡**不能**被要求改成 `strategy`。放宽意味着让改写往你的需求里加你没写过的约束 —— 那是卡片作者的决定,不该由「发送前顺手一点」来授权。真要放宽,去编辑那张卡。
267
+
268
+ 这个开关在「更多设置」里,且**只在卡片是 `strategy` 时出现**。`style` 卡上它不显示 —— 那种卡上两个选项效果完全一样,与其摆一个做不了事的控件、再配一段解释为什么不能放宽,不如不显示。服务端强制同一条规则,所以界面上不会给你一个会被忽略的按钮。
288
269
 
289
270
  ### `mode: template`
290
271
 
291
- Skips the model entirely and substitutes your draft into a fixed template
292
- (`{{input}}` is the placeholder). Instant and free, but the output never
293
- varies — right for a fixed litany, wrong for anything that should read as
294
- written for this message.
272
+ 完全跳过模型,把你的草稿代入固定模板(`{{input}}` 是占位符)。瞬时且免费,但输出永不变化 —— 适合固定祷词,不适合任何应当「像为你这次而写」的内容。
295
273
 
296
274
  ---
297
275
 
298
- ## Configuration
276
+ ## 配置
299
277
 
300
- Set in the plugin's loader row config:
278
+ 写在插件加载行的 config 里:
301
279
 
302
280
  ```yaml
303
281
  - id: persona-forge
304
282
  name: dsh-persona-forge
305
283
  config:
306
- sendMode: review # review | direct — the initial composer default
307
- factCheck: true # run the preservation check after each rewrite
308
- watchCards: true # reload the catalog when the card directory changes
284
+ sendMode: review # review | direct —— 输入框的初始默认
285
+ factCheck: false # 模型校验的默认值(默认关闭,见「技术事实校验」)。
286
+ # 本地技术记号检查不受这项控制 —— 它永远运行且免费。
287
+ watchCards: true # 角色卡目录变化时重载目录
309
288
  ```
310
289
 
290
+ `factCheck` 只决定**第二层**的默认值;菜单里的开关按会话覆盖它。设成 `true` 会让**每次改写都多一次模型调用**(延迟与 token 翻倍),只有草稿确实偏技术时才值得。
291
+
311
292
  ---
312
293
 
313
- ## HTTP contract
294
+ ## HTTP 契约
314
295
 
315
- All routes are `POST`, JSON, loopback-only (the socket must be loopback, the
316
- `Host` header must name a loopback host, and proxy-forwarding headers are
317
- refused). Mounted under `/persona-forge`.
296
+ 所有路由都是 `POST`、JSON、仅限回环(socket 必须是回环地址、`Host` 头必须指向回环主机、拒绝代理转发头)。挂载在 `/persona-forge` 下。
318
297
 
319
- | Route | Body | Returns |
298
+ | 路由 | 请求体 | 返回 |
320
299
  |---|---|---|
321
- | `/persona-forge/cards` | `{}` | `{ cards, diagnostics, directory, sendMode, factCheck }` |
322
- | `/persona-forge/rewrite` | `{ cardId, text, sessionId?, intensity? }` | `{ text, provider, model, mode, cardId, fidelity, intensity, elapsedMs, factCheck }` |
300
+ | `/persona-forge/cards` | `{}` | `{ cards, diagnostics, directory, sendMode, factCheck, draftModel }` |
301
+ | `/persona-forge/rewrite` | `{ cardId, text, sessionId?, intensity?, mode?, fidelity?, modelCheck? }` | `{ text, provider, model, mode, cardId, fidelity, intensity, elapsedMs, facts, factCheck }` |
323
302
  | `/persona-forge/cards/save` | `{ card }` | `{ id, path }` |
324
303
  | `/persona-forge/cards/delete` | `{ id }` | `{ removed }` |
325
- | `/persona-forge/cards/reset` | `{ id }` | `{ id }` — replaces the user card with the bundled one |
326
- | `/persona-forge/cards/diff` | `{}` | `{ rows: [{ id, name, bundledName, changed }] }` — only cards that differ |
304
+ | `/persona-forge/cards/reset` | `{ id }` | `{ id }` —— 用内置版本覆盖该用户卡 |
305
+ | `/persona-forge/cards/diff` | `{}` | `{ rows: [{ id, name, bundledName, changed }] }` —— 只列出有差异的卡 |
306
+ | `/persona-forge/cards/draft` | `{ brief, sessionId? }` | `{ card, provider, model, elapsedMs }` —— 只返回草稿,不落盘 |
327
307
  | `/persona-forge/reveal` | `{}` | `{ directory, opened }` |
328
308
 
329
- Failures use `{ ok: false, error: { code, message?, params? } }` with codes
330
- `rejected`, `no-card`, `unconfigured`, `timeout`, `upstream`, `internal`,
331
- `forbidden`, `method`, `not-found`.
309
+ 失败返回 `{ ok: false, error: { code, message?, params? } }`,`code` 取值:`rejected`、`no-card`、`unconfigured`、`timeout`、`upstream`、`internal`、`forbidden`、`method`、`not-found`。
332
310
 
333
311
  ---
334
312
 
335
- ## Compatibility
313
+ ## 兼容性
336
314
 
337
- The host half declares **no harness package dependency and no third-party
338
- dependency**: it reaches `llm`, `sessions`, `agentDefaultModel` and `webServer`
339
- through `ctx.get` with explicit absence handling, and it parses cards with its
340
- own bundled YAML reader. That keeps it loadable across harness releases whose
341
- package layout differs, keeps it from failing to activate on a composition
342
- missing one of those services, and means a `link:`-ed local checkout works
343
- without a separate install step. Model-route resolution prefers the session's
344
- own last request header and falls back to the harness default model.
315
+ 宿主侧**不声明任何 harness 包依赖,也没有任何第三方依赖**:它通过 `ctx.get` 取 `llm`、`sessions`、`agentDefaultModel`、`webServer`,并对缺失情况显式降级;角色卡由自带的 YAML 读取器解析。因此它在包布局不同的 harness 版本上仍可加载,不会因为某个组合缺少其中之一而激活失败,本地 `link:` 的检出也无需额外安装步骤即可工作。模型路由优先取会话自己最后一次请求头,回退到 harness 默认模型。
345
316
 
346
- The client half imports no harness Client package and is hand-written against
347
- theme tokens (`--dsw-alias-*`) only, so a renamed internal degrades appearance
348
- rather than blanking the slot.
317
+ 客户端侧不 import 任何 harness Client 包,只手工编写并仅使用主题 token(`--dsw-alias-*`),所以内部改名只会影响外观,不会让插槽变空白。
349
318
 
350
- Developed and verified against dsh `0.2.0-rc.2`.
319
+ 在 dsh `0.2.0-rc.2` 上开发与验证。
351
320
 
352
321
  ---
353
322
 
354
- ## Development
323
+ ## 开发
355
324
 
356
325
  ```
357
- node scripts/check.mjs # offline unit check: parser, cards, prompts, client syntax
358
- node scripts/host-check.mjs # host integration check: mounts the real plugin over a socket
359
- npm run check # both
326
+ node scripts/check.mjs # 离线单元自检:解析器、角色卡、提示词、客户端语法
327
+ node scripts/host-check.mjs # 宿主集成自检:在真实 socket 上挂载真实插件
328
+ npm run check # 两者都跑
360
329
  ```
361
330
 
362
- `check.mjs` needs no harness and no model. It covers the built-in YAML reader
363
- (including its refusals and dump/parse round trips), card normalization, the
364
- bundled cards' fact preservation, prompt construction, output normalization,
365
- the fact-check parser, and client-bundle syntax.
331
+ `check.mjs` 不需要 harness,也不调用模型。覆盖:内置 YAML 读取器(含其拒绝行为与 dump/parse 往返)、卡片归一化、内置卡的事实保全、提示词构造、输出归一化、事实校验解析、客户端 bundle 语法。
366
332
 
367
- `host-check.mjs` mounts the real plugin entry on a stub Cordis context and
368
- drives the real HTTP routes over a real socket, with a stubbed `llm` service.
369
- It covers route registration, session-model resolution, template mode, the
370
- loopback fence, and the save/delete round trip — the things a unit check
371
- cannot reach. It points `DSH_HOME` at a temporary directory, so it never
372
- touches your real cards.
333
+ `host-check.mjs` 在 stub Cordis 上下文上挂载真实插件入口,用真实 socket 驱动真实 HTTP 路由,`llm` 服务为桩实现。覆盖路由注册、会话模型解析、模板模式、回环防护、保存/删除往返 —— 这些是单元自检够不到的。它把 `DSH_HOME` 指向临时目录,**不会碰你真实的角色卡**。
373
334
 
374
- ### Character-card YAML
335
+ ### 角色卡 YAML
375
336
 
376
- The plugin ships its own YAML reader (`lib/yaml.js`) rather than depending on a
377
- YAML library. A published plugin declares its dependencies, but a locally
378
- `link:`-ed plugin does not get them installed, so a library import would work
379
- only when some unrelated package happened to hoist one.
337
+ 插件自带 YAML 读取器(`lib/yaml.js`),不依赖 YAML 库。已发布的插件会声明依赖,但本地 `link:` 的插件**不会**被安装依赖 —— 所以引用库只有在某个无关包恰好提升(hoist)了它时才能工作。
380
338
 
381
- The reader supports the subset cards need — block mappings and sequences,
382
- block scalars (`|`, `|-`, `|+`, `>`, …), quoted and plain scalars, booleans,
383
- null and comments — and **refuses** what it cannot read faithfully: flow
384
- collections, anchors, aliases, tags, merge keys and multi-document streams.
385
- Refusing is deliberate. Silently misreading a card would give you a persona
386
- that does not match the file you wrote, which is worse than an error message
387
- naming the line.
339
+ 读取器支持角色卡需要的子集 —— 块映射与块序列、块标量(`|`、`|-`、`|+`、`>` 等)、引号与普通标量、布尔、null、注释 —— 并**拒绝**它无法忠实读取的写法:流式集合、锚点、别名、标签、合并键、多文档流。拒绝是刻意的:静默误读一张卡,会给你一个与你写的文件不符的人设,那比一条指明行号的报错更糟。
388
340
 
389
- ### Layout
341
+ ### 目录结构
390
342
 
391
- | Path | Role |
343
+ | 路径 | 职责 |
392
344
  |---|---|
393
- | `lib/index.js` | Host entry: card store, directory watcher, route registration |
394
- | `lib/store.js` | Card loading, normalization, save/delete |
395
- | `lib/yaml.js` | The built-in YAML subset reader and writer |
396
- | `lib/prompts.js` | Rewrite system prompt, draft framing, fact-check prompt |
397
- | `lib/rewrite.js` | The model calls and output normalization |
398
- | `lib/routes.js` | HTTP routes and session-model resolution |
399
- | `lib/loopback.js` | The loopback trust fence |
400
- | `lib/client.js` | Browser half: composer control, review panel, settings page |
401
- | `cards/` | Bundled character cards (read-only; a user card of the same id shadows one) |
402
-
403
- ## License
345
+ | `lib/index.js` | 宿主入口:角色卡存储、目录监听、路由注册 |
346
+ | `lib/store.js` | 卡片加载、归一化、保存/删除 |
347
+ | `lib/yaml.js` | 内置 YAML 子集读取器与写出器 |
348
+ | `lib/prompts.js` | 改写系统提示词、草稿包装、事实校验提示词 |
349
+ | `lib/rewrite.js` | 模型调用与输出归一化 |
350
+ | `lib/routes.js` | HTTP 路由与会话模型解析 |
351
+ | `lib/loopback.js` | 回环信任防护 |
352
+ | `lib/client.js` | 浏览器侧:输入框控件、审查面板、设置页 |
353
+ | `cards/` | 内置角色卡(只读;同 id 的用户卡会遮蔽它) |
354
+
355
+ ## 许可证
404
356
 
405
357
  MIT