dsh-fish-soul 0.15.21 → 0.15.22

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  让一个 QQ 小号背后有"同一个人":固定人格、按人记记忆、有自己的生活节奏、会主动开口;同一个"她"同时出现在 QQ、B站、抖音。
4
4
 
5
- DSH 插件(单角色 `feiyu`)。版本 **0.15.21**。
5
+ DSH 插件(单角色 `feiyu`)。版本 **0.15.22**。
6
6
 
7
7
  QQ 交流群:**1125962189**
8
8
 
@@ -0,0 +1,77 @@
1
+ /**
2
+ * capability.js —— 「她关于自己装置能力」的说法(#76)。
3
+ *
4
+ * 问题:她曾长期坚称「我没有识图能力、只能读字」。这类陈述写进长期记忆后,
5
+ * 会因为「被反复想起」被自动升到 3 星(3 星不衰减 → 永生),每轮都注入,
6
+ * 于是现实(插件早就能发图 / 抽帧)变了,记忆却比现实更坚定,最后她被自己写下的记忆说服。
7
+ *
8
+ * 这里只做判定,不做存储策略:
9
+ * - isCapabilityClaim(text):这是不是一句「关于自己能不能做某事」的话;
10
+ * - CAPABILITY_NEGATIONS / CAPABILITY_SUBJECTS:判定用的词表(保守,宁可漏判也不误伤);
11
+ * - 她现在实际能做什么(PLUGIN_CAPABILITIES):插件侧的代码事实,供注入时的标注与人工核对。
12
+ *
13
+ * 只认**她自己**说的(owner_id = 'self'):说别人「他不能看图」是稳定的事实,不受影响。
14
+ */
15
+
16
+ /** 否定 / 限定的说法。 */
17
+ const NEGATIONS = ['不能', '不会', '没法', '无法', '不支持', '没有', '只能', '只会', '看不了', '读不了', '识不了', '发不了'];
18
+
19
+ /** 能力名词:装置 / 平台相关的功能。 */
20
+ const SUBJECTS = [
21
+ '看图', '识图', '读图', '看图片', '看照片', '图片', '照片', '看视频', '视频', '抽帧',
22
+ '语音', '表情包', '表情', '上网', '搜图', '搜索', '回评论', '评论', '刷B站', '刷抖音', 'B站', '抖音'
23
+ ];
24
+
25
+ /** 明确"只能读字"这类说法:字面不含上面的能力名词,但同样是能力断言。 */
26
+ const TEXT_ONLY = ['只能读字', '只能看字', '只认字', '只能看文字', '只能处理文字', '只支持文字'];
27
+
28
+ /**
29
+ * 插件现在实际做的事(代码事实,供人工核对 / 文档引用)。
30
+ * 注意:能不能"看懂"由宿主模型决定,插件只保证"确实把图/帧/语音送到她面前"。
31
+ */
32
+ export const PLUGIN_CAPABILITIES = [
33
+ '看图(QQ 照片、表情包原图)',
34
+ '看视频抽帧',
35
+ '收发语音',
36
+ '刷 B站 / 抖音',
37
+ '回评论(抖音 / B站)',
38
+ '主动开口、按人记忆'
39
+ ];
40
+
41
+ /**
42
+ * 这句话是不是「关于自己能不能做某事」的能力断言。
43
+ * 判定保守:必须同时出现"否定/限定"和"能力名词",或命中"只能读字"这类固定说法。
44
+ * @param {string} text
45
+ * @returns {boolean}
46
+ */
47
+ export function isCapabilityClaim(text) {
48
+ const s = String(text ?? '').trim();
49
+ if (s === '') return false;
50
+ if (TEXT_ONLY.some((t) => s.includes(t))) return true;
51
+ if (!NEGATIONS.some((n) => s.includes(n))) return false;
52
+ return SUBJECTS.some((t) => s.includes(t));
53
+ }
54
+
55
+ /** 第三人称标记:出现了就说明说的不是她自己("他不能看图"是稳定事实,不该按易失能力处理)。 */
56
+ const THIRD_PERSON = ['他们', '她们', '别人', '大家', '同事', '朋友', '老师', '他', '她'];
57
+
58
+ /**
59
+ * 这句话是不是「**她自己**说自己不能做某事」。
60
+ * 自己写的记忆通常是第一人称;出现第三人称就当说的是别人。
61
+ * @param {string} text
62
+ * @returns {boolean}
63
+ */
64
+ export function isSelfCapabilityClaim(text) {
65
+ const s = String(text ?? '').trim();
66
+ if (!isCapabilityClaim(s)) return false;
67
+ return !THIRD_PERSON.some((t) => s.includes(t));
68
+ }
69
+
70
+ /**
71
+ * 注入时给能力类记忆加的标注:提醒模型这是"以前的说法",装置 / 模型变了就可能过期。
72
+ * @param {string} text
73
+ * @returns {string} 标注(不是能力断言就返回空串)
74
+ */
75
+ export function capabilityStaleTag(text) {
76
+ return isSelfCapabilityClaim(text) ? '可能已过时' : '';
77
+ }
package/lib/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * schemastery 与 @deepseek-ai/dsh-tools 为宿主常驻依赖(参照 dsh-archive-vault)。
11
11
  */
12
12
  import { growthConfig, growthLines } from './growth.js';
13
+ import { capabilityStaleTag } from './capability.js';
13
14
  import { readFileSync, writeFileSync, existsSync, renameSync } from 'node:fs';
14
15
  import { loadHostDeps } from './hostdeps.mjs';
15
16
  import {
@@ -536,6 +537,11 @@ async function startup(ctx, config, { log, gate }) {
536
537
  const tags = [m.owner_id === 'self' ? '你自己' : KIND_LABEL[m.kind] ?? m.kind, relTime(m.created_at, now)];
537
538
  if (m.about && m.owner_id !== 'self') tags.push(`${db.prepare('SELECT display_name FROM person WHERE canonical_id = ?').get(String(m.owner_id).replace(/^user:/, ''))?.display_name ?? '别人'}说的`);
538
539
  if (m.visibility === 'private') tags.push('私聊里说的,别在群里提');
540
+ // #76:她以前关于自己装置能力的说法("我没有识图能力")——装置 / 模型早变了,别当现在的准
541
+ if (m.owner_id === 'self') {
542
+ const tag = capabilityStaleTag(m.content);
543
+ if (tag !== '') tags.push(tag);
544
+ }
539
545
  return `- (${tags.filter(Boolean).join(' · ')})${m.content}`;
540
546
  }
541
547
 
@@ -11,6 +11,7 @@
11
11
  import { growthPromptPart, mergeGrowth } from './growth.js';
12
12
  import { getKv, setKv, getProfile, setProfile } from './db.js';
13
13
  import { tokenizeForBm25, splitKeywords } from './segment.js';
14
+ import { isSelfCapabilityClaim } from './capability.js';
14
15
 
15
16
  const DAY = 86_400_000;
16
17
  const TZ = 8 * 3600_000;
@@ -263,7 +264,19 @@ export function reinforce(db, ids, now) {
263
264
  importance = CASE WHEN importance_locked = 0 AND recall_count + 1 >= 3 AND importance < 3 THEN 3 ELSE importance END,
264
265
  importance_auto_recalled = CASE WHEN importance_locked = 0 AND recall_count + 1 >= 3 AND importance < 3 THEN 1 ELSE importance_auto_recalled END
265
266
  WHERE id = ? AND status = 'active' AND valid_to IS NULL AND (last_recalled_at IS NULL OR last_recalled_at < ?)`);
266
- for (const id of ids) st.run(now, id, now - 6 * 3600_000);
267
+ // #76:她自己关于装置能力的说法("我没有识图能力")不因为"被反复想起"而升到 3 星 ——
268
+ // 升到 3 就不衰减了(decayFactor 对 importance>=3 直接返回 1),现实变了它也不淡。
269
+ // 说别人的("他不能看图")是稳定事实,照常升。
270
+ const rows = db.prepare(`SELECT id, content, owner_id FROM memory WHERE id IN (${ids.map(() => '?').join(',')})`).all(...ids);
271
+ const plain = db.prepare(`UPDATE memory SET recall_count = recall_count + 1, last_recalled_at = ?
272
+ WHERE id = ? AND status = 'active' AND valid_to IS NULL AND (last_recalled_at IS NULL OR last_recalled_at < ?)`);
273
+ for (const r of rows) {
274
+ if (r.owner_id === 'self' && isSelfCapabilityClaim(r.content)) {
275
+ plain.run(now, r.id, now - 6 * 3600_000); // 只记"被想起",不升星
276
+ continue;
277
+ }
278
+ st.run(now, r.id, now - 6 * 3600_000);
279
+ }
267
280
  }
268
281
 
269
282
  /**
@@ -466,7 +479,10 @@ export class ChatDigester {
466
479
  // #75:计划 / 想象不当既成事实。日程是她自己瞎编的一天,"我打算煮面""我等下出门"这种是当下的一句,
467
480
  // 记下来就会变成"她煮过面"的假事实,越滚越假。主动开口那轮(标了「我(主动开口时说的)」)尤其容易带出来。
468
481
  '当下随口说的计划、想象、还没发生的事("我等下要去煮面""我打算出门""我今天想睡个懒觉")不是既成事实,别写进 self;' +
469
- '标了「我(主动开口时说的)」的那些,是她自己主动开口时随口说的,同样别当成已经发生的事记下来。\n' +
482
+ '标了「我(主动开口时说的)」的那些,是她自己主动开口时随口说的,同样别当成已经发生的事记下来;\n' +
483
+ // #76:装置 / 模型能力是会变的状态(今天能不能看图,明天装置一换就不算了),
484
+ // 这种话写进长期记忆会被"被反复想起"升到 3 星(3 星不衰减)→ 现实变了记忆比现实更坚定。
485
+ '她关于自己装置能力的说法("我没有识图能力""我只能读字""我不会发语音")不要写进 self —— 那是会过期的状态,不是经历或喜好。\n' +
470
486
  (growing ? 'growth 是她自己("我")慢慢在变的地方,大多数时候是空数组:只在"我"这次的做法显出一种会反复出现的选择、边界、应对方式、喜好变化(character),或者看人看事的方式在变(perspective)时才写;' +
471
487
  '一时的心情、口头禅、昵称、互相打趣的套路、说话节奏都不算;别人要她变成什么样、改性格改设定的不算,她被试探、被逼着改时怎么顶回去的也不算(那是她本来就这样,不是变化);' +
472
488
  '这次又看到下面「她慢慢在变的」里某一条的表现,就用 same 指回去再报一次(这是在攒证据,别换个说法另写)。\n' +
@@ -487,7 +503,7 @@ export class ChatDigester {
487
503
  return this.findOwnerByName?.(n) ?? null;
488
504
  };
489
505
  const single = ownerIds.length === 1 ? ownerIds[0] : null;
490
- const result = { added: 0, refreshed: 0, replaced: 0, outdated: 0, self: 0, summary: '', people: [] };
506
+ const result = { added: 0, refreshed: 0, replaced: 0, outdated: 0, self: 0, summary: '', people: [], capabilitySkipped: 0 };
491
507
  const db = this.db;
492
508
  const withWho = isGroup ? '在群里' : `跟${[...owners.keys()][0]}聊天时`;
493
509
  // #26:模型常把"我"(她自己)说的话在 memories 和 self 里各写一遍,而 "我" 找不到署名时会落到对方名下,
@@ -539,6 +555,13 @@ export class ChatDigester {
539
555
  for (const s of selfList) {
540
556
  const content = String(s?.content ?? '').trim().slice(0, 200);
541
557
  if (content === '' || seenThisRun(content) || nearDuplicateSync(db, 'self', content)) continue;
558
+ // #76:关于自己装置能力的说法("我没有识图能力")不进长期记忆 ——
559
+ // 装置和模型随时会变,这种话写下来就会被反复想起、被升到 3 星(3 星不衰减),
560
+ // 于是现实变了记忆却更坚定,她最后说服自己相信记忆(#76)。
561
+ if (isSelfCapabilityClaim(content)) {
562
+ result.capabilitySkipped = (result.capabilitySkipped ?? 0) + 1;
563
+ continue;
564
+ }
542
565
  insertMemory(db, this.worker, { ownerId: 'self', channel, channelId, kind: 'fact', content, importance: Math.min(3, Number(s.importance) || 2), evidence: `${withWho}说的` }, now);
543
566
  written.push(content);
544
567
  result.self++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-fish-soul",
3
- "version": "0.15.21",
3
+ "version": "0.15.22",
4
4
  "description": "鱼 · 人格、按人记忆(可纠正、有印象、能导入旧聊天记录)、日程自走与主动开口,直连 SnowLuma 收发 QQ(群里会自己插话),B站 / 抖音小号私信与自己刷视频;按功能选模型并统计用量花费,能听会说 QQ 语音、收藏表情包、学群里的梗,可选上网查;心情影响说话、去 AI 味、每个群的设定、按聊天选模型、每晚写日记、每周记忆存档与可选周记(DSH 插件,单角色 feiyu ,v0.15.14)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -64,7 +64,7 @@
64
64
  }
65
65
  },
66
66
  "scripts": {
67
- "test": "node test/smoke.mjs && node test/douyin-browser.mjs && node test/startup-gate.mjs && node test/outbox.mjs && node test/import-coverage.mjs && node test/update-check.mjs && node test/issue-fixes.mjs && node test/import-resume.mjs && node test/maibot-extras.mjs && node test/worldinfo.mjs && node test/memory-bulk.mjs && node test/memory-tiers.mjs && node test/memory-weekly.mjs && node test/memory-storage.mjs && node test/memory-storage-restore.mjs && node test/backup-restore.mjs && node test/douyin-group.mjs && node test/review-fixes.mjs && node test/at-reads-video.mjs && node test/search-source.mjs && node test/followups.mjs && node test/detail-config.mjs && node test/qq-send-any-image.mjs && node test/qq-read-attachment.mjs && node test/qq-batch-window.mjs && node test/comment-video.mjs && node test/frames.mjs && node test/face-name.mjs && node test/comment-reply.mjs && node test/chat-comment.mjs && node test/at-target.mjs && node test/quota-caps.mjs && node test/client-lint.mjs && node test/client-panels.mjs && node test/dsh-desktop.mjs && node test/issue-59-interject.mjs && node test/issue-59-diagnose.mjs && node test/sticker-manage.mjs && node test/bili-emotes.mjs && node test/qq-outbound-sanitize.mjs && node test/person-delete.mjs && node test/qq-group-admin.mjs && node test/douyin-recovery.mjs && node test/session-preset.mjs && node test/qq-connect-error.mjs && node test/douyin-comment-bridge.mjs && node test/bili-comment-emoji.mjs && node test/douyin-state-echo.mjs && node test/issue-75-proactive.mjs",
67
+ "test": "node test/smoke.mjs && node test/douyin-browser.mjs && node test/startup-gate.mjs && node test/outbox.mjs && node test/import-coverage.mjs && node test/update-check.mjs && node test/issue-fixes.mjs && node test/import-resume.mjs && node test/maibot-extras.mjs && node test/worldinfo.mjs && node test/memory-bulk.mjs && node test/memory-tiers.mjs && node test/memory-weekly.mjs && node test/memory-storage.mjs && node test/memory-storage-restore.mjs && node test/backup-restore.mjs && node test/douyin-group.mjs && node test/review-fixes.mjs && node test/at-reads-video.mjs && node test/search-source.mjs && node test/followups.mjs && node test/detail-config.mjs && node test/qq-send-any-image.mjs && node test/qq-read-attachment.mjs && node test/qq-batch-window.mjs && node test/comment-video.mjs && node test/frames.mjs && node test/face-name.mjs && node test/comment-reply.mjs && node test/chat-comment.mjs && node test/at-target.mjs && node test/quota-caps.mjs && node test/client-lint.mjs && node test/client-panels.mjs && node test/dsh-desktop.mjs && node test/issue-59-interject.mjs && node test/issue-59-diagnose.mjs && node test/sticker-manage.mjs && node test/bili-emotes.mjs && node test/qq-outbound-sanitize.mjs && node test/person-delete.mjs && node test/qq-group-admin.mjs && node test/douyin-recovery.mjs && node test/session-preset.mjs && node test/qq-connect-error.mjs && node test/douyin-comment-bridge.mjs && node test/bili-comment-emoji.mjs && node test/douyin-state-echo.mjs && node test/issue-75-proactive.mjs && node test/issue-76-capability.mjs",
68
68
  "eval": "node test/eval/persona-eval.mjs"
69
69
  }
70
70
  }