dsh-soul 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/client/index.mjs +570 -0
- package/cordis.patch.yml +3 -0
- package/index.mjs +477 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# dsh-soul
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness 个性化设置插件,用于配置 Agent 的昵称、回复风格、语调和自定义指令。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- Web UI 个性化设置页面
|
|
8
|
+
- 启用或禁用个性化设置
|
|
9
|
+
- 设置用户昵称
|
|
10
|
+
- 选择回复风格:`professional`、`casual`、`friendly`、`humorous`、`academic`
|
|
11
|
+
- 选择语调:`neutral`、`formal`、`informal`、`enthusiastic`、`calm`
|
|
12
|
+
- 输入自定义指令和使用示例模板
|
|
13
|
+
- 配置持久化保存
|
|
14
|
+
- 配置更新后同步到所有活动 Agent
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```powershell
|
|
19
|
+
dsh plugin --profile web add dsh-soul
|
|
20
|
+
dsh --profile web web
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
插件配置由 `cordis.patch.yml` 提供:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
- insert:
|
|
27
|
+
- id: soul
|
|
28
|
+
name: dsh-soul
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 使用
|
|
32
|
+
|
|
33
|
+
启动 DSH 后,进入设置页面中的「个性化设置」栏目,修改配置并点击「保存设置」。
|
|
34
|
+
|
|
35
|
+
也可以使用斜杠命令:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
/soul show 查看当前配置
|
|
39
|
+
/soul reset 重置配置
|
|
40
|
+
/soul enable 启用个性化设置
|
|
41
|
+
/soul disable 禁用个性化设置
|
|
42
|
+
/soul 小明 设置昵称
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
配置保存后会同步到所有活动 Agent,当前会话下一次请求即可使用最新配置。
|
|
46
|
+
|
|
47
|
+
## 配置文件
|
|
48
|
+
|
|
49
|
+
配置文件位置:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
$DSH_HOME/soul-config.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
未设置 `DSH_HOME` 时使用:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
~/.dsh/soul-config.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
配置示例:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"enabled": true,
|
|
66
|
+
"nickname": "小明",
|
|
67
|
+
"style": "professional",
|
|
68
|
+
"tone": "neutral",
|
|
69
|
+
"customInstructions": "请保持简洁,优先给出结论。"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 实现原理
|
|
74
|
+
|
|
75
|
+
插件通过 `compilePrompt()` 将昵称、风格、语调和自定义指令编译成 system prompt,并注册到 DSH:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
spCtx.systemPrompt.section({
|
|
79
|
+
name: 'soul:persona',
|
|
80
|
+
order: 0,
|
|
81
|
+
text: () => compilePrompt(configCache || DEFAULT_CONFIG)
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
配置更新后,插件会遍历所有活动 Agent,使用标准 `UserMessage` 调用 `agent.inject()`:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
agent.inject(createUserMessage({
|
|
89
|
+
content: [{ type: 'text', text: prompt }],
|
|
90
|
+
source: {
|
|
91
|
+
kind: 'plugin',
|
|
92
|
+
plugin: 'dsh-soul',
|
|
93
|
+
form: 'snapshot',
|
|
94
|
+
sections: [{ name: 'soul:persona', text: prompt }]
|
|
95
|
+
}
|
|
96
|
+
}))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`agent.inject()` 会将最新配置放入 Agent 的待处理上下文,在下一次请求中生效;不会主动触发新请求,也不会修改历史消息。
|
|
100
|
+
|
|
101
|
+
## 许可证
|
|
102
|
+
|
|
103
|
+
MIT License
|
package/client/index.mjs
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
// client/index.mjs — dsh-soul Web UI 插件
|
|
2
|
+
//
|
|
3
|
+
// 在设置页面添加「个性化」栏目:
|
|
4
|
+
// - 启用/禁用个性化设置
|
|
5
|
+
// - 选择回复风格
|
|
6
|
+
// - 选择语调
|
|
7
|
+
// - 输入自定义指令
|
|
8
|
+
// - 查看示例
|
|
9
|
+
//
|
|
10
|
+
// 纯 JS + React jsx-runtime 手写(不依赖 JSX 构建),开箱即用。
|
|
11
|
+
// 注意:dsh web 使用 React 17+ 的 jsx-runtime,children 必须放在 props.children
|
|
12
|
+
// 里;不能把 children 当第三个参数传(第三个参数是 key)。
|
|
13
|
+
|
|
14
|
+
window.__ModuleLoader__.load({
|
|
15
|
+
id: 'dsh-soul',
|
|
16
|
+
factory: (require) => {
|
|
17
|
+
const module = { exports: {} }
|
|
18
|
+
const exports = module.exports
|
|
19
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
20
|
+
|
|
21
|
+
// react/jsx-runtime:官方 client bundle 使用 jsx/jsxs/Fragment。
|
|
22
|
+
// 我们用一个小包装 e() 让 children 写法保持直观,同时符合 jsx-runtime 约定。
|
|
23
|
+
const { jsx: h, jsxs: hs, Fragment } = require('react/jsx-runtime')
|
|
24
|
+
const React = require('react')
|
|
25
|
+
const ui = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
26
|
+
|
|
27
|
+
// 包装:把多余的参数收集成 props.children,避免直接调用 h(type, props, child)
|
|
28
|
+
// 时被当成 key 参数。
|
|
29
|
+
const e = (type, props, ...children) => {
|
|
30
|
+
const p = props || {}
|
|
31
|
+
if (children.length === 0) return h(type, p)
|
|
32
|
+
if (children.length === 1) return h(type, { ...p, children: children[0] })
|
|
33
|
+
return hs(type, { ...p, children })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// -------------------------------------------------------------------------
|
|
37
|
+
// 状态与控制器
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
const NS = 'soul'
|
|
41
|
+
const INITIAL = {
|
|
42
|
+
enabled: true,
|
|
43
|
+
nickname: '',
|
|
44
|
+
style: 'professional',
|
|
45
|
+
tone: 'neutral',
|
|
46
|
+
customInstructions: '',
|
|
47
|
+
loading: false,
|
|
48
|
+
saving: false,
|
|
49
|
+
error: null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 第三方插件不能 require('@deepseek-ai/dsh-client-runtime/client')——
|
|
53
|
+
// dsh web 的 module table 只 seed 9 个 platform 模块,runtime/client 不在里面。
|
|
54
|
+
// 第三方插件必须自实现 SnapshotStore。
|
|
55
|
+
const createSnapshotStore = (init) => {
|
|
56
|
+
let state = init
|
|
57
|
+
const listeners = new Set()
|
|
58
|
+
const notify = () => { for (const fn of [...listeners]) fn() }
|
|
59
|
+
return {
|
|
60
|
+
getSnapshot: () => state,
|
|
61
|
+
subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn) },
|
|
62
|
+
update: (mutator) => {
|
|
63
|
+
const next = { ...state }
|
|
64
|
+
mutator(next)
|
|
65
|
+
state = next
|
|
66
|
+
notify()
|
|
67
|
+
},
|
|
68
|
+
set: (next) => { state = next; notify() },
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hostBase() {
|
|
73
|
+
const origin = globalThis.location?.origin
|
|
74
|
+
return origin !== void 0 && origin !== 'null' ? origin : 'http://dsh.internal'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function messageOf(error) {
|
|
78
|
+
return error instanceof Error ? error.message : String(error)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var SoulController = class {
|
|
82
|
+
constructor(fetcher = (input, init) => fetch(input, init)) {
|
|
83
|
+
this.fetcher = fetcher
|
|
84
|
+
this.store = createSnapshotStore(INITIAL)
|
|
85
|
+
this.disposed = false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async loadConfig() {
|
|
89
|
+
if (this.disposed) return
|
|
90
|
+
this.store.update(s => { s.loading = true; s.error = null })
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const url = new URL('/api/soul/config', hostBase())
|
|
94
|
+
const response = await this.fetcher(url)
|
|
95
|
+
const payload = await response.json().catch(() => ({}))
|
|
96
|
+
|
|
97
|
+
if (!response.ok || payload.ok !== true) {
|
|
98
|
+
throw new Error(payload.error || `HTTP ${response.status}`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.store.update(s => {
|
|
102
|
+
s.enabled = payload.config.enabled
|
|
103
|
+
s.nickname = payload.config.nickname || ''
|
|
104
|
+
s.style = payload.config.style
|
|
105
|
+
s.tone = payload.config.tone
|
|
106
|
+
s.customInstructions = payload.config.customInstructions
|
|
107
|
+
s.loading = false
|
|
108
|
+
})
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (this.disposed) return
|
|
111
|
+
this.store.update(s => {
|
|
112
|
+
s.loading = false
|
|
113
|
+
s.error = messageOf(error)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async saveConfig(config) {
|
|
119
|
+
if (this.disposed) return
|
|
120
|
+
this.store.update(s => { s.saving = true; s.error = null })
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const url = new URL('/api/soul/config', hostBase())
|
|
124
|
+
const response = await this.fetcher(url, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'content-type': 'application/json' },
|
|
127
|
+
body: JSON.stringify(config)
|
|
128
|
+
})
|
|
129
|
+
const payload = await response.json().catch(() => ({}))
|
|
130
|
+
|
|
131
|
+
if (!response.ok || payload.ok !== true) {
|
|
132
|
+
throw new Error(payload.error || `HTTP ${response.status}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.store.update(s => {
|
|
136
|
+
s.enabled = payload.config.enabled
|
|
137
|
+
s.nickname = payload.config.nickname || ''
|
|
138
|
+
s.style = payload.config.style
|
|
139
|
+
s.tone = payload.config.tone
|
|
140
|
+
s.customInstructions = payload.config.customInstructions
|
|
141
|
+
s.saving = false
|
|
142
|
+
})
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (this.disposed) return
|
|
145
|
+
this.store.update(s => {
|
|
146
|
+
s.saving = false
|
|
147
|
+
s.error = messageOf(error)
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async resetConfig() {
|
|
153
|
+
if (this.disposed) return
|
|
154
|
+
this.store.update(s => { s.saving = true; s.error = null })
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const url = new URL('/api/soul/config/reset', hostBase())
|
|
158
|
+
const response = await this.fetcher(url, { method: 'POST' })
|
|
159
|
+
const payload = await response.json().catch(() => ({}))
|
|
160
|
+
|
|
161
|
+
if (!response.ok || payload.ok !== true) {
|
|
162
|
+
throw new Error(payload.error || `HTTP ${response.status}`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.store.update(s => {
|
|
166
|
+
s.enabled = payload.config.enabled
|
|
167
|
+
s.nickname = payload.config.nickname || ''
|
|
168
|
+
s.style = payload.config.style
|
|
169
|
+
s.tone = payload.config.tone
|
|
170
|
+
s.customInstructions = payload.config.customInstructions
|
|
171
|
+
s.saving = false
|
|
172
|
+
})
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (this.disposed) return
|
|
175
|
+
this.store.update(s => {
|
|
176
|
+
s.saving = false
|
|
177
|
+
s.error = messageOf(error)
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async dispose() {
|
|
183
|
+
this.disposed = true
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// -------------------------------------------------------------------------
|
|
188
|
+
// 样式(data-plugin-css 注入,避免与宿主样式冲突)
|
|
189
|
+
// -------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
const css = [
|
|
192
|
+
'.soul-section{margin:24px 0;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-l1)}',
|
|
193
|
+
'.soul-section h3{margin:0 0 16px 0;font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
194
|
+
'.soul-field{margin-bottom:16px;width:100%}',
|
|
195
|
+
'.soul-field label{display:block;margin-bottom:6px;font-size:13px;font-weight:500;color:var(--dsw-alias-label-secondary)}',
|
|
196
|
+
'.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
|
+
'.soul-field textarea{min-height:100px;resize:vertical}',
|
|
198
|
+
'.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
|
+
'.soul-toggle{display:flex;align-items:center;gap:8px;margin-bottom:16px}',
|
|
200
|
+
'.soul-toggle label{margin:0;font-size:13px;color:var(--dsw-alias-label-primary)}',
|
|
201
|
+
'.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
|
+
'.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}',
|
|
208
|
+
'.soul-status{margin-top:8px;font-size:12px;color:var(--dsw-alias-label-secondary)}',
|
|
209
|
+
'.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
|
+
'.soul-toast-success{background:#f6ffed;border:1px solid #b7eb8f;color:#52c41a}',
|
|
211
|
+
'@keyframes fadeInOut{0%{opacity:0;transform:translate(-50%,-50%) scale(0.9)}15%{opacity:1;transform:translate(-50%,-50%) scale(1)}85%{opacity:1;transform:translate(-50%,-50%) scale(1)}100%{opacity:0;transform:translate(-50%,-50%) scale(0.9)}}'
|
|
212
|
+
].join('')
|
|
213
|
+
|
|
214
|
+
const tagId = 'dsh-soul/styles.css'
|
|
215
|
+
let styleTagMissing = false
|
|
216
|
+
if (typeof document !== 'undefined') {
|
|
217
|
+
try {
|
|
218
|
+
styleTagMissing = document.querySelector(`style[data-plugin-css="${tagId}"]`) === null
|
|
219
|
+
} catch {
|
|
220
|
+
styleTagMissing = true
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (styleTagMissing) {
|
|
224
|
+
const tag = document.createElement('style')
|
|
225
|
+
tag.dataset.plugin = 'dsh-soul'
|
|
226
|
+
tag.dataset.pluginCss = tagId
|
|
227
|
+
tag.textContent = css
|
|
228
|
+
document.head.appendChild(tag)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// -------------------------------------------------------------------------
|
|
232
|
+
// 组件
|
|
233
|
+
// -------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
const STYLE_OPTIONS = [
|
|
236
|
+
{ value: 'professional', label: '专业严谨' },
|
|
237
|
+
{ value: 'casual', label: '轻松自然' },
|
|
238
|
+
{ value: 'friendly', label: '友好亲切' },
|
|
239
|
+
{ value: 'humorous', label: '幽默风趣' },
|
|
240
|
+
{ value: 'academic', label: '学术性' }
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
const TONE_OPTIONS = [
|
|
244
|
+
{ value: 'neutral', label: '中性客观' },
|
|
245
|
+
{ value: 'formal', label: '正式礼貌' },
|
|
246
|
+
{ value: 'informal', label: '非正式、口语化' },
|
|
247
|
+
{ value: 'enthusiastic', label: '热情积极' },
|
|
248
|
+
{ value: 'calm', label: '平静沉稳' }
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
const EXAMPLES = [
|
|
252
|
+
{ label: '专业严谨', value: '你是一个专业严谨的助手,采用学术性的语气,注重逻辑和准确性。' },
|
|
253
|
+
{ label: '友好亲切', value: '你是一个友好亲切的助手,采用轻松自然的语气,像朋友一样交流。' },
|
|
254
|
+
{ label: '幽默风趣', value: '你是一个幽默风趣的助手,适当使用比喻和玩笑,让对话更有趣。' },
|
|
255
|
+
{ label: '简洁直接', value: '你是一个简洁直接的助手,回答问题直击要点,避免冗余。' }
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
function SoulSettings(props) {
|
|
259
|
+
const { useSoulController, controller } = props
|
|
260
|
+
const state = useSoulController((state) => state)
|
|
261
|
+
const { enabled, nickname, style, tone, customInstructions, loading, saving, error } = state
|
|
262
|
+
|
|
263
|
+
const [localEnabled, setLocalEnabled] = React.useState(enabled)
|
|
264
|
+
const [localNickname, setLocalNickname] = React.useState(nickname || '')
|
|
265
|
+
const [localStyle, setLocalStyle] = React.useState(style)
|
|
266
|
+
const [localTone, setLocalTone] = React.useState(tone)
|
|
267
|
+
const [localInstructions, setLocalInstructions] = React.useState(customInstructions)
|
|
268
|
+
const [showSuccess, setShowSuccess] = React.useState(false)
|
|
269
|
+
const [showResetSuccess, setShowResetSuccess] = React.useState(false)
|
|
270
|
+
|
|
271
|
+
// 同步状态
|
|
272
|
+
React.useEffect(() => {
|
|
273
|
+
setLocalEnabled(enabled)
|
|
274
|
+
setLocalNickname(nickname || '')
|
|
275
|
+
setLocalStyle(style)
|
|
276
|
+
setLocalTone(tone)
|
|
277
|
+
setLocalInstructions(customInstructions)
|
|
278
|
+
}, [enabled, nickname, style, tone, customInstructions])
|
|
279
|
+
|
|
280
|
+
// 保存成功后显示提示,2秒后自动消失
|
|
281
|
+
React.useEffect(() => {
|
|
282
|
+
if (showSuccess) {
|
|
283
|
+
const timer = setTimeout(() => setShowSuccess(false), 2000)
|
|
284
|
+
return () => clearTimeout(timer)
|
|
285
|
+
}
|
|
286
|
+
}, [showSuccess])
|
|
287
|
+
|
|
288
|
+
// 重置成功后显示提示,2秒后自动消失
|
|
289
|
+
React.useEffect(() => {
|
|
290
|
+
if (showResetSuccess) {
|
|
291
|
+
const timer = setTimeout(() => setShowResetSuccess(false), 2000)
|
|
292
|
+
return () => clearTimeout(timer)
|
|
293
|
+
}
|
|
294
|
+
}, [showResetSuccess])
|
|
295
|
+
|
|
296
|
+
const handleSave = async () => {
|
|
297
|
+
const controller = props.controller
|
|
298
|
+
await controller.saveConfig({
|
|
299
|
+
enabled: localEnabled,
|
|
300
|
+
nickname: localNickname,
|
|
301
|
+
style: localStyle,
|
|
302
|
+
tone: localTone,
|
|
303
|
+
customInstructions: localInstructions
|
|
304
|
+
})
|
|
305
|
+
setShowSuccess(true)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const handleReset = async () => {
|
|
309
|
+
const controller = props.controller
|
|
310
|
+
await controller.resetConfig()
|
|
311
|
+
setShowResetSuccess(true)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const handleExampleClick = (example) => {
|
|
315
|
+
setLocalInstructions(example.value)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return e('div', { className: 'soul-section' },
|
|
319
|
+
e('h3', null, '个性化设置'),
|
|
320
|
+
e('div', { className: 'soul-toggle' },
|
|
321
|
+
e('input', {
|
|
322
|
+
type: 'checkbox',
|
|
323
|
+
id: 'soul-enabled',
|
|
324
|
+
checked: localEnabled,
|
|
325
|
+
onChange: (ev) => setLocalEnabled(ev.target.checked)
|
|
326
|
+
}),
|
|
327
|
+
e('label', { htmlFor: 'soul-enabled' }, '启用个性化设置')
|
|
328
|
+
),
|
|
329
|
+
|
|
330
|
+
localEnabled && e(Fragment, null,
|
|
331
|
+
e('div', { className: 'soul-field' },
|
|
332
|
+
e('label', { htmlFor: 'soul-nickname' }, '用户昵称'),
|
|
333
|
+
e('input', {
|
|
334
|
+
type: 'text',
|
|
335
|
+
id: 'soul-nickname',
|
|
336
|
+
value: localNickname,
|
|
337
|
+
onChange: (ev) => setLocalNickname(ev.target.value),
|
|
338
|
+
placeholder: '输入你的昵称,回复时会称呼你'
|
|
339
|
+
})
|
|
340
|
+
),
|
|
341
|
+
|
|
342
|
+
e('div', { className: 'soul-field' },
|
|
343
|
+
e('label', { htmlFor: 'soul-style' }, '回复风格'),
|
|
344
|
+
e('select', {
|
|
345
|
+
id: 'soul-style',
|
|
346
|
+
value: localStyle,
|
|
347
|
+
onChange: (ev) => setLocalStyle(ev.target.value)
|
|
348
|
+
},
|
|
349
|
+
...STYLE_OPTIONS.map(opt =>
|
|
350
|
+
e('option', { key: opt.value, value: opt.value }, opt.label)
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
),
|
|
354
|
+
|
|
355
|
+
e('div', { className: 'soul-field' },
|
|
356
|
+
e('label', { htmlFor: 'soul-tone' }, '语调'),
|
|
357
|
+
e('select', {
|
|
358
|
+
id: 'soul-tone',
|
|
359
|
+
value: localTone,
|
|
360
|
+
onChange: (ev) => setLocalTone(ev.target.value)
|
|
361
|
+
},
|
|
362
|
+
...TONE_OPTIONS.map(opt =>
|
|
363
|
+
e('option', { key: opt.value, value: opt.value }, opt.label)
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
),
|
|
367
|
+
|
|
368
|
+
e('div', { className: 'soul-field' },
|
|
369
|
+
e('label', { htmlFor: 'soul-instructions' }, '自定义指令'),
|
|
370
|
+
e('textarea', {
|
|
371
|
+
id: 'soul-instructions',
|
|
372
|
+
value: localInstructions,
|
|
373
|
+
onChange: (ev) => setLocalInstructions(ev.target.value),
|
|
374
|
+
placeholder: '输入自定义指令,例如:你是一个求知的人,采用专业的语气...'
|
|
375
|
+
})
|
|
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
|
+
)
|
|
388
|
+
),
|
|
389
|
+
|
|
390
|
+
error && e('div', { className: 'soul-error' }, error),
|
|
391
|
+
|
|
392
|
+
e('div', { className: 'soul-buttons' },
|
|
393
|
+
e(ui.Button, {
|
|
394
|
+
variant: 'primary',
|
|
395
|
+
onClick: handleSave,
|
|
396
|
+
disabled: saving
|
|
397
|
+
}, saving ? '保存中...' : '保存设置'),
|
|
398
|
+
e(ui.Button, {
|
|
399
|
+
onClick: handleReset,
|
|
400
|
+
disabled: saving
|
|
401
|
+
}, '重置默认')
|
|
402
|
+
),
|
|
403
|
+
|
|
404
|
+
loading && e('div', { className: 'soul-status' }, '加载中...'),
|
|
405
|
+
|
|
406
|
+
showSuccess && e('div', {
|
|
407
|
+
className: 'soul-toast soul-toast-success',
|
|
408
|
+
style: {
|
|
409
|
+
animation: 'fadeInOut 2s ease-in-out'
|
|
410
|
+
}
|
|
411
|
+
}, '✅ 设置已保存'),
|
|
412
|
+
|
|
413
|
+
showResetSuccess && e('div', {
|
|
414
|
+
className: 'soul-toast soul-toast-success',
|
|
415
|
+
style: {
|
|
416
|
+
animation: 'fadeInOut 2s ease-in-out'
|
|
417
|
+
}
|
|
418
|
+
}, '✅ 已重置为默认值')
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// -------------------------------------------------------------------------
|
|
423
|
+
// 文案
|
|
424
|
+
// -------------------------------------------------------------------------
|
|
425
|
+
|
|
426
|
+
const zh = {
|
|
427
|
+
'settings.title': '个性化设置',
|
|
428
|
+
'settings.description': '自定义Agent回复的风格和语调',
|
|
429
|
+
'button.save': '保存设置',
|
|
430
|
+
'button.reset': '重置默认',
|
|
431
|
+
'option.style': '回复风格',
|
|
432
|
+
'option.tone': '语调',
|
|
433
|
+
'option.instructions': '自定义指令',
|
|
434
|
+
'option.enabled': '启用个性化设置',
|
|
435
|
+
'example.title': '💡 示例指令(点击使用)',
|
|
436
|
+
'status.loading': '加载中...',
|
|
437
|
+
'status.saving': '保存中...'
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const en = {
|
|
441
|
+
'settings.title': 'Personalization Settings',
|
|
442
|
+
'settings.description': 'Customize agent response style and tone',
|
|
443
|
+
'button.save': 'Save Settings',
|
|
444
|
+
'button.reset': 'Reset to Default',
|
|
445
|
+
'option.style': 'Response Style',
|
|
446
|
+
'option.tone': 'Tone',
|
|
447
|
+
'option.instructions': 'Custom Instructions',
|
|
448
|
+
'option.enabled': 'Enable Personalization',
|
|
449
|
+
'example.title': '💡 Example Instructions (click to use)',
|
|
450
|
+
'status.loading': 'Loading...',
|
|
451
|
+
'status.saving': 'Saving...'
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// -------------------------------------------------------------------------
|
|
455
|
+
// 插件装配
|
|
456
|
+
// -------------------------------------------------------------------------
|
|
457
|
+
|
|
458
|
+
const inject = ['slots', 'locale']
|
|
459
|
+
|
|
460
|
+
function apply(ctx) {
|
|
461
|
+
const controller = new SoulController()
|
|
462
|
+
ctx.provide('soulController', controller)
|
|
463
|
+
ctx.effect(() => () => {
|
|
464
|
+
controller.dispose()
|
|
465
|
+
}, 'dsh-soul: browser lifecycle')
|
|
466
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-soul: dictionaries')
|
|
467
|
+
|
|
468
|
+
// DSH 0.1.x 不支持 settings.section 的 icon 字段
|
|
469
|
+
// 使用 DOM 操作动态替换图标(类似 dsh-better-sidebar 的方案)
|
|
470
|
+
const SOUL_NAV_MARKER = 'data-soul-settings-nav'
|
|
471
|
+
const SOUL_ICON_MARKER = 'data-soul-icon-replaced'
|
|
472
|
+
|
|
473
|
+
const registerSettingsNavIcon = () => {
|
|
474
|
+
let disposed = false
|
|
475
|
+
|
|
476
|
+
const sync = () => {
|
|
477
|
+
if (disposed) return
|
|
478
|
+
|
|
479
|
+
const buttons = document.querySelectorAll('[role="dialog"] nav button')
|
|
480
|
+
for (const button of buttons) {
|
|
481
|
+
if (button.textContent?.trim() === '个性化') {
|
|
482
|
+
button.setAttribute(SOUL_NAV_MARKER, '')
|
|
483
|
+
|
|
484
|
+
// 检查是否已经替换过图标
|
|
485
|
+
if (button.hasAttribute(SOUL_ICON_MARKER)) continue
|
|
486
|
+
|
|
487
|
+
// 查找并替换图标
|
|
488
|
+
const existingIcon = button.querySelector('svg')
|
|
489
|
+
if (existingIcon) {
|
|
490
|
+
// 标记已替换
|
|
491
|
+
button.setAttribute(SOUL_ICON_MARKER, 'true')
|
|
492
|
+
|
|
493
|
+
// 创建星形图标(跟随主题色)
|
|
494
|
+
const starSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
495
|
+
starSvg.setAttribute('viewBox', '0 0 24 24')
|
|
496
|
+
starSvg.setAttribute('fill', 'none')
|
|
497
|
+
starSvg.setAttribute('stroke', 'currentColor')
|
|
498
|
+
starSvg.setAttribute('stroke-width', '2')
|
|
499
|
+
starSvg.setAttribute('stroke-linecap', 'round')
|
|
500
|
+
starSvg.setAttribute('stroke-linejoin', 'round')
|
|
501
|
+
starSvg.style.width = '16px'
|
|
502
|
+
starSvg.style.height = '16px'
|
|
503
|
+
|
|
504
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
|
505
|
+
path.setAttribute('d', 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z')
|
|
506
|
+
starSvg.appendChild(path)
|
|
507
|
+
|
|
508
|
+
existingIcon.replaceWith(starSvg)
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
button.removeAttribute(SOUL_NAV_MARKER)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 延迟执行初始同步,等待设置页面渲染完成
|
|
517
|
+
const timer = setTimeout(sync, 500)
|
|
518
|
+
|
|
519
|
+
// 监听 DOM 变化(只监听子节点添加,不监听所有变化)
|
|
520
|
+
const observer = new MutationObserver(() => {
|
|
521
|
+
// 防抖:只在停止变化后执行
|
|
522
|
+
clearTimeout(timer)
|
|
523
|
+
setTimeout(sync, 100)
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
observer.observe(document.body, {
|
|
527
|
+
childList: true,
|
|
528
|
+
subtree: true
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
return () => {
|
|
532
|
+
disposed = true
|
|
533
|
+
clearTimeout(timer)
|
|
534
|
+
observer.disconnect()
|
|
535
|
+
document.querySelectorAll(`[${SOUL_NAV_MARKER}]`).forEach((element) => {
|
|
536
|
+
element.removeAttribute(SOUL_NAV_MARKER)
|
|
537
|
+
})
|
|
538
|
+
document.querySelectorAll(`[${SOUL_ICON_MARKER}]`).forEach((element) => {
|
|
539
|
+
element.removeAttribute(SOUL_ICON_MARKER)
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// 注册图标替换效果
|
|
545
|
+
ctx.effect(() => registerSettingsNavIcon(), 'dsh-soul: settings navigation icon')
|
|
546
|
+
|
|
547
|
+
// 注册到设置页面
|
|
548
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
549
|
+
name: 'settings.section',
|
|
550
|
+
id: 'soul-settings',
|
|
551
|
+
order: 50,
|
|
552
|
+
label: () => '个性化',
|
|
553
|
+
locale: NS,
|
|
554
|
+
inject: () => ({
|
|
555
|
+
hooks: {
|
|
556
|
+
soulController: controller.store
|
|
557
|
+
},
|
|
558
|
+
controller
|
|
559
|
+
})
|
|
560
|
+
}, SoulSettings))
|
|
561
|
+
|
|
562
|
+
// 加载初始配置
|
|
563
|
+
controller.loadConfig()
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
exports.apply = apply
|
|
567
|
+
exports.inject = inject
|
|
568
|
+
return module.exports
|
|
569
|
+
},
|
|
570
|
+
})
|
package/cordis.patch.yml
ADDED
package/index.mjs
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// index.mjs — dsh-soul host 入口
|
|
2
|
+
//
|
|
3
|
+
// 三层架构:
|
|
4
|
+
// - Identity(身份):角色定义、人格特征
|
|
5
|
+
// - Behavior(行为):决策逻辑、行为约束
|
|
6
|
+
// - Style(风格):语气、表达方式、格式
|
|
7
|
+
//
|
|
8
|
+
// 功能:
|
|
9
|
+
// - 支持自定义 Agent 回复风格和语调
|
|
10
|
+
// - 支持用户昵称,回复时使用昵称称呼
|
|
11
|
+
// - 保存用户配置到文件系统
|
|
12
|
+
// - 提供 HTTP API 供客户端调用
|
|
13
|
+
// - 注册斜杠命令 /soul 查看当前配置
|
|
14
|
+
// - 注入系统提示词到 Agent
|
|
15
|
+
|
|
16
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises'
|
|
17
|
+
import { join } from 'node:path'
|
|
18
|
+
import { homedir } from 'node:os'
|
|
19
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
20
|
+
|
|
21
|
+
const name = 'soul'
|
|
22
|
+
|
|
23
|
+
// 配置文件路径:$DSH_HOME/soul-config.json
|
|
24
|
+
function configPath() {
|
|
25
|
+
return join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'soul-config.json')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ==================== 三层架构 ====================
|
|
29
|
+
|
|
30
|
+
// Identity 层:身份定义
|
|
31
|
+
const IdentityLayer = {
|
|
32
|
+
// 角色定义
|
|
33
|
+
roles: {
|
|
34
|
+
'default': '助手',
|
|
35
|
+
'mentor': '导师',
|
|
36
|
+
'friend': '朋友',
|
|
37
|
+
'expert': '专家',
|
|
38
|
+
'critic': '批评家'
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// 构建身份描述
|
|
42
|
+
build(config) {
|
|
43
|
+
const parts = []
|
|
44
|
+
|
|
45
|
+
// 用户自定义角色
|
|
46
|
+
if (config.customInstructions) {
|
|
47
|
+
parts.push(config.customInstructions)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 用户昵称
|
|
51
|
+
if (config.nickname) {
|
|
52
|
+
parts.push(`你的用户叫"${config.nickname}",在回复中使用这个称呼`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parts.join('\n')
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Behavior 层:行为规则
|
|
60
|
+
const BehaviorLayer = {
|
|
61
|
+
// 风格映射
|
|
62
|
+
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: '平静沉稳'
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// 行为规则
|
|
80
|
+
rules: [
|
|
81
|
+
'在回答任何问题时,都必须体现上述身份特征',
|
|
82
|
+
'禁止使用标准AI助手的中性语气',
|
|
83
|
+
'保持角色一致性,不要跳出设定'
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
// 构建行为描述
|
|
87
|
+
build(config) {
|
|
88
|
+
const parts = []
|
|
89
|
+
|
|
90
|
+
// 风格
|
|
91
|
+
if (config.style && this.styles[config.style]) {
|
|
92
|
+
parts.push(`回复风格:${this.styles[config.style]}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 语调
|
|
96
|
+
if (config.tone && this.tones[config.tone]) {
|
|
97
|
+
parts.push(`语调:${this.tones[config.tone]}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return parts.join(',')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Style 层:输出格式
|
|
105
|
+
const StyleLayer = {
|
|
106
|
+
// 格式模板
|
|
107
|
+
templates: {
|
|
108
|
+
default: '{content}',
|
|
109
|
+
structured: '## {title}\n\n{content}',
|
|
110
|
+
casual: '{content}~',
|
|
111
|
+
formal: '尊敬的{nickname},{content}'
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// 构建风格描述
|
|
115
|
+
build(config) {
|
|
116
|
+
const parts = []
|
|
117
|
+
|
|
118
|
+
// 根据风格选择格式
|
|
119
|
+
if (config.style === 'casual') {
|
|
120
|
+
parts.push('使用轻松的语气')
|
|
121
|
+
} else if (config.style === 'formal') {
|
|
122
|
+
parts.push('使用正式的语气')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return parts.join(',')
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ==================== 配置管理 ====================
|
|
130
|
+
|
|
131
|
+
// 默认配置
|
|
132
|
+
const DEFAULT_CONFIG = {
|
|
133
|
+
enabled: true,
|
|
134
|
+
nickname: '',
|
|
135
|
+
style: 'professional',
|
|
136
|
+
tone: 'neutral',
|
|
137
|
+
customInstructions: '',
|
|
138
|
+
examples: [
|
|
139
|
+
{ label: '专业严谨', value: '你是一个专业严谨的助手,采用学术性的语气,注重逻辑和准确性。' },
|
|
140
|
+
{ label: '友好亲切', value: '你是一个友好亲切的助手,采用轻松自然的语气,像朋友一样交流。' },
|
|
141
|
+
{ label: '幽默风趣', value: '你是一个幽默风趣的助手,适当使用比喻和玩笑,让对话更有趣。' },
|
|
142
|
+
{ label: '简洁直接', value: '你是一个简洁直接的助手,回答问题直击要点,避免冗余。' }
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 内存中的配置缓存
|
|
147
|
+
let configCache = null
|
|
148
|
+
|
|
149
|
+
// 读取配置
|
|
150
|
+
async function loadConfig() {
|
|
151
|
+
if (configCache) return configCache
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const data = await readFile(configPath(), 'utf8')
|
|
155
|
+
configCache = { ...DEFAULT_CONFIG, ...JSON.parse(data) }
|
|
156
|
+
} catch {
|
|
157
|
+
configCache = { ...DEFAULT_CONFIG }
|
|
158
|
+
}
|
|
159
|
+
return configCache
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 保存配置
|
|
163
|
+
async function saveConfig(config) {
|
|
164
|
+
const dir = join(configPath(), '..')
|
|
165
|
+
await mkdir(dir, { recursive: true })
|
|
166
|
+
await writeFile(configPath(), JSON.stringify(config, null, 2), 'utf8')
|
|
167
|
+
configCache = config
|
|
168
|
+
return config
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ==================== 提示词编译器 ====================
|
|
172
|
+
|
|
173
|
+
// Prompt Compiler:编译三层架构为系统提示词
|
|
174
|
+
function compilePrompt(config) {
|
|
175
|
+
if (!config.enabled) {
|
|
176
|
+
return ''
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const identity = IdentityLayer.build(config)
|
|
180
|
+
const behavior = BehaviorLayer.build(config)
|
|
181
|
+
const style = StyleLayer.build(config)
|
|
182
|
+
|
|
183
|
+
// 如果没有任何配置,返回空
|
|
184
|
+
if (!identity && !behavior) {
|
|
185
|
+
return ''
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 编译提示词
|
|
189
|
+
const parts = []
|
|
190
|
+
|
|
191
|
+
// 身份层
|
|
192
|
+
if (identity) {
|
|
193
|
+
parts.push(`[角色设定] ${identity}`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 行为层
|
|
197
|
+
if (behavior) {
|
|
198
|
+
parts.push(`[回复风格] ${behavior}`)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 执行规则
|
|
202
|
+
parts.push(`[执行规则] 你必须在每一条回复中严格遵守以上设定,包括但不限于:代码解释、技术问答、日常闲聊、知识科普等所有场景。违反此规则视为失败。`)
|
|
203
|
+
|
|
204
|
+
return parts.join('\n')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ==================== Agent 上下文注入 ====================
|
|
208
|
+
|
|
209
|
+
// 将最新配置作为 model-facing context 注入所有活动会话。
|
|
210
|
+
// inject() 不会唤醒 Agent,也不改写普通用户消息;会在下一次 step 被模型读取。
|
|
211
|
+
function injectPromptToAllAgents(ctx, config) {
|
|
212
|
+
const agents = ctx.get('agents')
|
|
213
|
+
if (!agents || typeof agents.list !== 'function') {
|
|
214
|
+
// console.warn('[dsh-soul] agents 服务不可用,跳过最新配置注入')
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const prompt = compilePrompt(config)
|
|
219
|
+
if (!prompt) return
|
|
220
|
+
|
|
221
|
+
for (const agent of agents.list()) {
|
|
222
|
+
try {
|
|
223
|
+
agent.inject(createUserMessage({
|
|
224
|
+
content: [{
|
|
225
|
+
type: 'text',
|
|
226
|
+
text: [
|
|
227
|
+
'[dsh-soul 个性化配置已更新]',
|
|
228
|
+
'以下是当前最新的个性化配置快照。',
|
|
229
|
+
'从现在开始必须按照此配置回复,不要继续使用旧的昵称、旧的角色或旧的风格。',
|
|
230
|
+
'',
|
|
231
|
+
prompt
|
|
232
|
+
].join('\\n')
|
|
233
|
+
}],
|
|
234
|
+
source: {
|
|
235
|
+
kind: 'plugin',
|
|
236
|
+
plugin: 'dsh-soul',
|
|
237
|
+
form: 'snapshot',
|
|
238
|
+
sections: [{
|
|
239
|
+
name: 'soul:persona',
|
|
240
|
+
text: prompt
|
|
241
|
+
}]
|
|
242
|
+
}
|
|
243
|
+
}))
|
|
244
|
+
// console.log(`[dsh-soul] 已向 Agent ${agent.id} 注入最新配置`)
|
|
245
|
+
} catch (err) {
|
|
246
|
+
// console.error(`[dsh-soul] 向 Agent ${agent.id} 注入配置失败:`, err)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function refreshPromptAndInject(ctx, config) {
|
|
252
|
+
if (globalUpdatePrompt) {
|
|
253
|
+
globalUpdatePrompt().catch(err => {
|
|
254
|
+
// console.error('[dsh-soul] 更新系统提示词失败:', err)
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
injectPromptToAllAgents(ctx, config)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ==================== HTTP 路由 ====================
|
|
261
|
+
|
|
262
|
+
function registerRoutes(ctx) {
|
|
263
|
+
ctx.inject(['webServer'], (wsCtx) => {
|
|
264
|
+
// 获取配置
|
|
265
|
+
wsCtx.webServer.register({
|
|
266
|
+
kind: 'exact',
|
|
267
|
+
path: '/api/soul/config',
|
|
268
|
+
handler: async (req, res) => {
|
|
269
|
+
const send = (status, body) => {
|
|
270
|
+
res.writeHead(status, { 'content-type': 'application/json' })
|
|
271
|
+
res.end(JSON.stringify(body))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
if (req.method === 'GET') {
|
|
276
|
+
const config = await loadConfig()
|
|
277
|
+
send(200, { ok: true, config })
|
|
278
|
+
} else if (req.method === 'POST') {
|
|
279
|
+
let raw = ''
|
|
280
|
+
for await (const chunk of req) raw += chunk
|
|
281
|
+
const newConfig = JSON.parse(raw)
|
|
282
|
+
const current = await loadConfig()
|
|
283
|
+
const updated = await saveConfig({ ...current, ...newConfig })
|
|
284
|
+
|
|
285
|
+
// 配置变化后更新系统提示词,并注入所有活动会话
|
|
286
|
+
refreshPromptAndInject(ctx, updated)
|
|
287
|
+
|
|
288
|
+
send(200, { ok: true, config: updated })
|
|
289
|
+
} else {
|
|
290
|
+
send(405, { ok: false, error: 'Method not allowed' })
|
|
291
|
+
}
|
|
292
|
+
} catch (err) {
|
|
293
|
+
send(500, { ok: false, error: String(err.message || err) })
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// 获取系统提示词
|
|
299
|
+
wsCtx.webServer.register({
|
|
300
|
+
kind: 'exact',
|
|
301
|
+
path: '/api/soul/prompt',
|
|
302
|
+
handler: async (req, res) => {
|
|
303
|
+
const send = (status, body) => {
|
|
304
|
+
res.writeHead(status, { 'content-type': 'application/json' })
|
|
305
|
+
res.end(JSON.stringify(body))
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const config = await loadConfig()
|
|
310
|
+
const prompt = compilePrompt(config)
|
|
311
|
+
send(200, { ok: true, prompt, enabled: config.enabled })
|
|
312
|
+
} catch (err) {
|
|
313
|
+
send(500, { ok: false, error: String(err.message || err) })
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// 重置配置
|
|
319
|
+
wsCtx.webServer.register({
|
|
320
|
+
kind: 'exact',
|
|
321
|
+
path: '/api/soul/config/reset',
|
|
322
|
+
handler: async (req, res) => {
|
|
323
|
+
const send = (status, body) => {
|
|
324
|
+
res.writeHead(status, { 'content-type': 'application/json' })
|
|
325
|
+
res.end(JSON.stringify(body))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const config = await saveConfig({ ...DEFAULT_CONFIG })
|
|
330
|
+
|
|
331
|
+
// 配置变化后更新系统提示词,并注入所有活动会话
|
|
332
|
+
refreshPromptAndInject(ctx, config)
|
|
333
|
+
|
|
334
|
+
send(200, { ok: true, config })
|
|
335
|
+
} catch (err) {
|
|
336
|
+
send(500, { ok: false, error: String(err.message || err) })
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ==================== 斜杠命令 ====================
|
|
344
|
+
|
|
345
|
+
function registerCommands(ctx) {
|
|
346
|
+
ctx.inject(['commands'], (cmdCtx) => {
|
|
347
|
+
cmdCtx.commands.register({
|
|
348
|
+
name: 'soul',
|
|
349
|
+
description: '查看或管理个性化设置。用法:/soul [show|reset|enable|disable|昵称]',
|
|
350
|
+
input: { hint: '[show|reset|enable|disable|昵称]' },
|
|
351
|
+
async handler(invocation) {
|
|
352
|
+
const args = String(invocation.rawInput || '').trim().toLowerCase()
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const config = await loadConfig()
|
|
356
|
+
|
|
357
|
+
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
|
+
|
|
364
|
+
return {
|
|
365
|
+
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 设置昵称`
|
|
367
|
+
}
|
|
368
|
+
} else if (args === 'reset') {
|
|
369
|
+
const updated = await saveConfig({ ...DEFAULT_CONFIG })
|
|
370
|
+
// 更新系统提示词,并注入所有活动会话
|
|
371
|
+
refreshPromptAndInject(ctx, updated)
|
|
372
|
+
return { kind: 'success', text: '✅ 配置已重置为默认值' }
|
|
373
|
+
} else if (args === 'enable') {
|
|
374
|
+
config.enabled = true
|
|
375
|
+
const updated = await saveConfig(config)
|
|
376
|
+
// 更新系统提示词,并注入所有活动会话
|
|
377
|
+
refreshPromptAndInject(ctx, updated)
|
|
378
|
+
return { kind: 'success', text: '✅ 个性化设置已启用' }
|
|
379
|
+
} else if (args === 'disable') {
|
|
380
|
+
config.enabled = false
|
|
381
|
+
const updated = await saveConfig(config)
|
|
382
|
+
// 更新系统提示词,并注入所有活动会话
|
|
383
|
+
refreshPromptAndInject(ctx, updated)
|
|
384
|
+
return { kind: 'success', text: '✅ 个性化设置已禁用' }
|
|
385
|
+
} else {
|
|
386
|
+
// 设置昵称
|
|
387
|
+
config.nickname = args
|
|
388
|
+
const updated = await saveConfig(config)
|
|
389
|
+
// 更新系统提示词,并注入所有活动会话
|
|
390
|
+
refreshPromptAndInject(ctx, updated)
|
|
391
|
+
return { kind: 'success', text: `✅ 昵称已设置为:${args}` }
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
return { kind: 'error', text: `操作失败:${err.message}` }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
})
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ==================== 系统提示词注入 ====================
|
|
402
|
+
|
|
403
|
+
// 全局变量,用于在配置变化时更新提示词
|
|
404
|
+
let globalUpdatePrompt = null
|
|
405
|
+
|
|
406
|
+
function registerSystemPrompt(ctx) {
|
|
407
|
+
ctx.inject(['systemPrompt'], (spCtx) => {
|
|
408
|
+
let disposeSection = null
|
|
409
|
+
|
|
410
|
+
const registerSection = () => {
|
|
411
|
+
// 重新注册前先移除旧 section,触发 system-prompt/change,
|
|
412
|
+
// 让 DSH 丢弃当前会话已经缓存的 system prompt 快照。
|
|
413
|
+
disposeSection?.()
|
|
414
|
+
|
|
415
|
+
disposeSection = spCtx.systemPrompt.section({
|
|
416
|
+
name: 'soul:persona',
|
|
417
|
+
order: 0,
|
|
418
|
+
text: () => {
|
|
419
|
+
const prompt = compilePrompt(configCache || DEFAULT_CONFIG)
|
|
420
|
+
if (prompt) {
|
|
421
|
+
// console.log(`[dsh-soul] 系统提示词已组装:${prompt.substring(0, 100)}...`)
|
|
422
|
+
}
|
|
423
|
+
return prompt
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
registerSection()
|
|
429
|
+
|
|
430
|
+
globalUpdatePrompt = async () => {
|
|
431
|
+
await loadConfig()
|
|
432
|
+
registerSection()
|
|
433
|
+
// console.log('[dsh-soul] 系统提示词已更新,当前会话下一次请求将使用最新配置')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
ctx.effect(() => () => {
|
|
437
|
+
disposeSection?.()
|
|
438
|
+
disposeSection = null
|
|
439
|
+
if (globalUpdatePrompt) {
|
|
440
|
+
globalUpdatePrompt = null
|
|
441
|
+
}
|
|
442
|
+
}, 'dsh-soul: system prompt section')
|
|
443
|
+
})
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ==================== 主入口 ====================
|
|
447
|
+
|
|
448
|
+
export async function apply(ctx) {
|
|
449
|
+
// 加载配置
|
|
450
|
+
await loadConfig()
|
|
451
|
+
|
|
452
|
+
// 提供 soulConfig 服务
|
|
453
|
+
ctx.provide('soulConfig', {
|
|
454
|
+
async getConfig() {
|
|
455
|
+
return await loadConfig()
|
|
456
|
+
},
|
|
457
|
+
async updateConfig(newConfig) {
|
|
458
|
+
const current = await loadConfig()
|
|
459
|
+
const updated = { ...current, ...newConfig }
|
|
460
|
+
return await saveConfig(updated)
|
|
461
|
+
},
|
|
462
|
+
async getSystemPrompt() {
|
|
463
|
+
const config = await loadConfig()
|
|
464
|
+
return compilePrompt(config)
|
|
465
|
+
},
|
|
466
|
+
async resetConfig() {
|
|
467
|
+
return await saveConfig({ ...DEFAULT_CONFIG })
|
|
468
|
+
}
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
// 注册各模块
|
|
472
|
+
registerRoutes(ctx)
|
|
473
|
+
registerCommands(ctx)
|
|
474
|
+
registerSystemPrompt(ctx)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export { name }
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-soul",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "为 DeepSeek Harness 添加个性化设置",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.mjs",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
10
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"inject": [
|
|
18
|
+
"@deepseek-ai/dsh-client-locale",
|
|
19
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
20
|
+
],
|
|
21
|
+
"platform": "web"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"default": "./index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"./client": "./client/index.mjs",
|
|
29
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.mjs",
|
|
34
|
+
"client",
|
|
35
|
+
"cordis.patch.yml",
|
|
36
|
+
"README.md"
|
|
37
|
+
]
|
|
38
|
+
}
|