luckytri 1.0.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.
Files changed (235) hide show
  1. package/.github/SECURITY.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +250 -0
  4. package/README.md +250 -0
  5. package/bin/luckytri.js +107 -0
  6. package/docs/brand/banner.png +0 -0
  7. package/docs/brand/palette.png +0 -0
  8. package/docs/brand/portrait.png +0 -0
  9. package/docs/en/CHANGELOG.md +113 -0
  10. package/docs/en/architecture.md +152 -0
  11. package/docs/en/chat-style.md +62 -0
  12. package/docs/en/connect.md +63 -0
  13. package/docs/en/guide.md +216 -0
  14. package/docs/en/her-life.md +189 -0
  15. package/docs/en/npm-release.md +25 -0
  16. package/docs/en/plugins.md +82 -0
  17. package/docs/en/recovery.md +28 -0
  18. package/docs/en/time.md +71 -0
  19. package/docs/en/webui.md +73 -0
  20. package/docs/zh/CHANGELOG.md +113 -0
  21. package/docs/zh/architecture.md +152 -0
  22. package/docs/zh/chat-style.md +62 -0
  23. package/docs/zh/connect.md +63 -0
  24. package/docs/zh/guide.md +216 -0
  25. package/docs/zh/her-life.md +189 -0
  26. package/docs/zh/npm-release.md +33 -0
  27. package/docs/zh/plugins.md +82 -0
  28. package/docs/zh/recovery.md +28 -0
  29. package/docs/zh/time.md +71 -0
  30. package/docs/zh/webui.md +73 -0
  31. package/package.json +108 -0
  32. package/plugins/sdk/index.d.ts +155 -0
  33. package/plugins/sdk/testing.js +58 -0
  34. package/plugins/weather/index.js +82 -0
  35. package/plugins/weather/luckytri-plugin.json +29 -0
  36. package/public/app/assets/ChatsPage-C-FcRapR.js +3 -0
  37. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  38. package/public/app/assets/Empty-CpP5kQHH.js +2 -0
  39. package/public/app/assets/Empty-DmUB2-Bk.css +1 -0
  40. package/public/app/assets/Field-D4gY-INU.js +1 -0
  41. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  42. package/public/app/assets/HeartPage-CPy_thQd.js +1 -0
  43. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  44. package/public/app/assets/LifePage-CxWvwmSf.js +1 -0
  45. package/public/app/assets/MemoryPage-DC4rhQ68.js +1 -0
  46. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  47. package/public/app/assets/NaturePage-CTXHCE2D.css +1 -0
  48. package/public/app/assets/NaturePage-IvYw04Zq.js +3 -0
  49. package/public/app/assets/PeoplePage-BmUxkrOU.css +1 -0
  50. package/public/app/assets/PeoplePage-Cqefm9-q.js +1 -0
  51. package/public/app/assets/PluginsPage-DKSaKvXb.js +2 -0
  52. package/public/app/assets/PluginsPage-HyBe5D8w.css +1 -0
  53. package/public/app/assets/SystemPage-CAMPoYqM.css +1 -0
  54. package/public/app/assets/SystemPage-CJmtJkrK.js +1 -0
  55. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  56. package/public/app/assets/TimePage-Cw44inM4.js +3 -0
  57. package/public/app/assets/formatters-ZmW_xFHw.js +1 -0
  58. package/public/app/assets/index-BheRW-1t.js +4 -0
  59. package/public/app/assets/index-YDz1ree1.css +1 -0
  60. package/public/app/assets/knowledge-DG0e0uKw.js +1 -0
  61. package/public/app/assets/liquid-garden-IMoHBwzc.png +0 -0
  62. package/public/app/assets/luckytri-home-95QKeMtc.png +0 -0
  63. package/public/app/assets/media-BzPDBm7e.js +1 -0
  64. package/public/app/index.html +27 -0
  65. package/public/guide.en.html +1053 -0
  66. package/public/guide.html +893 -0
  67. package/public/plugin-kit/v1/bridge.js +30 -0
  68. package/public/plugin-kit/v1/kit.css +14 -0
  69. package/public/tutorial.en.md +214 -0
  70. package/public/tutorial.md +214 -0
  71. package/scripts/backup.js +311 -0
  72. package/scripts/browser.js +41 -0
  73. package/scripts/launch.js +94 -0
  74. package/scripts/plugin.js +150 -0
  75. package/scripts/proxy-config.js +167 -0
  76. package/scripts/recover.js +26 -0
  77. package/scripts/runtime.js +25 -0
  78. package/scripts/service.js +47 -0
  79. package/scripts/setup.js +50 -0
  80. package/scripts/start-server.js +41 -0
  81. package/scripts/stop.js +36 -0
  82. package/server/app.js +96 -0
  83. package/server/channels/adapters.js +27 -0
  84. package/server/channels/contract.js +57 -0
  85. package/server/channels/hub.js +165 -0
  86. package/server/channels/index.js +25 -0
  87. package/server/channels/onebot/adapter.js +288 -0
  88. package/server/channels/onebot/index.js +336 -0
  89. package/server/channels/onebot/token.js +3 -0
  90. package/server/channels/qqbot/adapter.js +253 -0
  91. package/server/channels/qqbot/api.js +199 -0
  92. package/server/channels/qqbot/credentials.js +11 -0
  93. package/server/channels/qqbot/gateway.js +274 -0
  94. package/server/channels/qqbot/index.js +389 -0
  95. package/server/channels/segments.js +40 -0
  96. package/server/channels/session-key.js +153 -0
  97. package/server/channels/state.js +42 -0
  98. package/server/core/api.js +564 -0
  99. package/server/core/capabilities.js +208 -0
  100. package/server/core/context-builder.js +288 -0
  101. package/server/core/context-compactor.js +454 -0
  102. package/server/core/conversation-cues.js +325 -0
  103. package/server/core/conversation-manager.js +87 -0
  104. package/server/core/events.js +60 -0
  105. package/server/core/feedback.js +8 -0
  106. package/server/core/group-style.js +72 -0
  107. package/server/core/image-preprocessor.js +208 -0
  108. package/server/core/initiative-context.js +66 -0
  109. package/server/core/input-budget.js +32 -0
  110. package/server/core/local-demo.js +98 -0
  111. package/server/core/message-manager.js +118 -0
  112. package/server/core/message-scheduler.js +78 -0
  113. package/server/core/model-json.js +190 -0
  114. package/server/core/model-manager.js +1213 -0
  115. package/server/core/model-presets.js +900 -0
  116. package/server/core/name-call.js +29 -0
  117. package/server/core/net-guard.js +49 -0
  118. package/server/core/network.js +157 -0
  119. package/server/core/orchestrator.js +569 -0
  120. package/server/core/perceivers.js +91 -0
  121. package/server/core/person-name.js +22 -0
  122. package/server/core/persona-manager.js +243 -0
  123. package/server/core/reply-delivery.js +393 -0
  124. package/server/core/reply-target-resolver.js +50 -0
  125. package/server/core/repository.js +293 -0
  126. package/server/core/response-generator.js +50 -0
  127. package/server/core/response-validator.js +508 -0
  128. package/server/core/retired-prompts.js +51 -0
  129. package/server/core/sessions.js +109 -0
  130. package/server/core/speaker-names.js +92 -0
  131. package/server/core/topic-tracker.js +65 -0
  132. package/server/core/turn-processor.js +630 -0
  133. package/server/core/turn.js +236 -0
  134. package/server/core/vision-manager.js +633 -0
  135. package/server/http.js +18 -0
  136. package/server/i18n/decisions.js +206 -0
  137. package/server/i18n/errors.js +497 -0
  138. package/server/i18n/index.js +144 -0
  139. package/server/i18n/labels.js +653 -0
  140. package/server/i18n/models.js +111 -0
  141. package/server/index.js +102 -0
  142. package/server/knowledge/api.js +342 -0
  143. package/server/knowledge/embedding-profile.js +114 -0
  144. package/server/knowledge/index.js +13 -0
  145. package/server/knowledge/manager.js +386 -0
  146. package/server/knowledge/retrieval.js +93 -0
  147. package/server/knowledge/schema.js +154 -0
  148. package/server/knowledge/vector-index.js +90 -0
  149. package/server/mind/affect.js +265 -0
  150. package/server/mind/anticipations.js +507 -0
  151. package/server/mind/api.js +703 -0
  152. package/server/mind/attention.js +200 -0
  153. package/server/mind/bonds.js +558 -0
  154. package/server/mind/budget.js +152 -0
  155. package/server/mind/clock.js +83 -0
  156. package/server/mind/context-selection.js +61 -0
  157. package/server/mind/continuity.js +562 -0
  158. package/server/mind/conversation-origin.js +175 -0
  159. package/server/mind/day-planner.js +324 -0
  160. package/server/mind/days.js +210 -0
  161. package/server/mind/evidence-read.js +45 -0
  162. package/server/mind/evidence.js +148 -0
  163. package/server/mind/faces.js +138 -0
  164. package/server/mind/guard.js +60 -0
  165. package/server/mind/index.js +445 -0
  166. package/server/mind/initiative.js +333 -0
  167. package/server/mind/life/activities.js +21 -0
  168. package/server/mind/life/context.js +309 -0
  169. package/server/mind/life/diary.js +786 -0
  170. package/server/mind/life/growth.js +252 -0
  171. package/server/mind/life/index.js +1049 -0
  172. package/server/mind/life/presence.js +305 -0
  173. package/server/mind/meetings.js +1069 -0
  174. package/server/mind/memory-policy.js +48 -0
  175. package/server/mind/memory.js +888 -0
  176. package/server/mind/nature.js +262 -0
  177. package/server/mind/novelty.js +19 -0
  178. package/server/mind/observations.js +84 -0
  179. package/server/mind/own-day.js +324 -0
  180. package/server/mind/own-voice.js +318 -0
  181. package/server/mind/periods.js +118 -0
  182. package/server/mind/reading.js +120 -0
  183. package/server/mind/salience.js +201 -0
  184. package/server/mind/schema.js +562 -0
  185. package/server/mind/self.js +484 -0
  186. package/server/mind/senses.js +53 -0
  187. package/server/mind/thoughts.js +274 -0
  188. package/server/mind/time/activity-clock.js +196 -0
  189. package/server/mind/time/agenda.js +242 -0
  190. package/server/mind/time/api.js +200 -0
  191. package/server/mind/time/attention.js +34 -0
  192. package/server/mind/time/availability.js +69 -0
  193. package/server/mind/time/commitment-review.js +198 -0
  194. package/server/mind/time/delivery-links.js +130 -0
  195. package/server/mind/time/executor.js +512 -0
  196. package/server/mind/time/experiences.js +128 -0
  197. package/server/mind/time/games.js +562 -0
  198. package/server/mind/time/index.js +739 -0
  199. package/server/mind/time/intent.js +166 -0
  200. package/server/mind/time/kinds.js +171 -0
  201. package/server/mind/time/model-material.js +77 -0
  202. package/server/mind/time/plugin-activity.js +207 -0
  203. package/server/mind/time/presentation.js +51 -0
  204. package/server/mind/time/schedule.js +83 -0
  205. package/server/mind/time/schema.js +84 -0
  206. package/server/mind/time/search.js +383 -0
  207. package/server/mind/time/sharing.js +472 -0
  208. package/server/mind/time/task-links.js +311 -0
  209. package/server/mind/time/tasks.js +587 -0
  210. package/server/mind/time/works.js +432 -0
  211. package/server/mind/traits.js +191 -0
  212. package/server/mind/util.js +110 -0
  213. package/server/mind/view.js +273 -0
  214. package/server/paths.js +102 -0
  215. package/server/plugins/api.js +204 -0
  216. package/server/plugins/host.js +967 -0
  217. package/server/plugins/install.js +295 -0
  218. package/server/plugins/manifest.js +201 -0
  219. package/server/plugins/permissions.js +42 -0
  220. package/server/plugins/runner/guard.mjs +36 -0
  221. package/server/plugins/runner/worker.mjs +250 -0
  222. package/server/plugins/schema.js +91 -0
  223. package/server/plugins/zip.js +112 -0
  224. package/server/startup-lock.js +118 -0
  225. package/server/storage/archive.js +241 -0
  226. package/server/storage/backup-cleanup-worker.js +8 -0
  227. package/server/storage/backup-cleanup.js +414 -0
  228. package/server/storage/backup-lock.js +76 -0
  229. package/server/storage/backup-scheduler.js +131 -0
  230. package/server/storage/store.js +199 -0
  231. package/server/studio/display-names.js +138 -0
  232. package/server/studio/management.js +102 -0
  233. package/server/studio/readiness.js +190 -0
  234. package/server/studio/settings.js +236 -0
  235. package/server/version.js +10 -0
@@ -0,0 +1,562 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { NATURE_DEFAULTS, NATURE_FIELDS } from "./nature.js";
3
+ import { evidence, parse } from "./util.js";
4
+ import { migrateTime } from "./time/schema.js";
5
+
6
+ // Every table here only grows. A change is a new row that cites the
7
+ // experience behind it; a withdrawal is a tombstone in mind_revocations.
8
+ export function migrateMind(db, store) {
9
+ migrateTime(db);
10
+ db.exec(`
11
+ CREATE TABLE IF NOT EXISTS mind_creations(id TEXT PRIMARY KEY,created INTEGER NOT NULL,plan_id TEXT NOT NULL UNIQUE,kind TEXT NOT NULL,title TEXT NOT NULL,content TEXT NOT NULL,sources TEXT NOT NULL,session_id TEXT,discretion TEXT NOT NULL,run_id TEXT NOT NULL);
12
+ CREATE INDEX IF NOT EXISTS mind_creations_time ON mind_creations(created);
13
+ CREATE TABLE IF NOT EXISTS mind_memory_permissions(memory_id TEXT NOT NULL,session_id TEXT NOT NULL,recall INTEGER NOT NULL,disclose INTEGER NOT NULL,expires INTEGER,updated INTEGER NOT NULL,PRIMARY KEY(memory_id,session_id));
14
+ CREATE TABLE IF NOT EXISTS mind_permission_events(id INTEGER PRIMARY KEY,memory_id TEXT NOT NULL,session_id TEXT NOT NULL,created INTEGER NOT NULL,value TEXT NOT NULL);
15
+ CREATE TABLE IF NOT EXISTS mind_nature (version INTEGER PRIMARY KEY, created INTEGER NOT NULL, value TEXT NOT NULL, note TEXT);
16
+ CREATE TABLE IF NOT EXISTS mind_trait_changes (id TEXT PRIMARY KEY, created INTEGER NOT NULL, trait TEXT NOT NULL, value INTEGER NOT NULL, delta INTEGER NOT NULL, reason TEXT NOT NULL, sources TEXT NOT NULL DEFAULT '[]', origin TEXT NOT NULL, nature_version INTEGER NOT NULL);
17
+ CREATE INDEX IF NOT EXISTS mind_trait_changes_trait ON mind_trait_changes(nature_version,trait,created);
18
+ CREATE TABLE IF NOT EXISTS mind_persona_growth (id TEXT PRIMARY KEY, created INTEGER NOT NULL, content TEXT NOT NULL, sources TEXT NOT NULL DEFAULT '[]', origin TEXT NOT NULL, nature_version INTEGER NOT NULL);
19
+ CREATE INDEX IF NOT EXISTS mind_persona_growth_time ON mind_persona_growth(nature_version,created);
20
+ CREATE TABLE IF NOT EXISTS mind_affect (id TEXT PRIMARY KEY, created INTEGER NOT NULL, feeling TEXT NOT NULL, intensity REAL NOT NULL, valence REAL NOT NULL, arousal REAL NOT NULL, cause TEXT, sources TEXT NOT NULL DEFAULT '[]', session_id TEXT, origin TEXT NOT NULL);
21
+ CREATE INDEX IF NOT EXISTS mind_affect_time ON mind_affect(created);
22
+ CREATE TABLE IF NOT EXISTS mind_people (user_id TEXT PRIMARY KEY, name TEXT, first_seen INTEGER, last_seen INTEGER, sessions TEXT NOT NULL DEFAULT '[]');
23
+ CREATE TABLE IF NOT EXISTS mind_bond_events (id TEXT PRIMARY KEY, created INTEGER NOT NULL, subject_kind TEXT NOT NULL, subject_id TEXT NOT NULL, change TEXT NOT NULL, familiarity REAL NOT NULL DEFAULT 0, closeness REAL NOT NULL DEFAULT 0, trust REAL NOT NULL DEFAULT 0, tension REAL NOT NULL DEFAULT 0, note TEXT, sources TEXT NOT NULL DEFAULT '[]', session_id TEXT, origin TEXT NOT NULL);
24
+ CREATE INDEX IF NOT EXISTS mind_bond_subject ON mind_bond_events(subject_kind,subject_id,created);
25
+ CREATE TABLE IF NOT EXISTS mind_self (id TEXT PRIMARY KEY, thread TEXT NOT NULL, created INTEGER NOT NULL, kind TEXT NOT NULL, content TEXT NOT NULL, strength REAL NOT NULL, status TEXT NOT NULL, sources TEXT NOT NULL DEFAULT '[]', days TEXT NOT NULL DEFAULT '[]', origin TEXT NOT NULL, session_id TEXT);
26
+ CREATE INDEX IF NOT EXISTS mind_self_thread ON mind_self(thread,created);
27
+ CREATE TABLE IF NOT EXISTS mind_faces (id TEXT PRIMARY KEY, session_id TEXT NOT NULL, created INTEGER NOT NULL, role TEXT NOT NULL DEFAULT '', tone TEXT NOT NULL DEFAULT '', aspiration TEXT NOT NULL DEFAULT '', content TEXT NOT NULL DEFAULT '', sources TEXT NOT NULL DEFAULT '[]', origin TEXT NOT NULL);
28
+ CREATE INDEX IF NOT EXISTS mind_faces_session ON mind_faces(session_id,created);
29
+ CREATE TABLE IF NOT EXISTS mind_thoughts (id TEXT PRIMARY KEY, created INTEGER NOT NULL, kind TEXT NOT NULL, content TEXT NOT NULL, sessions TEXT NOT NULL DEFAULT '[]', sources TEXT NOT NULL DEFAULT '[]', parent_id TEXT, importance REAL NOT NULL DEFAULT 0.5, revisit_at INTEGER, status TEXT NOT NULL DEFAULT 'open', hidden INTEGER NOT NULL DEFAULT 0, outreach TEXT NOT NULL DEFAULT '', outreach_session TEXT, outreach_status TEXT NOT NULL DEFAULT 'none', run_id TEXT);
30
+ CREATE INDEX IF NOT EXISTS mind_thoughts_time ON mind_thoughts(created);
31
+ CREATE TABLE IF NOT EXISTS mind_diary (id TEXT PRIMARY KEY, day TEXT NOT NULL, created INTEGER NOT NULL, content TEXT NOT NULL, mood TEXT, compare TEXT, sources TEXT NOT NULL DEFAULT '[]', run_id TEXT);
32
+ CREATE INDEX IF NOT EXISTS mind_diary_day ON mind_diary(day,created);
33
+ CREATE TABLE IF NOT EXISTS mind_snapshots (day TEXT PRIMARY KEY, created INTEGER NOT NULL, value TEXT NOT NULL);
34
+ CREATE TABLE IF NOT EXISTS mind_chapters (id TEXT PRIMARY KEY, chapter INTEGER NOT NULL, created INTEGER NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, period_start INTEGER, period_end INTEGER, run_id TEXT);
35
+ CREATE TABLE IF NOT EXISTS mind_choices (id TEXT PRIMARY KEY, created INTEGER NOT NULL, session_id TEXT NOT NULL, trace_id TEXT, choice TEXT NOT NULL, appraisal TEXT, reason TEXT, watermark INTEGER, occasion TEXT);
36
+ CREATE INDEX IF NOT EXISTS mind_choices_session ON mind_choices(session_id,created);
37
+ CREATE TABLE IF NOT EXISTS mind_revocations (id TEXT PRIMARY KEY, created INTEGER NOT NULL, target_kind TEXT NOT NULL, target_id TEXT NOT NULL, content TEXT, reason TEXT);
38
+ CREATE INDEX IF NOT EXISTS mind_revocations_target ON mind_revocations(target_kind,target_id);
39
+ CREATE TABLE IF NOT EXISTS mind_runs (id TEXT PRIMARY KEY, kind TEXT NOT NULL, started INTEGER NOT NULL, finished INTEGER, status TEXT NOT NULL, reason TEXT, tokens INTEGER NOT NULL DEFAULT 0, model TEXT, watermark INTEGER, summary TEXT);
40
+ CREATE INDEX IF NOT EXISTS mind_runs_time ON mind_runs(kind,started);
41
+ CREATE TABLE IF NOT EXISTS mind_usage (id INTEGER PRIMARY KEY, time INTEGER NOT NULL, category TEXT NOT NULL, stage TEXT NOT NULL, session_id TEXT, input INTEGER NOT NULL DEFAULT 0, cached INTEGER NOT NULL DEFAULT 0, output INTEGER NOT NULL DEFAULT 0, estimated INTEGER NOT NULL DEFAULT 0);
42
+ CREATE INDEX IF NOT EXISTS mind_usage_time ON mind_usage(time);
43
+ CREATE TABLE IF NOT EXISTS mind_attention (session_id TEXT PRIMARY KEY, looked_seq INTEGER NOT NULL DEFAULT 0, looked_at INTEGER);
44
+ CREATE TABLE IF NOT EXISTS mind_days (day TEXT PRIMARY KEY, created INTEGER NOT NULL, lived INTEGER NOT NULL DEFAULT 0, events INTEGER NOT NULL DEFAULT 0, valence REAL, arousal REAL, looked INTEGER NOT NULL DEFAULT 0, spoke INTEGER NOT NULL DEFAULT 0, people INTEGER NOT NULL DEFAULT 0);
45
+ CREATE TABLE IF NOT EXISTS mind_anticipations (id TEXT PRIMARY KEY, created INTEGER NOT NULL, kind TEXT NOT NULL, subject TEXT, session_id TEXT, content TEXT NOT NULL, due_at INTEGER NOT NULL, due_precision TEXT NOT NULL DEFAULT 'day', recurrence TEXT NOT NULL DEFAULT 'none', discretion TEXT NOT NULL DEFAULT 'open', sources TEXT NOT NULL DEFAULT '[]', origin TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', closed_at INTEGER, closed_note TEXT NOT NULL DEFAULT '', closed_sources TEXT NOT NULL DEFAULT '[]');
46
+ CREATE INDEX IF NOT EXISTS mind_anticipations_due ON mind_anticipations(status,due_at);
47
+ CREATE TABLE IF NOT EXISTS mind_periods (id TEXT PRIMARY KEY, level TEXT NOT NULL, created INTEGER NOT NULL, period_start INTEGER, period_end INTEGER, title TEXT NOT NULL DEFAULT '', content TEXT NOT NULL, compare TEXT NOT NULL DEFAULT '', sources TEXT NOT NULL DEFAULT '[]', run_id TEXT);
48
+ CREATE INDEX IF NOT EXISTS mind_periods_level ON mind_periods(level,created);
49
+ CREATE TABLE IF NOT EXISTS mind_meetings (id TEXT PRIMARY KEY, created INTEGER NOT NULL, session_id TEXT NOT NULL, choice TEXT NOT NULL, appraisal TEXT NOT NULL DEFAULT '', topic TEXT NOT NULL DEFAULT '', people TEXT NOT NULL DEFAULT '[]', sources TEXT NOT NULL DEFAULT '[]', will_thread TEXT, will_met INTEGER NOT NULL DEFAULT 0, discretion TEXT NOT NULL DEFAULT 'open', will_people TEXT NOT NULL DEFAULT '[]');
50
+ CREATE INDEX IF NOT EXISTS mind_meetings_time ON mind_meetings(created);
51
+ CREATE INDEX IF NOT EXISTS mind_meetings_session ON mind_meetings(session_id, created);
52
+ CREATE INDEX IF NOT EXISTS mind_meetings_will ON mind_meetings(will_thread, will_met, created);
53
+ CREATE TABLE IF NOT EXISTS mind_meeting_people (meeting_id TEXT NOT NULL, user_id TEXT NOT NULL, PRIMARY KEY (meeting_id, user_id));
54
+ CREATE INDEX IF NOT EXISTS mind_meeting_people_user ON mind_meeting_people(user_id, meeting_id);
55
+ CREATE INDEX IF NOT EXISTS core_memories_subject ON core_memories(subject,status);
56
+ CREATE TABLE IF NOT EXISTS mind_unlived (seq INTEGER PRIMARY KEY);
57
+ `);
58
+ const addColumn = (table, column, definition) => {
59
+ if (
60
+ !db
61
+ .prepare(`PRAGMA table_info(${table})`)
62
+ .all()
63
+ .some((c) => c.name === column)
64
+ )
65
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
66
+ };
67
+ addColumn("core_memories", "discretion", "TEXT NOT NULL DEFAULT 'open'");
68
+ addColumn("core_memories", "superseded_by", "TEXT");
69
+ addColumn("mind_attention", "deferred", "INTEGER NOT NULL DEFAULT 0");
70
+ addColumn("mind_thoughts", "resolved_at", "INTEGER");
71
+ addColumn("mind_thoughts", "resolution", "TEXT NOT NULL DEFAULT ''");
72
+ addColumn("mind_thoughts", "outreach_at", "INTEGER");
73
+ addColumn("mind_thoughts", "outreach_retry_at", "INTEGER");
74
+ addColumn("mind_thoughts", "outreach_reason", "TEXT NOT NULL DEFAULT ''");
75
+ addColumn("mind_thoughts", "outreach_draft", "TEXT NOT NULL DEFAULT '[]'");
76
+ addColumn("mind_meetings", "exchange", "TEXT NOT NULL DEFAULT 'null'");
77
+ addColumn(
78
+ "mind_thoughts",
79
+ "outreach_wait_reason",
80
+ "TEXT NOT NULL DEFAULT ''",
81
+ );
82
+ addColumn("mind_meetings", "will_people", "TEXT NOT NULL DEFAULT '[]'");
83
+ addColumn("mind_meetings", "reason", "TEXT NOT NULL DEFAULT ''");
84
+ addColumn("mind_anticipations", "activity", "TEXT NOT NULL DEFAULT ''");
85
+ db.prepare(
86
+ "UPDATE mind_runs SET status='interrupted',finished=? WHERE status='running'",
87
+ ).run(Date.now());
88
+ db.prepare(
89
+ "UPDATE mind_thoughts SET outreach_status='uncertain' WHERE outreach_status='sending'",
90
+ ).run();
91
+ migratePeople(db);
92
+ const alreadyMigrated = db
93
+ .prepare("SELECT 1 FROM core_config WHERE id='mind-migration-v1'")
94
+ .get();
95
+ if (alreadyMigrated) {
96
+ migrateMemoryProvenance(db);
97
+ restoreTruncatedFaces(db);
98
+ return;
99
+ }
100
+ db.exec("BEGIN IMMEDIATE");
101
+ try {
102
+ migrateNature(db, store);
103
+ migrateFacesAndPolicies(db);
104
+ migrateInnerLife(db);
105
+ migrateMemories(db);
106
+ migrateLifeSettings(db);
107
+ // History from before she had attention counts as already read.
108
+ db.exec(
109
+ "INSERT OR IGNORE INTO mind_attention(session_id,looked_seq,looked_at) SELECT session_id,MAX(seq),MAX(time) FROM core_events GROUP BY session_id",
110
+ );
111
+ db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
112
+ "mind-migration-v1",
113
+ JSON.stringify({ time: Date.now() }),
114
+ );
115
+ db.exec("COMMIT");
116
+ } catch (error) {
117
+ db.exec("ROLLBACK");
118
+ throw error;
119
+ }
120
+ migrateMemoryProvenance(db);
121
+ restoreTruncatedFaces(db);
122
+ }
123
+
124
+ // The first face import kept only 1200 characters, which cut a persona off
125
+ // mid-sentence. When that fragment is still the start of her nature, put the
126
+ // rest back. A different text that was cut the same way has no source left.
127
+ function restoreTruncatedFaces(db) {
128
+ if (
129
+ db.prepare("SELECT 1 FROM core_config WHERE id='mind-face-full-v1'").get()
130
+ )
131
+ return;
132
+ const row = db
133
+ .prepare("SELECT value FROM mind_nature ORDER BY version DESC LIMIT 1")
134
+ .get();
135
+ let base = "";
136
+ if (row) {
137
+ try {
138
+ base = String(JSON.parse(row.value).base || "");
139
+ } catch {
140
+ base = "";
141
+ }
142
+ }
143
+ if (base.length > 1200) {
144
+ const update = db.prepare("UPDATE mind_faces SET content=? WHERE id=?");
145
+ for (const face of db
146
+ .prepare(
147
+ "SELECT id, content FROM mind_faces WHERE origin='migration' AND length(content)=1200",
148
+ )
149
+ .all()) {
150
+ if (base.startsWith(face.content)) update.run(base, face.id);
151
+ }
152
+ }
153
+ db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
154
+ "mind-face-full-v1",
155
+ JSON.stringify({ time: Date.now() }),
156
+ );
157
+ }
158
+
159
+ // Carry forward who she has met and where, without inventing feelings from
160
+ // old chat volume. Bond events start growing from experiences in this mind.
161
+ function migratePeople(db) {
162
+ if (db.prepare("SELECT 1 FROM core_config WHERE id='mind-people-v1'").get())
163
+ return;
164
+ const now = Date.now();
165
+ db.exec("BEGIN IMMEDIATE");
166
+ try {
167
+ const people = db
168
+ .prepare(
169
+ `
170
+ WITH live AS (
171
+ SELECT seq,time,session_id,
172
+ CAST(json_extract(payload,'$.userId') AS TEXT) user_id,
173
+ NULLIF(CAST(json_extract(payload,'$.name') AS TEXT),'') name
174
+ FROM core_events
175
+ WHERE role='user'
176
+ AND COALESCE(json_extract(payload,'$.simulated'),0)=0
177
+ AND json_extract(payload,'$.userId') IS NOT NULL
178
+ AND CAST(json_extract(payload,'$.userId') AS TEXT) NOT IN ('','bot')
179
+ AND time<=?
180
+ ),
181
+ people AS (
182
+ SELECT user_id,MIN(time) first_seen,MAX(time) last_seen
183
+ FROM live GROUP BY user_id
184
+ ),
185
+ ranked_names AS (
186
+ SELECT user_id,name,ROW_NUMBER() OVER (
187
+ PARTITION BY user_id ORDER BY time DESC,seq DESC
188
+ ) rank
189
+ FROM live WHERE name IS NOT NULL
190
+ )
191
+ SELECT p.user_id,p.first_seen,p.last_seen,n.name
192
+ FROM people p LEFT JOIN ranked_names n ON n.user_id=p.user_id AND n.rank=1
193
+ `,
194
+ )
195
+ .all(now);
196
+ const sessions = db
197
+ .prepare(
198
+ `
199
+ SELECT user_id,session_id,MAX(time) last_seen FROM (
200
+ SELECT CAST(json_extract(payload,'$.userId') AS TEXT) user_id,
201
+ session_id,time
202
+ FROM core_events
203
+ WHERE role='user'
204
+ AND COALESCE(json_extract(payload,'$.simulated'),0)=0
205
+ AND json_extract(payload,'$.userId') IS NOT NULL
206
+ AND CAST(json_extract(payload,'$.userId') AS TEXT) NOT IN ('','bot')
207
+ AND time<=?
208
+ ) GROUP BY user_id,session_id ORDER BY last_seen DESC
209
+ `,
210
+ )
211
+ .all(now);
212
+ const sessionsByUser = new Map();
213
+ for (const row of sessions) {
214
+ const list = sessionsByUser.get(row.user_id) || [];
215
+ if (list.length < 40 && !list.includes(row.session_id)) {
216
+ list.push(row.session_id);
217
+ sessionsByUser.set(row.user_id, list);
218
+ }
219
+ }
220
+ const read = db.prepare("SELECT * FROM mind_people WHERE user_id=?");
221
+ const write = db.prepare(
222
+ "INSERT INTO mind_people(user_id,name,first_seen,last_seen,sessions) VALUES (?,?,?,?,?) ON CONFLICT(user_id) DO UPDATE SET name=excluded.name,first_seen=excluded.first_seen,last_seen=excluded.last_seen,sessions=excluded.sessions",
223
+ );
224
+ for (const person of people) {
225
+ const existing = read.get(person.user_id);
226
+ const known = parse(existing?.sessions, []);
227
+ const places = [
228
+ ...new Set([
229
+ ...(Array.isArray(known) ? known : []),
230
+ ...(sessionsByUser.get(person.user_id) || []).slice().reverse(),
231
+ ]),
232
+ ].slice(-40);
233
+ write.run(
234
+ person.user_id,
235
+ existing?.name || person.name || null,
236
+ existing?.first_seen == null
237
+ ? person.first_seen
238
+ : Math.min(existing.first_seen, person.first_seen),
239
+ existing?.last_seen == null
240
+ ? person.last_seen
241
+ : Math.max(existing.last_seen, person.last_seen),
242
+ JSON.stringify(places),
243
+ );
244
+ }
245
+ db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
246
+ "mind-people-v1",
247
+ JSON.stringify({ time: now }),
248
+ );
249
+ db.exec("COMMIT");
250
+ } catch (error) {
251
+ db.exec("ROLLBACK");
252
+ throw error;
253
+ }
254
+ }
255
+
256
+ function config(db, id) {
257
+ return parse(
258
+ db.prepare("SELECT value FROM core_config WHERE id=?").get(id)?.value,
259
+ null,
260
+ );
261
+ }
262
+
263
+ function saveConfig(db, id, value) {
264
+ db.prepare(
265
+ "INSERT INTO core_config(id,value) VALUES (?,?) ON CONFLICT(id) DO UPDATE SET value=excluded.value,version=version+1",
266
+ ).run(id, JSON.stringify(value));
267
+ }
268
+
269
+ function tableExists(db, name) {
270
+ return !!db
271
+ .prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?")
272
+ .get(name);
273
+ }
274
+
275
+ function migrateNature(db, store) {
276
+ if (db.prepare("SELECT 1 FROM mind_nature LIMIT 1").get()) return;
277
+ const settings = store.settings();
278
+ const persona = config(db, "persona") || {};
279
+ const value = { ...NATURE_DEFAULTS };
280
+ for (const key of NATURE_FIELDS)
281
+ if (persona[key] !== undefined) value[key] = persona[key];
282
+ value.name = String(persona.name || settings.name || value.name);
283
+ value.base = String(persona.base || settings.persona || "");
284
+ db.prepare(
285
+ "INSERT INTO mind_nature(version,created,value,note) VALUES (1,?,?,?)",
286
+ ).run(Date.now(), JSON.stringify(value), "从旧人格迁移");
287
+ }
288
+
289
+ // Per-group persona overrides become her first face in that group; the
290
+ // participation dice and comfort switch are not carried forward.
291
+ function migrateFacesAndPolicies(db) {
292
+ for (const row of db
293
+ .prepare("SELECT id,value FROM core_config WHERE id LIKE 'session:%'")
294
+ .all()) {
295
+ const policy = parse(row.value, {});
296
+ const session = row.id.slice("session:".length);
297
+ const override = policy.persona;
298
+ if (
299
+ override &&
300
+ typeof override === "object" &&
301
+ Object.keys(override).length
302
+ )
303
+ db.prepare(
304
+ "INSERT INTO mind_faces(id,session_id,created,role,tone,aspiration,content,sources,origin) VALUES (?,?,?,?,?,?,?,?,?)",
305
+ ).run(
306
+ randomUUID(),
307
+ session,
308
+ Date.now(),
309
+ "",
310
+ "",
311
+ "",
312
+ String(
313
+ override.base ||
314
+ Object.entries(override)
315
+ .map(([k, v]) => `${k}:${Array.isArray(v) ? v.join("、") : v}`)
316
+ .join(";"),
317
+ ),
318
+ "[]",
319
+ "migration",
320
+ );
321
+ const {
322
+ persona: _persona,
323
+ comfortOnDistress: _comfort,
324
+ topicBoost: _boost,
325
+ probability: _probability,
326
+ cooldown: _cooldown,
327
+ ...clean
328
+ } = policy;
329
+ if (JSON.stringify(clean) !== JSON.stringify(policy))
330
+ saveConfig(db, row.id, clean);
331
+ }
332
+ }
333
+
334
+ function migrateInnerLife(db) {
335
+ if (tableExists(db, "time_states")) {
336
+ const last = db
337
+ .prepare(
338
+ "SELECT * FROM time_states WHERE phase!='legacy' ORDER BY created DESC LIMIT 1",
339
+ )
340
+ .get();
341
+ if (last?.mood)
342
+ db.prepare(
343
+ "INSERT INTO mind_affect(id,created,feeling,intensity,valence,arousal,cause,sources,session_id,origin) VALUES (?,?,?,?,?,?,?,?,?,?)",
344
+ ).run(
345
+ randomUUID(),
346
+ last.created,
347
+ String(last.mood).slice(0, 20),
348
+ 0.3,
349
+ 0,
350
+ last.energy === "bright" ? 0.55 : last.energy === "low" ? 0.2 : 0.35,
351
+ String(last.attention || "").slice(0, 120),
352
+ "[]",
353
+ last.session_id,
354
+ "migration",
355
+ );
356
+ }
357
+ if (tableExists(db, "time_notes"))
358
+ for (const n of db.prepare("SELECT * FROM time_notes").all())
359
+ db.prepare(
360
+ "INSERT OR IGNORE INTO mind_thoughts(id,created,kind,content,sessions,sources,parent_id,importance,revisit_at,status,hidden,outreach,outreach_session,outreach_status) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
361
+ ).run(
362
+ n.id,
363
+ n.created,
364
+ n.kind,
365
+ n.content,
366
+ JSON.stringify([n.session_id]),
367
+ JSON.stringify(evidence(parse(n.sources, []))),
368
+ n.parent_id || null,
369
+ Number(n.importance ?? 0.5),
370
+ n.revisit_at || null,
371
+ n.status || "open",
372
+ n.hidden ? 1 : 0,
373
+ n.outreach || "",
374
+ n.session_id,
375
+ n.outreach_status === "pending" || !n.outreach_status
376
+ ? "none"
377
+ : n.outreach_status,
378
+ );
379
+ if (tableExists(db, "time_self_threads")) {
380
+ const rows = db
381
+ .prepare("SELECT * FROM time_self_threads ORDER BY created")
382
+ .all();
383
+ const root = new Map();
384
+ const kinds = {
385
+ curiosity: "curiosity",
386
+ care: "care",
387
+ stance: "view",
388
+ intention: "intention",
389
+ };
390
+ for (const t of rows) {
391
+ const thread = t.parent_id ? root.get(t.parent_id) || t.parent_id : t.id;
392
+ root.set(t.id, thread);
393
+ db.prepare(
394
+ "INSERT OR IGNORE INTO mind_self(id,thread,created,kind,content,strength,status,sources,days,origin,session_id) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
395
+ ).run(
396
+ t.id,
397
+ thread,
398
+ t.created,
399
+ kinds[t.kind] || "curiosity",
400
+ t.content,
401
+ Math.min(0.5, Number(t.confidence || 0.5) * 0.5),
402
+ t.status === "closed" ? "closed" : "active",
403
+ JSON.stringify(evidence(parse(t.sources, []))),
404
+ "[]",
405
+ "migration",
406
+ t.session_id,
407
+ );
408
+ }
409
+ }
410
+ }
411
+
412
+ // Consolidated facts used to wait for a human reviewer and so never reached
413
+ // a reply. Believable ones become what she thinks, with their confidence.
414
+ function migrateMemories(db) {
415
+ const hasGroundedSources = groundedSourceMatcher(db);
416
+ for (const m of db
417
+ .prepare(
418
+ "SELECT id,session_id,sources,confidence FROM core_memories WHERE status='candidate'",
419
+ )
420
+ .all()) {
421
+ const sources = parse(m.sources, []);
422
+ const joking = sources.some?.((s) =>
423
+ ["joke", "hearsay"].includes(s?.certainty),
424
+ );
425
+ if (
426
+ Number(m.confidence) >= 0.6 &&
427
+ !joking &&
428
+ hasGroundedSources(m.session_id, sources)
429
+ )
430
+ db.prepare("UPDATE core_memories SET status='confirmed' WHERE id=?").run(
431
+ m.id,
432
+ );
433
+ }
434
+ db.prepare(
435
+ "UPDATE core_memories SET discretion='private' WHERE discretion='open' AND (session_id LIKE '__private__%' OR session_id LIKE 'private:%' OR session_id LIKE '%:private:%')",
436
+ ).run();
437
+ if (!tableExists(db, "memory_candidates")) return;
438
+ for (const c of db
439
+ .prepare("SELECT * FROM memory_candidates WHERE status='pending'")
440
+ .all()) {
441
+ const privateScope =
442
+ c.scope === "private" || String(c.scope).includes("private");
443
+ const session =
444
+ c.scope === "private" ? `__private__:${c.user_id}` : c.scope;
445
+ db.prepare(
446
+ "INSERT OR IGNORE INTO core_memories(id,session_id,subject,content,type,confidence,importance,status,locked,sources,created,updated,discretion) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
447
+ ).run(
448
+ "candidate:" + c.id,
449
+ session,
450
+ c.user_id,
451
+ c.content,
452
+ "preference",
453
+ 0.95,
454
+ 0.7,
455
+ "confirmed",
456
+ 0,
457
+ JSON.stringify([{ text: c.source_text, certainty: "self_report" }]),
458
+ c.time || Date.now(),
459
+ Date.now(),
460
+ privateScope ? "private" : "open",
461
+ );
462
+ db.prepare("UPDATE memory_candidates SET status='migrated' WHERE id=?").run(
463
+ c.id,
464
+ );
465
+ }
466
+ }
467
+
468
+ // Old memory citations contain a message snapshot as well as a numeric ID.
469
+ // IDs can be reused when an old message store is folded into the new event
470
+ // store, so trust the snapshot only when that speaker said that exact text at
471
+ // that time in the memory's own conversation.
472
+ function groundedSourceMatcher(db) {
473
+ const find = db.prepare(
474
+ "SELECT 1 FROM messages WHERE session_id=? AND user_id=? AND text=? AND time=? AND role='user' AND is_demo=0 LIMIT 1",
475
+ );
476
+ const cache = new Map();
477
+ return (session, sources) => {
478
+ if (!Array.isArray(sources) || !sources.length) return false;
479
+ return sources.every((source) => {
480
+ const speaker = String(source?.speaker ?? "");
481
+ const content = typeof source?.text === "string" ? source.text : "";
482
+ const time = Number(source?.time);
483
+ if (!speaker || !content || !Number.isFinite(time)) return false;
484
+ const key = JSON.stringify([session, speaker, content, time]);
485
+ if (!cache.has(key))
486
+ cache.set(key, !!find.get(session, speaker, content, time));
487
+ return cache.get(key);
488
+ });
489
+ };
490
+ }
491
+
492
+ // Earlier builds promoted high-confidence memories without checking where
493
+ // their evidence came from. Quarantine only unlocked, pre-migration rows that
494
+ // match that automatic-promotion rule; explicit/locked memories remain intact.
495
+ function migrateMemoryProvenance(db) {
496
+ if (
497
+ db
498
+ .prepare("SELECT 1 FROM core_config WHERE id='mind-memory-provenance-v1'")
499
+ .get()
500
+ )
501
+ return;
502
+ const migration = parse(
503
+ db
504
+ .prepare("SELECT value FROM core_config WHERE id='mind-migration-v1'")
505
+ .get()?.value,
506
+ null,
507
+ );
508
+ if (!migration || !Number.isFinite(Number(migration.time))) return;
509
+
510
+ const hasGroundedSources = groundedSourceMatcher(db);
511
+ const candidates = db
512
+ .prepare(
513
+ `SELECT id,session_id,sources,confidence,locked,type,created,updated
514
+ FROM core_memories
515
+ WHERE status='confirmed' AND locked=0 AND COALESCE(type,'')!='reviewed'
516
+ AND id NOT LIKE 'legacy:%' AND id NOT LIKE 'candidate:%'
517
+ AND created<=? AND updated<=?`,
518
+ )
519
+ .all(Number(migration.time), Number(migration.time));
520
+ const quarantine = [];
521
+ for (const memory of candidates) {
522
+ const sources = parse(memory.sources, []);
523
+ const joking = sources.some?.((source) =>
524
+ ["joke", "hearsay"].includes(source?.certainty),
525
+ );
526
+ if (
527
+ Number(memory.confidence) >= 0.6 &&
528
+ !joking &&
529
+ !hasGroundedSources(memory.session_id, sources)
530
+ )
531
+ quarantine.push(memory.id);
532
+ }
533
+
534
+ db.exec("BEGIN IMMEDIATE");
535
+ try {
536
+ const demote = db.prepare(
537
+ "UPDATE core_memories SET status='candidate' WHERE id=? AND status='confirmed' AND locked=0",
538
+ );
539
+ for (const id of quarantine) demote.run(id);
540
+ db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
541
+ "mind-memory-provenance-v1",
542
+ JSON.stringify({ time: Date.now(), quarantined: quarantine.length }),
543
+ );
544
+ db.exec("COMMIT");
545
+ } catch (error) {
546
+ db.exec("ROLLBACK");
547
+ throw error;
548
+ }
549
+ }
550
+
551
+ function migrateLifeSettings(db) {
552
+ const time = config(db, "time");
553
+ if (!time || config(db, "life")) return;
554
+ saveConfig(db, "life", {
555
+ solitude: time.enabled !== false,
556
+ proactive: !!time.proactive,
557
+ idleMinutes: Number.isInteger(time.idleMinutes) ? time.idleMinutes : 20,
558
+ intervalMinutes: Math.max(30, Number(time.intervalMinutes) || 90),
559
+ });
560
+ if (Number(time.dailyTokens) > 0 && !config(db, "budget"))
561
+ saveConfig(db, "budget", { dailyTokens: Number(time.dailyTokens) });
562
+ }