dsh-memu-memory 1.1.2 → 1.1.3
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 +8 -6
- package/README.md +21 -5
- package/README.zh.md +21 -5
- package/docs/contributing.md +5 -3
- package/docs/env-and-commands.md +13 -0
- package/docs/host-api.md +35 -3
- package/docs/memory-semantics.md +32 -5
- package/docs/troubleshooting.md +36 -7
- package/lib/activity-log.js +190 -0
- package/lib/backup.js +1103 -0
- package/lib/binding-diagnostics.js +630 -0
- package/lib/client.js +697 -49
- package/lib/curator.js +81 -37
- package/lib/cycle.js +3 -0
- package/lib/embedding-catalog.js +763 -0
- package/lib/embedding-download.js +407 -0
- package/lib/embedding-runtime.js +710 -0
- package/lib/engine.js +2 -2
- package/lib/index.js +36 -0
- package/lib/inject.js +131 -30
- package/lib/local-embedding-entry.js +145 -0
- package/lib/local-embedding-manager.js +249 -0
- package/lib/local-embedding-protocol.js +173 -0
- package/lib/local-embedding-worker.js +652 -0
- package/lib/local-vector-service.js +283 -0
- package/lib/memory.js +385 -45
- package/lib/memu-config.js +10 -22
- package/lib/memu.js +53 -19
- package/lib/paths.js +24 -0
- package/lib/private.js +139 -13
- package/lib/rpc.js +45 -4
- package/lib/state.js +109 -161
- package/lib/tools.js +9 -5
- package/lib/vector-migration.js +878 -0
- package/lib/vector-profile.js +941 -0
- package/package.json +3 -2
- package/scripts/acceptance-local.mjs +5 -4
- package/scripts/chain-matrix.mjs +527 -0
- package/scripts/check-all.mjs +19 -0
- package/scripts/check-doc-drift.mjs +353 -0
- package/scripts/diagnose-bindings.mjs +176 -0
- package/scripts/local-runtime-install.mjs +142 -0
- package/scripts/local-runtime-verify.mjs +56 -0
- package/scripts/local-vector-verify.mjs +93 -0
- package/scripts/recall-bench.mjs +813 -0
- package/scripts/repetition-ab-scenarios.mjs +93 -0
- package/scripts/repetition-detector-snapshot.mjs +314 -0
- package/scripts/repetition-eval-live.mjs +360 -0
- package/scripts/repetition-eval.mjs +702 -0
package/lib/curator.js
CHANGED
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
import {
|
|
2
|
-
globalDbPath, loadGlobalIndex, updateGlobalIndex, newMemoryId, getSettings,
|
|
2
|
+
globalDbPath, loadGlobalIndex, updateGlobalIndex, newMemoryId, getSettings, effectiveVectorRoute,
|
|
3
3
|
} from './state.js'
|
|
4
4
|
import { commit } from './memu.js'
|
|
5
5
|
import { loadPrompt } from './prompts.js'
|
|
6
|
-
import { retryStoreWrite, redactSecrets } from './paths.js'
|
|
6
|
+
import { retryStoreWrite, redactSecrets, warn } from './paths.js'
|
|
7
|
+
import { record } from './activity-log.js'
|
|
8
|
+
import { assertVectorWriteAllowed, hasActiveVectorGeneration, readVectorState, withVectorWrite } from './vector-profile.js'
|
|
7
9
|
|
|
8
10
|
const MAX_SESSIONS = 10
|
|
9
11
|
const MAX_TOTAL_CHARS = 90_000
|
|
10
12
|
const MAX_SESSION_CHARS = 14_000
|
|
11
13
|
const jobs = new Map()
|
|
12
14
|
|
|
15
|
+
function managedVectorIntent() {
|
|
16
|
+
if (effectiveVectorRoute() !== 'managed-local') return false
|
|
17
|
+
try {
|
|
18
|
+
const state = readVectorState({ tolerateMissing: true }).state
|
|
19
|
+
return Boolean(state && (state.active || state.requested || state.pending))
|
|
20
|
+
} catch {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 与 memory.js 同一条口径:受管路由还没有代际时,引擎这一步按词法处理(索引照写),
|
|
26
|
+
// 否则 MemU 会以 "managed local vector profile is not active" 拒绝整次提炼。
|
|
27
|
+
function engineRoute() {
|
|
28
|
+
const route = effectiveVectorRoute()
|
|
29
|
+
return route === 'managed-local' && !hasActiveVectorGeneration() ? 'lexical' : route
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function withManagedCuratorWrite(action) {
|
|
33
|
+
if (!managedVectorIntent()) return action()
|
|
34
|
+
// Same rule as the private write path: requested-but-never-migrated has no
|
|
35
|
+
// generation to stay consistent with, so the global index + engine still get
|
|
36
|
+
// the memories. Failing here would throw away a whole curation run.
|
|
37
|
+
if (!hasActiveVectorGeneration()) {
|
|
38
|
+
warn('managed vector write degraded to index+engine: no active vector generation; run the profile switch to enable vectors')
|
|
39
|
+
record({ kind: 'vector', level: 'warn', scope: 'global', message: '向量未激活,全局提炼已降级为索引+引擎', detail: { reason: 'no active vector generation' } })
|
|
40
|
+
return action()
|
|
41
|
+
}
|
|
42
|
+
return withVectorWrite(async () => { assertVectorWriteAllowed(); return action() })
|
|
43
|
+
}
|
|
44
|
+
|
|
13
45
|
function textOf(blocks) {
|
|
14
46
|
if (!Array.isArray(blocks)) return ''
|
|
15
47
|
return blocks
|
|
@@ -259,46 +291,49 @@ export async function commitCuratorOutput(output, { job }) {
|
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
if (files.length === 0) {
|
|
262
|
-
if (pending.length) await retryStoreWrite(() => updateGlobalIndex(fresh => {
|
|
294
|
+
if (pending.length) await withManagedCuratorWrite(() => retryStoreWrite(() => updateGlobalIndex(fresh => {
|
|
263
295
|
for (const { id, entry } of pending) fresh.entries[id] = entry
|
|
264
|
-
}))
|
|
296
|
+
})))
|
|
265
297
|
return { files: [], created: 0, merged: 0, superseded: 0, pending: pending.length, skipped }
|
|
266
298
|
}
|
|
267
299
|
|
|
268
300
|
job.step = `committing ${files.length} global memories`
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (job?.abort?.signal?.aborted) throw new Error('cancelled by user')
|
|
279
|
-
await retryStoreWrite(() => updateGlobalIndex(fresh => {
|
|
280
|
-
for (const { id, entry } of pending) fresh.entries[id] = entry
|
|
281
|
-
for (const { id, entry, op, targetId, previous } of updates) {
|
|
282
|
-
if (previous && JSON.stringify(fresh.entries[targetId]) !== JSON.stringify(previous)) {
|
|
283
|
-
const heldId = newMemoryId('gmem')
|
|
284
|
-
fresh.entries[heldId] = { ...entry, status: 'pending_review', conflictingWith: targetId, reviewReason: 'target changed while curator was running' }
|
|
285
|
-
continue
|
|
286
|
-
}
|
|
287
|
-
if (op === 'merge' && previous) {
|
|
288
|
-
entry.content = entry.content || previous.content
|
|
289
|
-
entry.title = entry.title || previous.title
|
|
290
|
-
entry.evidence = [...(previous.evidence || []), ...(entry.evidence || [])].slice(-40)
|
|
291
|
-
}
|
|
292
|
-
fresh.entries[id] = entry
|
|
293
|
-
if ((op === 'supersede' || op === 'merge') && targetId && fresh.entries[targetId]) {
|
|
294
|
-
fresh.entries[targetId].status = 'superseded'
|
|
295
|
-
fresh.entries[targetId].supersededBy = id
|
|
296
|
-
fresh.entries[targetId].validTo = now
|
|
297
|
-
fresh.entries[targetId].updatedAt = now
|
|
298
|
-
}
|
|
301
|
+
await withManagedCuratorWrite(async () => {
|
|
302
|
+
// Managed agent mode still commits the exact same files to the active local
|
|
303
|
+
// vector database; only pending_review entries remain index-only.
|
|
304
|
+
const route = engineRoute()
|
|
305
|
+
if (route !== 'lexical') {
|
|
306
|
+
await commit({
|
|
307
|
+
payload: { recall_files: files, resource: [], user: {} },
|
|
308
|
+
route, db: globalDbPath(),
|
|
309
|
+
})
|
|
299
310
|
}
|
|
300
|
-
|
|
301
|
-
|
|
311
|
+
|
|
312
|
+
if (job?.abort?.signal?.aborted) throw new Error('cancelled by user')
|
|
313
|
+
await retryStoreWrite(() => updateGlobalIndex(fresh => {
|
|
314
|
+
for (const { id, entry } of pending) fresh.entries[id] = entry
|
|
315
|
+
for (const { id, entry, op, targetId, previous } of updates) {
|
|
316
|
+
if (previous && JSON.stringify(fresh.entries[targetId]) !== JSON.stringify(previous)) {
|
|
317
|
+
const heldId = newMemoryId('gmem')
|
|
318
|
+
fresh.entries[heldId] = { ...entry, status: 'pending_review', conflictingWith: targetId, reviewReason: 'target changed while curator was running' }
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
if (op === 'merge' && previous) {
|
|
322
|
+
entry.content = entry.content || previous.content
|
|
323
|
+
entry.title = entry.title || previous.title
|
|
324
|
+
entry.evidence = [...(previous.evidence || []), ...(entry.evidence || [])].slice(-40)
|
|
325
|
+
}
|
|
326
|
+
fresh.entries[id] = entry
|
|
327
|
+
if ((op === 'supersede' || op === 'merge') && targetId && fresh.entries[targetId]) {
|
|
328
|
+
fresh.entries[targetId].status = 'superseded'
|
|
329
|
+
fresh.entries[targetId].supersededBy = id
|
|
330
|
+
fresh.entries[targetId].validTo = now
|
|
331
|
+
fresh.entries[targetId].updatedAt = now
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}))
|
|
335
|
+
})
|
|
336
|
+
const summary = {
|
|
302
337
|
files,
|
|
303
338
|
created: updates.filter((u) => u.op === 'create').length,
|
|
304
339
|
merged: updates.filter((u) => u.op === 'merge').length,
|
|
@@ -306,8 +341,17 @@ export async function commitCuratorOutput(output, { job }) {
|
|
|
306
341
|
pending: pending.length,
|
|
307
342
|
skipped,
|
|
308
343
|
}
|
|
344
|
+
record({
|
|
345
|
+
kind: 'curate',
|
|
346
|
+
scope: 'global',
|
|
347
|
+
ok: true,
|
|
348
|
+
message: `全局提炼:新增 ${summary.created} / 合并 ${summary.merged} / 待复核 ${summary.pending}`
|
|
349
|
+
+ (summary.skipped.length ? ` / 跳过 ${summary.skipped.length}` : ''),
|
|
350
|
+
detail: { ...summary, skipped: summary.skipped.slice(0, 20) },
|
|
351
|
+
refs: files.map((file) => file.name).slice(0, 20),
|
|
352
|
+
})
|
|
353
|
+
return summary
|
|
309
354
|
}
|
|
310
|
-
|
|
311
355
|
export function startGlobalCuration(ctx, { sessionIds, parentSessionId, model } = {}) {
|
|
312
356
|
const ids = [...new Set((Array.isArray(sessionIds) ? sessionIds : []).filter((s) => typeof s === 'string' && s.trim()))]
|
|
313
357
|
const jobId = `curate-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
|
package/lib/cycle.js
CHANGED
|
@@ -7,6 +7,7 @@ import { expireShortTerm } from './memory.js'
|
|
|
7
7
|
import { requestPrivateConsolidation } from './private.js'
|
|
8
8
|
import { startGlobalCuration } from './curator.js'
|
|
9
9
|
import { runVisibilityMigrationPass } from './migrate.js'
|
|
10
|
+
import { record } from './activity-log.js'
|
|
10
11
|
|
|
11
12
|
const SWEEP_MS = 5 * 60 * 1000
|
|
12
13
|
// Long enough that the host has restored its session list and the agent objects
|
|
@@ -152,9 +153,11 @@ export async function runCurationPass(ctx, { now = Date.now(), log = () => {} }
|
|
|
152
153
|
try {
|
|
153
154
|
const job = await startGlobalCuration(ctx, { sessionIds: due, parentSessionId: parent, model: settings.curatorModel })
|
|
154
155
|
setCycleState({ lastCurateAt: new Date(now).toISOString(), lastCurateJob: job?.id || null })
|
|
156
|
+
record({ kind: 'curate', scope: 'global', ok: true, message: `自动提炼启动:${due.length} 个会话`, detail: { sessions: due.length, jobId: job?.id || null, parent } })
|
|
155
157
|
return { ran: due.length, job }
|
|
156
158
|
} catch (error) {
|
|
157
159
|
log(`auto curation failed: ${error?.message || error}`)
|
|
160
|
+
record({ kind: 'curate', scope: 'global', ok: false, level: 'error', message: '自动提炼失败:' + String(error?.message || error), detail: { sessions: due.length } })
|
|
158
161
|
return { ran: 0, error: String(error?.message || error) }
|
|
159
162
|
}
|
|
160
163
|
}
|