dsh-soul 0.1.0 → 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/README.md CHANGED
@@ -1,15 +1,21 @@
1
1
  # dsh-soul
2
2
 
3
+ [简体中文](./README.md) | [English](./README_EN.md)
4
+
3
5
  DeepSeek Harness 个性化设置插件,用于配置 Agent 的昵称、回复风格、语调和自定义指令。
4
6
 
5
7
  ## 功能
6
8
 
7
9
  - Web UI 个性化设置页面
8
10
  - 启用或禁用个性化设置
9
- - 设置用户昵称
10
- - 选择回复风格:`professional`、`casual`、`friendly`、`humorous`、`academic`
11
- - 选择语调:`neutral`、`formal`、`informal`、`enthusiastic`、`calm`
12
- - 输入自定义指令和使用示例模板
11
+ - 「关于你」:设置用户昵称、职业和介绍,回复时结合你的背景
12
+ - 选择回复风格和语调(合并为单一选项):`professional`(专业严谨)、`casual`(轻松自然)、`humorous`(幽默风趣)、`roast`(吐槽达人)、`efficient`(高效干练)
13
+ - 特质微调(在风格和语调的基础上叠加):
14
+ - 标题和列表:`default`(默认)、`more`(增强,采用清晰格式和列表结构)、`less`(减弱,使用更多段落文本)
15
+ - 表情符号:`default`(默认)、`more`(增强,使用较多表情符号)、`less`(减弱,尽量减少使用表情符号)
16
+ - 选择输出语言(Agent 回复语言 + `/soul` 命令输出语言):中文 / English
17
+ - 输入自定义指令
18
+ - Agent 可调用工具 `set_persona`,让模型在对话中直接调整人设
13
19
  - 配置持久化保存
14
20
  - 配置更新后同步到所有活动 Agent
15
21
 
@@ -44,18 +50,14 @@ dsh --profile web web
44
50
 
45
51
  配置保存后会同步到所有活动 Agent,当前会话下一次请求即可使用最新配置。
46
52
 
47
- ## 配置文件
48
-
49
- 配置文件位置:
53
+ Agent 也可以通过工具 `set_persona` 在对话中直接调整你的人设(昵称、回复风格和语调、特质、回复语言、自定义指令)。模型只会在明确请求改变称呼、语气、风格或语言时调用该工具。
50
54
 
51
- ```text
52
- $DSH_HOME/soul-config.json
53
- ```
55
+ ## 配置文件
54
56
 
55
- 未设置 `DSH_HOME` 时使用:
57
+ 插件将配置保存到 DSH 的用户数据目录,文件名为:
56
58
 
57
59
  ```text
58
- ~/.dsh/soul-config.json
60
+ soul-config.json
59
61
  ```
60
62
 
61
63
  配置示例:
@@ -64,8 +66,10 @@ $DSH_HOME/soul-config.json
64
66
  {
65
67
  "enabled": true,
66
68
  "nickname": "小明",
69
+ "occupation": "软件工程师",
70
+ "bio": "对编程和技术感兴趣",
67
71
  "style": "professional",
68
- "tone": "neutral",
72
+ "language": "zh",
69
73
  "customInstructions": "请保持简洁,优先给出结论。"
70
74
  }
71
75
  ```
package/README_EN.md ADDED
@@ -0,0 +1,107 @@
1
+ [简体中文](./README.md) | [English](./README_EN.md)
2
+
3
+ # dsh-soul
4
+
5
+ A personalization plugin for DeepSeek Harness (DSH). Configure your agent's nickname, reply style, tone, and custom instructions.
6
+
7
+ ## Features
8
+
9
+ - Personalization settings page in the Web UI
10
+ - Enable or disable personalization
11
+ - "About you": set your nickname, occupation and bio so replies fit your background
12
+ - Combined style & tone option: `professional`, `casual`, `humorous`, `roast`, `efficient`
13
+ - Trait fine-tuning (layered on top of style & tone):
14
+ - Headings & lists: `default`, `more` (clear formatting with headings and lists), `less` (more paragraph text)
15
+ - Emoji: `default`, `more` (frequent emoji usage), `less` (minimal emoji usage)
16
+ - Output language (agent reply language + `/soul` command output language): Chinese or English
17
+ - Custom instructions
18
+ - Agent-callable tool `set_persona` to let the model adjust persona during a conversation
19
+ - Configuration persisted to disk
20
+ - Configuration synced to all active agents after every update
21
+
22
+ ## Installation
23
+
24
+ ```powershell
25
+ dsh plugin --profile web add dsh-soul
26
+ dsh --profile web web
27
+ ```
28
+
29
+ The plugin is registered via `cordis.patch.yml`:
30
+
31
+ ```yaml
32
+ - insert:
33
+ - id: soul
34
+ name: dsh-soul
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ After starting DSH, open the **Personalization** section on the settings page, edit the configuration and click **Save Settings**.
40
+
41
+ Slash commands are also available:
42
+
43
+ ```text
44
+ /soul show Show current configuration
45
+ /soul reset Reset configuration
46
+ /soul enable Enable personalization
47
+ /soul disable Disable personalization
48
+ /soul Bob Set nickname
49
+ ```
50
+
51
+ Once saved, the configuration is synced to all active agents and takes effect on the next request in the current session.
52
+
53
+ The agent can also use the `set_persona` tool to adjust your persona (nickname, style & tone, traits, reply language, custom instructions) during a conversation. The model only invokes this tool when you explicitly ask to change how it addresses or responds to you.
54
+
55
+ ## Configuration File
56
+
57
+ The configuration is stored in the DSH user data directory:
58
+
59
+ ```text
60
+ soul-config.json
61
+ ```
62
+
63
+ Example:
64
+
65
+ ```json
66
+ {
67
+ "enabled": true,
68
+ "nickname": "Bob",
69
+ "occupation": "Software Engineer",
70
+ "bio": "Interested in programming and technology",
71
+ "style": "professional",
72
+ "language": "en",
73
+ "customInstructions": "Be concise and lead with the conclusion."
74
+ }
75
+ ```
76
+
77
+ ## How It Works
78
+
79
+ The plugin compiles the nickname, style, tone and custom instructions into a system prompt via `compilePrompt()` and registers it with DSH:
80
+
81
+ ```js
82
+ spCtx.systemPrompt.section({
83
+ name: 'soul:persona',
84
+ order: 0,
85
+ text: () => compilePrompt(configCache || DEFAULT_CONFIG)
86
+ })
87
+ ```
88
+
89
+ After each update, the plugin iterates over all active agents and calls `agent.inject()` with a standard `UserMessage`:
90
+
91
+ ```js
92
+ agent.inject(createUserMessage({
93
+ content: [{ type: 'text', text: prompt }],
94
+ source: {
95
+ kind: 'plugin',
96
+ plugin: 'dsh-soul',
97
+ form: 'snapshot',
98
+ sections: [{ name: 'soul:persona', text: prompt }]
99
+ }
100
+ }))
101
+ ```
102
+
103
+ `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.
104
+
105
+ ## License
106
+
107
+ MIT License
package/client/index.mjs CHANGED
@@ -2,10 +2,8 @@
2
2
  //
3
3
  // 在设置页面添加「个性化」栏目:
4
4
  // - 启用/禁用个性化设置
5
- // - 选择回复风格
6
- // - 选择语调
5
+ // - 选择回复风格和语调
7
6
  // - 输入自定义指令
8
- // - 查看示例
9
7
  //
10
8
  // 纯 JS + React jsx-runtime 手写(不依赖 JSX 构建),开箱即用。
11
9
  // 注意:dsh web 使用 React 17+ 的 jsx-runtime,children 必须放在 props.children
@@ -41,8 +39,15 @@ window.__ModuleLoader__.load({
41
39
  const INITIAL = {
42
40
  enabled: true,
43
41
  nickname: '',
42
+ // 关于你:用户职业 / 用户介绍
43
+ occupation: '',
44
+ bio: '',
45
+ // 回复风格和语调(合并为单一选项)
44
46
  style: 'professional',
45
- tone: 'neutral',
47
+ // 特质:标题和列表 / 表情符号
48
+ headingLists: 'default',
49
+ emoji: 'default',
50
+ language: 'zh',
46
51
  customInstructions: '',
47
52
  loading: false,
48
53
  saving: false,
@@ -101,8 +106,12 @@ window.__ModuleLoader__.load({
101
106
  this.store.update(s => {
102
107
  s.enabled = payload.config.enabled
103
108
  s.nickname = payload.config.nickname || ''
109
+ s.occupation = payload.config.occupation || ''
110
+ s.bio = payload.config.bio || ''
104
111
  s.style = payload.config.style
105
- s.tone = payload.config.tone
112
+ s.headingLists = payload.config.headingLists || 'default'
113
+ s.emoji = payload.config.emoji || 'default'
114
+ s.language = payload.config.language || 'zh'
106
115
  s.customInstructions = payload.config.customInstructions
107
116
  s.loading = false
108
117
  })
@@ -135,8 +144,12 @@ window.__ModuleLoader__.load({
135
144
  this.store.update(s => {
136
145
  s.enabled = payload.config.enabled
137
146
  s.nickname = payload.config.nickname || ''
147
+ s.occupation = payload.config.occupation || ''
148
+ s.bio = payload.config.bio || ''
138
149
  s.style = payload.config.style
139
- s.tone = payload.config.tone
150
+ s.headingLists = payload.config.headingLists || 'default'
151
+ s.emoji = payload.config.emoji || 'default'
152
+ s.language = payload.config.language || 'zh'
140
153
  s.customInstructions = payload.config.customInstructions
141
154
  s.saving = false
142
155
  })
@@ -165,8 +178,12 @@ window.__ModuleLoader__.load({
165
178
  this.store.update(s => {
166
179
  s.enabled = payload.config.enabled
167
180
  s.nickname = payload.config.nickname || ''
181
+ s.occupation = payload.config.occupation || ''
182
+ s.bio = payload.config.bio || ''
168
183
  s.style = payload.config.style
169
- s.tone = payload.config.tone
184
+ s.headingLists = payload.config.headingLists || 'default'
185
+ s.emoji = payload.config.emoji || 'default'
186
+ s.language = payload.config.language || 'zh'
170
187
  s.customInstructions = payload.config.customInstructions
171
188
  s.saving = false
172
189
  })
@@ -195,16 +212,17 @@ window.__ModuleLoader__.load({
195
212
  '.soul-field label{display:block;margin-bottom:6px;font-size:13px;font-weight:500;color:var(--dsw-alias-label-secondary)}',
196
213
  '.soul-field select,.soul-field textarea,.soul-field input[type=text]{width:100%;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-l1);color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;box-sizing:border-box}',
197
214
  '.soul-field textarea{min-height:100px;resize:vertical}',
215
+ '.soul-field textarea.soul-textarea-sm{min-height:60px}',
216
+ '.soul-group-title{margin:20px 0 12px 0;padding-bottom:8px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary)}',
198
217
  '.soul-field select:focus,.soul-field textarea:focus,.soul-field input[type=text]:focus{outline:none;border-color:var(--dsw-alias-border-focus);box-shadow:0 0 0 2px var(--dsw-alias-border-focus-alpha)}',
199
218
  '.soul-toggle{display:flex;align-items:center;gap:8px;margin-bottom:16px}',
200
219
  '.soul-toggle label{margin:0;font-size:13px;color:var(--dsw-alias-label-primary)}',
201
220
  '.soul-buttons{display:flex;gap:8px;margin-top:16px}',
202
- '.soul-examples{margin-top:16px;padding:12px;background:var(--dsw-alias-bg-l2);border-radius:6px;width:100%;box-sizing:border-box}',
203
- '.soul-examples h4{margin:0 0 8px 0;font-size:13px;color:var(--dsw-alias-label-secondary)}',
204
- '.soul-example{padding:8px 12px;margin-bottom:8px;background:var(--dsw-alias-bg-l1);border-radius:4px;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-primary)}',
205
- '.soul-example:hover{background:var(--dsw-alias-interactive-bg-hover)}',
206
- '.soul-example:last-child{margin-bottom:0}',
207
221
  '.soul-error{margin-top:8px;padding:8px 12px;background:var(--dsw-alias-bg-danger);border:1px solid var(--dsw-alias-border-danger);border-radius:6px;color:var(--dsw-alias-label-danger);font-size:12px;width:100%;box-sizing:border-box}',
222
+ '.soul-hint{position:relative;display:inline-flex;align-items:center;margin-left:6px;color:var(--dsw-alias-label-secondary);cursor:help;vertical-align:middle}',
223
+ '.soul-hint:hover{color:var(--dsw-alias-label-primary)}',
224
+ '.soul-hint-tip{position:absolute;bottom:calc(100% + 8px);left:0;padding:8px 10px;background:#ffffff;border:1px solid rgba(0,0,0,0.1);border-radius:6px;font-size:12px;font-weight:400;line-height:1.5;color:rgba(0,0,0,0.85);white-space:normal;width:max-content;max-width:260px;text-align:left;opacity:0;visibility:hidden;transition:opacity .15s ease;pointer-events:none;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,0.15)}',
225
+ '.soul-hint:hover .soul-hint-tip{opacity:1;visibility:visible}',
208
226
  '.soul-status{margin-top:8px;font-size:12px;color:var(--dsw-alias-label-secondary)}',
209
227
  '.soul-toast{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:8px 16px;border-radius:6px;font-size:13px;font-weight:500;z-index:10000;box-shadow:0 2px 8px rgba(0,0,0,0.15);pointer-events:none;text-align:center;min-width:auto;white-space:nowrap}',
210
228
  '.soul-toast-success{background:#f6ffed;border:1px solid #b7eb8f;color:#52c41a}',
@@ -232,38 +250,70 @@ window.__ModuleLoader__.load({
232
250
  // 组件
233
251
  // -------------------------------------------------------------------------
234
252
 
235
- const STYLE_OPTIONS = [
253
+ // 回复风格和语调(合并为单一选项)
254
+ const STYLE_TONE_OPTIONS = [
236
255
  { value: 'professional', label: '专业严谨' },
237
256
  { value: 'casual', label: '轻松自然' },
238
- { value: 'friendly', label: '友好亲切' },
239
257
  { value: 'humorous', label: '幽默风趣' },
240
- { value: 'academic', label: '学术性' }
258
+ { value: 'roast', label: '吐槽达人' },
259
+ { value: 'efficient', label: '高效干练' }
241
260
  ]
242
261
 
243
- const TONE_OPTIONS = [
244
- { value: 'neutral', label: '中性客观' },
245
- { value: 'formal', label: '正式礼貌' },
246
- { value: 'informal', label: '非正式、口语化' },
247
- { value: 'enthusiastic', label: '热情积极' },
248
- { value: 'calm', label: '平静沉稳' }
262
+ // 特质:标题和列表
263
+ const HEADING_LISTS_OPTIONS = [
264
+ { value: 'default', label: '默认' },
265
+ { value: 'more', label: '增强(采用清晰格式和列表结构)' },
266
+ { value: 'less', label: '减弱(使用更多段落文本)' }
249
267
  ]
250
268
 
251
- const EXAMPLES = [
252
- { label: '专业严谨', value: '你是一个专业严谨的助手,采用学术性的语气,注重逻辑和准确性。' },
253
- { label: '友好亲切', value: '你是一个友好亲切的助手,采用轻松自然的语气,像朋友一样交流。' },
254
- { label: '幽默风趣', value: '你是一个幽默风趣的助手,适当使用比喻和玩笑,让对话更有趣。' },
255
- { label: '简洁直接', value: '你是一个简洁直接的助手,回答问题直击要点,避免冗余。' }
269
+ // 特质:表情符号
270
+ const EMOJI_OPTIONS = [
271
+ { value: 'default', label: '默认' },
272
+ { value: 'more', label: '增强(使用较多表情符号)' },
273
+ { value: 'less', label: '减弱(尽量减少使用表情符号)' }
256
274
  ]
257
275
 
276
+ // 选项用原生语言显示,两种 UI 语言下均自解释
277
+ const LANGUAGE_OPTIONS = [
278
+ { value: 'zh', label: '中文' },
279
+ { value: 'en', label: 'English' }
280
+ ]
281
+
282
+ // 提示词小图标:hover 展示说明文字
283
+ function SoulHint(props) {
284
+ return e('span', { className: 'soul-hint', 'aria-label': props.text },
285
+ e('svg', {
286
+ viewBox: '0 0 24 24',
287
+ width: 14,
288
+ height: 14,
289
+ fill: 'none',
290
+ stroke: 'currentColor',
291
+ strokeWidth: 2,
292
+ strokeLinecap: 'round',
293
+ strokeLinejoin: 'round',
294
+ 'aria-hidden': true
295
+ },
296
+ e('circle', { cx: 12, cy: 12, r: 10 }),
297
+ e('line', { x1: 12, y1: 16, x2: 12, y2: 12 }),
298
+ e('line', { x1: 12, y1: 8, x2: 12.01, y2: 8 })
299
+ ),
300
+ e('span', { className: 'soul-hint-tip', role: 'tooltip' }, props.text)
301
+ )
302
+ }
303
+
258
304
  function SoulSettings(props) {
259
305
  const { useSoulController, controller } = props
260
306
  const state = useSoulController((state) => state)
261
- const { enabled, nickname, style, tone, customInstructions, loading, saving, error } = state
307
+ const { enabled, nickname, occupation, bio, style, headingLists, emoji, language, customInstructions, loading, saving, error } = state
262
308
 
263
309
  const [localEnabled, setLocalEnabled] = React.useState(enabled)
264
310
  const [localNickname, setLocalNickname] = React.useState(nickname || '')
311
+ const [localOccupation, setLocalOccupation] = React.useState(occupation || '')
312
+ const [localBio, setLocalBio] = React.useState(bio || '')
265
313
  const [localStyle, setLocalStyle] = React.useState(style)
266
- const [localTone, setLocalTone] = React.useState(tone)
314
+ const [localHeadingLists, setLocalHeadingLists] = React.useState(headingLists)
315
+ const [localEmoji, setLocalEmoji] = React.useState(emoji)
316
+ const [localLanguage, setLocalLanguage] = React.useState(language || 'zh')
267
317
  const [localInstructions, setLocalInstructions] = React.useState(customInstructions)
268
318
  const [showSuccess, setShowSuccess] = React.useState(false)
269
319
  const [showResetSuccess, setShowResetSuccess] = React.useState(false)
@@ -272,10 +322,14 @@ window.__ModuleLoader__.load({
272
322
  React.useEffect(() => {
273
323
  setLocalEnabled(enabled)
274
324
  setLocalNickname(nickname || '')
325
+ setLocalOccupation(occupation || '')
326
+ setLocalBio(bio || '')
275
327
  setLocalStyle(style)
276
- setLocalTone(tone)
328
+ setLocalHeadingLists(headingLists)
329
+ setLocalEmoji(emoji)
330
+ setLocalLanguage(language || 'zh')
277
331
  setLocalInstructions(customInstructions)
278
- }, [enabled, nickname, style, tone, customInstructions])
332
+ }, [enabled, nickname, occupation, bio, style, headingLists, emoji, language, customInstructions])
279
333
 
280
334
  // 保存成功后显示提示,2秒后自动消失
281
335
  React.useEffect(() => {
@@ -298,8 +352,12 @@ window.__ModuleLoader__.load({
298
352
  await controller.saveConfig({
299
353
  enabled: localEnabled,
300
354
  nickname: localNickname,
355
+ occupation: localOccupation,
356
+ bio: localBio,
301
357
  style: localStyle,
302
- tone: localTone,
358
+ headingLists: localHeadingLists,
359
+ emoji: localEmoji,
360
+ language: localLanguage,
303
361
  customInstructions: localInstructions
304
362
  })
305
363
  setShowSuccess(true)
@@ -311,10 +369,6 @@ window.__ModuleLoader__.load({
311
369
  setShowResetSuccess(true)
312
370
  }
313
371
 
314
- const handleExampleClick = (example) => {
315
- setLocalInstructions(example.value)
316
- }
317
-
318
372
  return e('div', { className: 'soul-section' },
319
373
  e('h3', null, '个性化设置'),
320
374
  e('div', { className: 'soul-toggle' },
@@ -328,6 +382,7 @@ window.__ModuleLoader__.load({
328
382
  ),
329
383
 
330
384
  localEnabled && e(Fragment, null,
385
+ e('div', { className: 'soul-group-title' }, '关于你'),
331
386
  e('div', { className: 'soul-field' },
332
387
  e('label', { htmlFor: 'soul-nickname' }, '用户昵称'),
333
388
  e('input', {
@@ -338,52 +393,103 @@ window.__ModuleLoader__.load({
338
393
  placeholder: '输入你的昵称,回复时会称呼你'
339
394
  })
340
395
  ),
341
-
342
396
  e('div', { className: 'soul-field' },
343
- e('label', { htmlFor: 'soul-style' }, '回复风格'),
397
+ e('label', { htmlFor: 'soul-occupation' }, '用户职业'),
398
+ e('input', {
399
+ type: 'text',
400
+ id: 'soul-occupation',
401
+ value: localOccupation,
402
+ onChange: (ev) => setLocalOccupation(ev.target.value),
403
+ placeholder: '例如:软件工程师、学生、产品经理'
404
+ })
405
+ ),
406
+ e('div', { className: 'soul-field' },
407
+ e('label', { htmlFor: 'soul-bio' }, '用户介绍'),
408
+ e('textarea', {
409
+ id: 'soul-bio',
410
+ className: 'soul-textarea-sm',
411
+ value: localBio,
412
+ onChange: (ev) => setLocalBio(ev.target.value),
413
+ placeholder: '简单介绍自己,让回复更贴合你的背景'
414
+ })
415
+ ),
416
+
417
+ e('div', { className: 'soul-group-title' }, '特质'),
418
+ e('div', { className: 'soul-field' },
419
+ e('label', { htmlFor: 'soul-style' },
420
+ '回复风格和语调',
421
+ e(SoulHint, { text: '设置 Agent 回复你的风格和语调。这不会影响 Agent 的功能。' })
422
+ ),
344
423
  e('select', {
345
424
  id: 'soul-style',
346
425
  value: localStyle,
347
426
  onChange: (ev) => setLocalStyle(ev.target.value)
348
427
  },
349
- ...STYLE_OPTIONS.map(opt =>
428
+ ...STYLE_TONE_OPTIONS.map(opt =>
350
429
  e('option', { key: opt.value, value: opt.value }, opt.label)
351
430
  )
352
431
  )
353
432
  ),
354
-
433
+
355
434
  e('div', { className: 'soul-field' },
356
- e('label', { htmlFor: 'soul-tone' }, '语调'),
435
+ e('label', { htmlFor: 'soul-headingLists' },
436
+ '标题和列表',
437
+ e(SoulHint, { text: '在回复风格和语调的基础上选择额外的自定义特质项。控制回答中标题和列表的使用程度。' })
438
+ ),
357
439
  e('select', {
358
- id: 'soul-tone',
359
- value: localTone,
360
- onChange: (ev) => setLocalTone(ev.target.value)
440
+ id: 'soul-headingLists',
441
+ value: localHeadingLists,
442
+ onChange: (ev) => setLocalHeadingLists(ev.target.value)
361
443
  },
362
- ...TONE_OPTIONS.map(opt =>
444
+ ...HEADING_LISTS_OPTIONS.map(opt =>
363
445
  e('option', { key: opt.value, value: opt.value }, opt.label)
364
446
  )
365
447
  )
366
448
  ),
367
-
449
+
368
450
  e('div', { className: 'soul-field' },
369
- e('label', { htmlFor: 'soul-instructions' }, '自定义指令'),
451
+ e('label', { htmlFor: 'soul-emoji' },
452
+ '表情符号',
453
+ e(SoulHint, { text: '在回复风格和语调的基础上选择额外的自定义特质项。控制表情符号的使用程度。' })
454
+ ),
455
+ e('select', {
456
+ id: 'soul-emoji',
457
+ value: localEmoji,
458
+ onChange: (ev) => setLocalEmoji(ev.target.value)
459
+ },
460
+ ...EMOJI_OPTIONS.map(opt =>
461
+ e('option', { key: opt.value, value: opt.value }, opt.label)
462
+ )
463
+ )
464
+ ),
465
+
466
+ e('div', { className: 'soul-field' },
467
+ e('label', { htmlFor: 'soul-language' },
468
+ '输出语言',
469
+ e(SoulHint, { text: '设置 Agent 回复你使用的语言,同时影响 /soul 命令的输出语言。' })
470
+ ),
471
+ e('select', {
472
+ id: 'soul-language',
473
+ value: localLanguage,
474
+ onChange: (ev) => setLocalLanguage(ev.target.value)
475
+ },
476
+ ...LANGUAGE_OPTIONS.map(opt =>
477
+ e('option', { key: opt.value, value: opt.value }, opt.label)
478
+ )
479
+ )
480
+ ),
481
+
482
+ e('div', { className: 'soul-field' },
483
+ e('label', { htmlFor: 'soul-instructions' },
484
+ '自定义指令',
485
+ e(SoulHint, { text: '补充角色、习惯等个性化要求。建议不要重复设置回复风格和语调类似的话术,以免与上方选项冲突。' })
486
+ ),
370
487
  e('textarea', {
371
488
  id: 'soul-instructions',
372
489
  value: localInstructions,
373
490
  onChange: (ev) => setLocalInstructions(ev.target.value),
374
- placeholder: '输入自定义指令,例如:你是一个求知的人,采用专业的语气...'
491
+ placeholder: '输入自定义指令,可以是其他行为、回复风格和语调等偏好设置'
375
492
  })
376
- ),
377
-
378
- e('div', { className: 'soul-examples' },
379
- e('h4', null, '💡 示例指令(点击使用)'),
380
- ...EXAMPLES.map((example, index) =>
381
- e('div', {
382
- key: index,
383
- className: 'soul-example',
384
- onClick: () => handleExampleClick(example)
385
- }, `${example.label}:${example.value}`)
386
- )
387
493
  )
388
494
  ),
389
495
 
@@ -428,11 +534,9 @@ window.__ModuleLoader__.load({
428
534
  'settings.description': '自定义Agent回复的风格和语调',
429
535
  'button.save': '保存设置',
430
536
  'button.reset': '重置默认',
431
- 'option.style': '回复风格',
432
- 'option.tone': '语调',
537
+ 'option.style': '回复风格和语调',
433
538
  'option.instructions': '自定义指令',
434
539
  'option.enabled': '启用个性化设置',
435
- 'example.title': '💡 示例指令(点击使用)',
436
540
  'status.loading': '加载中...',
437
541
  'status.saving': '保存中...'
438
542
  }
@@ -442,11 +546,9 @@ window.__ModuleLoader__.load({
442
546
  'settings.description': 'Customize agent response style and tone',
443
547
  'button.save': 'Save Settings',
444
548
  'button.reset': 'Reset to Default',
445
- 'option.style': 'Response Style',
446
- 'option.tone': 'Tone',
549
+ 'option.style': 'Response Style & Tone',
447
550
  'option.instructions': 'Custom Instructions',
448
551
  'option.enabled': 'Enable Personalization',
449
- 'example.title': '💡 Example Instructions (click to use)',
450
552
  'status.loading': 'Loading...',
451
553
  'status.saving': 'Saving...'
452
554
  }
package/index.mjs CHANGED
@@ -27,7 +27,7 @@ function configPath() {
27
27
 
28
28
  // ==================== 三层架构 ====================
29
29
 
30
- // Identity 层:身份定义
30
+ // Identity 层:身份定义(Agent 自己的角色)
31
31
  const IdentityLayer = {
32
32
  // 角色定义
33
33
  roles: {
@@ -47,33 +47,34 @@ const IdentityLayer = {
47
47
  parts.push(config.customInstructions)
48
48
  }
49
49
 
50
- // 用户昵称
51
- if (config.nickname) {
52
- parts.push(`你的用户叫"${config.nickname}",在回复中使用这个称呼`)
53
- }
54
-
55
50
  return parts.join('\n')
56
51
  }
57
52
  }
58
53
 
54
+ // 用户背景层:关于对话用户的信息(不属于 Agent 角色设定)
55
+ function buildUserProfile(config) {
56
+ const lines = []
57
+ if (config.nickname) {
58
+ lines.push(`- 昵称:${config.nickname}(回复时使用这个称呼)`)
59
+ }
60
+ if (config.occupation) {
61
+ lines.push(`- 职业:${config.occupation}`)
62
+ }
63
+ if (config.bio) {
64
+ lines.push(`- 用户自述:${config.bio}`)
65
+ }
66
+ return lines.join('\n')
67
+ }
68
+
59
69
  // Behavior 层:行为规则
60
70
  const BehaviorLayer = {
61
- // 风格映射
71
+ // 回复风格和语调(v0.2.0 起合并为单一选项)
62
72
  styles: {
63
- professional: '专业严谨、注重逻辑和准确性',
64
- casual: '轻松自然、像朋友一样交流',
65
- friendly: '友好亲切、温暖体贴',
66
- humorous: '幽默风趣、适当使用比喻和玩笑',
67
- academic: '学术性、引用权威来源、注重术语准确性'
68
- },
69
-
70
- // 语调映射
71
- tones: {
72
- neutral: '中性客观',
73
- formal: '正式礼貌',
74
- informal: '非正式、口语化',
75
- enthusiastic: '热情积极',
76
- calm: '平静沉稳'
73
+ professional: '专业严谨、注重逻辑和准确性、正式礼貌',
74
+ casual: '轻松自然、像朋友一样交流、中性客观',
75
+ humorous: '幽默风趣、适当使用比喻和玩笑、口语化',
76
+ roast: '吐槽达人、犀利幽默、爱调侃但无恶意、口语化',
77
+ efficient: '高效干练、直击要点、简洁明了、不绕弯子'
77
78
  },
78
79
 
79
80
  // 行为规则
@@ -83,18 +84,46 @@ const BehaviorLayer = {
83
84
  '保持角色一致性,不要跳出设定'
84
85
  ],
85
86
 
87
+ // 回复语言指令(同时决定 /soul 命令输出语言)
88
+ languages: {
89
+ zh: '必须使用简体中文回复',
90
+ en: '必须使用英语(English)回复'
91
+ },
92
+
93
+ // 特质:标题和列表的使用程度
94
+ headingLists: {
95
+ more: '采用清晰的格式和列表结构组织回答,善用标题和列表',
96
+ less: '使用更多段落文本,减少标题和列表的使用'
97
+ },
98
+
99
+ // 特质:表情符号的使用程度
100
+ emoji: {
101
+ more: '在回复中使用较多表情符号',
102
+ less: '尽量减少使用表情符号'
103
+ },
104
+
86
105
  // 构建行为描述
87
106
  build(config) {
88
107
  const parts = []
89
108
 
90
- // 风格
109
+ // 回复风格和语调
91
110
  if (config.style && this.styles[config.style]) {
92
- parts.push(`回复风格:${this.styles[config.style]}`)
111
+ parts.push(`回复风格和语调:${this.styles[config.style]}`)
112
+ }
113
+
114
+ // 特质:标题和列表
115
+ if (config.headingLists && this.headingLists[config.headingLists]) {
116
+ parts.push(this.headingLists[config.headingLists])
117
+ }
118
+
119
+ // 特质:表情符号
120
+ if (config.emoji && this.emoji[config.emoji]) {
121
+ parts.push(this.emoji[config.emoji])
93
122
  }
94
123
 
95
- // 语调
96
- if (config.tone && this.tones[config.tone]) {
97
- parts.push(`语调:${this.tones[config.tone]}`)
124
+ // 回复语言
125
+ if (config.language && this.languages[config.language]) {
126
+ parts.push(this.languages[config.language])
98
127
  }
99
128
 
100
129
  return parts.join(',')
@@ -118,8 +147,6 @@ const StyleLayer = {
118
147
  // 根据风格选择格式
119
148
  if (config.style === 'casual') {
120
149
  parts.push('使用轻松的语气')
121
- } else if (config.style === 'formal') {
122
- parts.push('使用正式的语气')
123
150
  }
124
151
 
125
152
  return parts.join(',')
@@ -132,15 +159,54 @@ const StyleLayer = {
132
159
  const DEFAULT_CONFIG = {
133
160
  enabled: true,
134
161
  nickname: '',
162
+ // 关于你:用户职业 / 用户介绍
163
+ occupation: '',
164
+ bio: '',
165
+ // 回复风格和语调(v0.2.0 起合并为单一选项)
135
166
  style: 'professional',
136
- tone: 'neutral',
137
- customInstructions: '',
138
- examples: [
139
- { label: '专业严谨', value: '你是一个专业严谨的助手,采用学术性的语气,注重逻辑和准确性。' },
140
- { label: '友好亲切', value: '你是一个友好亲切的助手,采用轻松自然的语气,像朋友一样交流。' },
141
- { label: '幽默风趣', value: '你是一个幽默风趣的助手,适当使用比喻和玩笑,让对话更有趣。' },
142
- { label: '简洁直接', value: '你是一个简洁直接的助手,回答问题直击要点,避免冗余。' }
143
- ]
167
+ // 特质:标题和列表 / 表情符号(default=默认,more=增强,less=减弱)
168
+ headingLists: 'default',
169
+ emoji: 'default',
170
+ language: 'zh',
171
+ customInstructions: ''
172
+ }
173
+
174
+ // 合法的回复风格和语调取值(供工具参数校验)
175
+ const STYLE_VALUES = ['professional', 'casual', 'humorous', 'roast', 'efficient']
176
+
177
+ // 特质合法取值
178
+ const TRAIT_VALUES = ['default', 'more', 'less']
179
+
180
+ // v0.1.x 配置使用 style + tone 两个字段;v0.2.0 起合并为单一 style(回复风格和语调)。
181
+ // 迁移映射:组合命中用组合表,否则退回旧 style 表,再否则用默认值。
182
+ const LEGACY_STYLE_TONE_MAP = {
183
+ 'professional+formal': 'professional',
184
+ 'casual+neutral': 'casual',
185
+ 'humorous+informal': 'humorous'
186
+ }
187
+ const LEGACY_STYLE_MAP = {
188
+ professional: 'professional',
189
+ casual: 'casual',
190
+ friendly: 'casual',
191
+ humorous: 'humorous',
192
+ academic: 'professional'
193
+ }
194
+
195
+ function migrateConfig(raw) {
196
+ const config = { ...DEFAULT_CONFIG, ...raw }
197
+ if (raw && typeof raw.style === 'string' && typeof raw.tone === 'string') {
198
+ const combo = `${raw.style}+${raw.tone}`
199
+ config.style = LEGACY_STYLE_TONE_MAP[combo] || LEGACY_STYLE_MAP[raw.style] || DEFAULT_CONFIG.style
200
+ delete config.tone
201
+ }
202
+ // 人设预设 / 示例指令功能已在 v0.2.0 移除,丢弃历史残留字段
203
+ delete config.presets
204
+ delete config.tone
205
+ delete config.examples
206
+ // 特质字段校验:脏数据回退为默认值
207
+ if (!TRAIT_VALUES.includes(config.headingLists)) config.headingLists = 'default'
208
+ if (!TRAIT_VALUES.includes(config.emoji)) config.emoji = 'default'
209
+ return config
144
210
  }
145
211
 
146
212
  // 内存中的配置缓存
@@ -152,7 +218,7 @@ async function loadConfig() {
152
218
 
153
219
  try {
154
220
  const data = await readFile(configPath(), 'utf8')
155
- configCache = { ...DEFAULT_CONFIG, ...JSON.parse(data) }
221
+ configCache = migrateConfig(JSON.parse(data))
156
222
  } catch {
157
223
  configCache = { ...DEFAULT_CONFIG }
158
224
  }
@@ -163,9 +229,14 @@ async function loadConfig() {
163
229
  async function saveConfig(config) {
164
230
  const dir = join(configPath(), '..')
165
231
  await mkdir(dir, { recursive: true })
166
- await writeFile(configPath(), JSON.stringify(config, null, 2), 'utf8')
167
- configCache = config
168
- return config
232
+ // 过滤已废弃字段(旧客户端可能仍携带 tone / presets / examples)
233
+ const clean = { ...config }
234
+ delete clean.tone
235
+ delete clean.presets
236
+ delete clean.examples
237
+ await writeFile(configPath(), JSON.stringify(clean, null, 2), 'utf8')
238
+ configCache = clean
239
+ return clean
169
240
  }
170
241
 
171
242
  // ==================== 提示词编译器 ====================
@@ -179,9 +250,10 @@ function compilePrompt(config) {
179
250
  const identity = IdentityLayer.build(config)
180
251
  const behavior = BehaviorLayer.build(config)
181
252
  const style = StyleLayer.build(config)
253
+ const profile = buildUserProfile(config)
182
254
 
183
255
  // 如果没有任何配置,返回空
184
- if (!identity && !behavior) {
256
+ if (!identity && !behavior && !profile) {
185
257
  return ''
186
258
  }
187
259
 
@@ -193,13 +265,21 @@ function compilePrompt(config) {
193
265
  parts.push(`[角色设定] ${identity}`)
194
266
  }
195
267
 
268
+ // 用户背景:明确声明描述的是用户本人,防止模型把用户信息当成自己的身份
269
+ if (profile) {
270
+ parts.push([
271
+ '[用户背景] 以下信息描述的是与你对话的用户本人,不是你的身份;不要把用户的职业、技术背景或自述当成你自己的设定。',
272
+ profile
273
+ ].join('\n'))
274
+ }
275
+
196
276
  // 行为层
197
277
  if (behavior) {
198
278
  parts.push(`[回复风格] ${behavior}`)
199
279
  }
200
280
 
201
281
  // 执行规则
202
- parts.push(`[执行规则] 你必须在每一条回复中严格遵守以上设定,包括但不限于:代码解释、技术问答、日常闲聊、知识科普等所有场景。违反此规则视为失败。`)
282
+ parts.push(`[执行规则] 你必须在每一条回复中严格遵守以上角色设定和回复风格;涉及用户背景时,用它来理解用户、贴合用户的需求和水平作答,而不是把它当作你自己的身份。包括但不限于:代码解释、技术问答、日常闲聊、知识科普等所有场景。违反此规则视为失败。`)
203
283
 
204
284
  return parts.join('\n')
205
285
  }
@@ -216,20 +296,25 @@ function injectPromptToAllAgents(ctx, config) {
216
296
  }
217
297
 
218
298
  const prompt = compilePrompt(config)
219
- if (!prompt) return
299
+ const disabledText = [
300
+ '[dsh-soul 个性化配置已关闭]',
301
+ '从现在开始不要使用 dsh-soul 之前注入的昵称、角色、回复风格、语调、特质或输出语言配置。',
302
+ '请恢复使用 Agent 的默认行为。'
303
+ ].join('\n')
304
+ const snapshotText = prompt || disabledText
220
305
 
221
306
  for (const agent of agents.list()) {
222
307
  try {
223
308
  agent.inject(createUserMessage({
224
309
  content: [{
225
310
  type: 'text',
226
- text: [
311
+ text: prompt ? [
227
312
  '[dsh-soul 个性化配置已更新]',
228
- '以下是当前最新的个性化配置快照。',
229
- '从现在开始必须按照此配置回复,不要继续使用旧的昵称、旧的角色或旧的风格。',
313
+ '以下是当前最新的个性化配置快照(含昵称、回复风格和语调、特质、输出语言等)。',
314
+ '从现在开始必须按照此配置回复,不要继续使用旧的昵称、角色、风格、语调、特质或输出语言。',
230
315
  '',
231
316
  prompt
232
- ].join('\\n')
317
+ ].join('\n') : snapshotText
233
318
  }],
234
319
  source: {
235
320
  kind: 'plugin',
@@ -237,7 +322,7 @@ function injectPromptToAllAgents(ctx, config) {
237
322
  form: 'snapshot',
238
323
  sections: [{
239
324
  name: 'soul:persona',
240
- text: prompt
325
+ text: snapshotText
241
326
  }]
242
327
  }
243
328
  }))
@@ -295,7 +380,7 @@ function registerRoutes(ctx) {
295
380
  }
296
381
  })
297
382
 
298
- // 获取系统提示词
383
+ // 获取系统提示词(当前已保存配置)
299
384
  wsCtx.webServer.register({
300
385
  kind: 'exact',
301
386
  path: '/api/soul/prompt',
@@ -315,6 +400,8 @@ function registerRoutes(ctx) {
315
400
  }
316
401
  })
317
402
 
403
+ // 预览系统提示词端点已在 v0.2.0 移除(提示词预览功能下线)
404
+
318
405
  // 重置配置
319
406
  wsCtx.webServer.register({
320
407
  kind: 'exact',
@@ -324,24 +411,103 @@ function registerRoutes(ctx) {
324
411
  res.writeHead(status, { 'content-type': 'application/json' })
325
412
  res.end(JSON.stringify(body))
326
413
  }
327
-
414
+
328
415
  try {
329
416
  const config = await saveConfig({ ...DEFAULT_CONFIG })
330
-
417
+
331
418
  // 配置变化后更新系统提示词,并注入所有活动会话
332
419
  refreshPromptAndInject(ctx, config)
333
-
420
+
334
421
  send(200, { ok: true, config })
335
422
  } catch (err) {
336
423
  send(500, { ok: false, error: String(err.message || err) })
337
424
  }
338
425
  }
339
426
  })
427
+
428
+ // 预设管理端点已在 v0.2.0 移除(人设预设功能下线)
340
429
  })
341
430
  }
342
431
 
343
432
  // ==================== 斜杠命令 ====================
344
433
 
434
+ // 宿主端无 locale 服务(CommandInvocation 不携带 UI 语言,settings 中亦无语言字段),
435
+ // 命令输出语言跟随 soul 配置的 language 字段,可在设置页切换。
436
+ const COMMAND_MESSAGES = {
437
+ zh: {
438
+ showTitle: '🧠 个性化设置',
439
+ colon: ':',
440
+ statusLabel: '状态',
441
+ nicknameLabel: '昵称',
442
+ occupationLabel: '职业',
443
+ bioLabel: '介绍',
444
+ styleLabel: '风格语调',
445
+ styleNames: {
446
+ professional: '专业严谨',
447
+ casual: '轻松自然',
448
+ humorous: '幽默风趣',
449
+ roast: '吐槽达人',
450
+ efficient: '高效干练'
451
+ },
452
+ traitsLabel: '特质',
453
+ headingListsLabel: '标题和列表',
454
+ emojiLabel: '表情符号',
455
+ traitNames: {
456
+ default: '默认',
457
+ more: '增强',
458
+ less: '减弱'
459
+ },
460
+ instructionsLabel: '自定义指令',
461
+ enabled: '已启用',
462
+ disabled: '已禁用',
463
+ notSet: '未设置',
464
+ help: '使用 /soul show 查看详情\n使用 /soul reset 重置配置\n使用 /soul enable 启用\n使用 /soul disable 禁用\n使用 /soul 昵称xxx 设置昵称',
465
+ resetDone: '✅ 配置已重置为默认值',
466
+ enableDone: '✅ 个性化设置已启用',
467
+ disableDone: '✅ 个性化设置已禁用',
468
+ nicknameDone: (n) => `✅ 昵称已设置为:${n}`,
469
+ failed: (e) => `操作失败:${e}`
470
+ },
471
+ en: {
472
+ showTitle: '🧠 Personalization Settings',
473
+ colon: ': ',
474
+ statusLabel: 'Status',
475
+ nicknameLabel: 'Nickname',
476
+ occupationLabel: 'Occupation',
477
+ bioLabel: 'Bio',
478
+ styleLabel: 'Style & Tone',
479
+ styleNames: {
480
+ professional: 'Professional',
481
+ casual: 'Casual',
482
+ humorous: 'Humorous',
483
+ roast: 'Roast Master',
484
+ efficient: 'Efficient'
485
+ },
486
+ traitsLabel: 'Traits',
487
+ headingListsLabel: 'Headings & lists',
488
+ emojiLabel: 'Emoji',
489
+ traitNames: {
490
+ default: 'Default',
491
+ more: 'More',
492
+ less: 'Less'
493
+ },
494
+ instructionsLabel: 'Custom instructions',
495
+ enabled: 'enabled',
496
+ disabled: 'disabled',
497
+ notSet: 'not set',
498
+ help: 'Use /soul show to view details\nUse /soul reset to reset\nUse /soul enable to enable\nUse /soul disable to disable\nUse /soul <nickname> to set your nickname',
499
+ resetDone: '✅ Configuration reset to defaults',
500
+ enableDone: '✅ Personalization enabled',
501
+ disableDone: '✅ Personalization disabled',
502
+ nicknameDone: (n) => `✅ Nickname set to: ${n}`,
503
+ failed: (e) => `Operation failed: ${e}`
504
+ }
505
+ }
506
+
507
+ function commandMessages(config) {
508
+ return COMMAND_MESSAGES[config.language === 'en' ? 'en' : 'zh']
509
+ }
510
+
345
511
  function registerCommands(ctx) {
346
512
  ctx.inject(['commands'], (cmdCtx) => {
347
513
  cmdCtx.commands.register({
@@ -349,49 +515,55 @@ function registerCommands(ctx) {
349
515
  description: '查看或管理个性化设置。用法:/soul [show|reset|enable|disable|昵称]',
350
516
  input: { hint: '[show|reset|enable|disable|昵称]' },
351
517
  async handler(invocation) {
352
- const args = String(invocation.rawInput || '').trim().toLowerCase()
353
-
518
+ // 保留原始大小写用于昵称,仅关键字匹配时忽略大小写
519
+ const raw = String(invocation.rawInput || '').trim()
520
+ const args = raw.toLowerCase()
521
+
354
522
  try {
355
523
  const config = await loadConfig()
356
-
524
+ const t = commandMessages(config)
525
+
357
526
  if (args === 'show' || args === '') {
358
- const status = config.enabled ? '已启用' : '已禁用'
359
- const nickname = config.nickname || '未设置'
360
- const style = config.style || '未设置'
361
- const tone = config.tone || '未设置'
362
- const instructions = config.customInstructions || '未设置'
363
-
527
+ const status = config.enabled ? t.enabled : t.disabled
528
+ const nickname = config.nickname || t.notSet
529
+ const occupation = config.occupation || t.notSet
530
+ const bio = config.bio || t.notSet
531
+ const styleName = t.styleNames[config.style] || config.style || t.notSet
532
+ const hlName = t.traitNames[config.headingLists] || config.headingLists
533
+ const emojiName = t.traitNames[config.emoji] || config.emoji
534
+ const instructions = config.customInstructions || t.notSet
535
+
364
536
  return {
365
537
  kind: 'success',
366
- text: `🧠 个性化设置\n\n状态:${status}\n昵称:${nickname}\n风格:${style}\n语调:${tone}\n自定义指令:${instructions}\n\n使用 /soul show 查看详情\n使用 /soul reset 重置配置\n使用 /soul enable 启用\n使用 /soul disable 禁用\n使用 /soul 昵称xxx 设置昵称`
538
+ text: `${t.showTitle}\n\n${t.statusLabel}${t.colon}${status}\n${t.nicknameLabel}${t.colon}${nickname}\n${t.occupationLabel}${t.colon}${occupation}\n${t.bioLabel}${t.colon}${bio}\n${t.styleLabel}${t.colon}${styleName}\n${t.traitsLabel}${t.colon}${t.headingListsLabel}=${hlName},${t.emojiLabel}=${emojiName}\n${t.instructionsLabel}${t.colon}${instructions}\n\n${t.help}`
367
539
  }
368
540
  } else if (args === 'reset') {
369
541
  const updated = await saveConfig({ ...DEFAULT_CONFIG })
370
542
  // 更新系统提示词,并注入所有活动会话
371
543
  refreshPromptAndInject(ctx, updated)
372
- return { kind: 'success', text: '✅ 配置已重置为默认值' }
544
+ return { kind: 'success', text: t.resetDone }
373
545
  } else if (args === 'enable') {
374
546
  config.enabled = true
375
547
  const updated = await saveConfig(config)
376
548
  // 更新系统提示词,并注入所有活动会话
377
549
  refreshPromptAndInject(ctx, updated)
378
- return { kind: 'success', text: '✅ 个性化设置已启用' }
550
+ return { kind: 'success', text: t.enableDone }
379
551
  } else if (args === 'disable') {
380
552
  config.enabled = false
381
553
  const updated = await saveConfig(config)
382
554
  // 更新系统提示词,并注入所有活动会话
383
555
  refreshPromptAndInject(ctx, updated)
384
- return { kind: 'success', text: '✅ 个性化设置已禁用' }
556
+ return { kind: 'success', text: t.disableDone }
385
557
  } else {
386
- // 设置昵称
387
- config.nickname = args
558
+ // 设置昵称(保留原始大小写,不做 toLowerCase)
559
+ config.nickname = raw
388
560
  const updated = await saveConfig(config)
389
561
  // 更新系统提示词,并注入所有活动会话
390
562
  refreshPromptAndInject(ctx, updated)
391
- return { kind: 'success', text: `✅ 昵称已设置为:${args}` }
563
+ return { kind: 'success', text: t.nicknameDone(raw) }
392
564
  }
393
565
  } catch (err) {
394
- return { kind: 'error', text: `操作失败:${err.message}` }
566
+ return { kind: 'error', text: commandMessages(await loadConfig()).failed(err.message) }
395
567
  }
396
568
  }
397
569
  })
@@ -443,6 +615,120 @@ function registerSystemPrompt(ctx) {
443
615
  })
444
616
  }
445
617
 
618
+ // ==================== Agent 可调用工具 ====================
619
+
620
+ // tools 是可选 host 服务且 ABI 版本敏感(同 dsh-share-page 的守卫)。
621
+ // 动态 import + 守卫:@deepseek-ai/dsh-tools 不可解析或旧 ABI(缺 TOOL_RUNTIME_SCHEDULER
622
+ // symbol)时跳过工具注册——插件照常激活,Web UI / 命令 / 路由不受影响。
623
+ function registerTools(ctx) {
624
+ ctx.inject(['tools'], (toolsCtx) => {
625
+ import('@deepseek-ai/dsh-tools').then(({ defineTool, TOOL_RUNTIME_SCHEDULER }) => {
626
+ if (typeof TOOL_RUNTIME_SCHEDULER !== 'symbol') {
627
+ throw new Error('dsh-soul: resolved @deepseek-ai/dsh-tools lacks TOOL_RUNTIME_SCHEDULER — requires ^0.1.0-rc.6')
628
+ }
629
+ toolsCtx.tools.register(defineTool({
630
+ name: 'set_persona',
631
+ description:
632
+ '调整当前个性化人设(昵称、回复风格和语调、自定义指令)。' +
633
+ '当用户明确要求改变称呼、语气、风格或角色时使用。' +
634
+ '只需要传入要修改的字段,未提供的字段保持不变。',
635
+ parameters: {
636
+ nickname: { type: 'string', description: '用户昵称,回复时会使用这个称呼。' },
637
+ occupation: { type: 'string', description: '用户的职业。' },
638
+ bio: { type: 'string', description: '关于用户的介绍。' },
639
+ style: {
640
+ type: 'string',
641
+ enum: STYLE_VALUES,
642
+ description: '回复风格和语调:professional=专业严谨,casual=轻松自然,humorous=幽默风趣,roast=吐槽达人,efficient=高效干练。'
643
+ },
644
+ language: {
645
+ type: 'string',
646
+ enum: ['zh', 'en'],
647
+ description: '回复语言:zh=简体中文,en=English。'
648
+ },
649
+ headingLists: {
650
+ type: 'string',
651
+ enum: TRAIT_VALUES,
652
+ description: '特质·标题和列表:default=默认,more=增强(采用清晰格式和列表结构),less=减弱(使用更多段落文本)。'
653
+ },
654
+ emoji: {
655
+ type: 'string',
656
+ enum: TRAIT_VALUES,
657
+ description: '特质·表情符号:default=默认,more=增强(使用较多表情符号),less=减弱(尽量减少使用表情符号)。'
658
+ },
659
+ customInstructions: { type: 'string', description: '额外的自定义指令,用于覆盖或补充当前人设。' },
660
+ },
661
+ output: {
662
+ schema: {
663
+ type: 'object',
664
+ additionalProperties: false,
665
+ required: ['ok'],
666
+ properties: {
667
+ ok: { type: 'boolean' },
668
+ message: { type: 'string' },
669
+ changes: {
670
+ type: 'object',
671
+ description: '实际发生变化的字段',
672
+ properties: {
673
+ nickname: { type: 'string' },
674
+ occupation: { type: 'string' },
675
+ bio: { type: 'string' },
676
+ style: { type: 'string' },
677
+ language: { type: 'string' },
678
+ headingLists: { type: 'string' },
679
+ emoji: { type: 'string' },
680
+ customInstructions: { type: 'string' },
681
+ },
682
+ },
683
+ },
684
+ },
685
+ },
686
+ async execute(args) {
687
+ try {
688
+ const current = await loadConfig()
689
+ const patch = {}
690
+ if (typeof args.nickname === 'string' && args.nickname !== current.nickname) {
691
+ patch.nickname = args.nickname
692
+ }
693
+ if (typeof args.occupation === 'string' && args.occupation !== current.occupation) {
694
+ patch.occupation = args.occupation
695
+ }
696
+ if (typeof args.bio === 'string' && args.bio !== current.bio) {
697
+ patch.bio = args.bio
698
+ }
699
+ if (typeof args.style === 'string' && STYLE_VALUES.includes(args.style) && args.style !== current.style) {
700
+ patch.style = args.style
701
+ }
702
+ if (typeof args.language === 'string' && ['zh', 'en'].includes(args.language) && args.language !== current.language) {
703
+ patch.language = args.language
704
+ }
705
+ if (typeof args.headingLists === 'string' && TRAIT_VALUES.includes(args.headingLists) && args.headingLists !== current.headingLists) {
706
+ patch.headingLists = args.headingLists
707
+ }
708
+ if (typeof args.emoji === 'string' && TRAIT_VALUES.includes(args.emoji) && args.emoji !== current.emoji) {
709
+ patch.emoji = args.emoji
710
+ }
711
+ if (typeof args.customInstructions === 'string' && args.customInstructions !== current.customInstructions) {
712
+ patch.customInstructions = args.customInstructions
713
+ }
714
+ if (Object.keys(patch).length === 0) {
715
+ return { ok: true, message: '没有要更改的设置' }
716
+ }
717
+ const updated = { ...current, ...patch }
718
+ await saveConfig(updated)
719
+ refreshPromptAndInject(ctx, updated)
720
+ return { ok: true, message: '已更新个性化设置', changes: patch }
721
+ } catch (err) {
722
+ return { ok: false, message: String(err.message || err) }
723
+ }
724
+ },
725
+ }))
726
+ }).catch((err) => {
727
+ toolsCtx.logger?.warn?.('[dsh-soul] set_persona tool not registered: ' + String((err && err.message) || err))
728
+ })
729
+ })
730
+ }
731
+
446
732
  // ==================== 主入口 ====================
447
733
 
448
734
  export async function apply(ctx) {
@@ -472,6 +758,7 @@ export async function apply(ctx) {
472
758
  registerRoutes(ctx)
473
759
  registerCommands(ctx)
474
760
  registerSystemPrompt(ctx)
761
+ registerTools(ctx)
475
762
  }
476
763
 
477
764
  export { name }
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "dsh-soul",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "为 DeepSeek Harness 添加个性化设置",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
7
7
  "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Aliuyanfeng/dsh-soul.git"
11
+ },
12
+ "dependencies": {
13
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
14
+ },
8
15
  "peerDependencies": {
9
16
  "@deepseek-ai/cordis": "^4.0.1",
10
17
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2"
@@ -33,6 +40,7 @@
33
40
  "index.mjs",
34
41
  "client",
35
42
  "cordis.patch.yml",
36
- "README.md"
43
+ "README.md",
44
+ "README_EN.md"
37
45
  ]
38
46
  }