dsh-feini 0.1.1 → 0.1.3
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/INSTALL.md +29 -1
- package/bin/cli.mjs +39 -1
- package/package.json +1 -1
- package/src/proactive.mjs +24 -0
package/INSTALL.md
CHANGED
|
@@ -113,7 +113,35 @@ AI 不会替你扫 NapCat 的码、也不会替你买 key,所以第 3 步和
|
|
|
113
113
|
| 它重复说同一句 | 发送超时后补发 | 看日志里的「核对」;已经修过,还犯就把日志发我 |
|
|
114
114
|
| 它老忘事 | 笔记本没写进去 | 直接说「记住:…」;改 `notebook.md` 立刻生效 |
|
|
115
115
|
|
|
116
|
-
##
|
|
116
|
+
## 五、想跟桌宠共用一个身份
|
|
117
|
+
|
|
118
|
+
想在桌面上有一只、QQ 里这只又跟它是同一个人的话,装两个东西:
|
|
119
|
+
|
|
120
|
+
1. **桌宠**:dsh-market 里搜 `pet`(`dsh-pet` 或 `dsh-whale-widget`),装上,在它的设置里把名字和性格写好
|
|
121
|
+
2. **QQ 这边**:按上面装好 `dsh-feini`
|
|
122
|
+
|
|
123
|
+
然后把两边的身份接起来:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
dsh-feini link-pet
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
它会找到桌宠的配置(`%USERPROFILE%\.dsh\dsh-pet\main-config.jsonc`),把 `systemPromptPath` 指过去;之后改桌宠的人设,QQ 里说话那个也跟着变
|
|
130
|
+
|
|
131
|
+
改完要重启一次:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
dsh-feini stop
|
|
135
|
+
dsh-feini start
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
几个说明:
|
|
139
|
+
|
|
140
|
+
- **人设共用,记忆不共用**:桌宠有自己的记忆,QQ 这边有自己的 `notebook.md`。别把两边的 `memory.json` 指到同一个文件——两边同时写会打架
|
|
141
|
+
- **想断开**:把 `config.json` 里的 `systemPromptPath` 改回 `%USERPROFILE%\.dsh\dsh-feini\persona.md`,重启
|
|
142
|
+
- **桌宠没装也别慌**:`link-pet` 会提示找不到配置,正常用 `persona.md` 就行
|
|
143
|
+
|
|
144
|
+
## 六、更新与卸载
|
|
117
145
|
|
|
118
146
|
```bash
|
|
119
147
|
npm i -g dsh-feini # 更新
|
package/bin/cli.mjs
CHANGED
|
@@ -106,6 +106,7 @@ const help = `dsh-feini —— 在 QQ 里养一个自己的伙伴
|
|
|
106
106
|
dsh-feini stop 停桥
|
|
107
107
|
dsh-feini status 看状态 + 最近日志
|
|
108
108
|
dsh-feini sea 手动重建记忆海(桥在跑时每两分钟自动建)
|
|
109
|
+
dsh-feini link-pet 和桌面宠物共用同一份人设(读 dsh-pet 的配置)
|
|
109
110
|
dsh-feini path 打印数据目录
|
|
110
111
|
|
|
111
112
|
参数(init 可用,也可以不交互直接给):
|
|
@@ -186,7 +187,8 @@ async function cmdInit(argv) {
|
|
|
186
187
|
},
|
|
187
188
|
modelVision: {
|
|
188
189
|
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
189
|
-
|
|
190
|
+
// 智谱现在的视觉模型;旧名 glm-4v-flash 已经不太稳,识图会 400
|
|
191
|
+
model: 'glm-5.3-flash',
|
|
190
192
|
apiKeyEnv: 'FEINI_VISION_KEY',
|
|
191
193
|
keyFile: path.join(home, 'vision-key.txt'),
|
|
192
194
|
},
|
|
@@ -290,6 +292,41 @@ function cmdPath() {
|
|
|
290
292
|
console.log(home);
|
|
291
293
|
}
|
|
292
294
|
|
|
295
|
+
/**
|
|
296
|
+
* 和桌面宠物共用同一个人设:把 systemPromptPath 指到 dsh-pet 的配置上。
|
|
297
|
+
* 这样 QQ 里说话的和桌面上那只就是同一个身份,改一处两边一起变。
|
|
298
|
+
*/
|
|
299
|
+
function cmdLinkPet() {
|
|
300
|
+
const cfg = readJson(configPath, null);
|
|
301
|
+
if (!cfg) {
|
|
302
|
+
console.error('还没有配置,先跑 dsh-feini init');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
const petConfig = path.join(os.homedir(), '.dsh', 'dsh-pet', 'main-config.jsonc');
|
|
306
|
+
if (!fs.existsSync(petConfig)) {
|
|
307
|
+
console.error(`没找到桌宠配置:${petConfig}`);
|
|
308
|
+
console.error('先把桌宠装上(dsh-market 里搜 pet),并在它的设置里把人设写好,再跑这条');
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
let raw = '';
|
|
312
|
+
try {
|
|
313
|
+
raw = fs.readFileSync(petConfig, 'utf8');
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.error('读不了桌宠配置:' + e.message);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
if (!/"whisperPrompt"\s*:/.test(raw)) {
|
|
319
|
+
console.error('这份桌宠配置里没有 whisperPrompt,版本可能不一样');
|
|
320
|
+
console.error(`那就手动把 config.json 的 systemPromptPath 改成:${petConfig}`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
cfg.systemPromptPath = petConfig;
|
|
324
|
+
writeJson(configPath, cfg);
|
|
325
|
+
console.log(`好了,以后 dsh-feini 读这份人设:\n ${petConfig}`);
|
|
326
|
+
console.log('改完要重启桥才生效:dsh-feini stop && dsh-feini start');
|
|
327
|
+
console.log(`想断开就把它改回:${path.join(home, 'persona.md')}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
293
330
|
/** 手动重建记忆海(桥在跑的话每两分钟自己也会建一次) */
|
|
294
331
|
function cmdSea() {
|
|
295
332
|
const cfg = readJson(configPath, null);
|
|
@@ -320,6 +357,7 @@ try {
|
|
|
320
357
|
else if (cmd === 'stop') cmdStop();
|
|
321
358
|
else if (cmd === 'status') cmdStatus();
|
|
322
359
|
else if (cmd === 'sea') cmdSea();
|
|
360
|
+
else if (cmd === 'link-pet') cmdLinkPet();
|
|
323
361
|
else if (cmd === 'path') cmdPath();
|
|
324
362
|
else {
|
|
325
363
|
console.error(`不认识的命令:${cmd}\n`);
|
package/package.json
CHANGED
package/src/proactive.mjs
CHANGED
|
@@ -159,6 +159,13 @@ export function startProactive({ config, getSystemPrompt, apiKey, bot, history,
|
|
|
159
159
|
if (sleptLate && now - state.lastUserAt < quietMs) return;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// 他说了「晚安」:就当他睡了,7 小时内不主动、也不闹脾气。
|
|
163
|
+
// 2026-10-31 02:33 那次就是因为没有这条——他 01:5x 说晚安,之后还被追着喊了三回。
|
|
164
|
+
if (state.goodnightAt && now - state.goodnightAt < quietMs) return;
|
|
165
|
+
|
|
166
|
+
// 不在活跃时段(默认 07:00–02:00)就别开口
|
|
167
|
+
if (!inWindow(window.from, window.to)) return;
|
|
168
|
+
|
|
162
169
|
try {
|
|
163
170
|
// 他刚说过话:别插嘴,等安静够 5 分钟再说
|
|
164
171
|
if (state.lastUserAt && now - state.lastUserAt < afterReplyMin * 60000) return;
|
|
@@ -201,11 +208,28 @@ export function startProactive({ config, getSystemPrompt, apiKey, bot, history,
|
|
|
201
208
|
},
|
|
202
209
|
/** 他回话了:不是立刻消气——傲娇要哄到位 */
|
|
203
210
|
noteUserReply(text) {
|
|
211
|
+
const t = String(text || '');
|
|
204
212
|
state.lastUserAt = Date.now();
|
|
205
213
|
state.pendingSince = 0;
|
|
206
214
|
// 他刚回话,下一轮主动重新计时:不然对话一结束、安静满 5 分钟就立刻开口
|
|
207
215
|
state.nextAt = Date.now() + randBetween(gapMin, gapMax) * 60000;
|
|
208
216
|
|
|
217
|
+
// 他说「晚安 / 去睡了」:记下来,接下来 7 小时不主动也不闹。
|
|
218
|
+
// 醒来说第一句就清掉(下面 else 分支),不用他专门说「我醒了」。
|
|
219
|
+
if (/(晚安|我睡了|先去睡|去睡了|睡觉去了|睡啦|睡咯|晚安啦)/.test(t)) {
|
|
220
|
+
state.goodnightAt = Date.now();
|
|
221
|
+
state.sulking = false;
|
|
222
|
+
state.sulkCount = 0;
|
|
223
|
+
state.coaxNeed = 0;
|
|
224
|
+
state.coaxCount = 0;
|
|
225
|
+
writeSulk(sulkFile, null);
|
|
226
|
+
log('他道晚安了,接下来不主动找他');
|
|
227
|
+
} else if (state.goodnightAt && Date.now() - state.goodnightAt > 30 * 60000) {
|
|
228
|
+
// 隔了半小时以上又开口,说明醒了:解除静默
|
|
229
|
+
state.goodnightAt = 0;
|
|
230
|
+
log('他醒了,恢复主动');
|
|
231
|
+
}
|
|
232
|
+
|
|
209
233
|
if (state.sulking) {
|
|
210
234
|
const t = String(text || '');
|
|
211
235
|
const coax = /(对不起|我错|别生气|不要气|哄你|抱抱|亲亲|爱你|想你|乖|抱歉|么么|摸摸|宝宝|我别)/;
|