dsh-memu-memory 1.2.0 → 1.2.2

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 (100) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +19 -8
  3. package/README.zh.md +12 -6
  4. package/docs/contributing.md +16 -3
  5. package/docs/env-and-commands.md +8 -0
  6. package/docs/host-api.md +3 -1
  7. package/docs/memory-semantics.md +21 -2
  8. package/docs/troubleshooting.md +42 -3
  9. package/lib/activity-log.js +22 -5
  10. package/lib/backup.js +24 -2
  11. package/lib/binding-diagnostics.js +3 -3
  12. package/lib/client.js +1610 -117
  13. package/lib/consolidation-worker.js +68 -17
  14. package/lib/curator-evidence.js +131 -0
  15. package/lib/curator-policy.js +81 -0
  16. package/lib/curator.js +1299 -83
  17. package/lib/cycle.js +3 -0
  18. package/lib/embedding-download.js +39 -4
  19. package/lib/engine.js +6 -3
  20. package/lib/global-graph.js +282 -0
  21. package/lib/graph.js +190 -14
  22. package/lib/inbound.js +19 -0
  23. package/lib/index.js +33 -1
  24. package/lib/inject.js +277 -17
  25. package/lib/lexicon.js +1115 -0
  26. package/lib/local-vector-service.js +8 -6
  27. package/lib/managed-runtime.js +33 -2
  28. package/lib/memory.js +585 -76
  29. package/lib/memu.js +2 -2
  30. package/lib/migrate.js +83 -2
  31. package/lib/private-links.js +587 -0
  32. package/lib/private.js +128 -7
  33. package/lib/prompts/global-curator.md +76 -19
  34. package/lib/prompts/private-consolidation.coding.md +4 -1
  35. package/lib/prompts/private-consolidation.md +3 -1
  36. package/lib/prompts/private-consolidation.mixed.md +3 -1
  37. package/lib/prompts/private-consolidation.rp.md +3 -1
  38. package/lib/prompts.js +1 -1
  39. package/lib/rpc.js +622 -13
  40. package/lib/state.js +4 -1
  41. package/lib/text-retrieval.js +661 -0
  42. package/lib/text-similarity.js +124 -0
  43. package/lib/tools.js +230 -16
  44. package/lib/vector-migration.js +28 -3
  45. package/lib/vector-profile.js +24 -0
  46. package/lib/webui/standalone.css +17 -0
  47. package/lib/webui/standalone.js +2 -0
  48. package/package.json +3 -2
  49. package/scripts/acceptance-local.mjs +8 -5
  50. package/scripts/curator-pair-calibrate.mjs +259 -0
  51. package/scripts/lexicon-calibrate.mjs +230 -0
  52. package/scripts/lexicon-dedupe.mjs +318 -0
  53. package/scripts/replay.mjs +573 -0
  54. package/tests/absorb-review.test.mjs +353 -0
  55. package/tests/appendix-b.test.mjs +6 -2
  56. package/tests/appendix-b2.test.mjs +2 -2
  57. package/tests/client-export.test.mjs +520 -0
  58. package/tests/client-render.test.mjs +19 -3
  59. package/tests/consolidation-credential.test.mjs +16 -5
  60. package/tests/consolidation-feed.test.mjs +96 -0
  61. package/tests/consolidation-fork.test.mjs +144 -10
  62. package/tests/consolidation-snapshot.test.mjs +82 -4
  63. package/tests/curator-absorb.test.mjs +443 -0
  64. package/tests/curator-calibrate.test.mjs +173 -0
  65. package/tests/curator-clusters.test.mjs +281 -0
  66. package/tests/curator-commit.test.mjs +324 -0
  67. package/tests/curator-evidence.test.mjs +102 -0
  68. package/tests/curator-link.test.mjs +316 -0
  69. package/tests/curator-pairing.test.mjs +252 -0
  70. package/tests/curator-probe.test.mjs +15 -6
  71. package/tests/curator-review-mode.test.mjs +314 -0
  72. package/tests/disclosure.test.mjs +2 -1
  73. package/tests/fixtures/curator-pairs/labeled.v1.jsonl +90 -0
  74. package/tests/fixtures/lexicon-stub-hooks.mjs +15 -0
  75. package/tests/fixtures/lexicon-stub.mjs +284 -0
  76. package/tests/graph-cascade.test.mjs +223 -0
  77. package/tests/graph.test.mjs +113 -0
  78. package/tests/help-page.test.mjs +415 -0
  79. package/tests/hybrid-recall.test.mjs +32 -2
  80. package/tests/inbound.test.mjs +6 -3
  81. package/tests/inject-guide.test.mjs +28 -0
  82. package/tests/inject.test.mjs +157 -5
  83. package/tests/lexicon-calibrate.test.mjs +48 -0
  84. package/tests/lexicon-dedupe.test.mjs +171 -0
  85. package/tests/lexicon-frequency.test.mjs +179 -0
  86. package/tests/lexicon-inject.test.mjs +410 -0
  87. package/tests/lexicon-perf.test.mjs +436 -0
  88. package/tests/lexicon-ui.test.mjs +547 -0
  89. package/tests/lexicon.test.mjs +922 -0
  90. package/tests/managed-memory.test.mjs +55 -0
  91. package/tests/managed-runtime.test.mjs +32 -0
  92. package/tests/private-links.test.mjs +445 -0
  93. package/tests/prompts.test.mjs +35 -0
  94. package/tests/recall-dedup.test.mjs +446 -0
  95. package/tests/sidebar-beat.test.mjs +5 -1
  96. package/tests/tool-schema.test.mjs +1 -1
  97. package/tests/tools.test.mjs +112 -3
  98. package/tests/vector-migration.test.mjs +58 -1
  99. package/tests/webui-auth.test.mjs +348 -2
  100. package/tests/webui.test.mjs +191 -2
package/lib/memu.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { spawn } from 'node:child_process'
4
4
  import { dataDir, ensureDir, redactSecrets, windowsSystemEnv } from './paths.js'
5
- import { readCurrent, verifyEngine } from './engine.js'
5
+ import { ENGINE_VERSION, readCurrent, verifyEngine } from './engine.js'
6
6
  import { getSettings } from './state.js'
7
7
  import { activeProfile, captureVectorGeneration, assertVectorGeneration, assertProfileVerified, assertMigrationProfile } from './vector-profile.js'
8
8
  import { embeddingWorkerManager } from './local-embedding-manager.js'
@@ -219,6 +219,6 @@ export async function forget({ db, name, timeoutMs = 60_000 } = {}) {
219
219
 
220
220
  export async function probe() {
221
221
  const current = currentEngine()
222
- const verified = await verifyEngine(current.python)
222
+ const verified = await verifyEngine(current.python, { expectedVersion: current.version || ENGINE_VERSION })
223
223
  return { ...verified, current }
224
224
  }
package/lib/migrate.js CHANGED
@@ -7,11 +7,18 @@
7
7
  // the pass exists to rewrite the files: a library nobody writes to again would otherwise
8
8
  // keep the dead `sessionId` fields on disk forever, and an auditor reading the JSON would
9
9
  // have to know about a level the code no longer has.
10
+ //
11
+ // The lexicon half (§2.1 of the construction spec) is the same shape: older builds
12
+ // wrote `pending` proposals and revision drafts; the current lifecycle has only active/archived. The rewrite rule
13
+ // lives in `lib/lexicon.js` (`migrateLegacyLexiconStatuses`); this file only finds the
14
+ // files and runs it, one library at a time, so a single corrupt lexicon cannot take the
15
+ // sweep — or startup — down with it.
10
16
  import fs from 'node:fs'
11
17
  import path from 'node:path'
12
- import { indexDir, readJson, stamp } from './paths.js'
18
+ import { dataDir, indexDir, isSafeId, readJson, readJsonStrict, stamp } from './paths.js'
13
19
  import { listLibrariesAll, updatePrivateIndex } from './state.js'
14
20
  import { countLegacyInRaw, migrateEntryVisibility } from './visibility.js'
21
+ import { migrateLegacyLexiconStatuses } from './lexicon.js'
15
22
 
16
23
  const DEFAULT_MIN_INTERVAL_MS = 60_000
17
24
  const lastRun = { at: 0 }
@@ -109,11 +116,85 @@ export function maybeMigrateLibraries({ minIntervalMs = DEFAULT_MIN_INTERVAL_MS,
109
116
  return migrateLibraries({ now })
110
117
  }
111
118
 
119
+ // ── lexicon lifecycle (§2.1): pending → active/archived ─────────────────────────
120
+ // The directory holds exactly `<libraryId>.json`; a name that does not pass `isSafeId`
121
+ // is skipped rather than guessed at, the same path boundary `lib/lexicon.js` enforces
122
+ // before it builds a filename.
123
+
124
+ function lexiconDir() {
125
+ return path.join(dataDir(), 'lexicon')
126
+ }
127
+
128
+ /** Library ids that own a lexicon file. Sorted, safe names only. */
129
+ export function scanLexiconLibraries() {
130
+ let names = []
131
+ try { names = fs.readdirSync(lexiconDir()) } catch { return [] }
132
+ return names
133
+ .filter((name) => name.endsWith('.json'))
134
+ .map((name) => name.slice(0, -'.json'.length))
135
+ .filter((id) => isSafeId(id))
136
+ .sort()
137
+ }
138
+
139
+ // Counted on the raw file, before any load-time interpretation, so a sweep can tell a
140
+ // library that still needs the rewrite from one that is already migrated. `readJsonStrict`
141
+ // (not `readJson`) on purpose: a scan must not move files, and `readJson` quarantines a
142
+ // corrupt one as a side effect. A file that does not parse counts 0 — it cannot hold
143
+ // statuses to migrate, and `lib/lexicon.js` already blocks writes to it until restored.
144
+ function legacyLexiconCount(libraryId) {
145
+ const result = readJsonStrict(path.join(lexiconDir(), `${libraryId}.json`))
146
+ if (!result.ok) return 0
147
+ const terms = result.value?.terms
148
+ if (!terms || typeof terms !== 'object') return 0
149
+ return Object.values(terms).filter((entry) => entry && entry.status === 'pending').length
150
+ }
151
+
152
+ /** Lexicon files that still hold legacy `pending` entries. */
153
+ export function scanLegacyLexiconLibraries() {
154
+ return scanLexiconLibraries()
155
+ .map((libraryId) => ({ libraryId, legacy: legacyLexiconCount(libraryId) }))
156
+ .filter((report) => report.legacy > 0)
157
+ }
158
+
159
+ /**
160
+ * Rewrite every lexicon that still carries `pending` entries.
161
+ *
162
+ * Per-library try/catch is the point: a single corrupt or write-protected lexicon must
163
+ * not stop the others, and must never abort startup (the pass runs from the cycle tick).
164
+ * A library with nothing to do is not written at all — `migrateLegacyLexiconStatuses`
165
+ * skips the write (and the revision bump) when no entry changed.
166
+ */
167
+ export function migrateLexiconLibraries() {
168
+ const reports = []
169
+ for (const { libraryId, legacy } of scanLegacyLexiconLibraries()) {
170
+ try {
171
+ const result = migrateLegacyLexiconStatuses(libraryId)
172
+ reports.push({
173
+ libraryId,
174
+ before: legacy,
175
+ migrated: result.migrated,
176
+ ...(result.details ? { details: result.details } : {}),
177
+ })
178
+ } catch (error) {
179
+ reports.push({ libraryId, before: legacy, migrated: 0, error: String(error?.message || error) })
180
+ }
181
+ }
182
+ return reports
183
+ }
184
+
112
185
  export function runVisibilityMigrationPass({ now = stamp(), log = () => {} } = {}) {
113
186
  const reports = migrateLibraries({ now })
114
187
  for (const report of reports) {
115
188
  if (report.error) log(`visibility migration failed for ${report.libraryId}: ${report.error}`)
116
189
  else if (report.migrated > 0) log(`migrated ${report.migrated} legacy memory/memories to shared in ${report.libraryId}`)
117
190
  }
118
- return { reports }
191
+ // §2.1: legacy lexicons may still carry pending proposals/revision drafts. The rewrite is
192
+ // idempotent and only writes files that change, so running it on every cycle tick
193
+ // costs a directory read plus one JSON parse per file that still has pending entries.
194
+ const lexicon = migrateLexiconLibraries()
195
+ for (const report of lexicon) {
196
+ if (report.error) log(`lexicon lifecycle migration failed for ${report.libraryId}: ${report.error}`)
197
+ else if (report.migrated > 0) log(`migrated ${report.migrated} legacy lexicon term(s) to the active/archived lifecycle in ${report.libraryId}`)
198
+ }
199
+ return { reports, lexicon }
119
200
  }