@smyslenny/agent-memory 4.0.0-alpha.1 → 4.1.0-alpha.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/dist/bin/agent-memory.js +1164 -882
- package/dist/bin/agent-memory.js.map +1 -1
- package/dist/index.d.ts +48 -6
- package/dist/index.js +124 -10
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +140 -23
- package/dist/mcp/server.js.map +1 -1
- package/docs/design/.next-id +1 -0
- package/docs/design/0016-v41-warm-boot-surface-emotion.md +228 -0
- package/package.json +2 -2
package/dist/mcp/server.js
CHANGED
|
@@ -89,6 +89,11 @@ function migrateDatabase(db, from, to) {
|
|
|
89
89
|
v = 5;
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
92
|
+
if (v === 5) {
|
|
93
|
+
migrateV5ToV6(db);
|
|
94
|
+
v = 6;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
92
97
|
throw new Error(`Unsupported schema migration path: v${from} \u2192 v${to} (stuck at v${v})`);
|
|
93
98
|
}
|
|
94
99
|
}
|
|
@@ -174,6 +179,8 @@ function inferSchemaVersion(db) {
|
|
|
174
179
|
const hasV4Embeddings = hasEmbeddings && tableHasColumn(db, "embeddings", "provider_id") && tableHasColumn(db, "embeddings", "status") && tableHasColumn(db, "embeddings", "content_hash") && tableHasColumn(db, "embeddings", "id");
|
|
175
180
|
const hasMaintenanceJobs = tableExists(db, "maintenance_jobs");
|
|
176
181
|
const hasFeedbackEvents = tableExists(db, "feedback_events");
|
|
182
|
+
const hasEmotionTag = tableHasColumn(db, "memories", "emotion_tag");
|
|
183
|
+
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag) return 6;
|
|
177
184
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents) return 5;
|
|
178
185
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings) return 4;
|
|
179
186
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasEmbeddings) return 3;
|
|
@@ -200,6 +207,9 @@ function ensureIndexes(db) {
|
|
|
200
207
|
if (tableExists(db, "maintenance_jobs")) {
|
|
201
208
|
db.exec("CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_phase_status ON maintenance_jobs(phase, status, started_at DESC);");
|
|
202
209
|
}
|
|
210
|
+
if (tableHasColumn(db, "memories", "emotion_tag")) {
|
|
211
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_emotion_tag ON memories(emotion_tag) WHERE emotion_tag IS NOT NULL;");
|
|
212
|
+
}
|
|
203
213
|
if (tableExists(db, "feedback_events")) {
|
|
204
214
|
db.exec("CREATE INDEX IF NOT EXISTS idx_feedback_events_memory ON feedback_events(memory_id, created_at DESC);");
|
|
205
215
|
if (tableHasColumn(db, "feedback_events", "agent_id") && tableHasColumn(db, "feedback_events", "source")) {
|
|
@@ -333,11 +343,30 @@ function migrateV4ToV5(db) {
|
|
|
333
343
|
throw e;
|
|
334
344
|
}
|
|
335
345
|
}
|
|
346
|
+
function migrateV5ToV6(db) {
|
|
347
|
+
if (tableHasColumn(db, "memories", "emotion_tag")) {
|
|
348
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(6));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
db.exec("BEGIN");
|
|
353
|
+
db.exec("ALTER TABLE memories ADD COLUMN emotion_tag TEXT;");
|
|
354
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_emotion_tag ON memories(emotion_tag) WHERE emotion_tag IS NOT NULL;");
|
|
355
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(6));
|
|
356
|
+
db.exec("COMMIT");
|
|
357
|
+
} catch (e) {
|
|
358
|
+
try {
|
|
359
|
+
db.exec("ROLLBACK");
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
throw e;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
336
365
|
var SCHEMA_VERSION, SCHEMA_SQL;
|
|
337
366
|
var init_db = __esm({
|
|
338
367
|
"src/core/db.ts"() {
|
|
339
368
|
"use strict";
|
|
340
|
-
SCHEMA_VERSION =
|
|
369
|
+
SCHEMA_VERSION = 6;
|
|
341
370
|
SCHEMA_SQL = `
|
|
342
371
|
-- Memory entries
|
|
343
372
|
CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -355,6 +384,7 @@ CREATE TABLE IF NOT EXISTS memories (
|
|
|
355
384
|
source TEXT,
|
|
356
385
|
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
357
386
|
hash TEXT,
|
|
387
|
+
emotion_tag TEXT,
|
|
358
388
|
UNIQUE(hash, agent_id)
|
|
359
389
|
);
|
|
360
390
|
|
|
@@ -934,8 +964,8 @@ function createMemory(db, input) {
|
|
|
934
964
|
const timestamp = now();
|
|
935
965
|
db.prepare(
|
|
936
966
|
`INSERT INTO memories (id, content, type, priority, emotion_val, vitality, stability,
|
|
937
|
-
access_count, created_at, updated_at, source, agent_id, hash)
|
|
938
|
-
VALUES (?, ?, ?, ?, ?, 1.0, ?, 0, ?, ?, ?, ?, ?)`
|
|
967
|
+
access_count, created_at, updated_at, source, agent_id, hash, emotion_tag)
|
|
968
|
+
VALUES (?, ?, ?, ?, ?, 1.0, ?, 0, ?, ?, ?, ?, ?, ?)`
|
|
939
969
|
).run(
|
|
940
970
|
id,
|
|
941
971
|
input.content,
|
|
@@ -947,7 +977,8 @@ function createMemory(db, input) {
|
|
|
947
977
|
timestamp,
|
|
948
978
|
input.source ?? null,
|
|
949
979
|
agentId,
|
|
950
|
-
hash2
|
|
980
|
+
hash2,
|
|
981
|
+
input.emotion_tag ?? null
|
|
951
982
|
);
|
|
952
983
|
db.prepare("INSERT INTO memories_fts (id, content) VALUES (?, ?)").run(id, tokenizeForIndex(input.content));
|
|
953
984
|
markEmbeddingDirtyIfNeeded(db, id, hash2, resolveEmbeddingProviderId(input.embedding_provider_id));
|
|
@@ -991,6 +1022,10 @@ function updateMemory(db, id, input) {
|
|
|
991
1022
|
fields.push("source = ?");
|
|
992
1023
|
values.push(input.source);
|
|
993
1024
|
}
|
|
1025
|
+
if (input.emotion_tag !== void 0) {
|
|
1026
|
+
fields.push("emotion_tag = ?");
|
|
1027
|
+
values.push(input.emotion_tag);
|
|
1028
|
+
}
|
|
994
1029
|
fields.push("updated_at = ?");
|
|
995
1030
|
values.push(now());
|
|
996
1031
|
values.push(id);
|
|
@@ -1032,6 +1067,10 @@ function listMemories(db, opts) {
|
|
|
1032
1067
|
conditions.push("vitality >= ?");
|
|
1033
1068
|
params.push(opts.min_vitality);
|
|
1034
1069
|
}
|
|
1070
|
+
if (opts?.emotion_tag) {
|
|
1071
|
+
conditions.push("emotion_tag = ?");
|
|
1072
|
+
params.push(opts.emotion_tag);
|
|
1073
|
+
}
|
|
1035
1074
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1036
1075
|
const limit = opts?.limit ?? 100;
|
|
1037
1076
|
const offset = opts?.offset ?? 0;
|
|
@@ -14901,8 +14940,68 @@ function getPathsByPrefix(db, prefix, agent_id = "default") {
|
|
|
14901
14940
|
|
|
14902
14941
|
// src/sleep/boot.ts
|
|
14903
14942
|
init_memory();
|
|
14943
|
+
function formatRelativeDate(isoDate) {
|
|
14944
|
+
const diffMs = Date.now() - new Date(isoDate).getTime();
|
|
14945
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
14946
|
+
if (diffDays <= 0) return "\u4ECA\u5929";
|
|
14947
|
+
if (diffDays === 1) return "\u6628\u5929";
|
|
14948
|
+
if (diffDays <= 7) return `${diffDays}\u5929\u524D`;
|
|
14949
|
+
return isoDate.slice(0, 10);
|
|
14950
|
+
}
|
|
14951
|
+
function loadWarmBootLayers(db, agentId) {
|
|
14952
|
+
const identity = listMemories(db, { agent_id: agentId, type: "identity", limit: 50 });
|
|
14953
|
+
const emotion = listMemories(db, { agent_id: agentId, type: "emotion", limit: 5 });
|
|
14954
|
+
const event = listMemories(db, { agent_id: agentId, type: "event", limit: 7 });
|
|
14955
|
+
const knowledge = listMemories(db, {
|
|
14956
|
+
agent_id: agentId,
|
|
14957
|
+
type: "knowledge",
|
|
14958
|
+
min_vitality: 0.5,
|
|
14959
|
+
limit: 10
|
|
14960
|
+
});
|
|
14961
|
+
return { identity, emotion, event, knowledge };
|
|
14962
|
+
}
|
|
14963
|
+
function formatNarrativeBoot(layers, agentName) {
|
|
14964
|
+
const lines = [];
|
|
14965
|
+
lines.push(`# ${agentName}\u7684\u56DE\u5FC6`);
|
|
14966
|
+
lines.push("");
|
|
14967
|
+
if (layers.identity.length > 0) {
|
|
14968
|
+
lines.push("## \u6211\u662F\u8C01");
|
|
14969
|
+
for (const mem of layers.identity) {
|
|
14970
|
+
lines.push(`- ${mem.content.split("\n")[0].slice(0, 200)}`);
|
|
14971
|
+
}
|
|
14972
|
+
lines.push("");
|
|
14973
|
+
}
|
|
14974
|
+
if (layers.emotion.length > 0) {
|
|
14975
|
+
lines.push("## \u6700\u8FD1\u7684\u5FC3\u60C5");
|
|
14976
|
+
for (const mem of layers.emotion) {
|
|
14977
|
+
const tag = mem.emotion_tag;
|
|
14978
|
+
const time3 = formatRelativeDate(mem.updated_at);
|
|
14979
|
+
const tagStr = tag ? `${tag}, ${time3}` : time3;
|
|
14980
|
+
lines.push(`- ${mem.content.split("\n")[0].slice(0, 200)} (${tagStr})`);
|
|
14981
|
+
}
|
|
14982
|
+
lines.push("");
|
|
14983
|
+
}
|
|
14984
|
+
if (layers.event.length > 0) {
|
|
14985
|
+
lines.push("## \u6700\u8FD1\u53D1\u751F\u7684\u4E8B");
|
|
14986
|
+
for (const mem of layers.event) {
|
|
14987
|
+
const time3 = formatRelativeDate(mem.updated_at);
|
|
14988
|
+
lines.push(`- ${mem.content.split("\n")[0].slice(0, 200)} (${time3})`);
|
|
14989
|
+
}
|
|
14990
|
+
lines.push("");
|
|
14991
|
+
}
|
|
14992
|
+
if (layers.knowledge.length > 0) {
|
|
14993
|
+
lines.push("## \u8FD8\u8BB0\u5F97\u7684\u77E5\u8BC6");
|
|
14994
|
+
for (const mem of layers.knowledge) {
|
|
14995
|
+
lines.push(`- ${mem.content.split("\n")[0].slice(0, 200)}`);
|
|
14996
|
+
}
|
|
14997
|
+
lines.push("");
|
|
14998
|
+
}
|
|
14999
|
+
return lines.join("\n");
|
|
15000
|
+
}
|
|
14904
15001
|
function boot(db, opts) {
|
|
14905
15002
|
const agentId = opts?.agent_id ?? "default";
|
|
15003
|
+
const format = opts?.format ?? "json";
|
|
15004
|
+
const agentName = opts?.agent_name ?? "Agent";
|
|
14906
15005
|
const corePaths = opts?.corePaths ?? [
|
|
14907
15006
|
"core://agent",
|
|
14908
15007
|
"core://user",
|
|
@@ -14946,10 +15045,16 @@ function boot(db, opts) {
|
|
|
14946
15045
|
}
|
|
14947
15046
|
}
|
|
14948
15047
|
}
|
|
14949
|
-
|
|
15048
|
+
const result = {
|
|
14950
15049
|
identityMemories: [...memories.values()],
|
|
14951
15050
|
bootPaths
|
|
14952
15051
|
};
|
|
15052
|
+
if (format === "narrative") {
|
|
15053
|
+
const layers = loadWarmBootLayers(db, agentId);
|
|
15054
|
+
result.layers = layers;
|
|
15055
|
+
result.narrative = formatNarrativeBoot(layers, agentName);
|
|
15056
|
+
}
|
|
15057
|
+
return result;
|
|
14953
15058
|
}
|
|
14954
15059
|
|
|
14955
15060
|
// src/sleep/sync.ts
|
|
@@ -15550,7 +15655,8 @@ async function syncOne(db, input) {
|
|
|
15550
15655
|
agent_id: input.agent_id,
|
|
15551
15656
|
uri: input.uri,
|
|
15552
15657
|
provider: input.provider,
|
|
15553
|
-
conservative: input.conservative
|
|
15658
|
+
conservative: input.conservative,
|
|
15659
|
+
emotion_tag: input.emotion_tag
|
|
15554
15660
|
};
|
|
15555
15661
|
const guardResult = await guard(db, memInput);
|
|
15556
15662
|
switch (guardResult.action) {
|
|
@@ -15830,21 +15936,26 @@ async function rememberMemory(db, input) {
|
|
|
15830
15936
|
source: input.source,
|
|
15831
15937
|
agent_id: input.agent_id,
|
|
15832
15938
|
provider: input.provider,
|
|
15833
|
-
conservative: input.conservative
|
|
15939
|
+
conservative: input.conservative,
|
|
15940
|
+
emotion_tag: input.emotion_tag
|
|
15834
15941
|
});
|
|
15835
15942
|
}
|
|
15836
15943
|
|
|
15837
15944
|
// src/app/recall.ts
|
|
15838
15945
|
async function recallMemory(db, input) {
|
|
15839
|
-
|
|
15946
|
+
const result = await recallMemories(db, input.query, {
|
|
15840
15947
|
agent_id: input.agent_id,
|
|
15841
|
-
limit: input.limit,
|
|
15948
|
+
limit: input.emotion_tag ? (input.limit ?? 10) * 3 : input.limit,
|
|
15842
15949
|
min_vitality: input.min_vitality,
|
|
15843
15950
|
lexicalLimit: input.lexicalLimit,
|
|
15844
15951
|
vectorLimit: input.vectorLimit,
|
|
15845
15952
|
provider: input.provider,
|
|
15846
15953
|
recordAccess: input.recordAccess
|
|
15847
15954
|
});
|
|
15955
|
+
if (input.emotion_tag) {
|
|
15956
|
+
result.results = result.results.filter((r) => r.memory.emotion_tag === input.emotion_tag).slice(0, input.limit ?? 10);
|
|
15957
|
+
}
|
|
15958
|
+
return result;
|
|
15848
15959
|
}
|
|
15849
15960
|
|
|
15850
15961
|
// src/app/surface.ts
|
|
@@ -16093,7 +16204,7 @@ async function surfaceMemories(db, input) {
|
|
|
16093
16204
|
signals.set(memory.id, { memory });
|
|
16094
16205
|
}
|
|
16095
16206
|
}
|
|
16096
|
-
const results = [...signals.values()].map((signal) => signal.memory).filter((memory) => memory.vitality >= minVitality).filter((memory) => input.types?.length ? input.types.includes(memory.type) : true).map((memory) => {
|
|
16207
|
+
const results = [...signals.values()].map((signal) => signal.memory).filter((memory) => memory.vitality >= minVitality).filter((memory) => input.types?.length ? input.types.includes(memory.type) : true).filter((memory) => input.emotion_tag ? memory.emotion_tag === input.emotion_tag : true).map((memory) => {
|
|
16097
16208
|
const signal = signals.get(memory.id) ?? { memory };
|
|
16098
16209
|
const memoryTokens = new Set(tokenize(memory.content));
|
|
16099
16210
|
const lexicalOverlap = overlapScore2(memoryTokens, queryTokens);
|
|
@@ -16763,10 +16874,11 @@ function createMcpServer(dbPath, agentId) {
|
|
|
16763
16874
|
uri: external_exports.string().optional().describe("URI path (e.g. core://user/name, emotion://2026-02-20/love)"),
|
|
16764
16875
|
emotion_val: external_exports.number().min(-1).max(1).default(0).describe("Emotional valence (-1 negative to +1 positive)"),
|
|
16765
16876
|
source: external_exports.string().optional().describe("Source annotation (e.g. session ID, date)"),
|
|
16766
|
-
agent_id: external_exports.string().optional().describe("Override agent scope (defaults to current agent)")
|
|
16877
|
+
agent_id: external_exports.string().optional().describe("Override agent scope (defaults to current agent)"),
|
|
16878
|
+
emotion_tag: external_exports.string().optional().describe("Emotion label for emotion-type memories (e.g. \u5B89\u5FC3, \u5F00\u5FC3, \u62C5\u5FC3)")
|
|
16767
16879
|
},
|
|
16768
|
-
async ({ content, type, uri, emotion_val, source, agent_id }) => {
|
|
16769
|
-
const result = await rememberMemory(db, { content, type, uri, emotion_val, source, agent_id: agent_id ?? aid });
|
|
16880
|
+
async ({ content, type, uri, emotion_val, source, agent_id, emotion_tag }) => {
|
|
16881
|
+
const result = await rememberMemory(db, { content, type, uri, emotion_val, source, agent_id: agent_id ?? aid, emotion_tag });
|
|
16770
16882
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
16771
16883
|
}
|
|
16772
16884
|
);
|
|
@@ -16776,10 +16888,11 @@ function createMcpServer(dbPath, agentId) {
|
|
|
16776
16888
|
{
|
|
16777
16889
|
query: external_exports.string().describe("Search query (natural language)"),
|
|
16778
16890
|
limit: external_exports.number().default(10).describe("Max results to return"),
|
|
16779
|
-
agent_id: external_exports.string().optional().describe("Override agent scope (defaults to current agent)")
|
|
16891
|
+
agent_id: external_exports.string().optional().describe("Override agent scope (defaults to current agent)"),
|
|
16892
|
+
emotion_tag: external_exports.string().optional().describe("Filter results by emotion tag (e.g. \u5B89\u5FC3, \u5F00\u5FC3)")
|
|
16780
16893
|
},
|
|
16781
|
-
async ({ query, limit, agent_id }) => {
|
|
16782
|
-
const result = await recallMemory(db, { query, limit, agent_id: agent_id ?? aid });
|
|
16894
|
+
async ({ query, limit, agent_id, emotion_tag }) => {
|
|
16895
|
+
const result = await recallMemory(db, { query, limit, agent_id: agent_id ?? aid, emotion_tag });
|
|
16783
16896
|
return { content: [{ type: "text", text: JSON.stringify(formatRecallPayload(result), null, 2) }] };
|
|
16784
16897
|
}
|
|
16785
16898
|
);
|
|
@@ -16827,19 +16940,20 @@ function createMcpServer(dbPath, agentId) {
|
|
|
16827
16940
|
"boot",
|
|
16828
16941
|
"Load startup memories. Default output is narrative markdown; pass format=json for legacy output.",
|
|
16829
16942
|
{
|
|
16830
|
-
format: external_exports.enum(["narrative", "json"]).default("narrative").optional()
|
|
16943
|
+
format: external_exports.enum(["narrative", "json"]).default("narrative").optional(),
|
|
16944
|
+
agent_name: external_exports.string().optional().describe("Agent name for narrative header (default: Agent)")
|
|
16831
16945
|
},
|
|
16832
|
-
async ({ format }) => {
|
|
16946
|
+
async ({ format, agent_name }) => {
|
|
16833
16947
|
const outputFormat = format ?? "narrative";
|
|
16834
|
-
const
|
|
16948
|
+
const result = boot(db, { agent_id: aid, format: outputFormat, agent_name: agent_name ?? void 0 });
|
|
16835
16949
|
if (outputFormat === "json") {
|
|
16836
16950
|
return {
|
|
16837
16951
|
content: [{
|
|
16838
16952
|
type: "text",
|
|
16839
16953
|
text: JSON.stringify({
|
|
16840
|
-
count:
|
|
16841
|
-
bootPaths:
|
|
16842
|
-
memories:
|
|
16954
|
+
count: result.identityMemories.length,
|
|
16955
|
+
bootPaths: result.bootPaths,
|
|
16956
|
+
memories: result.identityMemories.map((memory) => ({
|
|
16843
16957
|
id: memory.id,
|
|
16844
16958
|
content: memory.content,
|
|
16845
16959
|
type: memory.type,
|
|
@@ -16849,12 +16963,15 @@ function createMcpServer(dbPath, agentId) {
|
|
|
16849
16963
|
}]
|
|
16850
16964
|
};
|
|
16851
16965
|
}
|
|
16966
|
+
if (result.narrative) {
|
|
16967
|
+
return { content: [{ type: "text", text: result.narrative }] };
|
|
16968
|
+
}
|
|
16852
16969
|
const identity = listMemories(db, { agent_id: aid, type: "identity", limit: 12 });
|
|
16853
16970
|
const emotion = listMemories(db, { agent_id: aid, type: "emotion", min_vitality: 0.1, limit: 12 }).sort((a, b) => b.vitality - a.vitality);
|
|
16854
16971
|
const knowledge = listMemories(db, { agent_id: aid, type: "knowledge", min_vitality: 0.1, limit: 16 }).sort((a, b) => b.vitality - a.vitality);
|
|
16855
16972
|
const event = listMemories(db, { agent_id: aid, type: "event", min_vitality: 0, limit: 24 }).sort((a, b) => b.vitality - a.vitality);
|
|
16856
16973
|
const stats = countMemories(db, aid);
|
|
16857
|
-
return { content: [{ type: "text", text: formatWarmBootNarrative(identity.length > 0 ? identity :
|
|
16974
|
+
return { content: [{ type: "text", text: formatWarmBootNarrative(identity.length > 0 ? identity : result.identityMemories, emotion, knowledge, event, stats) }] };
|
|
16858
16975
|
}
|
|
16859
16976
|
);
|
|
16860
16977
|
server.tool(
|