dsh-cortex-memory 0.2.1 → 0.3.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.
- package/LICENSE +3 -2
- package/README.md +55 -7
- package/bench/synthetic.js +1 -1
- package/client.js +576 -55
- package/docs/install-windows.md +3 -3
- package/docs/multimodal.md +1 -1
- package/index.js +17 -2
- package/package.json +14 -2
- package/src/api/client.js +5 -2
- package/src/api/routes.js +88 -0
- package/src/core/config.js +8 -0
- package/src/core/feature-overrides.js +2 -2
- package/src/engines/life.js +182 -19
- package/src/host/runtime.js +25 -16
- package/src/host/scheduler.js +177 -0
- package/src/llm/adapter.js +65 -4
- package/src/prompt/assemble.js +8 -3
- package/src/self/emotion.js +4 -2
- package/src/self/evolution.js +15 -1
- package/src/self/identity.js +10 -1
- package/src/service/cortex.js +676 -8
- package/src/store/database.js +10 -0
- package/src/store/repositories/summon.js +138 -0
- package/src/store/schema.sql +45 -0
- package/src/tools/definitions.js +13 -3
package/src/store/database.js
CHANGED
|
@@ -19,6 +19,15 @@ function ensureTraceColumns(db) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function ensureSummonColumns(db) {
|
|
23
|
+
const cards = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'summon_cards'").get();
|
|
24
|
+
if (!cards) return;
|
|
25
|
+
const columns = db.prepare('PRAGMA table_info(summon_cards)').all();
|
|
26
|
+
if (!columns.some((column) => column.name === 'manual_json')) {
|
|
27
|
+
db.exec("ALTER TABLE summon_cards ADD COLUMN manual_json TEXT NOT NULL DEFAULT '{}'");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
function ensureFactsColumns(db) {
|
|
23
32
|
const columns = db.prepare('PRAGMA table_info(facts)').all();
|
|
24
33
|
if (!columns.some((column) => column.name === 'superseded_at')) {
|
|
@@ -76,6 +85,7 @@ export function migrate(db, { version = SCHEMA_VERSION } = {}) {
|
|
|
76
85
|
db.exec(readSchema());
|
|
77
86
|
ensureTraceColumns(db);
|
|
78
87
|
ensureFactsColumns(db);
|
|
88
|
+
ensureSummonColumns(db);
|
|
79
89
|
const row = db.prepare('SELECT value FROM meta WHERE key = ?').get('schema_version');
|
|
80
90
|
if (!row) {
|
|
81
91
|
db.prepare('INSERT INTO meta(key, value, updated_at) VALUES (?, ?, ?)').run('schema_version', String(version), nowIso());
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { makeId } from '../../core/ids.js';
|
|
2
|
+
import { nowIso } from '../../core/time.js';
|
|
3
|
+
|
|
4
|
+
function parse(value, fallback) {
|
|
5
|
+
try { return JSON.parse(value); } catch { return fallback; }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function rowToCard(row) {
|
|
9
|
+
if (!row) return null;
|
|
10
|
+
const manual = parse(row.manual_json ?? '{}', {});
|
|
11
|
+
return {
|
|
12
|
+
id: row.id,
|
|
13
|
+
scopeId: row.scope_id,
|
|
14
|
+
side: row.side,
|
|
15
|
+
title: manual.title ?? row.title,
|
|
16
|
+
content: manual.content ?? row.content,
|
|
17
|
+
triggers: Array.isArray(manual.triggers) ? manual.triggers : parse(row.triggers_json, []),
|
|
18
|
+
category: row.category,
|
|
19
|
+
sourceKind: row.source_kind,
|
|
20
|
+
sourceId: row.source_id,
|
|
21
|
+
day: row.day,
|
|
22
|
+
importance: manual.importance ?? Number(row.importance),
|
|
23
|
+
manual: manual,
|
|
24
|
+
status: row.status,
|
|
25
|
+
createdAt: row.created_at,
|
|
26
|
+
updatedAt: row.updated_at,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function rowToVolume(row) {
|
|
31
|
+
if (!row) return null;
|
|
32
|
+
return {
|
|
33
|
+
id: row.id,
|
|
34
|
+
scopeId: row.scope_id,
|
|
35
|
+
side: row.side,
|
|
36
|
+
title: row.title,
|
|
37
|
+
category: row.category,
|
|
38
|
+
index: row.index_text,
|
|
39
|
+
content: row.content,
|
|
40
|
+
cardIds: parse(row.card_ids_json, []),
|
|
41
|
+
periodStart: row.period_start,
|
|
42
|
+
periodEnd: row.period_end,
|
|
43
|
+
status: row.status,
|
|
44
|
+
createdAt: row.created_at,
|
|
45
|
+
updatedAt: row.updated_at,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createSummonRepository(db) {
|
|
50
|
+
function upsertCard(card = {}) {
|
|
51
|
+
const existing = db.prepare(
|
|
52
|
+
'SELECT * FROM summon_cards WHERE scope_id = ? AND source_kind = ? AND source_id = ?',
|
|
53
|
+
).get(card.scopeId ?? 'global', card.sourceKind ?? null, card.sourceId ?? null);
|
|
54
|
+
const now = nowIso();
|
|
55
|
+
const id = existing?.id ?? card.id ?? makeId('scd');
|
|
56
|
+
db.prepare(`
|
|
57
|
+
INSERT INTO summon_cards(id, scope_id, side, title, content, triggers_json, category,
|
|
58
|
+
source_kind, source_id, day, importance, manual_json, status, created_at, updated_at)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
60
|
+
ON CONFLICT(scope_id, source_kind, source_id) DO UPDATE SET
|
|
61
|
+
side = excluded.side, title = excluded.title, content = excluded.content,
|
|
62
|
+
triggers_json = excluded.triggers_json, category = excluded.category,
|
|
63
|
+
day = excluded.day, importance = excluded.importance, status = excluded.status,
|
|
64
|
+
updated_at = excluded.updated_at
|
|
65
|
+
`).run(
|
|
66
|
+
id, card.scopeId ?? 'global', card.side, card.title, card.content,
|
|
67
|
+
JSON.stringify(card.triggers ?? []), card.category ?? null,
|
|
68
|
+
card.sourceKind ?? null, card.sourceId ?? null, card.day,
|
|
69
|
+
Number(card.importance ?? 0.5), existing?.manual_json ?? '{}', card.status ?? 'active',
|
|
70
|
+
existing?.created_at ?? now, now,
|
|
71
|
+
);
|
|
72
|
+
return getCard(id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getCard(id) {
|
|
76
|
+
return rowToCard(db.prepare('SELECT * FROM summon_cards WHERE id = ?').get(id));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function listCards({ scopeId = 'global', day = null, side = null, limit = 300 } = {}) {
|
|
80
|
+
const clauses = ['scope_id = ?', "status = 'active'"];
|
|
81
|
+
const values = [scopeId];
|
|
82
|
+
if (day) { clauses.push('day = ?'); values.push(day); }
|
|
83
|
+
if (side) { clauses.push('side = ?'); values.push(side); }
|
|
84
|
+
values.push(Math.max(1, Math.min(1000, Number(limit) || 300)));
|
|
85
|
+
return db.prepare(`SELECT * FROM summon_cards WHERE ${clauses.join(' AND ')} ORDER BY day DESC, importance DESC, created_at DESC LIMIT ?`).all(...values).map(rowToCard);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function updateCard(id, patch = {}) {
|
|
89
|
+
const current = getCard(id);
|
|
90
|
+
if (!current) return null;
|
|
91
|
+
const manual = {
|
|
92
|
+
...(current.manual ?? {}),
|
|
93
|
+
...(patch.title == null ? {} : { title: String(patch.title) }),
|
|
94
|
+
...(patch.content == null ? {} : { content: String(patch.content) }),
|
|
95
|
+
...(Array.isArray(patch.triggers) ? { triggers: patch.triggers.map(String) } : {}),
|
|
96
|
+
...(patch.importance == null ? {} : { importance: Number(patch.importance) }),
|
|
97
|
+
};
|
|
98
|
+
db.prepare('UPDATE summon_cards SET manual_json = ?, updated_at = ? WHERE id = ?')
|
|
99
|
+
.run(JSON.stringify(manual), nowIso(), id);
|
|
100
|
+
return getCard(id);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function upsertVolume(volume = {}) {
|
|
104
|
+
const existing = db.prepare(
|
|
105
|
+
'SELECT * FROM summon_volumes WHERE scope_id = ? AND side = ? AND category = ? AND period_start = ? AND period_end = ?',
|
|
106
|
+
).get(volume.scopeId ?? 'global', volume.side, volume.category ?? null, volume.periodStart ?? null, volume.periodEnd ?? null);
|
|
107
|
+
const now = nowIso();
|
|
108
|
+
const id = existing?.id ?? makeId('svl');
|
|
109
|
+
db.prepare(`
|
|
110
|
+
INSERT INTO summon_volumes(id, scope_id, side, title, category, index_text, content,
|
|
111
|
+
card_ids_json, period_start, period_end, status, created_at, updated_at)
|
|
112
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
113
|
+
ON CONFLICT(scope_id, side, category, period_start, period_end) DO UPDATE SET
|
|
114
|
+
title = excluded.title, index_text = excluded.index_text, content = excluded.content,
|
|
115
|
+
card_ids_json = excluded.card_ids_json, status = excluded.status, updated_at = excluded.updated_at
|
|
116
|
+
`).run(
|
|
117
|
+
id, volume.scopeId ?? 'global', volume.side, volume.title, volume.category ?? null,
|
|
118
|
+
volume.index ?? '', volume.content ?? '', JSON.stringify(volume.cardIds ?? []),
|
|
119
|
+
volume.periodStart ?? null, volume.periodEnd ?? null, volume.status ?? 'active',
|
|
120
|
+
existing?.created_at ?? now, now,
|
|
121
|
+
);
|
|
122
|
+
return getVolume(id);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getVolume(id) {
|
|
126
|
+
return rowToVolume(db.prepare('SELECT * FROM summon_volumes WHERE id = ?').get(id));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function listVolumes({ scopeId = 'global', side = null, limit = 100 } = {}) {
|
|
130
|
+
const clauses = ['scope_id = ?', "status = 'active'"];
|
|
131
|
+
const values = [scopeId];
|
|
132
|
+
if (side) { clauses.push('side = ?'); values.push(side); }
|
|
133
|
+
values.push(Math.max(1, Math.min(500, Number(limit) || 100)));
|
|
134
|
+
return db.prepare(`SELECT * FROM summon_volumes WHERE ${clauses.join(' AND ')} ORDER BY period_end DESC, updated_at DESC LIMIT ?`).all(...values).map(rowToVolume);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { upsertCard, getCard, listCards, updateCard, upsertVolume, getVolume, listVolumes };
|
|
138
|
+
}
|
package/src/store/schema.sql
CHANGED
|
@@ -482,3 +482,48 @@ CREATE INDEX IF NOT EXISTS dream_runs_scope_at ON dream_runs(scope_id, at);
|
|
|
482
482
|
|
|
483
483
|
|
|
484
484
|
|
|
485
|
+
|
|
486
|
+
-- 召唤卡:Cortex 原生的 L2 卡层,不依赖 shiori。
|
|
487
|
+
-- side: left = 情感卡,right = 工作卡。
|
|
488
|
+
CREATE TABLE IF NOT EXISTS summon_cards (
|
|
489
|
+
id TEXT PRIMARY KEY,
|
|
490
|
+
scope_id TEXT NOT NULL,
|
|
491
|
+
side TEXT NOT NULL CHECK(side IN ('left', 'right')),
|
|
492
|
+
title TEXT NOT NULL,
|
|
493
|
+
content TEXT NOT NULL,
|
|
494
|
+
triggers_json TEXT NOT NULL DEFAULT '[]',
|
|
495
|
+
category TEXT,
|
|
496
|
+
source_kind TEXT,
|
|
497
|
+
source_id TEXT,
|
|
498
|
+
day TEXT NOT NULL,
|
|
499
|
+
importance REAL NOT NULL DEFAULT 0.5,
|
|
500
|
+
manual_json TEXT NOT NULL DEFAULT '{}',
|
|
501
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
502
|
+
created_at TEXT NOT NULL,
|
|
503
|
+
updated_at TEXT NOT NULL,
|
|
504
|
+
UNIQUE(scope_id, source_kind, source_id)
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
CREATE INDEX IF NOT EXISTS summon_cards_scope_day ON summon_cards(scope_id, day, side);
|
|
508
|
+
CREATE INDEX IF NOT EXISTS summon_cards_scope_status ON summon_cards(scope_id, status);
|
|
509
|
+
|
|
510
|
+
-- 召唤策:按左右脑与事件类别合并的册;index 只放一行索引,正文按需读。
|
|
511
|
+
CREATE TABLE IF NOT EXISTS summon_volumes (
|
|
512
|
+
id TEXT PRIMARY KEY,
|
|
513
|
+
scope_id TEXT NOT NULL,
|
|
514
|
+
side TEXT NOT NULL CHECK(side IN ('left', 'right')),
|
|
515
|
+
title TEXT NOT NULL,
|
|
516
|
+
category TEXT,
|
|
517
|
+
index_text TEXT NOT NULL,
|
|
518
|
+
content TEXT NOT NULL,
|
|
519
|
+
card_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
520
|
+
period_start TEXT,
|
|
521
|
+
period_end TEXT,
|
|
522
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
523
|
+
created_at TEXT NOT NULL,
|
|
524
|
+
updated_at TEXT NOT NULL,
|
|
525
|
+
UNIQUE(scope_id, side, category, period_start, period_end)
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
CREATE INDEX IF NOT EXISTS summon_volumes_scope_side ON summon_volumes(scope_id, side, status);
|
|
529
|
+
CREATE INDEX IF NOT EXISTS summon_volumes_period ON summon_volumes(period_start, period_end);
|
package/src/tools/definitions.js
CHANGED
|
@@ -219,7 +219,7 @@ export function createToolDefinitions(service) {
|
|
|
219
219
|
additionalProperties: false,
|
|
220
220
|
required: ['action'],
|
|
221
221
|
properties: {
|
|
222
|
-
action: { type: 'string', enum: ['diary-write', 'diary-list', 'diary-forget', 'dream', 'sleep', 'sleep-runs'] },
|
|
222
|
+
action: { type: 'string', enum: ['diary-generate', 'diary-write', 'diary-list', 'diary-forget', 'dream', 'sleep', 'sleep-runs'] },
|
|
223
223
|
text: { type: 'string' },
|
|
224
224
|
mood: { type: 'string' },
|
|
225
225
|
id: { type: 'string' },
|
|
@@ -230,6 +230,11 @@ export function createToolDefinitions(service) {
|
|
|
230
230
|
output: TEXT_OUTPUT,
|
|
231
231
|
isConcurrencySafe: () => false,
|
|
232
232
|
async execute(args = {}) {
|
|
233
|
+
if (args.action === 'diary-generate') {
|
|
234
|
+
if (!service.scopeWritable(service.settings().scope)) return { text: '未执行:当前 scope 只读共享。' };
|
|
235
|
+
const item = await service.life.diaryGenerate();
|
|
236
|
+
return { text: `日记 ${item.id}(${item.model},来源 ${item.sourceCount} 条):\n${item.text}` };
|
|
237
|
+
}
|
|
233
238
|
if (args.action === 'diary-write') {
|
|
234
239
|
if (!service.scopeWritable(service.settings().scope)) return { text: '未执行:当前 scope 只读共享。' };
|
|
235
240
|
const item = service.life.diaryWrite({ text: args.text, mood: args.mood });
|
|
@@ -259,13 +264,13 @@ export function createToolDefinitions(service) {
|
|
|
259
264
|
},
|
|
260
265
|
{
|
|
261
266
|
name: 'cortex_emotion',
|
|
262
|
-
description: '
|
|
267
|
+
description: 'Generate an AI-authored feeling, detect emotions in text, record an emotion event for a target, show affect attitudes, or list the 24-category lexicon.',
|
|
263
268
|
parameters: {
|
|
264
269
|
type: 'object',
|
|
265
270
|
additionalProperties: false,
|
|
266
271
|
required: ['action'],
|
|
267
272
|
properties: {
|
|
268
|
-
action: { type: 'string', enum: ['detect', 'record', 'affect', 'events', 'lexicon'] },
|
|
273
|
+
action: { type: 'string', enum: ['generate', 'detect', 'record', 'affect', 'events', 'lexicon'] },
|
|
269
274
|
text: { type: 'string' },
|
|
270
275
|
target: { type: 'string' },
|
|
271
276
|
traceId: { type: 'string' },
|
|
@@ -275,6 +280,11 @@ export function createToolDefinitions(service) {
|
|
|
275
280
|
output: TEXT_OUTPUT,
|
|
276
281
|
isConcurrencySafe: () => false,
|
|
277
282
|
async execute(args = {}) {
|
|
283
|
+
if (args.action === 'generate') {
|
|
284
|
+
if (!service.scopeWritable(service.settings().scope)) return { text: '未执行:当前 scope 只读共享。' };
|
|
285
|
+
const result = await service.life.feelingGenerate({ source: 'cortex:manual:feeling' });
|
|
286
|
+
return { text: `感受(${result.model},来源 ${result.sourceCount} 条):${result.text}\n情绪:${result.emotions.map((item) => item.name).join('、')} → ${result.target}` };
|
|
287
|
+
}
|
|
278
288
|
if (args.action === 'detect') {
|
|
279
289
|
const result = service.emotion.detect(args.text);
|
|
280
290
|
return { text: result.emotions.length ? result.emotions.map((item) => `${item.name}:${item.score}`).join(' / ') : '没有命中情绪词。' };
|