dsh-soul 0.3.3 → 0.5.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 +25 -9
- package/README_EN.md +25 -9
- package/client/index.mjs +509 -218
- package/index.mjs +637 -242
- package/lib/config.mjs +48 -1
- package/package.json +1 -1
- package/scripts/verify-config.mjs +39 -1
package/lib/config.mjs
CHANGED
|
@@ -20,7 +20,9 @@ export const DEFAULT_CONFIG = {
|
|
|
20
20
|
headingLists: 'default',
|
|
21
21
|
emoji: 'default',
|
|
22
22
|
language: 'zh',
|
|
23
|
-
customInstructions: ''
|
|
23
|
+
customInstructions: '',
|
|
24
|
+
// set_persona 工具确认模式:开启后 Agent 的人设修改返回待确认提议而不落盘
|
|
25
|
+
requireToolConfirmation: false
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// 合法的回复风格和语调取值(供工具参数校验)
|
|
@@ -44,6 +46,39 @@ export const FIELD_LIMITS = {
|
|
|
44
46
|
// 需做长度校验的自由文本字段
|
|
45
47
|
const TEXT_FIELDS = ['nickname', 'occupation', 'bio', 'customInstructions']
|
|
46
48
|
|
|
49
|
+
// ==================== 人设预设 ====================
|
|
50
|
+
|
|
51
|
+
// 人设预设覆盖的配置字段(enabled 为全局开关、不进入预设)
|
|
52
|
+
export const PERSONA_FIELDS = ['nickname', 'occupation', 'bio', 'style', 'headingLists', 'emoji', 'language', 'customInstructions']
|
|
53
|
+
|
|
54
|
+
// 人设预设名称上限
|
|
55
|
+
export const PERSONA_NAME_MAX = 30
|
|
56
|
+
|
|
57
|
+
// 禁用作预设名的保留键(防原型污染)
|
|
58
|
+
const FORBIDDEN_PERSONA_NAMES = new Set(['__proto__', 'constructor', 'prototype'])
|
|
59
|
+
|
|
60
|
+
// 校验并归一化人设预设名称:trim 后 1..PERSONA_NAME_MAX 字符;非法返回 null
|
|
61
|
+
export function sanitizePersonaName(raw) {
|
|
62
|
+
if (typeof raw !== 'string') return null
|
|
63
|
+
const name = raw.trim()
|
|
64
|
+
if (!name || name.length > PERSONA_NAME_MAX) return null
|
|
65
|
+
if (FORBIDDEN_PERSONA_NAMES.has(name)) return null
|
|
66
|
+
return name
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 归一化磁盘上的预设库:只保留普通对象条目,剔除保留键与非对象条目
|
|
70
|
+
export function normalizePersonas(raw) {
|
|
71
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {}
|
|
72
|
+
const out = {}
|
|
73
|
+
for (const key of Object.keys(raw)) {
|
|
74
|
+
if (FORBIDDEN_PERSONA_NAMES.has(key)) continue
|
|
75
|
+
const entry = raw[key]
|
|
76
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue
|
|
77
|
+
out[key] = { ...entry }
|
|
78
|
+
}
|
|
79
|
+
return out
|
|
80
|
+
}
|
|
81
|
+
|
|
47
82
|
// v0.1.x 配置使用 style + tone 两个字段;v0.2.0 起合并为单一 style(回复风格和语调)。
|
|
48
83
|
// 迁移映射:组合命中用组合表,否则退回旧 style 表,再否则用默认值。
|
|
49
84
|
const LEGACY_STYLE_TONE_MAP = {
|
|
@@ -75,6 +110,10 @@ export function migrateConfig(raw) {
|
|
|
75
110
|
// 特质字段校验:脏数据回退为默认值
|
|
76
111
|
if (!TRAIT_VALUES.includes(config.headingLists)) config.headingLists = 'default'
|
|
77
112
|
if (!TRAIT_VALUES.includes(config.emoji)) config.emoji = 'default'
|
|
113
|
+
// 确认模式:非布尔脏数据回退为默认值
|
|
114
|
+
if (typeof config.requireToolConfirmation !== 'boolean') config.requireToolConfirmation = DEFAULT_CONFIG.requireToolConfirmation
|
|
115
|
+
// 人设预设库归一化:剔除危险键与非对象条目(磁盘无该字段时保持缺席)
|
|
116
|
+
if ('personas' in config) config.personas = normalizePersonas(config.personas)
|
|
78
117
|
return config
|
|
79
118
|
}
|
|
80
119
|
|
|
@@ -103,6 +142,14 @@ export function sanitizeConfig(raw) {
|
|
|
103
142
|
}
|
|
104
143
|
}
|
|
105
144
|
|
|
145
|
+
if ('requireToolConfirmation' in raw) {
|
|
146
|
+
if (typeof raw.requireToolConfirmation === 'boolean') {
|
|
147
|
+
patch.requireToolConfirmation = raw.requireToolConfirmation
|
|
148
|
+
} else {
|
|
149
|
+
errors.requireToolConfirmation = '必须为布尔值(true / false)'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
106
153
|
for (const key of TEXT_FIELDS) {
|
|
107
154
|
if (!(key in raw)) continue
|
|
108
155
|
const value = raw[key]
|
package/package.json
CHANGED
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
DEFAULT_CONFIG,
|
|
11
11
|
FIELD_LIMITS,
|
|
12
12
|
migrateConfig,
|
|
13
|
-
|
|
13
|
+
normalizePersonas,
|
|
14
|
+
sanitizeConfig,
|
|
15
|
+
sanitizePersonaName
|
|
14
16
|
} from '../lib/config.mjs'
|
|
15
17
|
|
|
16
18
|
let passed = 0
|
|
@@ -139,4 +141,40 @@ check('非对象输入整体拒绝', () => {
|
|
|
139
141
|
assert.ok(sanitizeConfig(null).errors._)
|
|
140
142
|
})
|
|
141
143
|
|
|
144
|
+
console.log('requireToolConfirmation / 人设预设')
|
|
145
|
+
|
|
146
|
+
check('sanitizeConfig:requireToolConfirmation 布尔校验', () => {
|
|
147
|
+
const { patch, errors } = sanitizeConfig({ requireToolConfirmation: true })
|
|
148
|
+
assert.deepEqual(errors, {})
|
|
149
|
+
assert.equal(patch.requireToolConfirmation, true)
|
|
150
|
+
assert.ok(sanitizeConfig({ requireToolConfirmation: 'yes' }).errors.requireToolConfirmation)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
check('sanitizePersonaName:trim、长度与保留键拒绝', () => {
|
|
154
|
+
assert.equal(sanitizePersonaName(' 工作模式 '), '工作模式')
|
|
155
|
+
assert.equal(sanitizePersonaName(''), null)
|
|
156
|
+
assert.equal(sanitizePersonaName(' '), null)
|
|
157
|
+
assert.equal(sanitizePersonaName('x'.repeat(31)), null)
|
|
158
|
+
assert.equal(sanitizePersonaName('__proto__'), null)
|
|
159
|
+
assert.equal(sanitizePersonaName(123), null)
|
|
160
|
+
assert.equal(sanitizePersonaName(null), null)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
check('normalizePersonas:剔除保留键与非对象条目', () => {
|
|
164
|
+
const raw = JSON.parse('{"__proto__":{"a":1},"work":{"style":"roast"},"bad":1}')
|
|
165
|
+
const out = normalizePersonas(raw)
|
|
166
|
+
assert.deepEqual(Object.keys(out).sort(), ['work'])
|
|
167
|
+
assert.deepEqual(normalizePersonas(null), {})
|
|
168
|
+
assert.deepEqual(normalizePersonas([1, 2]), {})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
check('migrateConfig:personas 归一化透传,缺省时保持缺席;确认模式脏数据回退', () => {
|
|
172
|
+
const config = migrateConfig({ personas: { work: { style: 'roast' } } })
|
|
173
|
+
assert.deepEqual(config.personas, { work: { style: 'roast' } })
|
|
174
|
+
const clean = migrateConfig({ nickname: 'x' })
|
|
175
|
+
assert.equal('personas' in clean, false)
|
|
176
|
+
const dirty = migrateConfig({ requireToolConfirmation: 'yes' })
|
|
177
|
+
assert.equal(dirty.requireToolConfirmation, false)
|
|
178
|
+
})
|
|
179
|
+
|
|
142
180
|
console.log(`\n全部通过:${passed} 项检查`)
|