dsh-soul 0.6.2 → 0.7.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LiuYanFeng
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
@@ -7,18 +7,21 @@ DeepSeek Harness 个性化设置插件,用于配置 Agent 的昵称、回复
7
7
  ## 功能
8
8
 
9
9
  - Web UI 个性化设置页面
10
- - 设置页体验:中英双语文案(随界面语言切换)、dirty 检测(无改动禁用保存)、保存结果提示、「查看当前生效提示词」只读面板
10
+ - 设置页体验:中英双语文案(随界面语言切换)、dirty 检测(无改动禁用保存)、保存结果提示、提示词预览只读面板
11
11
  - 启用或禁用个性化设置
12
12
  - 「关于你」:设置用户昵称、职业和介绍,回复时结合你的背景
13
13
  - 选择回复风格和语调(合并为单一选项):`professional`(专业严谨)、`casual`(轻松自然)、`humorous`(幽默风趣)、`roast`(吐槽达人)、`efficient`(高效干练)
14
14
  - 特质微调(在风格和语调的基础上叠加):
15
15
  - 标题和列表:`default`(默认)、`more`(增强,采用清晰格式和列表结构)、`less`(减弱,使用更多段落文本)
16
16
  - 表情符号:`default`(默认)、`more`(增强,使用较多表情符号)、`less`(减弱,尽量减少使用表情符号)
17
+ - 表格:`default`(默认)、`more`(增强,呈现对比或多字段信息时优先使用表格)、`less`(减弱,避免表格、改用列表或段落)
18
+ - 回复长度偏好:`concise`(简洁,只讲要点、不展开)、`normal`(适中,不额外约束,默认)、`detailed`(详尽,充分展开背景、步骤与推理)
17
19
  - 选择输出语言(Agent 回复语言 + `/soul` 命令输出语言):中文 / English
18
20
  - 提示词随输出语言本地化:`language=en` 时 system prompt 使用英文描述
19
21
  - 输入自定义指令
20
22
  - Agent 可调用工具 `set_persona`,让模型在对话中直接调整人设
21
- - 人设预设:多套命名人设一键切换(保存 / 使用 / 列表 / 删除,Web UI 与命令双入口)
23
+ - 人设预设:7 个内置人设(苏格拉底式提问者 / 极简主义者 / 资深架构师 / 教学型讲解者 / 严格代码审阅者 / 头脑风暴伙伴 / 自驱型协作者,随插件提供、不可删除)+ 多套自建人设一键切换(保存 / 使用 / 列表 / 删除,Web UI 与命令双入口);每行摘要显示风格与偏离默认的维度
24
+ - 预设只管 Agent 的人格:**切换预设不会动「关于你」**(昵称 / 职业 / 介绍是你本人的资料,与「Agent 怎么说话」是两码事)。内置与自建一视同仁——保存预设时只快照风格 / 特质 / 回复长度 / 输出语言 / 自定义指令,不包含身份字段;早期版本存下的完整快照会在读取时自动剥离
22
25
  - `set_persona` 确认模式(`requireToolConfirmation`):Agent 的人设修改需经 `/soul confirm` 确认后才生效
23
26
  - 输入框光轨:Agent 回复中时,输入框边框显示沿边循环流动的光轨(默认颜色 `#679EFE`)
24
27
  - 颜色:预设色板、取色器或十六进制输入(如 `#679EFE`)
@@ -28,14 +31,21 @@ DeepSeek Harness 个性化设置插件,用于配置 Agent 的昵称、回复
28
31
  - `/soul set` 键值方式修改配置项
29
32
  - 配置持久化保存
30
33
  - 配置输入校验:字段白名单、类型、长度上限(昵称/职业 50、介绍 500、自定义指令 2000 字符)、枚举与十六进制颜色校验,非法或超限字段整单拒绝
34
+ - 配置持久化健壮性:写入为**原子替换**(先写临时文件再 `rename`,中断不会留下半份配置);读取区分「文件不存在」与「文件损坏」——损坏时保留原文件、另存 `.corrupt` 备份、**拒绝覆写**,并把原因直接显示在设置页,避免自定义指令与人设库被静默清空
35
+ - 配置损坏时有**逃生口**:「重置」会先把损坏文件移开(备份为 `.corrupt`)再写入默认值 —— 否则损坏状态下五条写路径全被拒,用户只能自己删文件
36
+ - 送达可观测:`/soul show` 的「送达」行、`GET /api/soul/status`、设置页提示条(`configError` = 配置读不出来;`deliveryWarning` = 配置送不到活动会话)
37
+ - 提示词插值已关闭:自定义指令里的 `{{…}}` 按字面保留,不会触发宿主提示词装配失败
31
38
  - 配置更新后同步到所有活动 Agent
32
39
  - 变更检测:仅在影响 Agent 行为的配置实际变化时刷新提示词并注入会话;纯外观配置(输入框光轨)只落盘,不产生注入消息
40
+ - 插件图标:插件管理列表与侧栏入口显示专属图标(`assets/icon.svg`,36×36,随 npm 包发布)
41
+ - 设置导航图标:设置面板左侧「个性化」那一项显示与插件图标同一套几何的图标——同一条光轨与火花,颜色与线宽则跟随邻居(单色 `currentColor`);DSH 的导航图标按 section id 硬编码、插件声明不了,故由客户端做 DOM 替换
42
+ - 提示词预览(只读,设置页底部折叠区):无未保存编辑时显示**当前生效**的提示词;表单一有改动就转为预览**草稿**——展示保存后真正会注入的内容,边改边看(停止输入 400ms 后自动重新编译),并在保存 / 重置 / 应用预设后自动刷新。未通过校验的字段会单独列出且不计入预览
33
43
 
34
44
  ## 安装
35
45
 
36
46
  ```powershell
37
- dsh plugin --profile web add dsh-soul
38
- dsh plugin --profile web update dsh-soul
47
+ dsh plugin --profile <profile> add dsh-soul
48
+ dsh plugin --profile <profile> update dsh-soul
39
49
  ```
40
50
 
41
51
  插件配置由 `cordis.patch.yml` 提供:
@@ -61,7 +71,7 @@ dsh plugin --profile web update dsh-soul
61
71
  若 DSH 提示「dsh-soul@x.y.z 与 DSH a.b.c 不兼容」,说明插件版本早于该 DSH 版本,升级插件即可:
62
72
 
63
73
  ```powershell
64
- dsh plugin --profile web update dsh-soul
74
+ dsh plugin --profile <profile> update dsh-soul
65
75
  ```
66
76
 
67
77
  ## 截图
@@ -89,15 +99,15 @@ dsh plugin --profile web update dsh-soul
89
99
  也可以使用斜杠命令:
90
100
 
91
101
  ```text
92
- /soul show 查看当前配置(含确认模式与预设数量)
102
+ /soul show 查看当前配置(含确认模式、预设数量与送达状态)
93
103
  /soul set k=v 修改配置项(如 /soul set style=humorous language=en;光轨:trailColor=#679EFE trailSpeed=fast trailWidth=thick)
94
- /soul save <名> 保存当前配置为人设预设
95
- /soul use <名> 应用人设预设
96
- /soul list 查看人设预设(✔ 标记当前匹配项)
97
- /soul del <名> 删除人设预设(delete / rm 别名)
104
+ /soul save <名> 保存当前人设为预设(只含人格维度,不含「关于你」;内置名不可占用)
105
+ /soul use <名> 应用人设预设(内置预设同样可用)
106
+ /soul list 查看人设预设(✔ 标记当前匹配项;内置项带 [内置] 标记)
107
+ /soul del <名> 删除人设预设(delete / rm 别名;内置预设不可删)
98
108
  /soul confirm 应用待确认的人设变更(确认模式)
99
109
  /soul reject 拒绝待确认的人设变更
100
- /soul reset 重置配置(保留人设预设库)
110
+ /soul reset 重置配置(保留人设预设库);配置损坏时这是逃生口,会先备份为 .corrupt
101
111
  /soul enable 启用个性化设置
102
112
  /soul disable 禁用个性化设置
103
113
  /soul 小明 设置昵称
@@ -134,7 +144,7 @@ soul-config.json
134
144
  }
135
145
  ```
136
146
 
137
- 人设预设保存在同一文件的 `personas` 字段:名称 → 人设字段快照(昵称/职业/介绍/风格/特质/语言/自定义指令)+ `updatedAt`;预设库变更不影响活动配置,使用预设时才应用到配置并同步会话。
147
+ 自建人设预设保存在同一文件的 `personas` 字段:名称 → 人设字段快照(风格 / 特质 / 回复长度 / 输出语言 / 自定义指令)+ `updatedAt`。**预设不含「关于你」**(昵称 / 职业 / 介绍)——那是使用者本人的资料,切换预设只换 Agent 的说话方式;这条由 `PROFILE_FIELDS` 与 `PERSONA_FIELDS`(`lib/config.mjs`)结构性保证:保存快照、应用取键、★ 匹配判据、磁盘归一化四条路径都按 `PERSONA_FIELDS` 白名单走,所以内置与自建行为一致。预设库变更不影响活动配置,使用预设时才应用到配置并同步会话。内置人设不落盘(见 `lib/personas.mjs`),列表 / 使用 / ★ 匹配统一走「内置 + 自建」的合并视图,同名以内置为准,内置名既不可保存占用也不可删除。
138
148
 
139
149
  输入框光轨字段:`trailEnabled`(是否启用,默认 `true`)、`trailColor`(6 位十六进制颜色,统一大写存储,默认 `#679EFE`)、`trailSpeed`(`slow` / `normal` / `fast`,默认 `slow`)、`trailWidth`(`thin` / `normal` / `thick`,默认 `thin`)。这四个字段属纯外观配置,只落盘、不进入 system prompt、不触发会话注入。
140
150
 
@@ -142,32 +152,40 @@ soul-config.json
142
152
 
143
153
  ## 实现原理
144
154
 
145
- 插件通过 `compilePrompt()` 将昵称、风格、语调和自定义指令编译成 system prompt,并注册到 DSH:
155
+ 「保存后下一次请求即生效」由**两条通道**共同保证(缺一不可,见 `DEBUGGING.md` 3.5)。
156
+
157
+ **通道一:system prompt section。** 插件把配置编译成 system prompt 并注册到 DSH:
146
158
 
147
159
  ```js
148
160
  spCtx.systemPrompt.section({
149
161
  name: 'soul:persona',
150
162
  order: 0,
163
+ // 关闭宿主提示词插值:默认开启时,用户自定义指令里任何一对完整的 {{…}}
164
+ // 都会让装配抛错(该抛错在 agent.step() 开头且无 try/catch ⇒ 每轮都失败)
165
+ interpolate: false,
151
166
  text: () => compilePrompt(configCache || DEFAULT_CONFIG)
152
167
  })
153
168
  ```
154
169
 
155
- 配置更新后,插件会遍历所有活动 Agent,使用标准 `UserMessage` 调用 `agent.inject()`:
170
+ 宿主在**每一个 step 之前**都会重新装配提示词,对函数式 `text` provider **不做缓存**,并在文本变化时自行提交新的 system 快照 —— 所以这一条的更新时机是「下一次请求」。
171
+
172
+ **通道二:活动会话注入。** 配置变化后,插件遍历所有活动 Agent,用标准 `UserMessage` 调用 `agent.inject()`:
156
173
 
157
174
  ```js
158
175
  agent.inject(createUserMessage({
159
- content: [{ type: 'text', text: prompt }],
160
- source: {
161
- kind: 'plugin',
162
- plugin: 'dsh-soul',
163
- form: 'snapshot',
164
- sections: [{ name: 'soul:persona', text: prompt }]
165
- }
176
+ content: [{ type: 'text', text: `[dsh-soul 个性化配置已更新]\n…\n${prompt}` }],
177
+ // 会话格式 v4 要求生产者自持 kind(不得再用共享的 kind:'plugin' + plugin 字段,
178
+ // 那会让每一轮在 step 开始前失败);构造见 lib/injection.mjs
179
+ source: { kind: 'plugin:dsh-soul', form: 'snapshot', sections: [{ name: 'soul:persona', text: prompt }] }
166
180
  }))
167
181
  ```
168
182
 
169
- `agent.inject()` 会将最新配置放入 Agent 的待处理上下文,在下一次请求中生效;不会主动触发新请求,也不会修改历史消息。
183
+ `agent.inject()` 会把最新配置放入 Agent 的待处理上下文,在下一次请求中生效;不会主动触发新请求,也不会修改历史消息。它同时是「即时生效」的可见载体:会话里会出现一条 `[dsh-soul 个性化配置已更新]` 消息。
184
+
185
+ > 为什么两条都要:**只保留通道一时,实测「会话进行中修改人设」不会在下一轮生效**(0.7.1 开发中曾据此删除通道二,随后原样撤回)。`npm run verify:host` 与 `npm run verify:e2e` 分别把两条通道钉住,DSH 升级后请重跑。
186
+
187
+ **诊断。** 这两条通道静默失效时症状都是「改了配置不生效」,因此插件会如实记录送达结果:`/soul show` 末尾的「送达」行、`GET /api/soul/status`、以及设置页的提示条(`configError` = 配置读不出来,`deliveryWarning` = 配置送不到活动会话)。
170
188
 
171
189
  ## 许可证
172
190
 
173
- MIT License
191
+ MIT License,完整文本见 [LICENSE](LICENSE)。
package/README_EN.md CHANGED
@@ -7,18 +7,21 @@ A personalization plugin for DeepSeek Harness (DSH). Configure your agent's nick
7
7
  ## Features
8
8
 
9
9
  - Personalization settings page in the Web UI
10
- - Localized settings UI (English/Chinese, follows the interface language), dirty tracking (save disabled when unchanged), save-result toasts, and a read-only "active prompt" viewer
10
+ - Localized settings UI (English/Chinese, follows the interface language), dirty tracking (save disabled when unchanged), save-result toasts, and a read-only prompt preview panel
11
11
  - Enable or disable personalization
12
12
  - "About you": set your nickname, occupation and bio so replies fit your background
13
13
  - Combined style & tone option: `professional`, `casual`, `humorous`, `roast`, `efficient`
14
14
  - Trait fine-tuning (layered on top of style & tone):
15
15
  - Headings & lists: `default`, `more` (clear formatting with headings and lists), `less` (more paragraph text)
16
16
  - Emoji: `default`, `more` (frequent emoji usage), `less` (minimal emoji usage)
17
+ - Tables: `default`, `more` (prefer tables for comparisons and multi-field data), `less` (avoid tables; use lists or paragraphs)
18
+ - Reply length preference: `concise` (key points only), `normal` (no extra constraint, default), `detailed` (expand background, steps and reasoning)
17
19
  - Output language (agent reply language + `/soul` command output language): Chinese or English
18
20
  - The compiled system prompt follows the output language (English descriptions when `language=en`)
19
21
  - Custom instructions
20
22
  - Agent-callable tool `set_persona` to let the model adjust persona during a conversation
21
- - Named personas: save the current config under a name and switch with one click (save / use / list / delete, in both the Web UI and slash commands)
23
+ - Named personas: 7 built-in personas (Socratic Questioner / Minimalist / Senior Architect / Teaching Explainer / Strict Code Reviewer / Brainstorm Partner / Self-Driven Collaborator — shipped with the plugin, cannot be deleted) plus your own named personas, switched with one click (save / use / list / delete, in both the Web UI and slash commands); each row summarises the style and every dimension that differs from its default
24
+ - Personas cover the **agent's personality only**: switching a persona never touches **"About you"** (nickname / occupation / bio describe *you*, not how the agent talks). Built-in and self-saved personas behave identically — saving a persona snapshots style / traits / reply length / reply language / custom instructions, never the identity fields; full-config snapshots written by earlier versions are stripped on read
22
25
  - `set_persona` confirmation mode (`requireToolConfirmation`): agent persona changes take effect only after `/soul confirm`
23
26
  - Composer light trail: while the agent is replying, a light trail loops around the composer border (default color `#679EFE`)
24
27
  - Color: preset swatches, a color picker, or a hex input (e.g. `#679EFE`)
@@ -28,16 +31,25 @@ A personalization plugin for DeepSeek Harness (DSH). Configure your agent's nick
28
31
  - `/soul set` key=value field updates
29
32
  - Configuration persisted to disk
30
33
  - Input validation: field whitelist, types, length limits (nickname/occupation 50, bio 500, custom instructions 2000 chars), enum and hex-color checks; invalid or oversized fields reject the whole write
34
+ - Durable config: writes are **atomic** (temp file + `rename`, so an interrupted write can never leave half a config); reads tell "file missing" apart from "file corrupted" — a corrupt file is left untouched, backed up as `.corrupt`, **refused as a write target**, and the reason is shown right in the settings page so your custom instructions and persona library are never silently wiped
35
+ - Reset is the **escape hatch** for a corrupt config: since every other write path refuses to touch a corrupt file, `/soul reset` (and the settings-page reset button) moves it aside as `.corrupt` and writes a fresh default config, reporting the backup path — so a bad file never locks you out of the plugin without a restart
36
+ - Delivery is **observable**: `/soul show` ends with a delivery line, `GET /api/soul/status` reports `version` / `enabled` / `configPath` / `configError` / `promptChars` / both channels / `deliveryWarning`, and the settings page shows the same reason as a banner. `configError` (**cannot read** the config) and `deliveryWarning` (**cannot deliver** it to active sessions) are reported separately — same symptom, different fix
37
+ - Prompt interpolation is disabled: `{{…}}` in custom instructions stays literal and can no longer break system-prompt assembly
31
38
  - Configuration synced to all active agents after every update
32
39
  - Change detection: the prompt is refreshed and sessions injected only when a behavior-affecting field actually changed — appearance-only config (composer light trail) is persisted without injecting anything
40
+ - Plugin icon: a dedicated icon in the plugin manager list and the sidebar entry (`assets/icon.svg`, 36×36, shipped with the npm package)
41
+ - Settings nav icon: the "Personalization" entry in the settings panel draws the same geometry as the plugin icon — same trail and spark, but coloured and weighted like its neighbours (monochrome `currentColor`). DSH hardcodes nav glyphs by section id and offers no way for a plugin to declare one, so the client swaps the glyph in the DOM
42
+ - Prompt preview (read-only, collapsible section at the bottom of the settings page): shows the **currently active** prompt while the form is untouched; as soon as any edit is pending it compiles the **draft** instead — what will actually be injected once you save — refreshing live (400 ms after you stop typing) and again after save / reset / applying a persona. Fields that fail validation are listed separately and excluded from the preview
33
43
 
34
44
  ## Installation
35
45
 
36
46
  ```powershell
37
- dsh plugin --profile web add dsh-soul
38
- dsh plugin --profile web update dsh-soul
47
+ dsh plugin --profile <profile> add dsh-soul
48
+ dsh plugin --profile <profile> update dsh-soul
39
49
  ```
40
50
 
51
+ Replace `<profile>` with your own profile name (e.g. `desktop` when using the DSH desktop build).
52
+
41
53
  The plugin is registered via `cordis.patch.yml`:
42
54
 
43
55
  ```yaml
@@ -61,7 +73,7 @@ So DSH **0.1.x and 0.2.x** are supported, prereleases included. Run `npm run ver
61
73
  If DSH reports "dsh-soul@x.y.z is incompatible with DSH a.b.c", the plugin predates that DSH version — updating it is enough:
62
74
 
63
75
  ```powershell
64
- dsh plugin --profile web update dsh-soul
76
+ dsh plugin --profile <profile> update dsh-soul
65
77
  ```
66
78
 
67
79
  ## Screenshots
@@ -89,15 +101,15 @@ After starting DSH, open the **Personalization** section on the settings page, e
89
101
  Slash commands are also available:
90
102
 
91
103
  ```text
92
- /soul show Show current configuration (confirmation mode & persona count)
104
+ /soul show Show current configuration (confirmation mode, persona count & delivery status)
93
105
  /soul set k=v Change config fields (e.g. /soul set style=humorous language=en; trail: trailColor=#679EFE trailSpeed=fast trailWidth=thick)
94
- /soul save <name> Save the current config as a persona
95
- /soul use <name> Apply a persona
96
- /soul list List personas (✔ marks the active match)
97
- /soul del <name> Delete a persona (delete / rm aliases)
106
+ /soul save <name> Save the current persona as a preset (personality fields only, never "About you"; built-in names are reserved)
107
+ /soul use <name> Apply a persona (built-in personas work the same way)
108
+ /soul list List personas (✔ marks the active match; built-ins carry a [built-in] tag)
109
+ /soul del <name> Delete a persona (delete / rm aliases; built-in personas cannot be deleted)
98
110
  /soul confirm Apply the pending persona proposal (confirmation mode)
99
111
  /soul reject Discard the pending persona proposal
100
- /soul reset Reset configuration (keeps the persona library)
112
+ /soul reset Reset configuration (keeps the persona library; with a corrupt config this is the escape hatch — the bad file is moved aside as .corrupt first)
101
113
  /soul enable Enable personalization
102
114
  /soul disable Disable personalization
103
115
  /soul Bob Set nickname
@@ -134,7 +146,7 @@ Example:
134
146
  }
135
147
  ```
136
148
 
137
- Personas are stored in the same file under the `personas` field: name → persona field snapshot (nickname / occupation / bio / style / traits / language / custom instructions) + `updatedAt`. Persona library changes never touch the active config; a persona is applied to the config (and synced to sessions) only when used.
149
+ Your own personas are stored in the same file under the `personas` field: name → persona field snapshot (style / traits / reply length / reply language / custom instructions) + `updatedAt`. **A persona never contains "About you"** (nickname / occupation / bio) — those describe you, and switching a persona only changes how the agent talks. This is enforced structurally by `PROFILE_FIELDS` and `PERSONA_FIELDS` in `lib/config.mjs`: snapshotting, applying, ★ matching and on-disk normalisation all go through the `PERSONA_FIELDS` allow-list, so built-in and self-saved personas behave the same way. Persona library changes never touch the active config; a persona is applied to the config (and synced to sessions) only when used. Built-in personas are not persisted — they live in `lib/personas.mjs` — and listing / applying / match detection all go through the merged view of built-ins plus your own; on a name collision the built-in wins, and a built-in name can neither be saved over nor deleted.
138
150
 
139
151
  Composer light trail fields: `trailEnabled` (on/off, default `true`), `trailColor` (6-digit hex, stored uppercase, default `#679EFE`), `trailSpeed` (`slow` / `normal` / `fast`, default `slow`), `trailWidth` (`thin` / `normal` / `thick`, default `thin`). These are appearance-only: they are persisted but never enter the system prompt and never trigger a session injection.
140
152
 
@@ -142,32 +154,42 @@ Length limits: nickname / occupation 50 chars, bio 500 chars, custom instruction
142
154
 
143
155
  ## How It Works
144
156
 
145
- The plugin compiles the nickname, style, tone and custom instructions into a system prompt via `compilePrompt()` and registers it with DSH:
157
+ "Takes effect on the next request after saving" is guaranteed by **two channels** — both are required (see `DEBUGGING.md` 3.5).
158
+
159
+ **Channel 1: the system-prompt section.** The plugin compiles the config into a system prompt and registers it with DSH:
146
160
 
147
161
  ```js
148
162
  spCtx.systemPrompt.section({
149
163
  name: 'soul:persona',
150
164
  order: 0,
165
+ // Host prompt interpolation is disabled: with it on, any complete {{…}} pair in your
166
+ // custom instructions makes assembly throw — and that throw happens at the top of
167
+ // agent.step() with no try/catch, so every single turn fails.
168
+ interpolate: false,
151
169
  text: () => compilePrompt(configCache || DEFAULT_CONFIG)
152
170
  })
153
171
  ```
154
172
 
155
- After each update, the plugin iterates over all active agents and calls `agent.inject()` with a standard `UserMessage`:
173
+ The host re-assembles the prompt **before every step**, does **not** cache function-form `text` providers, and commits a new system snapshot whenever the text changes — so this channel updates on the **next request**.
174
+
175
+ **Channel 2: injection into active sessions.** After a config change the plugin walks every active agent and calls `agent.inject()` with a standard `UserMessage`:
156
176
 
157
177
  ```js
158
178
  agent.inject(createUserMessage({
159
- content: [{ type: 'text', text: prompt }],
160
- source: {
161
- kind: 'plugin',
162
- plugin: 'dsh-soul',
163
- form: 'snapshot',
164
- sections: [{ name: 'soul:persona', text: prompt }]
165
- }
179
+ content: [{ type: 'text', text: `[dsh-soul personalization updated]\n…\n${prompt}` }],
180
+ // Session format v4 requires producers to carry their own kind (the shared
181
+ // kind:'plugin' + plugin field is rejected, failing every turn before step);
182
+ // construction lives in lib/injection.mjs
183
+ source: { kind: 'plugin:dsh-soul', form: 'snapshot', sections: [{ name: 'soul:persona', text: prompt }] }
166
184
  }))
167
185
  ```
168
186
 
169
- `agent.inject()` places the latest configuration into the agent's pending context so it takes effect on the next request. It does not trigger a new request and does not modify message history.
187
+ `agent.inject()` places the latest config into the agent's pending context so it takes effect on the next request; it does not trigger a new request and does not rewrite message history. It is also the visible carrier of "already updated": a `[dsh-soul personalization updated]` message shows up in the conversation.
188
+
189
+ > Why both are needed: **with channel 1 alone, changing the persona mid-conversation was measured not to take effect on the next turn** (channel 2 was removed on that assumption during 0.7.1 development, then rolled back verbatim). `npm run verify:host` and `npm run verify:e2e` pin each channel down in place; re-run them after every DSH upgrade.
190
+
191
+ **Diagnostics.** Both channels fail silently with the same symptom — "I changed the config and nothing happened" — so the plugin records delivery honestly: the delivery line at the end of `/soul show`, `GET /api/soul/status`, and the banner on the settings page (`configError` = the config **cannot be read**; `deliveryWarning` = it **cannot be delivered** to active sessions).
170
192
 
171
193
  ## License
172
194
 
173
- MIT License
195
+ MIT License — see [LICENSE](LICENSE) for the full text.
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" role="img" aria-label="dsh-soul">
2
+ <defs>
3
+ <!-- 光轨:头部明亮,尾端渐隐 -->
4
+ <linearGradient id="soulTrail" x1="18" y1="6" x2="8.6" y2="23.4" gradientUnits="userSpaceOnUse">
5
+ <stop offset="0" stop-color="#ADD3FF"/>
6
+ <stop offset="0.3" stop-color="#679EFE"/>
7
+ <stop offset="0.72" stop-color="#679EFE" stop-opacity="0.38"/>
8
+ <stop offset="1" stop-color="#679EFE" stop-opacity="0"/>
9
+ </linearGradient>
10
+ <!-- 灵魂火花:上浅下深,深浅主题下都不塌成一块 -->
11
+ <linearGradient id="soulSpark" x1="18" y1="11.6" x2="18" y2="24.4" gradientUnits="userSpaceOnUse">
12
+ <stop offset="0" stop-color="#B6DBFF"/>
13
+ <stop offset="0.5" stop-color="#679EFE"/>
14
+ <stop offset="1" stop-color="#4560D6"/>
15
+ </linearGradient>
16
+ </defs>
17
+ <!-- 光轨:绕行约 250°,左上留口,尾端渐隐 -->
18
+ <path d="M18 6A12 12 0 1 1 6.72 22.1" stroke="url(#soulTrail)" stroke-width="2.8" stroke-linecap="round"/>
19
+ <!-- 灵魂火花 -->
20
+ <path d="M18 11.6C18.52 15.15 20.85 17.48 24.4 18C20.85 18.52 18.52 20.85 18 24.4C17.48 20.85 15.15 18.52 11.6 18C15.15 17.48 17.48 15.15 18 11.6Z" fill="url(#soulSpark)"/>
21
+ </svg>