dsh-whale-loli 0.9.7 → 0.9.8

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
@@ -44,9 +44,17 @@
44
44
 
45
45
  ```
46
46
  - id: whale-affinity-daemon
47
- name: 'dsh-whale-loli/affinity-daemon'
47
+ name: 'dsh-whale-loli' # 必须裸包名(见下)
48
+ config:
49
+ daemon: true
48
50
  ```
49
51
 
52
+ `name` 只能写**裸包名**:宿主的客户端模块扫描器(`dsh-client-modules` 的
53
+ `exactPackageSpecifier()`)只接受「裸名」或「`@scope/name`」,写成子路径
54
+ (`dsh-whale-loli/affinity-daemon`)会让 `package.json` 解析失败(`ERR_PACKAGE_PATH_NOT_EXPORTED`),
55
+ 于是 `dsh.client` 永远发现不了、面板永不出现,**而宿主一个错都不报**。
56
+ 同一行插件用 `config.daemon: true` 分派到宿主半边,记忆那半边则按 `file` 分派。
57
+
50
58
  它在**宿主根作用域**提供一个 remote 服务 `whale-affinity`,浏览器半边(`lib/client.js`)
51
59
  用 `ctx.remote.$mount()` 挂上,再调 `status()` / `set(scope, value)`。
52
60
 
@@ -55,6 +63,28 @@
55
63
  `inject: ['commands']` 会让预设加载时一直等它(`waiting for commands`),把会话 resume 拖挂。
56
64
  设置页没有这两个限制。
57
65
 
66
+ #### 取命名空间必须用 `ctx.inject()`,不能在服务对象上点
67
+
68
+ `$mount()` 成功之后,网关注册的服务名就是 `remote.whale-affinity`(带点号)。
69
+ 它是**服务**,不是普通属性,所以:
70
+
71
+ ```js
72
+ remote['whale-affinity'] // ✗ cannot get property "remote.whale-affinity" without inject
73
+ ctx.inject(['remote.whale-affinity'], (child) => {
74
+ const gateway = child['remote.whale-affinity']; // ✓
75
+ });
76
+ ```
77
+
78
+ 原因是 cordis 的 get 陷阱(`cordis/lib/index.js:676`)对「不在 fiber.store 里、也没 inject」的属性
79
+ 一律抛那句报错,而服务上的 `tracker.associate` 会把 `remote['whale-affinity']` 重定向成
80
+ `ctx['remote.whale-affinity']`。这个名字只有挂载后才存在,所以必须用**动态 inject** 等它出现
81
+ (`verify-client-gateway-access.mjs` 用真实 cordis 正反两面都验过)。
82
+
83
+ 这一步的教训值得记:**自写的校验脚本曾在 `contribution` 形状上跟着源码一起写错
84
+ (`methods` 而宿主读的是 `descriptors`),互相印证、永远绿。** 所以现在凡是对接宿主的字段,
85
+ 校验脚本都要拿宿主源码里真正读的那个名字来断言。
86
+
87
+
58
88
  ## 记忆功能
59
89
 
60
90
  小鱼的记忆本分**四个区**,各管各的:
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(remote, method) {
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 = remote?.[NAMESPACE]?.[method];
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(remote, method, args) {
148
- return attach(remote, method)
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(remote, 'status', []);
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
- }, [remote]);
234
+ }, []);
220
235
 
221
236
  useEffect(() => {
222
237
  let alive = true;
223
238
  (async () => {
224
239
  try {
225
- const next = await call(remote, 'status', []);
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 = remote?.[NAMESPACE]?.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
- }, [remote]);
264
+ }, []);
250
265
 
251
266
  const commit = useCallback(
252
267
  async (scope, value) => {
253
268
  setBusy(scope);
254
269
  try {
255
- await call(remote, 'set', [scope, value]);
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
- [remote, reload],
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
- // 走带外通道记档(`report` 在 $mount 失败时根本调不到,见 beacon 注释)。
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.7"
12
+ "version": "0.9.8"
13
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-whale-loli",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
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",