dsh-memu-memory 1.1.2 → 1.2.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 (199) hide show
  1. package/AGENTS.md +8 -6
  2. package/README.md +50 -5
  3. package/README.zh.md +49 -5
  4. package/docs/active-memory-ab.md +385 -0
  5. package/docs/binding-lifecycle.md +99 -0
  6. package/docs/contributing.md +6 -4
  7. package/docs/emotion-model-spec.md +281 -0
  8. package/docs/env-and-commands.md +13 -0
  9. package/docs/host-api.md +35 -3
  10. package/docs/memory-semantics.md +40 -11
  11. package/docs/recall-benchmark.md +238 -0
  12. package/docs/troubleshooting.md +395 -366
  13. package/lib/active-memory.js +165 -0
  14. package/lib/activity-log.js +190 -0
  15. package/lib/backup.js +1103 -0
  16. package/lib/binding-diagnostics.js +635 -0
  17. package/lib/client.js +728 -58
  18. package/lib/consolidation-ledger.js +347 -0
  19. package/lib/consolidation-receipt.js +284 -0
  20. package/lib/consolidation-snapshot.js +274 -0
  21. package/lib/consolidation-worker.js +115 -0
  22. package/lib/curator.js +81 -37
  23. package/lib/cycle.js +43 -6
  24. package/lib/embedding-catalog.js +763 -0
  25. package/lib/embedding-download.js +407 -0
  26. package/lib/embedding-runtime.js +710 -0
  27. package/lib/engine.js +2 -2
  28. package/lib/index.js +36 -0
  29. package/lib/inject.js +232 -103
  30. package/lib/local-embedding-entry.js +145 -0
  31. package/lib/local-embedding-manager.js +267 -0
  32. package/lib/local-embedding-protocol.js +173 -0
  33. package/lib/local-embedding-worker.js +652 -0
  34. package/lib/local-vector-service.js +283 -0
  35. package/lib/memory.js +599 -57
  36. package/lib/memu-config.js +10 -22
  37. package/lib/memu.js +53 -19
  38. package/lib/mood.js +268 -39
  39. package/lib/paths.js +24 -0
  40. package/lib/preset-scope.js +1 -1
  41. package/lib/private.js +450 -29
  42. package/lib/prompts/private-consolidation.coding.md +1 -0
  43. package/lib/prompts/private-consolidation.md +1 -0
  44. package/lib/prompts/private-consolidation.mixed.md +1 -0
  45. package/lib/prompts/private-consolidation.rp.md +1 -0
  46. package/lib/prompts.js +4 -4
  47. package/lib/remote-summary.js +51 -25
  48. package/lib/rpc.js +54 -5
  49. package/lib/state.js +137 -172
  50. package/lib/tools.js +58 -7
  51. package/lib/vector-migration.js +878 -0
  52. package/lib/vector-profile.js +968 -0
  53. package/package.json +4 -2
  54. package/scripts/acceptance-local.mjs +7 -5
  55. package/scripts/active-memory-ab-live.mjs +655 -0
  56. package/scripts/active-memory-ab.mjs +1302 -0
  57. package/scripts/chain-matrix.mjs +537 -0
  58. package/scripts/check-all.mjs +29 -1
  59. package/scripts/check-doc-drift.mjs +353 -0
  60. package/scripts/diagnose-bindings.mjs +176 -0
  61. package/scripts/local-runtime-install.mjs +142 -0
  62. package/scripts/local-runtime-verify.mjs +56 -0
  63. package/scripts/local-vector-verify.mjs +93 -0
  64. package/scripts/recall-bench.mjs +813 -0
  65. package/scripts/repetition-ab-scenarios.mjs +93 -0
  66. package/scripts/repetition-detector-snapshot.mjs +314 -0
  67. package/scripts/repetition-eval-live.mjs +360 -0
  68. package/scripts/repetition-eval.mjs +702 -0
  69. package/tests/activation.test.mjs +89 -0
  70. package/tests/active-memory-ab.test.mjs +745 -0
  71. package/tests/active-memory.test.mjs +129 -0
  72. package/tests/activity-log.test.mjs +126 -0
  73. package/tests/activity-wiring.test.mjs +103 -0
  74. package/tests/appendix-b.test.mjs +212 -0
  75. package/tests/appendix-b2.test.mjs +166 -0
  76. package/tests/audit-regression.test.mjs +272 -0
  77. package/tests/audit-safety.test.mjs +227 -0
  78. package/tests/backend-compensation.test.mjs +272 -0
  79. package/tests/backup.test.mjs +793 -0
  80. package/tests/binding-diagnostics.test.mjs +720 -0
  81. package/tests/browser.test.mjs +366 -0
  82. package/tests/client-render.test.mjs +892 -0
  83. package/tests/coalesce.test.mjs +113 -0
  84. package/tests/config.test.mjs +159 -0
  85. package/tests/consolidation-credential.test.mjs +320 -0
  86. package/tests/consolidation-fork.test.mjs +357 -0
  87. package/tests/consolidation-ledger.test.mjs +298 -0
  88. package/tests/consolidation-snapshot.test.mjs +321 -0
  89. package/tests/consolidation-value.test.mjs +89 -0
  90. package/tests/context-scope.test.mjs +40 -0
  91. package/tests/cue.test.mjs +44 -0
  92. package/tests/curator-probe.test.mjs +433 -0
  93. package/tests/curator.test.mjs +150 -0
  94. package/tests/cycle.test.mjs +411 -0
  95. package/tests/disclosure.test.mjs +231 -0
  96. package/tests/doc-drift.test.mjs +222 -0
  97. package/tests/embedding-catalog.test.mjs +266 -0
  98. package/tests/embedding-download.test.mjs +391 -0
  99. package/tests/embedding-runtime.test.mjs +389 -0
  100. package/tests/emotion-chain.test.mjs +84 -0
  101. package/tests/emotion-contract.test.mjs +445 -0
  102. package/tests/engine.test.mjs +35 -0
  103. package/tests/fixtures/active-memory-ab/README.md +56 -0
  104. package/tests/fixtures/active-memory-ab/cases/01-clean-dense-turn.json +351 -0
  105. package/tests/fixtures/active-memory-ab/cases/02-clean-thin-turn.json +122 -0
  106. package/tests/fixtures/active-memory-ab/cases/03-clean-starved-small.json +125 -0
  107. package/tests/fixtures/active-memory-ab/cases/04-clean-large-thin.json +137 -0
  108. package/tests/fixtures/active-memory-ab/cases/05-clean-half-step.json +166 -0
  109. package/tests/fixtures/active-memory-ab/cases/06-clean-duplicate-reread.json +197 -0
  110. package/tests/fixtures/active-memory-ab/cases/07-clean-recent-recall.json +125 -0
  111. package/tests/fixtures/active-memory-ab/cases/08-global-relevant.json +111 -0
  112. package/tests/fixtures/active-memory-ab/cases/09-global-irrelevant-conflict.json +158 -0
  113. package/tests/fixtures/active-memory-ab/cases/10-no-human-turn.json +97 -0
  114. package/tests/fixtures/active-memory-ab/cases/11-clean-scale-100.json +137 -0
  115. package/tests/fixtures/active-memory-ab/cases/12-clean-scale-1000.json +137 -0
  116. package/tests/fixtures/active-memory-ab/cases/13-clean-scale-5000.json +137 -0
  117. package/tests/fixtures/active-memory-ab/cases/14-clean-scale-20000.json +137 -0
  118. package/tests/fixtures/active-memory-ab/lead-ai-reviewed-reference.json +191 -0
  119. package/tests/fixtures/active-memory-ab/manifest.json +199 -0
  120. package/tests/fixtures/recall-bench/01-paraphrase.json +184 -0
  121. package/tests/fixtures/recall-bench/02-exact.json +153 -0
  122. package/tests/fixtures/recall-bench/03-negation.json +173 -0
  123. package/tests/fixtures/recall-bench/04-no-answer.json +162 -0
  124. package/tests/fixtures/recall-bench/05-conflict.json +194 -0
  125. package/tests/fixtures/recall-bench/06-boundary.json +273 -0
  126. package/tests/fixtures/recall-bench/07-secret.json +191 -0
  127. package/tests/fixtures/recall-bench/08-coding.json +164 -0
  128. package/tests/fixtures/recall-bench-gold-candidates/01-paraphrase.json +193 -0
  129. package/tests/fixtures/recall-bench-gold-candidates/02-exact.json +197 -0
  130. package/tests/fixtures/recall-bench-gold-candidates/03-negation.json +244 -0
  131. package/tests/fixtures/recall-bench-gold-candidates/04-no-answer.json +189 -0
  132. package/tests/fixtures/recall-bench-gold-candidates/05-conflict.json +245 -0
  133. package/tests/fixtures/recall-bench-gold-candidates/06-boundary.json +279 -0
  134. package/tests/fixtures/recall-bench-gold-candidates/07-secret.json +224 -0
  135. package/tests/fixtures/recall-bench-gold-candidates/08-coding.json +218 -0
  136. package/tests/fixtures/repetition-eval/01-zh-rp.json +93 -0
  137. package/tests/fixtures/repetition-eval/02-en.json +97 -0
  138. package/tests/fixtures/repetition-eval/03-code.json +91 -0
  139. package/tests/fixtures/repetition-eval/04-names.json +116 -0
  140. package/tests/fixtures/repetition-eval/05-user-quote.json +96 -0
  141. package/tests/fixtures/repetition-eval/06-agreement.json +86 -0
  142. package/tests/graph-view.test.mjs +187 -0
  143. package/tests/graph.test.mjs +77 -0
  144. package/tests/hook-order.test.mjs +72 -0
  145. package/tests/humanlike.test.mjs +232 -0
  146. package/tests/hybrid-recall.test.mjs +92 -0
  147. package/tests/inbound.test.mjs +393 -0
  148. package/tests/inject-guide.test.mjs +364 -0
  149. package/tests/inject.test.mjs +222 -0
  150. package/tests/injection-budget-settings.test.mjs +35 -0
  151. package/tests/injection-budget.test.mjs +64 -0
  152. package/tests/injection-chain.test.mjs +191 -0
  153. package/tests/loader-register.mjs +3 -0
  154. package/tests/loader.mjs +12 -0
  155. package/tests/local-embedding-entry.test.mjs +95 -0
  156. package/tests/local-embedding-manager.test.mjs +476 -0
  157. package/tests/local-embedding-protocol.test.mjs +95 -0
  158. package/tests/local-embedding-worker.test.mjs +346 -0
  159. package/tests/managed-memory.test.mjs +536 -0
  160. package/tests/managed-runtime.test.mjs +40 -0
  161. package/tests/media.test.mjs +132 -0
  162. package/tests/migration-payload.test.mjs +42 -0
  163. package/tests/mood.test.mjs +109 -0
  164. package/tests/multiprocess.test.mjs +29 -0
  165. package/tests/new-session-gate.test.mjs +361 -0
  166. package/tests/package-shipping.test.mjs +84 -0
  167. package/tests/preset-scope.test.mjs +212 -0
  168. package/tests/prompts.test.mjs +250 -0
  169. package/tests/react-snapshot.test.mjs +23 -0
  170. package/tests/recall-bench.test.mjs +306 -0
  171. package/tests/remote.test.mjs +140 -0
  172. package/tests/rename.test.mjs +30 -0
  173. package/tests/repetition-detector-snapshot.test.mjs +231 -0
  174. package/tests/repetition-eval-live.test.mjs +177 -0
  175. package/tests/repetition-eval.test.mjs +373 -0
  176. package/tests/salient-date.test.mjs +142 -0
  177. package/tests/settings-beat.test.mjs +221 -0
  178. package/tests/short-term.test.mjs +166 -0
  179. package/tests/sidebar-beat.test.mjs +208 -0
  180. package/tests/sidebar-refresh.test.mjs +123 -0
  181. package/tests/sidebar.test.mjs +70 -0
  182. package/tests/snippet.test.mjs +47 -0
  183. package/tests/source-kind.test.mjs +52 -0
  184. package/tests/state.test.mjs +150 -0
  185. package/tests/stats.test.mjs +112 -0
  186. package/tests/storage.test.mjs +125 -0
  187. package/tests/stubs/dsh-llm.js +5 -0
  188. package/tests/stubs/dsh-session.js +7 -0
  189. package/tests/stubs/dsh-tools.js +101 -0
  190. package/tests/tool-schema.test.mjs +73 -0
  191. package/tests/tools.test.mjs +184 -0
  192. package/tests/ui-defaults.test.mjs +57 -0
  193. package/tests/update-fields.test.mjs +138 -0
  194. package/tests/vector-migration.test.mjs +733 -0
  195. package/tests/vector-profile.test.mjs +425 -0
  196. package/tests/visibility-migration.test.mjs +152 -0
  197. package/tests/webui-auth.test.mjs +454 -0
  198. package/tests/webui.test.mjs +164 -0
  199. package/tests/windows-env.test.mjs +89 -0
@@ -1,6 +1,8 @@
1
1
  import { getSummaryConfig } from './memu-config.js'
2
2
  import { loadPromptForDomain, CONSOLIDATION_CONTRACTS } from './prompts.js'
3
3
  import { getSettings } from './state.js'
4
+ import { normalizeEmotion } from './mood.js'
5
+ import { record } from './activity-log.js'
4
6
 
5
7
  function parseJsonObject(text) {
6
8
  const raw = String(text || '').trim()
@@ -12,31 +14,42 @@ function parseJsonObject(text) {
12
14
  try { return JSON.parse(raw.slice(start, end + 1)) } catch { return null }
13
15
  }
14
16
 
15
- function cleanMemories(value) {
17
+ // 逐条清洗。非法字段不修,只把这一条连同原因丢进 problems:一条坏数据不该让
18
+ // 整批整理白跑,也不该被悄悄改成"看起来合法"的情绪留在库里。
19
+ function cleanMemories(value, problems = []) {
16
20
  const list = Array.isArray(value) ? value : []
17
- return list
18
- .map((m) => ({
19
- title: String(m?.title || '').trim().slice(0, 120),
20
- content: String(m?.content || '').trim(),
21
- tags: Array.isArray(m?.tags) ? m.tags.slice(0, 20).map((t) => String(t)) : [],
22
- openLoop: m?.open_loop === true || m?.openLoop === true,
23
- confidence: Number.isFinite(Number(m?.confidence)) ? Number(m.confidence) : 0.8,
24
- layer: ['long_term', 'short_term'].includes(m?.layer) ? m.layer : 'long_term',
25
- disclosure: ['can_tell', 'only_if_asked', 'never_say'].includes(m?.disclosure) ? m.disclosure : 'can_tell',
26
- // A consolidation pass that forgets the field must not invent a scope: the default
27
- // here has to match the tool path (`privateRemember`, which defaults to 'shared').
28
- // 'session' used to be the fallback, which silently turned every sloppy summary into
29
- // a memory that died with its conversation (and the level itself is gone now).
30
- visibility: ['shared', 'secret'].includes(m?.visibility) ? m.visibility : 'shared',
31
- owner: ['me', 'us', 'about_user', 'about_project'].includes(m?.owner) ? m.owner : 'me',
32
- source: ['user_said', 'inferred', 'imagined', 'told'].includes(m?.source) ? m.source : 'inferred',
33
- moodValence: Number.isFinite(m?.mood_valence) ? Math.max(-1, Math.min(1, m.mood_valence)) : null,
34
- arousal: Number.isFinite(m?.arousal) ? Math.max(0, Math.min(1, m.arousal)) : null,
35
- peak: m?.peak === true,
36
- salientDate: typeof m?.salient_date === 'string' ? m.salient_date : null,
37
- }))
38
- .filter((m) => m.content)
39
- .slice(0, 20)
21
+ const out = []
22
+ list.forEach((m, index) => {
23
+ try {
24
+ out.push({
25
+ title: String(m?.title || '').trim().slice(0, 120),
26
+ content: String(m?.content || '').trim(),
27
+ tags: Array.isArray(m?.tags) ? m.tags.slice(0, 20).map((t) => String(t)) : [],
28
+ openLoop: m?.open_loop === true || m?.openLoop === true,
29
+ confidence: Number.isFinite(Number(m?.confidence)) ? Number(m.confidence) : 0.8,
30
+ layer: ['long_term', 'short_term'].includes(m?.layer) ? m.layer : 'long_term',
31
+ disclosure: ['can_tell', 'only_if_asked', 'never_say'].includes(m?.disclosure) ? m.disclosure : 'can_tell',
32
+ // A consolidation pass that forgets the field must not invent a scope: the default
33
+ // here has to match the tool path (`privateRemember`, which defaults to 'shared').
34
+ // 'session' used to be the fallback, which silently turned every sloppy summary into
35
+ // a memory that died with its conversation (and the level itself is gone now).
36
+ visibility: ['shared', 'secret'].includes(m?.visibility) ? m.visibility : 'shared',
37
+ owner: ['me', 'us', 'about_user', 'about_project'].includes(m?.owner) ? m.owner : 'me',
38
+ source: ['user_said', 'inferred', 'imagined', 'told'].includes(m?.source) ? m.source : 'inferred',
39
+ // 缺字段是"没读到情绪",null 才是;这里不能拿 valence 反推四轴。
40
+ emotion: normalizeEmotion(m?.emotion ?? null),
41
+ // 缺失或非法给 null 而不是 0:0 表示"明确中性",不是"不知道"。
42
+ dominance: Number.isFinite(m?.dominance) ? Math.max(-1, Math.min(1, m.dominance)) : null,
43
+ moodValence: Number.isFinite(m?.mood_valence) ? Math.max(-1, Math.min(1, m.mood_valence)) : null,
44
+ arousal: Number.isFinite(m?.arousal) ? Math.max(0, Math.min(1, m.arousal)) : null,
45
+ peak: m?.peak === true,
46
+ salientDate: typeof m?.salient_date === 'string' ? m.salient_date : null,
47
+ })
48
+ } catch (error) {
49
+ problems.push({ index, title: String(m?.title || '').slice(0, 120), error: String(error?.message || error) })
50
+ }
51
+ })
52
+ return out.filter((m) => m.content).slice(0, 20)
40
53
  }
41
54
 
42
55
  // Remote mode: the configured chat endpoint does the summarizing. The session
@@ -87,7 +100,20 @@ export async function summarizeRemote({ transcript, libraryName, domain, signal,
87
100
  const text = data?.choices?.[0]?.message?.content
88
101
  const parsed = parseJsonObject(text)
89
102
  if (!parsed) throw new Error(`remote summary returned non-JSON: ${String(text || body).slice(0, 600)}`)
90
- return cleanMemories(parsed.memories)
103
+ const problems = []
104
+ const memories = cleanMemories(parsed.memories, problems)
105
+ if (problems.length) {
106
+ // 远程模型的错误只能靠日志查,静默丢弃等于把问题留给下一个人复现。
107
+ record({
108
+ kind: 'consolidate',
109
+ scope: 'private',
110
+ ok: false,
111
+ level: 'warn',
112
+ message: `远程整理有 ${problems.length} 条被拒绝:${problems[0].error}`,
113
+ detail: { rejected: problems },
114
+ })
115
+ }
116
+ return memories
91
117
  } finally {
92
118
  clearTimeout(timer)
93
119
  signal?.removeEventListener?.('abort', onAbort)
package/lib/rpc.js CHANGED
@@ -16,9 +16,11 @@ import { maybeMigrateLibraries } from './migrate.js'
16
16
  import { getPromptStates, savePromptOverride, resetPrompt, resetAllPrompts } from './prompts.js'
17
17
  import { statsView } from './stats.js'
18
18
  import { getMemuConfig, saveMemuConfig, setEmbeddingSource, testMemuConfig, getSummaryConfig, getVisionConfig } from './memu-config.js'
19
+ import { getLocalVectorState, downloadLocalVectorModel, installLocalVectorRuntime, selectLocalVectorModel, switchLocalVectorProfile, rollbackLocalVectorProfile, abortLocalVectorMigration, testLocalVector } from './local-vector-service.js'
19
20
  import { webUiHtml, WEBUI_PATH, standaloneHtml, standaloneAsset, SETTINGS_PATH } from './webui.js'
20
21
  import { getGraph, listEntities, createEntity, renameEntity, linkEntries, unlinkEntry, getRelationship, updateRelationship, graphExport, restoreGraphData } from './graph.js'
21
22
  import { getAttachment, attachmentThumbnail, attachmentPreview, exportMediaBundle, importMediaBundle, streamMediaExport, importMediaStream } from './media.js'
23
+ import { listEvents, logStats, clearEvents } from './activity-log.js'
22
24
 
23
25
  const ENDPOINT = '/dsh-memu-memory'
24
26
 
@@ -84,6 +86,11 @@ async function privateListView(scope) {
84
86
  decay: Math.min(1, Math.max(0, decayStrength(e) / 1.5)),
85
87
  moodValence: e?.moodValence ?? null,
86
88
  arousal: e?.arousal ?? null,
89
+ // 构成与掌控感分开投影:同一个 valence 说得出"多难过",说不出"是怒还是怕"。
90
+ emotion: e?.emotion ?? null,
91
+ dominance: e?.dominance ?? null,
92
+ // 来源是审计字段,只读:面板显示"这个情绪是谁给的",不给改。
93
+ emotionSource: e?.emotionSource ?? null,
87
94
  peak: e?.peak === true,
88
95
  salientDate: e?.salientDate || null,
89
96
  attachments: attachmentPreview(libraryId, e || {}),
@@ -95,7 +102,7 @@ async function privateListView(scope) {
95
102
  content: String(content ?? e?.content ?? '').slice(0, 4000),
96
103
  })
97
104
  try {
98
- const result = await privateList(scope)
105
+ const result = await privateList({ ...scope, preferIndex: true })
99
106
  return {
100
107
  libraryId: result.libraryId,
101
108
  source: result.source || 'engine',
@@ -451,6 +458,7 @@ async function dispatch(ctx, endpoint, payload = {}) {
451
458
  } catch {}
452
459
  const rows = Object.entries(state.sessions || {})
453
460
  .map(([sessionId, binding]) => {
461
+ if (binding.consolidationFork) return null
454
462
  const inFolder = binding.scopeKey === scopeKey
455
463
  || (binding.libraryId && state.libraries?.[binding.libraryId]?.scopeKey === scopeKey)
456
464
  if (!inFolder) return null
@@ -492,6 +500,7 @@ async function dispatch(ctx, endpoint, payload = {}) {
492
500
  memuConfig: getMemuConfig(),
493
501
  summaryConfig: getSummaryConfig(),
494
502
  visionConfig: getVisionConfig(),
503
+ localVector: getLocalVectorState(),
495
504
  settings: getSettings(),
496
505
  libraries: libraryView(),
497
506
  archivedLibraries: archivedLibraryView(),
@@ -524,6 +533,9 @@ async function dispatch(ctx, endpoint, payload = {}) {
524
533
  newSessionDefault: getSettings().newSessionDefault || 'both',
525
534
  },
526
535
  libraries: listLibraries().map(({ id, name, scopeKey: key }) => ({ id, name, scopeKey: key })),
536
+ // Memory domains accepted by setDomain. Prompt page types are a different
537
+ // concept (getPromptDomains), so this stays a fixed list.
538
+ domains: ['rp', 'coding', 'general', 'mixed'],
527
539
  // A folder with both layers off never prompts: there is nothing to ask.
528
540
  folder,
529
541
  shouldPrompt: folder.useLibrary !== false || folder.useGlobal !== false,
@@ -550,7 +562,7 @@ async function dispatch(ctx, endpoint, payload = {}) {
550
562
  case 'resetSettings': {
551
563
  const patch = {}
552
564
  for (const key of [
553
- 'autoInject', 'privateTopK', 'globalTopK', 'anchorTokens', 'turnTokens', 'dedupeTurns',
565
+ 'autoInject', 'privateTopK', 'globalTopK', 'anchorTokens', 'turnTokens', 'injectionTotalTokens', 'dedupeTurns',
554
566
  'globalEveryNTurns', 'minUserChars', 'mustRememberCap', 'randomCueChance',
555
567
  'autoInstallEngine', 'newSessionPromptDismissed', 'newSessionDefault', 'habitsInjection',
556
568
  'sleepConsolidation', 'sleepAfterIdleMinutes', 'autoCurateEveryHours',
@@ -564,7 +576,7 @@ async function dispatch(ctx, endpoint, payload = {}) {
564
576
  case 'setSettings': {
565
577
  const patch = payload?.patch && typeof payload.patch === 'object' ? payload.patch : {}
566
578
  const allowed = new Set([
567
- 'autoInject', 'privateTopK', 'globalTopK', 'anchorTokens', 'turnTokens',
579
+ 'autoInject', 'privateTopK', 'globalTopK', 'anchorTokens', 'turnTokens', 'injectionTotalTokens',
568
580
  'relevanceThreshold', 'dedupeTurns', 'globalEveryNTurns', 'minUserChars',
569
581
  // newSessionDefault has to be writable here: "remember my choice" stores
570
582
  // it, and a dropped patch silently turned every remembered answer into
@@ -765,7 +777,9 @@ async function dispatch(ctx, endpoint, payload = {}) {
765
777
  // `source` is writable: correcting provenance is the point of showing it.
766
778
  // `salientDate` is writable for the same reason: an entry whose year was
767
779
  // dropped has to be repairable from the browser, not only from JSON.
768
- const allowed = ['content', 'title', 'tags', 'status', 'openLoop', 'disclosure', 'confidence', 'owner', 'visibility', 'layer', 'source', 'salientDate', 'track', 'peak', 'moodValence', 'arousal', 'attachments', 'context']
780
+ // `emotion`/`dominance` 可写:面板修的是那条记忆所述事件的感受。
781
+ // `emotionSource` 故意不在名单里:来源是审计结论,能改就等于能伪造。
782
+ const allowed = ['content', 'title', 'tags', 'status', 'openLoop', 'disclosure', 'confidence', 'owner', 'visibility', 'layer', 'source', 'salientDate', 'track', 'peak', 'moodValence', 'arousal', 'emotion', 'dominance', 'attachments', 'context']
769
783
  // An unknown key used to be dropped in silence, so a typo here or in a script
770
784
  // came back as a successful save that changed nothing. Refuse instead: the
771
785
  // caller can only fix a field name it is told is wrong.
@@ -880,8 +894,10 @@ async function dispatch(ctx, endpoint, payload = {}) {
880
894
  const sessionId = payload?.sessionId || null
881
895
  const exec = sessionId ? execFor(sessionId) : { agent: null }
882
896
  try {
883
- const result = await globalList({ exec, domain: payload?.domain })
897
+ const result = await globalList({ exec, domain: payload?.domain, preferIndex: true })
884
898
  return {
899
+ warning: result.warning || null,
900
+ source: result.source || null,
885
901
  files: (result.files || []).map((f) => ({
886
902
  id: f.name,
887
903
  title: f.entry?.title || f.description || '',
@@ -1086,6 +1102,24 @@ async function dispatch(ctx, endpoint, payload = {}) {
1086
1102
  })
1087
1103
  case 'testMemuConfig':
1088
1104
  return testMemuConfig()
1105
+ case 'getLocalVectorState':
1106
+ return getLocalVectorState()
1107
+ case 'downloadLocalVectorModel':
1108
+ return await downloadLocalVectorModel(String(payload?.id || ''), { confirmed: payload?.confirmed === true })
1109
+ case 'selectLocalVectorModel':
1110
+ return selectLocalVectorModel(String(payload?.id || ''))
1111
+ case 'installLocalVectorRuntime':
1112
+ return await installLocalVectorRuntime({ modelId: payload?.id || null, confirmed: payload?.confirmed === true })
1113
+ case 'switchLocalVectorProfile':
1114
+ if (payload?.confirmed !== true) throw new Error('switching the local vector profile requires explicit confirmation')
1115
+ return await switchLocalVectorProfile({ id: payload?.id })
1116
+ case 'rollbackLocalVectorProfile':
1117
+ if (payload?.confirmed !== true) throw new Error('rolling back the local vector profile requires explicit confirmation')
1118
+ return await rollbackLocalVectorProfile()
1119
+ case 'abortLocalVectorMigration':
1120
+ return abortLocalVectorMigration()
1121
+ case 'testLocalVector':
1122
+ return await testLocalVector()
1089
1123
  case 'savePrompt': {
1090
1124
  const key = String(payload?.key || '')
1091
1125
  const text = String(payload?.text ?? '')
@@ -1104,6 +1138,21 @@ async function dispatch(ctx, endpoint, payload = {}) {
1104
1138
  return { sizes: engineVersionSizes() }
1105
1139
  case 'getEngine':
1106
1140
  return engineView()
1141
+ case 'getActivityLog':
1142
+ return {
1143
+ events: listEvents({
1144
+ limit: payload?.limit,
1145
+ kind: payload?.kind,
1146
+ level: payload?.level,
1147
+ scope: payload?.scope,
1148
+ sessionId: payload?.sessionId,
1149
+ since: payload?.since,
1150
+ q: payload?.q,
1151
+ }),
1152
+ stats: logStats(),
1153
+ }
1154
+ case 'clearActivityLog':
1155
+ return clearEvents()
1107
1156
  default:
1108
1157
  throw new Error(`unknown endpoint: ${endpoint}`)
1109
1158
  }