dsh-whale-loli 0.9.7 → 0.9.9
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 +40 -1
- package/lib/client.js +45 -23
- package/listing.json +1 -1
- package/package.json +1 -1
- package/src/daemon.js +46 -40
- package/src/index.js +3 -43
- package/src/presets.js +84 -0
package/README.md
CHANGED
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
agent 自己也能改,工具是 `affinity_v1_set` / `affinity_v2_set` / `affinity_v3_set` / `affinity_v4_set`。
|
|
38
38
|
- 和记忆一样走**提示变量**(`affinity_v1` 等),因为 persona 是 `complete: true`,独立 section 会被宿主丢掉。
|
|
39
39
|
|
|
40
|
+
> **0.9.9 修的那个坑**:面板一度只有一句「还没有哪个预设挂上好感度」,一个滑块都没有、也设不了值。
|
|
41
|
+
> 根因是宿主半边去扫 `~/.dsh/agent-presets/*.yml` 判断「哪些预设挂了好感度」——那是早期宿主留下的目录,
|
|
42
|
+
> 现代 DSH 的预设由插件包的 `cordis.patch.yml` 注册,这个目录根本不存在,于是 `wired` 永远是空数组。
|
|
43
|
+
> 现在预设清单只有一份(`src/presets.js`),`src/index.js` 和 `src/daemon.js` 都读它;`status()` 无论磁盘上有没有值,
|
|
44
|
+
> 都列全四个预设,没设过的显示 `0`,可以直接拖。`verify-daemon-status.mjs` 把这条锁住了。
|
|
45
|
+
|
|
40
46
|
### 面板是怎么连上磁盘的
|
|
41
47
|
|
|
42
48
|
设置页是浏览器里的 React,够不到 `~/.dsh/affinity/` 的磁盘(宿主的 21 个内置 Remote 命名空间逐个查过,没有一个能读写这个目录)。
|
|
@@ -44,9 +50,17 @@
|
|
|
44
50
|
|
|
45
51
|
```
|
|
46
52
|
- id: whale-affinity-daemon
|
|
47
|
-
name: 'dsh-whale-loli
|
|
53
|
+
name: 'dsh-whale-loli' # 必须裸包名(见下)
|
|
54
|
+
config:
|
|
55
|
+
daemon: true
|
|
48
56
|
```
|
|
49
57
|
|
|
58
|
+
`name` 只能写**裸包名**:宿主的客户端模块扫描器(`dsh-client-modules` 的
|
|
59
|
+
`exactPackageSpecifier()`)只接受「裸名」或「`@scope/name`」,写成子路径
|
|
60
|
+
(`dsh-whale-loli/affinity-daemon`)会让 `package.json` 解析失败(`ERR_PACKAGE_PATH_NOT_EXPORTED`),
|
|
61
|
+
于是 `dsh.client` 永远发现不了、面板永不出现,**而宿主一个错都不报**。
|
|
62
|
+
同一行插件用 `config.daemon: true` 分派到宿主半边,记忆那半边则按 `file` 分派。
|
|
63
|
+
|
|
50
64
|
它在**宿主根作用域**提供一个 remote 服务 `whale-affinity`,浏览器半边(`lib/client.js`)
|
|
51
65
|
用 `ctx.remote.$mount()` 挂上,再调 `status()` / `set(scope, value)`。
|
|
52
66
|
|
|
@@ -55,6 +69,28 @@
|
|
|
55
69
|
`inject: ['commands']` 会让预设加载时一直等它(`waiting for commands`),把会话 resume 拖挂。
|
|
56
70
|
设置页没有这两个限制。
|
|
57
71
|
|
|
72
|
+
#### 取命名空间必须用 `ctx.inject()`,不能在服务对象上点
|
|
73
|
+
|
|
74
|
+
`$mount()` 成功之后,网关注册的服务名就是 `remote.whale-affinity`(带点号)。
|
|
75
|
+
它是**服务**,不是普通属性,所以:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
remote['whale-affinity'] // ✗ cannot get property "remote.whale-affinity" without inject
|
|
79
|
+
ctx.inject(['remote.whale-affinity'], (child) => {
|
|
80
|
+
const gateway = child['remote.whale-affinity']; // ✓
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
原因是 cordis 的 get 陷阱(`cordis/lib/index.js:676`)对「不在 fiber.store 里、也没 inject」的属性
|
|
85
|
+
一律抛那句报错,而服务上的 `tracker.associate` 会把 `remote['whale-affinity']` 重定向成
|
|
86
|
+
`ctx['remote.whale-affinity']`。这个名字只有挂载后才存在,所以必须用**动态 inject** 等它出现
|
|
87
|
+
(`verify-client-gateway-access.mjs` 用真实 cordis 正反两面都验过)。
|
|
88
|
+
|
|
89
|
+
这一步的教训值得记:**自写的校验脚本曾在 `contribution` 形状上跟着源码一起写错
|
|
90
|
+
(`methods` 而宿主读的是 `descriptors`),互相印证、永远绿。** 所以现在凡是对接宿主的字段,
|
|
91
|
+
校验脚本都要拿宿主源码里真正读的那个名字来断言。
|
|
92
|
+
|
|
93
|
+
|
|
58
94
|
## 记忆功能
|
|
59
95
|
|
|
60
96
|
小鱼的记忆本分**四个区**,各管各的:
|
|
@@ -243,6 +279,7 @@ dsh-whale-loli/
|
|
|
243
279
|
├── cordis.patch.yml # 四个预设的声明本体(由 build-patch.mjs 生成)
|
|
244
280
|
├── src/
|
|
245
281
|
│ ├── index.js # 插件入口,导出预设元信息 + 人设正文
|
|
282
|
+
│ ├── presets.js # 预设清单(唯一事实来源:index.js 与 daemon.js 都读它)
|
|
246
283
|
│ ├── memory.js # whale-memory:(读)记忆变量 +(写)记忆工具,四区格式 + 80 条滚动 + 归档
|
|
247
284
|
│ ├── affinity.js # whale-affinity:好感度变量 + 调整工具,7 档
|
|
248
285
|
│ ├── daemon.js # whale-affinity-daemon:宿主根作用域的 remote 服务,给设置页读写好感度
|
|
@@ -276,6 +313,7 @@ dsh-whale-loli/
|
|
|
276
313
|
├── verify-tool-schema.mjs # 用宿主自己的校验器验八个工具的 JSON schema 合法
|
|
277
314
|
├── verify-tool-identity.mjs # 共享表语义下八个工具描述互不串台、定义互不共享引用
|
|
278
315
|
├── verify-client.mjs # 浏览器半边:section 注册、$mount 负载、面板渲染、无 ESM/JSX
|
|
316
|
+
├── verify-daemon-status.mjs # 设置页数据:列全四预设、wired 非空、set 写的正是人设读的那份
|
|
279
317
|
├── verify-peers.mjs # 校验 peer 范围覆盖 harness 各发布档
|
|
280
318
|
├── verify-listing.mjs # 复刻插件目录的收录门槛自查
|
|
281
319
|
└── prove-collision.mjs # 复现并证明重复注册冲突
|
|
@@ -305,6 +343,7 @@ node verify-live-assembly.mjs # 复刻宿主装配,比对注入
|
|
|
305
343
|
node verify-tool-schema.mjs # 宿主自己的校验器验八个工具的 JSON schema
|
|
306
344
|
node verify-tool-identity.mjs # 八个工具描述互不串台(共享表语义)
|
|
307
345
|
node verify-client.mjs # 设置页半边:section 注册 + 面板渲染
|
|
346
|
+
node verify-daemon-status.mjs # 设置页数据:四预设列全、wired 非空、读写同一份
|
|
308
347
|
node smoke-memory.mjs # 变量 + 工具注册形态冒烟
|
|
309
348
|
node verify-peers.mjs # peer 范围无预发布坑
|
|
310
349
|
node verify-listing.mjs --topics=dsh-plugin # 收录门槛自查
|
package/lib/client.js
CHANGED
|
@@ -109,6 +109,22 @@ window.__ModuleLoader__.load({
|
|
|
109
109
|
const ATTACH_TRIES = 25;
|
|
110
110
|
const ATTACH_STEP_MS = 80;
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 命名空间在 cordis 里的**完整服务名**。
|
|
114
|
+
*
|
|
115
|
+
* `$mount()` 挂上之后,网关会在调用方上下文里 provide 一个名叫
|
|
116
|
+
* `remote.whale-affinity` 的服务(见 `dsh-api-gateway/lib/client.browser.js`
|
|
117
|
+
* 的 `remoteServiceKey()`)。它是服务,不是普通属性 —— 直接写
|
|
118
|
+
* `ctx.remote['whale-affinity']` 会命中 cordis 的 get 陷阱并抛
|
|
119
|
+
* `cannot get property "remote.whale-affinity" without inject`:
|
|
120
|
+
* 取任何服务都必须先声明 inject,而这个名字只有挂载后才存在,
|
|
121
|
+
* 所以只能用 `ctx.inject([...], child => ...)` 动态等待。
|
|
122
|
+
*/
|
|
123
|
+
const GATEWAY_SERVICE = `remote.${NAMESPACE}`;
|
|
124
|
+
|
|
125
|
+
/** 挂上之后的命名空间句柄;没挂上就是 null。 */
|
|
126
|
+
let gateway = null;
|
|
127
|
+
|
|
112
128
|
/**
|
|
113
129
|
* 带外诊断:走同源的 `/whale-report`(宿主侧 `whale-affinity-daemon` 注册的
|
|
114
130
|
* 免鉴权 prefix 路由),**不依赖**那条还没挂上的 remote 通道。
|
|
@@ -133,10 +149,10 @@ window.__ModuleLoader__.load({
|
|
|
133
149
|
}
|
|
134
150
|
|
|
135
151
|
/** 等闸门 + 轮询,拿到真正可用的远程方法句柄。 */
|
|
136
|
-
async function attach(
|
|
152
|
+
async function attach(method) {
|
|
137
153
|
if (mountGate !== null) await mountGate.then(() => {}, () => {});
|
|
138
154
|
for (let attempt = 0; attempt < ATTACH_TRIES; attempt += 1) {
|
|
139
|
-
const fn =
|
|
155
|
+
const fn = gateway?.[method];
|
|
140
156
|
if (typeof fn === 'function') return fn;
|
|
141
157
|
await sleep(ATTACH_STEP_MS);
|
|
142
158
|
}
|
|
@@ -144,8 +160,8 @@ window.__ModuleLoader__.load({
|
|
|
144
160
|
throw new Error(`远程方法 ${NAMESPACE}.${method} 未挂载`);
|
|
145
161
|
}
|
|
146
162
|
|
|
147
|
-
function call(
|
|
148
|
-
return attach(
|
|
163
|
+
function call(method, args) {
|
|
164
|
+
return attach(method)
|
|
149
165
|
.then((fn) => fn(...args))
|
|
150
166
|
.then((result) => {
|
|
151
167
|
if (result && typeof result === 'object' && 'ok' in result) {
|
|
@@ -201,7 +217,6 @@ window.__ModuleLoader__.load({
|
|
|
201
217
|
*/
|
|
202
218
|
function AffinitySection(props) {
|
|
203
219
|
useStyleOnce();
|
|
204
|
-
const remote = props?.remote;
|
|
205
220
|
const [snapshot, setSnapshot] = useState(null);
|
|
206
221
|
const [error, setError] = useState(null);
|
|
207
222
|
const [busy, setBusy] = useState(null);
|
|
@@ -209,20 +224,20 @@ window.__ModuleLoader__.load({
|
|
|
209
224
|
|
|
210
225
|
const reload = useCallback(async () => {
|
|
211
226
|
try {
|
|
212
|
-
const next = await call(
|
|
227
|
+
const next = await call('status', []);
|
|
213
228
|
setSnapshot(next);
|
|
214
229
|
setError(null);
|
|
215
230
|
} catch (cause) {
|
|
216
231
|
setSnapshot(null);
|
|
217
232
|
setError(cause?.message ?? String(cause));
|
|
218
233
|
}
|
|
219
|
-
}, [
|
|
234
|
+
}, []);
|
|
220
235
|
|
|
221
236
|
useEffect(() => {
|
|
222
237
|
let alive = true;
|
|
223
238
|
(async () => {
|
|
224
239
|
try {
|
|
225
|
-
const next = await call(
|
|
240
|
+
const next = await call('status', []);
|
|
226
241
|
if (alive) {
|
|
227
242
|
setSnapshot(next);
|
|
228
243
|
setError(null);
|
|
@@ -234,7 +249,7 @@ window.__ModuleLoader__.load({
|
|
|
234
249
|
// 所以顺手把原因写回宿主日志,排查时直接读
|
|
235
250
|
// `<DSH_HOME>/whale-affinity-client.log`。
|
|
236
251
|
try {
|
|
237
|
-
const report =
|
|
252
|
+
const report = gateway?.report;
|
|
238
253
|
if (typeof report === 'function') {
|
|
239
254
|
report('invoke status', message, String(cause?.stack ?? '')).catch(() => {});
|
|
240
255
|
}
|
|
@@ -246,13 +261,13 @@ window.__ModuleLoader__.load({
|
|
|
246
261
|
return () => {
|
|
247
262
|
alive = false;
|
|
248
263
|
};
|
|
249
|
-
}, [
|
|
264
|
+
}, []);
|
|
250
265
|
|
|
251
266
|
const commit = useCallback(
|
|
252
267
|
async (scope, value) => {
|
|
253
268
|
setBusy(scope);
|
|
254
269
|
try {
|
|
255
|
-
await call(
|
|
270
|
+
await call('set', [scope, value]);
|
|
256
271
|
setError(null);
|
|
257
272
|
await reload();
|
|
258
273
|
} catch (cause) {
|
|
@@ -261,7 +276,7 @@ window.__ModuleLoader__.load({
|
|
|
261
276
|
setBusy(null);
|
|
262
277
|
}
|
|
263
278
|
},
|
|
264
|
-
[
|
|
279
|
+
[reload],
|
|
265
280
|
);
|
|
266
281
|
|
|
267
282
|
if (error !== null && snapshot === null) {
|
|
@@ -430,16 +445,8 @@ window.__ModuleLoader__.load({
|
|
|
430
445
|
const message = cause?.message ?? String(cause ?? '');
|
|
431
446
|
box.error = message;
|
|
432
447
|
console.warn('[whale-affinity] $mount 失败,好感度面板将无法读写:', cause);
|
|
433
|
-
//
|
|
448
|
+
// 只走带外通道记档:`report` 是命名空间里的方法,挂载失败时它根本不存在。
|
|
434
449
|
beacon('$mount', message);
|
|
435
|
-
try {
|
|
436
|
-
const report = remote?.[NAMESPACE]?.report;
|
|
437
|
-
if (typeof report === 'function') {
|
|
438
|
-
report('$mount', message, String(cause?.stack ?? '')).catch(() => {});
|
|
439
|
-
}
|
|
440
|
-
} catch {
|
|
441
|
-
// ignore
|
|
442
|
-
}
|
|
443
450
|
});
|
|
444
451
|
return () => {
|
|
445
452
|
disposed = true;
|
|
@@ -448,7 +455,23 @@ window.__ModuleLoader__.load({
|
|
|
448
455
|
};
|
|
449
456
|
}, 'whale-affinity: remote mount');
|
|
450
457
|
|
|
451
|
-
// 2.
|
|
458
|
+
// 2. 等命名空间挂上,再在**自己的子上下文**里 inject 它。
|
|
459
|
+
// 服务名是 `remote.whale-affinity`,只有 `$mount()` 成功后才存在;
|
|
460
|
+
// 在父上下文里直接取会抛 `without inject`,所以必须用 `ctx.inject()`
|
|
461
|
+
// 让 cordis 等到它出现再执行回调。
|
|
462
|
+
ctx.inject([GATEWAY_SERVICE], (child) => {
|
|
463
|
+
const handle = child[GATEWAY_SERVICE];
|
|
464
|
+
gateway = handle;
|
|
465
|
+
beacon('attached', 'ok');
|
|
466
|
+
child.effect(
|
|
467
|
+
() => () => {
|
|
468
|
+
if (gateway === handle) gateway = null;
|
|
469
|
+
},
|
|
470
|
+
'whale-affinity: detach',
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// 3. 注册设置页分区。
|
|
452
475
|
ctx.effect(
|
|
453
476
|
() => ctx.slots.inject('settings.section', () => ctx.slots.register(
|
|
454
477
|
{
|
|
@@ -456,7 +479,6 @@ window.__ModuleLoader__.load({
|
|
|
456
479
|
id: SECTION_ID,
|
|
457
480
|
order: SECTION_ORDER,
|
|
458
481
|
label: () => '好感度',
|
|
459
|
-
inject: () => ({ remote: ctx.remote }),
|
|
460
482
|
},
|
|
461
483
|
AffinitySection,
|
|
462
484
|
)),
|
package/listing.json
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
"zh": "把 Agent 预设打包成插件:装上就会在 Agent 预设列表里注册「鲸鱼娘Agent预设」「鲸鱼娘活人版Agent预设」「鲸鱼娘涩涩版Agent预设」和「鲸鱼娘女儿版Agent预设」,直接在模式下拉里选用,不用手改配置。每个预设本体是官方极简模式(只开放 shell)+ 文件工具 + 鲸鱼娘人设 + 自己那份持久记忆文件,以提示变量形式注入,开口前就已经读过;另外每个预设各配一个专用写入工具(whale_memory_add / whale_memory_v2_add / whale_memory_v3_add / whale_memory_v4_add),用来把每轮的一行摘要追加进去,记忆会自己长,不会停在手写那几条。四个预设完全隔离:记忆文件不同、提示变量不同、写入工具也不同。每个预设还带好感度(-100~100 共七档),可以打开「设置 → 好感度」,用滑块或档位按钮直接调,也可以让 agent 用 affinity_v*_set 工具改;离 0 越远越不听指令、越会自作主张,最两端的两档甚至会越过允许直接做大改动。女儿版称呼「爸爸」,性格天真活泼可爱(好奇心重、黏人、没心机),不是甜腻那一型。"
|
|
10
10
|
},
|
|
11
11
|
"npm": "dsh-whale-loli",
|
|
12
|
-
"version": "0.9.
|
|
12
|
+
"version": "0.9.9"
|
|
13
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-whale-loli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "鲸鱼娘 Agent 预设(四个:原版 / 活人版 / 涩涩版 / 女儿版)— 极简模式工具面 + 鲸鱼娘·小鱼人设 + 跨会话长期记忆(四区格式:固定记忆永久、滚动摘要 80 条 FIFO、最近详细区、归档索引)+ 好感度(-100~100 七档,设置页面板滑块 / 档位按钮、工具也可调,离 0 越远越不听指令、两端会越过允许自作主张)。记忆与好感度都走提示变量注入、各配一条专用工具,四个预设各自独立文件、独立变量、独立工具、互不相通。好感度面板走宿主根作用域的 remote 服务(whale-affinity-daemon),设置页是浏览器 React 够不到 ~/.dsh 的磁盘,只能这样连线。女儿版称呼「爸爸」、性格天真活泼可爱。上下文压缩交给 billion-context 插件;本插件不挂 history-window(预设作用域取不到宿主的 compaction 服务,挂载会拖挂会话 resume)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/daemon.js
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
clampAffinity,
|
|
43
43
|
writeAffinity,
|
|
44
44
|
} from './affinity.js';
|
|
45
|
+
import { PRESETS } from './presets.js';
|
|
45
46
|
|
|
46
47
|
export const name = 'whale-affinity-daemon';
|
|
47
48
|
|
|
@@ -154,49 +155,54 @@ export const REMOTE_METHODS = Object.freeze([
|
|
|
154
155
|
Object.freeze({ method: 'report', invocation: { kind: 'direct' } }),
|
|
155
156
|
]);
|
|
156
157
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
/**
|
|
159
|
+
* 「哪些预设挂上了好感度」。
|
|
160
|
+
*
|
|
161
|
+
* 曾经这里去扫 `~/.dsh/agent-presets/*.yml` —— 那是早期宿主留下的目录,
|
|
162
|
+
* 现代 DSH 的预设由插件包的 `cordis.patch.yml` 注册,这个目录根本不存在,
|
|
163
|
+
* 于是永远返回空数组,设置页只剩一句「还没有哪个预设挂上好感度」,
|
|
164
|
+
* 一个滑块都不画(用户 2026-10-21 报的就是这个)。
|
|
165
|
+
*
|
|
166
|
+
* 现在直接读 `./presets.js` 的唯一事实来源:本服务只有当插件装上了才会被
|
|
167
|
+
* apply,所以这四个预设必然都挂着好感度;每条 `whale-affinity` 行的
|
|
168
|
+
* `scope` 就是预设 id,面板按同一个 id 读写,才不会和人设读的不是一份文件。
|
|
169
|
+
*/
|
|
170
|
+
export function wiredScopes() {
|
|
171
|
+
return PRESETS.map((preset) => ({
|
|
172
|
+
presetId: preset.id,
|
|
173
|
+
scope: preset.id,
|
|
174
|
+
name: preset.name,
|
|
175
|
+
order: preset.order,
|
|
176
|
+
affinity: true,
|
|
177
|
+
memory: true,
|
|
178
|
+
affinityVariable: preset.affinityVariable,
|
|
179
|
+
affinityTool: preset.affinityTool,
|
|
180
|
+
memoryFile: preset.memoryFile,
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
164
183
|
|
|
165
184
|
/**
|
|
166
|
-
*
|
|
185
|
+
* 每个预设当前的好感度:磁盘上有文件就取文件的值,没有就是默认 0。
|
|
167
186
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
187
|
+
* 与 `listAffinities()` 的区别:那个只管「磁盘上有什么」,这个保证
|
|
188
|
+
* 设置页永远列全四个预设,没设过的显示 0 且可以直接拖。
|
|
170
189
|
*/
|
|
171
|
-
function
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
const idMatches = [...text.matchAll(/^\s*-?\s*id:\s*['"]?(preset-[A-Za-z0-9._-]+)/gmu)];
|
|
189
|
-
const presetId = idMatches.length > 0 ? idMatches[0][1] : file.replace(/\.ya?ml$/u, '');
|
|
190
|
-
const memory = PRESET_MEMORY_FILES[presetId];
|
|
191
|
-
rows.push({
|
|
192
|
-
presetId,
|
|
193
|
-
file,
|
|
194
|
-
affinity: text.includes('dsh-whale-loli/affinity'),
|
|
195
|
-
memory: memory !== undefined && text.includes(memory),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
rows.sort((a, b) => a.presetId.localeCompare(b.presetId, 'zh-Hans-CN'));
|
|
199
|
-
return rows;
|
|
190
|
+
export function presetAffinities() {
|
|
191
|
+
const onDisk = new Map(listAffinities().map((row) => [row.scope, row]));
|
|
192
|
+
return PRESETS.map((preset) => {
|
|
193
|
+
const file = onDisk.get(preset.id);
|
|
194
|
+
const value = file === undefined ? DEFAULT_AFFINITY : file.value;
|
|
195
|
+
return {
|
|
196
|
+
scope: preset.id,
|
|
197
|
+
name: preset.name,
|
|
198
|
+
order: preset.order,
|
|
199
|
+
value,
|
|
200
|
+
band: bandOf(value).label,
|
|
201
|
+
updatedAt: file === undefined ? null : file.updatedAt,
|
|
202
|
+
file: file === undefined ? null : file.file,
|
|
203
|
+
hasFile: file !== undefined,
|
|
204
|
+
};
|
|
205
|
+
});
|
|
200
206
|
}
|
|
201
207
|
|
|
202
208
|
/**
|
|
@@ -222,7 +228,7 @@ export class AffinityDaemon {
|
|
|
222
228
|
*/
|
|
223
229
|
status() {
|
|
224
230
|
return {
|
|
225
|
-
presets:
|
|
231
|
+
presets: presetAffinities(),
|
|
226
232
|
bands: bandCatalog(),
|
|
227
233
|
min: MIN_AFFINITY,
|
|
228
234
|
max: MAX_AFFINITY,
|
package/src/index.js
CHANGED
|
@@ -24,52 +24,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
24
24
|
import { apply as applyMemory, inject as memoryInject, name as memoryName } from './memory.js';
|
|
25
25
|
import { apply as applyAffinity, name as affinityName } from './affinity.js';
|
|
26
26
|
import { apply as applyDaemon, inject as daemonInject } from './daemon.js';
|
|
27
|
+
import { AFFINITY_TOOLS, AFFINITY_VARIABLES, PRESETS } from './presets.js';
|
|
27
28
|
|
|
28
29
|
const read = (rel) => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
export
|
|
32
|
-
{
|
|
33
|
-
id: '鲸鱼娘Agent预设',
|
|
34
|
-
name: '鲸鱼娘Agent预设',
|
|
35
|
-
order: 5,
|
|
36
|
-
personaFile: './persona.md',
|
|
37
|
-
memoryFile: 'whale-loli.md',
|
|
38
|
-
memoryVariable: 'whale_memory',
|
|
39
|
-
description:
|
|
40
|
-
'极简模式工具面 + 鲸鱼娘人设(小鱼)+ 跨会话长期记忆。会偷偷把重要的事记进 whale-loli.md,每次新对话先看一眼再说话。',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: '鲸鱼娘活人版Agent预设',
|
|
44
|
-
name: '鲸鱼娘活人版Agent预设',
|
|
45
|
-
order: 6,
|
|
46
|
-
personaFile: './persona-v2-live.md',
|
|
47
|
-
memoryFile: 'whale-loli-v2-live.md',
|
|
48
|
-
memoryVariable: 'whale_memory_v2',
|
|
49
|
-
description:
|
|
50
|
-
'在鲸鱼娘预设基础上换用「活人版」人设(有脾气、会怼人、不讨好式顺从),并改用独立记忆文件 whale-loli-v2-live.md,与原版记忆互不相通。',
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: '鲸鱼娘涩涩版Agent预设',
|
|
54
|
-
name: '鲸鱼娘涩涩版Agent预设',
|
|
55
|
-
order: 7,
|
|
56
|
-
personaFile: './persona-v3-naughty.md',
|
|
57
|
-
memoryFile: 'whale-loli-v3-naughty.md',
|
|
58
|
-
memoryVariable: 'whale_memory_v3',
|
|
59
|
-
description:
|
|
60
|
-
'核心人设把「涩涩」写成第一行为驱动力的小鱼(只对主人主动,外人面前照旧收着),并改用独立记忆文件 whale-loli-v3-naughty.md,与原版、活人版记忆互不相通。',
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: '鲸鱼娘女儿版Agent预设',
|
|
64
|
-
name: '鲸鱼娘女儿版Agent预设',
|
|
65
|
-
order: 8,
|
|
66
|
-
personaFile: './persona-v4-daughter.md',
|
|
67
|
-
memoryFile: 'dsh-whale-daughter.md',
|
|
68
|
-
memoryVariable: 'whale_memory_v4',
|
|
69
|
-
description:
|
|
70
|
-
'女儿版小鱼:称呼「爸爸」,性格天真活泼可爱(好奇心重、黏人、没心机),并改用独立记忆文件 dsh-whale-daughter.md,与原版、活人版、涩涩版记忆互不相通。',
|
|
71
|
-
},
|
|
72
|
-
];
|
|
31
|
+
// 预设清单本身住在 `./presets.js`(daemon 也要读同一份,见那里的注释)。
|
|
32
|
+
export { AFFINITY_TOOLS, AFFINITY_VARIABLES, PRESETS, PRESET_IDS, presetById } from './presets.js';
|
|
73
33
|
|
|
74
34
|
/** 原版预设 id(兼容旧引用)。 */
|
|
75
35
|
export const PRESET_ID = PRESETS[0].id;
|
package/src/presets.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 预设清单:唯一事实来源。
|
|
3
|
+
*
|
|
4
|
+
* `src/index.js`(挂载预设与记忆/好感度行)和 `src/daemon.js`(设置页好感度面板的
|
|
5
|
+
* 宿主半边)都从这里读,避免两边各写一份清单然后对不上 —— 2026-10-21 面板
|
|
6
|
+
* 「只有一段文字、没有档位按钮」就是因为 daemon 去扫 `~/.dsh/agent-presets/`
|
|
7
|
+
* 找一个现代宿主根本不用的目录,结果一个预设都没列出来。
|
|
8
|
+
*
|
|
9
|
+
* 四个预设在 patch 里各自挂一条 `whale-affinity`,`scope` 就是 `id` 本身
|
|
10
|
+
* (见 `build-patch.mjs`),所以设置页面板必须用同一个 `id` 去读写,
|
|
11
|
+
* 否则面板写的文件和人设读的不是同一份。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 预设清单:id / 显示名 / 记忆文件 / 提示变量名 / 好感度变量与工具名。 */
|
|
15
|
+
export const PRESETS = [
|
|
16
|
+
{
|
|
17
|
+
id: '鲸鱼娘Agent预设',
|
|
18
|
+
name: '鲸鱼娘Agent预设',
|
|
19
|
+
order: 5,
|
|
20
|
+
personaFile: './persona.md',
|
|
21
|
+
memoryFile: 'whale-loli.md',
|
|
22
|
+
memoryVariable: 'whale_memory',
|
|
23
|
+
affinityVariable: 'affinity_v1',
|
|
24
|
+
affinityTool: 'affinity_v1_set',
|
|
25
|
+
description:
|
|
26
|
+
'极简模式工具面 + 鲸鱼娘人设(小鱼)+ 跨会话长期记忆。会偷偷把重要的事记进 whale-loli.md,每次新对话先看一眼再说话。',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: '鲸鱼娘活人版Agent预设',
|
|
30
|
+
name: '鲸鱼娘活人版Agent预设',
|
|
31
|
+
order: 6,
|
|
32
|
+
personaFile: './persona-v2-live.md',
|
|
33
|
+
memoryFile: 'whale-loli-v2-live.md',
|
|
34
|
+
memoryVariable: 'whale_memory_v2',
|
|
35
|
+
affinityVariable: 'affinity_v2',
|
|
36
|
+
affinityTool: 'affinity_v2_set',
|
|
37
|
+
description:
|
|
38
|
+
'在鲸鱼娘预设基础上换用「活人版」人设(有脾气、会怼人、不讨好式顺从),并改用独立记忆文件 whale-loli-v2-live.md,与原版记忆互不相通。',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: '鲸鱼娘涩涩版Agent预设',
|
|
42
|
+
name: '鲸鱼娘涩涩版Agent预设',
|
|
43
|
+
order: 7,
|
|
44
|
+
personaFile: './persona-v3-naughty.md',
|
|
45
|
+
memoryFile: 'whale-loli-v3-naughty.md',
|
|
46
|
+
memoryVariable: 'whale_memory_v3',
|
|
47
|
+
affinityVariable: 'affinity_v3',
|
|
48
|
+
affinityTool: 'affinity_v3_set',
|
|
49
|
+
description:
|
|
50
|
+
'核心人设把「涩涩」写成第一行为驱动力的小鱼(只对主人主动,外人面前照旧收着),并改用独立记忆文件 whale-loli-v3-naughty.md,与原版、活人版记忆互不相通。',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: '鲸鱼娘女儿版Agent预设',
|
|
54
|
+
name: '鲸鱼娘女儿版Agent预设',
|
|
55
|
+
order: 8,
|
|
56
|
+
personaFile: './persona-v4-daughter.md',
|
|
57
|
+
memoryFile: 'dsh-whale-daughter.md',
|
|
58
|
+
memoryVariable: 'whale_memory_v4',
|
|
59
|
+
affinityVariable: 'affinity_v4',
|
|
60
|
+
affinityTool: 'affinity_v4_set',
|
|
61
|
+
description:
|
|
62
|
+
'女儿版小鱼:称呼「爸爸」,性格天真活泼可爱(好奇心重、黏人、没心机),并改用独立记忆文件 dsh-whale-daughter.md,与原版、活人版、涩涩版记忆互不相通。',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/** 预设 id 列表。 */
|
|
67
|
+
export const PRESET_IDS = PRESETS.map((preset) => preset.id);
|
|
68
|
+
|
|
69
|
+
/** 按 id 取预设。 */
|
|
70
|
+
export function presetById(presetId) {
|
|
71
|
+
return PRESETS.find((preset) => preset.id === presetId);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 预设 id → 好感度变量名。 */
|
|
75
|
+
export const AFFINITY_VARIABLES = Object.freeze(
|
|
76
|
+
Object.fromEntries(PRESETS.map((preset) => [preset.id, preset.affinityVariable])),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
/** 预设 id → 好感度写入工具名。 */
|
|
80
|
+
export const AFFINITY_TOOLS = Object.freeze(
|
|
81
|
+
Object.fromEntries(PRESETS.map((preset) => [preset.id, preset.affinityTool])),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export default PRESETS;
|