dsh-memu-memory 1.1.3 → 1.2.1
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/AGENTS.md +1 -1
- package/README.md +39 -6
- package/README.zh.md +39 -7
- package/docs/active-memory-ab.md +385 -0
- package/docs/binding-lifecycle.md +99 -0
- package/docs/contributing.md +3 -3
- package/docs/emotion-model-spec.md +281 -0
- package/docs/env-and-commands.md +8 -0
- package/docs/host-api.md +2 -2
- package/docs/memory-semantics.md +12 -10
- package/docs/recall-benchmark.md +238 -0
- package/docs/troubleshooting.md +396 -395
- package/lib/active-memory.js +165 -0
- package/lib/activity-log.js +23 -6
- package/lib/binding-diagnostics.js +8 -3
- package/lib/client.js +335 -35
- package/lib/consolidation-ledger.js +347 -0
- package/lib/consolidation-receipt.js +284 -0
- package/lib/consolidation-snapshot.js +274 -0
- package/lib/consolidation-worker.js +144 -0
- package/lib/curator.js +86 -31
- package/lib/cycle.js +40 -6
- package/lib/graph.js +139 -5
- package/lib/index.js +33 -1
- package/lib/inject.js +104 -76
- package/lib/local-embedding-manager.js +24 -6
- package/lib/memory.js +534 -88
- package/lib/mood.js +268 -39
- package/lib/preset-scope.js +1 -1
- package/lib/private.js +430 -43
- package/lib/prompts/private-consolidation.coding.md +1 -0
- package/lib/prompts/private-consolidation.md +1 -0
- package/lib/prompts/private-consolidation.mixed.md +1 -0
- package/lib/prompts/private-consolidation.rp.md +1 -0
- package/lib/prompts.js +4 -4
- package/lib/remote-summary.js +51 -25
- package/lib/rpc.js +54 -5
- package/lib/state.js +29 -12
- package/lib/text-similarity.js +106 -0
- package/lib/tools.js +113 -10
- package/lib/vector-migration.js +28 -3
- package/lib/vector-profile.js +51 -0
- package/package.json +4 -2
- package/scripts/acceptance-local.mjs +2 -1
- package/scripts/active-memory-ab-live.mjs +655 -0
- package/scripts/active-memory-ab.mjs +1302 -0
- package/scripts/chain-matrix.mjs +11 -1
- package/scripts/check-all.mjs +10 -1
- package/scripts/replay.mjs +573 -0
- package/tests/activation.test.mjs +89 -0
- package/tests/active-memory-ab.test.mjs +745 -0
- package/tests/active-memory.test.mjs +129 -0
- package/tests/activity-log.test.mjs +126 -0
- package/tests/activity-wiring.test.mjs +103 -0
- package/tests/appendix-b.test.mjs +216 -0
- package/tests/appendix-b2.test.mjs +166 -0
- package/tests/audit-regression.test.mjs +272 -0
- package/tests/audit-safety.test.mjs +227 -0
- package/tests/backend-compensation.test.mjs +272 -0
- package/tests/backup.test.mjs +793 -0
- package/tests/binding-diagnostics.test.mjs +720 -0
- package/tests/browser.test.mjs +366 -0
- package/tests/client-render.test.mjs +892 -0
- package/tests/coalesce.test.mjs +113 -0
- package/tests/config.test.mjs +159 -0
- package/tests/consolidation-credential.test.mjs +331 -0
- package/tests/consolidation-feed.test.mjs +96 -0
- package/tests/consolidation-fork.test.mjs +489 -0
- package/tests/consolidation-ledger.test.mjs +298 -0
- package/tests/consolidation-snapshot.test.mjs +360 -0
- package/tests/consolidation-value.test.mjs +89 -0
- package/tests/context-scope.test.mjs +40 -0
- package/tests/cue.test.mjs +44 -0
- package/tests/curator-commit.test.mjs +277 -0
- package/tests/curator-probe.test.mjs +433 -0
- package/tests/curator.test.mjs +150 -0
- package/tests/cycle.test.mjs +411 -0
- package/tests/disclosure.test.mjs +231 -0
- package/tests/doc-drift.test.mjs +222 -0
- package/tests/embedding-catalog.test.mjs +266 -0
- package/tests/embedding-download.test.mjs +391 -0
- package/tests/embedding-runtime.test.mjs +389 -0
- package/tests/emotion-chain.test.mjs +84 -0
- package/tests/emotion-contract.test.mjs +445 -0
- package/tests/engine.test.mjs +35 -0
- package/tests/fixtures/active-memory-ab/README.md +56 -0
- package/tests/fixtures/active-memory-ab/cases/01-clean-dense-turn.json +351 -0
- package/tests/fixtures/active-memory-ab/cases/02-clean-thin-turn.json +122 -0
- package/tests/fixtures/active-memory-ab/cases/03-clean-starved-small.json +125 -0
- package/tests/fixtures/active-memory-ab/cases/04-clean-large-thin.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/05-clean-half-step.json +166 -0
- package/tests/fixtures/active-memory-ab/cases/06-clean-duplicate-reread.json +197 -0
- package/tests/fixtures/active-memory-ab/cases/07-clean-recent-recall.json +125 -0
- package/tests/fixtures/active-memory-ab/cases/08-global-relevant.json +111 -0
- package/tests/fixtures/active-memory-ab/cases/09-global-irrelevant-conflict.json +158 -0
- package/tests/fixtures/active-memory-ab/cases/10-no-human-turn.json +97 -0
- package/tests/fixtures/active-memory-ab/cases/11-clean-scale-100.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/12-clean-scale-1000.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/13-clean-scale-5000.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/14-clean-scale-20000.json +137 -0
- package/tests/fixtures/active-memory-ab/lead-ai-reviewed-reference.json +191 -0
- package/tests/fixtures/active-memory-ab/manifest.json +199 -0
- package/tests/fixtures/recall-bench/01-paraphrase.json +184 -0
- package/tests/fixtures/recall-bench/02-exact.json +153 -0
- package/tests/fixtures/recall-bench/03-negation.json +173 -0
- package/tests/fixtures/recall-bench/04-no-answer.json +162 -0
- package/tests/fixtures/recall-bench/05-conflict.json +194 -0
- package/tests/fixtures/recall-bench/06-boundary.json +273 -0
- package/tests/fixtures/recall-bench/07-secret.json +191 -0
- package/tests/fixtures/recall-bench/08-coding.json +164 -0
- package/tests/fixtures/recall-bench-gold-candidates/01-paraphrase.json +193 -0
- package/tests/fixtures/recall-bench-gold-candidates/02-exact.json +197 -0
- package/tests/fixtures/recall-bench-gold-candidates/03-negation.json +244 -0
- package/tests/fixtures/recall-bench-gold-candidates/04-no-answer.json +189 -0
- package/tests/fixtures/recall-bench-gold-candidates/05-conflict.json +245 -0
- package/tests/fixtures/recall-bench-gold-candidates/06-boundary.json +279 -0
- package/tests/fixtures/recall-bench-gold-candidates/07-secret.json +224 -0
- package/tests/fixtures/recall-bench-gold-candidates/08-coding.json +218 -0
- package/tests/fixtures/repetition-eval/01-zh-rp.json +93 -0
- package/tests/fixtures/repetition-eval/02-en.json +97 -0
- package/tests/fixtures/repetition-eval/03-code.json +91 -0
- package/tests/fixtures/repetition-eval/04-names.json +116 -0
- package/tests/fixtures/repetition-eval/05-user-quote.json +96 -0
- package/tests/fixtures/repetition-eval/06-agreement.json +86 -0
- package/tests/graph-cascade.test.mjs +190 -0
- package/tests/graph-view.test.mjs +187 -0
- package/tests/graph.test.mjs +94 -0
- package/tests/hook-order.test.mjs +72 -0
- package/tests/humanlike.test.mjs +232 -0
- package/tests/hybrid-recall.test.mjs +122 -0
- package/tests/inbound.test.mjs +393 -0
- package/tests/inject-guide.test.mjs +364 -0
- package/tests/inject.test.mjs +222 -0
- package/tests/injection-budget-settings.test.mjs +35 -0
- package/tests/injection-budget.test.mjs +64 -0
- package/tests/injection-chain.test.mjs +191 -0
- package/tests/loader-register.mjs +3 -0
- package/tests/loader.mjs +12 -0
- package/tests/local-embedding-entry.test.mjs +95 -0
- package/tests/local-embedding-manager.test.mjs +476 -0
- package/tests/local-embedding-protocol.test.mjs +95 -0
- package/tests/local-embedding-worker.test.mjs +346 -0
- package/tests/managed-memory.test.mjs +591 -0
- package/tests/managed-runtime.test.mjs +40 -0
- package/tests/media.test.mjs +132 -0
- package/tests/migration-payload.test.mjs +42 -0
- package/tests/mood.test.mjs +109 -0
- package/tests/multiprocess.test.mjs +29 -0
- package/tests/new-session-gate.test.mjs +361 -0
- package/tests/package-shipping.test.mjs +84 -0
- package/tests/preset-scope.test.mjs +212 -0
- package/tests/prompts.test.mjs +250 -0
- package/tests/react-snapshot.test.mjs +23 -0
- package/tests/recall-bench.test.mjs +306 -0
- package/tests/recall-dedup.test.mjs +417 -0
- package/tests/remote.test.mjs +140 -0
- package/tests/rename.test.mjs +30 -0
- package/tests/repetition-detector-snapshot.test.mjs +231 -0
- package/tests/repetition-eval-live.test.mjs +177 -0
- package/tests/repetition-eval.test.mjs +373 -0
- package/tests/salient-date.test.mjs +142 -0
- package/tests/settings-beat.test.mjs +221 -0
- package/tests/short-term.test.mjs +166 -0
- package/tests/sidebar-beat.test.mjs +212 -0
- package/tests/sidebar-refresh.test.mjs +123 -0
- package/tests/sidebar.test.mjs +70 -0
- package/tests/snippet.test.mjs +47 -0
- package/tests/source-kind.test.mjs +52 -0
- package/tests/state.test.mjs +150 -0
- package/tests/stats.test.mjs +112 -0
- package/tests/storage.test.mjs +125 -0
- package/tests/stubs/dsh-llm.js +5 -0
- package/tests/stubs/dsh-session.js +7 -0
- package/tests/stubs/dsh-tools.js +101 -0
- package/tests/tool-schema.test.mjs +73 -0
- package/tests/tools.test.mjs +184 -0
- package/tests/ui-defaults.test.mjs +57 -0
- package/tests/update-fields.test.mjs +138 -0
- package/tests/vector-migration.test.mjs +790 -0
- package/tests/vector-profile.test.mjs +425 -0
- package/tests/visibility-migration.test.mjs +152 -0
- package/tests/webui-auth.test.mjs +454 -0
- package/tests/webui.test.mjs +165 -0
- package/tests/windows-env.test.mjs +89 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-memu-memory-update-'))
|
|
8
|
+
process.env.DSH_MEMU_HOME = home
|
|
9
|
+
|
|
10
|
+
const state = await import('../lib/state.js')
|
|
11
|
+
const memory = await import('../lib/memory.js')
|
|
12
|
+
const inject = await import('../lib/inject.js')
|
|
13
|
+
const rpc = await import('../lib/rpc.js')
|
|
14
|
+
|
|
15
|
+
test.after(() => {
|
|
16
|
+
try { state.flushState() } catch {}
|
|
17
|
+
try { fs.rmSync(home, { recursive: true, force: true }) } catch {}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const binding = state.ensureSession('upd', { cwd: path.join(home, 'coding') })
|
|
21
|
+
const exec = { agent: { session: { header: { id: 'upd', cwd: path.join(home, 'coding') } } } }
|
|
22
|
+
const local = (y, m, d) => new Date(y, m - 1, d, 12, 0, 0)
|
|
23
|
+
|
|
24
|
+
// The write side settles a relative day into a real date, and the repair side has to
|
|
25
|
+
// agree with it: re-submitting "10-29" for a body that says 明天 would otherwise
|
|
26
|
+
// re-create exactly the yearly anniversary the repair was meant to remove.
|
|
27
|
+
test('a relative day settles the year, a month-day that does not match is left alone', () => {
|
|
28
|
+
const now = local(2026, 10, 28)
|
|
29
|
+
assert.equal(memory.reconcileSalientDate('10-29', '明天要去考驾照', now), '2026-10-29')
|
|
30
|
+
assert.equal(memory.reconcileSalientDate('10-30', '后天出发', now), '2026-10-30')
|
|
31
|
+
assert.equal(memory.reconcileSalientDate('10-30', '明天要去考驾照', now), '10-30', 'a month-day the text does not point at must not be rewritten')
|
|
32
|
+
assert.equal(memory.reconcileSalientDate('10-28', '用户的生日是 10 月 28 日', now), '10-28', 'no relative day, so it stays annual')
|
|
33
|
+
assert.equal(memory.reconcileSalientDate('10-29', '定在 2027-10-29 见面', now), '2027-10-29', 'an explicit full date in the text wins')
|
|
34
|
+
assert.equal(memory.reconcileSalientDate('2026-10-29', '明天', now), '2026-10-29', 'an already-dated value is never rewritten')
|
|
35
|
+
assert.equal(memory.reconcileSalientDate('', '明天', now), '')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('the write path stores the settled date, not a yearly anniversary', async () => {
|
|
39
|
+
const dated = await memory.privateRemember({ exec, title: '面试', content: '面试定在 2027-03-05 上午。', salientDate: '03-05' })
|
|
40
|
+
assert.equal(dated.entry.salientDate, '2027-03-05')
|
|
41
|
+
const annual = await memory.privateRemember({ exec, title: '生日', content: '用户的生日是 10 月 28 日。', salientDate: '10-28' })
|
|
42
|
+
assert.equal(annual.entry.salientDate, '10-28')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// A dated memory the anchor does not date is a memory the model cannot reason about:
|
|
46
|
+
// it reads "明天要考驾照" every turn and cannot tell when 明天 is, or that it passed.
|
|
47
|
+
test('the anchor line carries the date and says which kind it is', () => {
|
|
48
|
+
const lib = state.ensureSession('upd-anchor', { cwd: path.join(home, 'anchor') })
|
|
49
|
+
const now = local(2026, 10, 29)
|
|
50
|
+
const index = state.loadPrivateIndex(lib.libraryId)
|
|
51
|
+
const base = { track: 'memory', status: 'active', createdAt: now.toISOString(), updatedAt: now.toISOString(), confidence: 0.8 }
|
|
52
|
+
index.entries['mem-dated'] = { ...base, layer: 'must_remember', title: '考驾照', content: '明天要去考驾照。', salientDate: '2026-10-29', pinReason: '用户明确要求记住这件事', pinSource: 'user_explicit' }
|
|
53
|
+
index.entries['mem-annual'] = { ...base, layer: 'long_term', title: '第一次一起看海', content: '那天下着小雨。', salientDate: '10-29' }
|
|
54
|
+
index.entries['mem-plain'] = { ...base, layer: 'long_term', title: '普通记忆', content: '没有日期。' }
|
|
55
|
+
state.savePrivateIndex(lib.libraryId, index)
|
|
56
|
+
const anchor = inject.buildAnchor(lib, state.getSettings(), 'upd-anchor')
|
|
57
|
+
assert.match(anchor.text, /mem-dated[^\n]* · date=2026-10-29/)
|
|
58
|
+
assert.match(anchor.text, /mem-annual[^\n]* · anniversary=10-29/)
|
|
59
|
+
assert.doesNotMatch(anchor.text, /mem-dated[^\n]*anniversary=/, 'a one-off is not labelled as a recurring date')
|
|
60
|
+
assert.doesNotMatch(anchor.text, /mem-plain[^\n]* · (date|anniversary)=/, 'an undated entry grows no label')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('provenance and track can be corrected after the fact', async () => {
|
|
64
|
+
const saved = await memory.privateRemember({ exec, title: '引用', content: '用户说他不喜欢远程办公。' })
|
|
65
|
+
assert.equal(saved.entry.source, 'inferred')
|
|
66
|
+
const fixed = await memory.privateUpdate({ exec, id: saved.id, source: 'user_said', track: 'habit' })
|
|
67
|
+
assert.equal(fixed.entry.source, 'user_said', 'a quote filed as an inference must be repairable')
|
|
68
|
+
assert.equal(fixed.entry.track, 'habit')
|
|
69
|
+
await assert.rejects(() => memory.privateUpdate({ exec, id: saved.id, source: 'hearsay' }), /source/)
|
|
70
|
+
await assert.rejects(() => memory.privateUpdate({ exec, id: saved.id, track: 'hunch' }), /track/)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// A value the caller supplied is accepted or refused, never ignored: the old code
|
|
74
|
+
// answered `confidence: 'high'` with success and changed nothing at all.
|
|
75
|
+
test('dirty numbers are refused, out-of-range numbers are clamped', async () => {
|
|
76
|
+
const saved = await memory.privateRemember({ exec, title: '情绪', content: '那天很吵。' })
|
|
77
|
+
await assert.rejects(() => memory.privateUpdate({ exec, id: saved.id, confidence: 'high' }), /confidence/)
|
|
78
|
+
await assert.rejects(() => memory.privateUpdate({ exec, id: saved.id, moodValence: 'sad' }), /moodValence/)
|
|
79
|
+
await assert.rejects(() => memory.privateUpdate({ exec, id: saved.id, arousal: NaN }), /arousal/)
|
|
80
|
+
const clamped = await memory.privateUpdate({ exec, id: saved.id, moodValence: 3, arousal: 2 })
|
|
81
|
+
assert.equal(clamped.entry.moodValence, 1)
|
|
82
|
+
assert.equal(clamped.entry.arousal, 1)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// accuracy is derived; correcting the charge must move it too, otherwise the repair
|
|
86
|
+
// only changes how the entry is stored and not how far it is trusted when recalled.
|
|
87
|
+
test('accuracy is recomputed when the charged fields change', async () => {
|
|
88
|
+
const saved = await memory.privateRemember({ exec, title: '闪光灯记忆', content: '那一刻所有人都愣住了。', confidence: 0.8 })
|
|
89
|
+
assert.equal(saved.entry.accuracy, 0.8)
|
|
90
|
+
const charged = await memory.privateUpdate({ exec, id: saved.id, arousal: 0.9 })
|
|
91
|
+
assert.equal(charged.entry.accuracy, memory.accuracyOf({ peak: false, arousal: 0.9, confidence: 0.8 }))
|
|
92
|
+
assert.ok(charged.entry.accuracy < 0.8, 'a charged memory is vivid, not automatically accurate')
|
|
93
|
+
const peaked = await memory.privateUpdate({ exec, id: saved.id, peak: true })
|
|
94
|
+
assert.equal(peaked.entry.peak, true)
|
|
95
|
+
assert.equal(peaked.entry.accuracy, memory.accuracyOf({ peak: true, arousal: 0.9, confidence: 0.8 }))
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// The tool description has promised "short_term to re-arm the deadline" since the
|
|
99
|
+
// tool existed, while the code skipped the whole block when the layer did not change.
|
|
100
|
+
test('asking for short_term again re-arms the deadline instead of doing nothing', async () => {
|
|
101
|
+
const saved = await memory.privateRemember({ exec, title: '草稿', content: '还在整理的想法。', layer: 'short_term' })
|
|
102
|
+
assert.equal(saved.entry.layer, 'short_term')
|
|
103
|
+
const first = saved.entry.expiresAt
|
|
104
|
+
assert.ok(first, 'a short_term entry carries a deadline')
|
|
105
|
+
await new Promise(resolve => setTimeout(resolve, 12))
|
|
106
|
+
const rearmed = await memory.privateUpdate({ exec, id: saved.id, layer: 'short_term' })
|
|
107
|
+
assert.ok(Date.parse(rearmed.entry.expiresAt) > Date.parse(first), 'the deadline moves forward')
|
|
108
|
+
assert.equal(rearmed.entry.promotedFrom, undefined, 'a re-arm is not a promotion')
|
|
109
|
+
const promoted = await memory.privateUpdate({ exec, id: saved.id, layer: 'long_term' })
|
|
110
|
+
assert.equal(promoted.entry.layer, 'long_term')
|
|
111
|
+
assert.equal(promoted.entry.expiresAt, undefined)
|
|
112
|
+
assert.equal(promoted.entry.promotedFrom, 'short_term')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('an attachment list can only reference files this library received', async () => {
|
|
116
|
+
const saved = await memory.privateRemember({ exec, title: '照片', content: '一张照片的记忆。' })
|
|
117
|
+
const missing = 'a'.repeat(64)
|
|
118
|
+
await assert.rejects(() => memory.privateUpdate({ exec, id: saved.id, attachments: [missing] }), /attachment/)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// The panel and any script talk to updateMemory, where an unknown key used to be
|
|
122
|
+
// dropped in silence: a typo came back as a successful save that changed nothing.
|
|
123
|
+
test('the RPC refuses unknown fields and accepts the newly editable ones', async () => {
|
|
124
|
+
const saved = await memory.privateRemember({ exec, title: '面板', content: '从面板改的记忆。' })
|
|
125
|
+
await assert.rejects(
|
|
126
|
+
() => rpc.dispatch({}, 'updateMemory', { libraryId: binding.libraryId, id: saved.id, patch: { salientdate: '2026-10-29' } }),
|
|
127
|
+
/unknown memory field/,
|
|
128
|
+
'a near miss in the field name must not look like a successful save',
|
|
129
|
+
)
|
|
130
|
+
const applied = await rpc.dispatch({}, 'updateMemory', {
|
|
131
|
+
libraryId: binding.libraryId,
|
|
132
|
+
id: saved.id,
|
|
133
|
+
patch: { salientDate: '2026-10-29', source: 'user_said', track: 'habit' },
|
|
134
|
+
})
|
|
135
|
+
assert.equal(applied.entry.salientDate, '2026-10-29')
|
|
136
|
+
assert.equal(applied.entry.source, 'user_said')
|
|
137
|
+
assert.equal(applied.entry.track, 'habit')
|
|
138
|
+
})
|