@jungjaehoon/mama-core 1.3.3 → 1.4.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/README.md +67 -19
- package/db/migrations/001-initial-decision-graph.sql +6 -1
- package/db/migrations/002-add-error-patterns.sql +1 -1
- package/db/migrations/014-add-is-static-column.sql +5 -0
- package/db/migrations/015-add-fts5-search.sql +34 -0
- package/db/migrations/016-add-memory-v2-columns.sql +14 -0
- package/db/migrations/017-create-memory-scopes.sql +10 -0
- package/db/migrations/018-create-memory-scope-bindings.sql +15 -0
- package/db/migrations/019-create-memory-events.sql +18 -0
- package/db/migrations/020-create-audit-findings.sql +18 -0
- package/db/migrations/021-create-memory-truth.sql +20 -0
- package/db/migrations/022-create-channel-summaries.sql +9 -0
- package/db/migrations/023-create-channel-summary-state.sql +9 -0
- package/db/migrations/024-add-event-date.sql +9 -0
- package/db/migrations/025-extend-kind-check.sql +59 -0
- package/dist/config-loader.d.ts +1 -1
- package/dist/config-loader.js +3 -3
- package/dist/db-adapter/base-adapter.d.ts +2 -2
- package/dist/db-adapter/index.js +2 -8
- package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -1
- package/dist/db-adapter/node-sqlite-adapter.js +99 -45
- package/dist/db-manager.d.ts +32 -4
- package/dist/db-manager.js +133 -8
- package/dist/decision-formatter.d.ts +2 -0
- package/dist/embedding-server/mobile/session-manager.js +22 -7
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.js +2 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.js +31 -3
- package/dist/mama-api.d.ts +67 -2
- package/dist/mama-api.js +295 -80
- package/dist/memory/api.d.ts +81 -0
- package/dist/memory/api.js +972 -0
- package/dist/memory/bootstrap-builder.d.ts +10 -0
- package/dist/memory/bootstrap-builder.js +66 -0
- package/dist/memory/channel-summary-state-store.d.ts +19 -0
- package/dist/memory/channel-summary-state-store.js +223 -0
- package/dist/memory/channel-summary-store.d.ts +8 -0
- package/dist/memory/channel-summary-store.js +41 -0
- package/dist/memory/event-store.d.ts +5 -0
- package/dist/memory/event-store.js +72 -0
- package/dist/memory/evolution-engine.d.ts +22 -0
- package/dist/memory/evolution-engine.js +133 -0
- package/dist/memory/extraction-prompt.d.ts +4 -0
- package/dist/memory/extraction-prompt.js +89 -0
- package/dist/memory/finding-store.d.ts +5 -0
- package/dist/memory/finding-store.js +75 -0
- package/dist/memory/profile-builder.d.ts +3 -0
- package/dist/memory/profile-builder.js +23 -0
- package/dist/memory/scope-store.d.ts +5 -0
- package/dist/memory/scope-store.js +13 -0
- package/dist/memory/truth-store.d.ts +11 -0
- package/dist/memory/truth-store.js +102 -0
- package/dist/memory/types.d.ts +267 -0
- package/dist/memory/types.js +83 -0
- package/package.json +4 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MemoryAgentBootstrap, MemoryScopeRef } from './types.js';
|
|
2
|
+
interface BuildMemoryAgentBootstrapParams {
|
|
3
|
+
scopes: MemoryScopeRef[];
|
|
4
|
+
channelKey?: string;
|
|
5
|
+
currentGoal?: string;
|
|
6
|
+
mainAgentState?: MemoryAgentBootstrap['main_agent_state'];
|
|
7
|
+
}
|
|
8
|
+
export declare function buildMemoryAgentBootstrap(params: BuildMemoryAgentBootstrapParams): Promise<MemoryAgentBootstrap>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=bootstrap-builder.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildMemoryAgentBootstrap = buildMemoryAgentBootstrap;
|
|
4
|
+
const event_store_js_1 = require("./event-store.js");
|
|
5
|
+
const finding_store_js_1 = require("./finding-store.js");
|
|
6
|
+
const profile_builder_js_1 = require("./profile-builder.js");
|
|
7
|
+
const truth_store_js_1 = require("./truth-store.js");
|
|
8
|
+
const channel_summary_store_js_1 = require("./channel-summary-store.js");
|
|
9
|
+
async function buildMemoryAgentBootstrap(params) {
|
|
10
|
+
const [truthRows, findings, recentEvents, channelSummary] = await Promise.all([
|
|
11
|
+
(0, truth_store_js_1.queryRelevantTruth)({
|
|
12
|
+
query: '',
|
|
13
|
+
scopes: params.scopes,
|
|
14
|
+
includeHistory: true,
|
|
15
|
+
}),
|
|
16
|
+
(0, finding_store_js_1.listOpenAuditFindings)(),
|
|
17
|
+
(0, event_store_js_1.listRecentMemoryEvents)(10),
|
|
18
|
+
params.channelKey ? (0, channel_summary_store_js_1.getChannelSummary)(params.channelKey) : Promise.resolve(null),
|
|
19
|
+
]);
|
|
20
|
+
const scopedTruth = truthRows.filter((row) => row.scope_refs.some((scopeRef) => params.scopes.some((scope) => scope.kind === scopeRef.kind && scope.id === scopeRef.id)));
|
|
21
|
+
const profile = (0, profile_builder_js_1.classifyProfileEntries)(scopedTruth.map((row) => ({
|
|
22
|
+
id: row.memory_id,
|
|
23
|
+
topic: row.topic,
|
|
24
|
+
kind: 'decision',
|
|
25
|
+
summary: row.effective_summary,
|
|
26
|
+
details: row.effective_details,
|
|
27
|
+
confidence: row.trust_score,
|
|
28
|
+
status: row.truth_status === 'quarantined' ? 'stale' : row.truth_status,
|
|
29
|
+
scopes: row.scope_refs,
|
|
30
|
+
source: {
|
|
31
|
+
package: 'mama-core',
|
|
32
|
+
source_type: 'truth_projection',
|
|
33
|
+
},
|
|
34
|
+
created_at: 0,
|
|
35
|
+
updated_at: 0,
|
|
36
|
+
})));
|
|
37
|
+
return {
|
|
38
|
+
current_goal: params.currentGoal,
|
|
39
|
+
scope_context: params.scopes,
|
|
40
|
+
channel_summary_markdown: channelSummary?.summary_markdown,
|
|
41
|
+
truth_snapshot: scopedTruth.map((row) => ({
|
|
42
|
+
id: row.memory_id,
|
|
43
|
+
topic: row.topic,
|
|
44
|
+
summary: row.effective_summary,
|
|
45
|
+
trust_score: row.trust_score,
|
|
46
|
+
})),
|
|
47
|
+
open_audit_findings: findings.map((finding) => ({
|
|
48
|
+
id: finding.finding_id,
|
|
49
|
+
kind: finding.kind,
|
|
50
|
+
severity: finding.severity,
|
|
51
|
+
summary: finding.summary,
|
|
52
|
+
})),
|
|
53
|
+
recent_memory_events: recentEvents.map((event) => ({
|
|
54
|
+
id: event.event_id,
|
|
55
|
+
type: event.event_type,
|
|
56
|
+
topic: event.topic,
|
|
57
|
+
created_at: event.created_at,
|
|
58
|
+
})),
|
|
59
|
+
profile_snapshot: {
|
|
60
|
+
static: profile.static.map((entry) => ({ id: entry.id, summary: entry.summary })),
|
|
61
|
+
dynamic: profile.dynamic.map((entry) => ({ id: entry.id, summary: entry.summary })),
|
|
62
|
+
},
|
|
63
|
+
main_agent_state: params.mainAgentState,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=bootstrap-builder.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ChannelSummaryStateRecord, MemoryAuditAck, MemoryScopeRef } from './types.js';
|
|
2
|
+
export declare function getChannelSummaryState(channelKey: string): Promise<ChannelSummaryStateRecord | null>;
|
|
3
|
+
export declare function recordChannelAudit(input: {
|
|
4
|
+
channelKey: string;
|
|
5
|
+
turnId: string;
|
|
6
|
+
topic: string;
|
|
7
|
+
scopeRefs: MemoryScopeRef[];
|
|
8
|
+
ack: MemoryAuditAck;
|
|
9
|
+
savedMemories?: Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
topic: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
}>;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
eventIds: string[];
|
|
16
|
+
findingIds: string[];
|
|
17
|
+
state: ChannelSummaryStateRecord;
|
|
18
|
+
}>;
|
|
19
|
+
//# sourceMappingURL=channel-summary-state-store.d.ts.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getChannelSummaryState = getChannelSummaryState;
|
|
7
|
+
exports.recordChannelAudit = recordChannelAudit;
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
10
|
+
const channel_summary_store_js_1 = require("./channel-summary-store.js");
|
|
11
|
+
const event_store_js_1 = require("./event-store.js");
|
|
12
|
+
const finding_store_js_1 = require("./finding-store.js");
|
|
13
|
+
const MAX_ACTIVE_DECISIONS = 6;
|
|
14
|
+
const MAX_RECENT_MILESTONES = 8;
|
|
15
|
+
const MAX_RECENT_AUDIT_OUTCOMES = 6;
|
|
16
|
+
function truncate(text, maxLength) {
|
|
17
|
+
if (text.length <= maxLength) {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
return `${text.slice(0, maxLength - 1)}…`;
|
|
21
|
+
}
|
|
22
|
+
function createStateHash(state) {
|
|
23
|
+
return node_crypto_1.default.createHash('sha1').update(JSON.stringify(state)).digest('hex');
|
|
24
|
+
}
|
|
25
|
+
function deserializeState(row) {
|
|
26
|
+
const parsed = JSON.parse(String(row.state_json));
|
|
27
|
+
return {
|
|
28
|
+
channel_key: String(row.channel_key),
|
|
29
|
+
active_topic: parsed.active_topic,
|
|
30
|
+
active_decisions: parsed.active_decisions ?? [],
|
|
31
|
+
recent_milestones: parsed.recent_milestones ?? [],
|
|
32
|
+
recent_audit_outcomes: parsed.recent_audit_outcomes ?? [],
|
|
33
|
+
state_hash: String(row.state_hash),
|
|
34
|
+
updated_at: Number(row.updated_at),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createEmptyState(channelKey) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const base = {
|
|
40
|
+
active_topic: undefined,
|
|
41
|
+
active_decisions: [],
|
|
42
|
+
recent_milestones: [],
|
|
43
|
+
recent_audit_outcomes: [],
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
channel_key: channelKey,
|
|
47
|
+
...base,
|
|
48
|
+
state_hash: createStateHash(base),
|
|
49
|
+
updated_at: now,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createLegacySeedState(channelKey, legacySummary) {
|
|
53
|
+
const base = {
|
|
54
|
+
active_topic: undefined,
|
|
55
|
+
active_decisions: [],
|
|
56
|
+
recent_milestones: [
|
|
57
|
+
{
|
|
58
|
+
topic: 'legacy_summary',
|
|
59
|
+
action: 'no_op',
|
|
60
|
+
summary: legacySummary.summary_markdown.replace(/\s+/g, ' ').trim(),
|
|
61
|
+
timestamp: legacySummary.updated_at,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
recent_audit_outcomes: [],
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
channel_key: channelKey,
|
|
68
|
+
...base,
|
|
69
|
+
state_hash: createStateHash(base),
|
|
70
|
+
updated_at: legacySummary.updated_at,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function renderChannelSummaryMarkdown(state) {
|
|
74
|
+
const lines = ['## Channel Summary'];
|
|
75
|
+
if (state.active_topic) {
|
|
76
|
+
lines.push(`- Current topic: ${state.active_topic}`);
|
|
77
|
+
}
|
|
78
|
+
if (state.active_decisions.length > 0) {
|
|
79
|
+
lines.push('', '## Active Decisions');
|
|
80
|
+
for (const decision of state.active_decisions) {
|
|
81
|
+
lines.push(`- ${decision.topic}: ${truncate(decision.summary, 160)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (state.recent_milestones.length > 0) {
|
|
85
|
+
lines.push('', '## Recent Milestones');
|
|
86
|
+
for (const milestone of state.recent_milestones) {
|
|
87
|
+
lines.push(`- ${milestone.topic} (${milestone.action}): ${truncate(milestone.summary, 140)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (state.recent_audit_outcomes.length > 0) {
|
|
91
|
+
lines.push('', '## Audit Signals');
|
|
92
|
+
for (const outcome of state.recent_audit_outcomes) {
|
|
93
|
+
const note = outcome.reason ? ` — ${truncate(outcome.reason, 120)}` : '';
|
|
94
|
+
lines.push(`- ${outcome.topic}: ${outcome.status}${note}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
99
|
+
function reduceState(previous, input) {
|
|
100
|
+
const next = {
|
|
101
|
+
...previous,
|
|
102
|
+
active_decisions: previous.active_decisions.slice(),
|
|
103
|
+
recent_milestones: previous.recent_milestones.slice(),
|
|
104
|
+
recent_audit_outcomes: previous.recent_audit_outcomes.slice(),
|
|
105
|
+
updated_at: input.timestamp,
|
|
106
|
+
};
|
|
107
|
+
next.recent_audit_outcomes.unshift({
|
|
108
|
+
topic: input.topic,
|
|
109
|
+
status: input.ack.status,
|
|
110
|
+
reason: input.ack.reason,
|
|
111
|
+
timestamp: input.timestamp,
|
|
112
|
+
});
|
|
113
|
+
next.recent_audit_outcomes = next.recent_audit_outcomes.slice(0, MAX_RECENT_AUDIT_OUTCOMES);
|
|
114
|
+
if (input.ack.status === 'applied' && input.savedMemories && input.savedMemories.length > 0) {
|
|
115
|
+
next.active_topic = input.savedMemories[0]?.topic ?? input.topic;
|
|
116
|
+
for (const memory of input.savedMemories) {
|
|
117
|
+
next.active_decisions = next.active_decisions.filter((entry) => entry.memory_id !== memory.id && entry.topic !== memory.topic);
|
|
118
|
+
next.active_decisions.unshift({
|
|
119
|
+
memory_id: memory.id,
|
|
120
|
+
topic: memory.topic,
|
|
121
|
+
summary: memory.summary,
|
|
122
|
+
updated_at: input.timestamp,
|
|
123
|
+
});
|
|
124
|
+
next.recent_milestones.unshift({
|
|
125
|
+
topic: memory.topic,
|
|
126
|
+
action: input.ack.action,
|
|
127
|
+
summary: memory.summary,
|
|
128
|
+
timestamp: input.timestamp,
|
|
129
|
+
memory_id: memory.id,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
next.active_decisions = next.active_decisions.slice(0, MAX_ACTIVE_DECISIONS);
|
|
133
|
+
next.recent_milestones = next.recent_milestones.slice(0, MAX_RECENT_MILESTONES);
|
|
134
|
+
}
|
|
135
|
+
const stateHash = createStateHash({
|
|
136
|
+
active_topic: next.active_topic,
|
|
137
|
+
active_decisions: next.active_decisions,
|
|
138
|
+
recent_milestones: next.recent_milestones,
|
|
139
|
+
recent_audit_outcomes: next.recent_audit_outcomes,
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
...next,
|
|
143
|
+
state_hash: stateHash,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function getChannelSummaryState(channelKey) {
|
|
147
|
+
await (0, db_manager_js_1.initDB)();
|
|
148
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
149
|
+
const row = adapter
|
|
150
|
+
.prepare(`
|
|
151
|
+
SELECT channel_key, state_json, state_hash, updated_at
|
|
152
|
+
FROM channel_summary_state
|
|
153
|
+
WHERE channel_key = ?
|
|
154
|
+
`)
|
|
155
|
+
.get(channelKey);
|
|
156
|
+
return row ? deserializeState(row) : null;
|
|
157
|
+
}
|
|
158
|
+
async function recordChannelAudit(input) {
|
|
159
|
+
await (0, db_manager_js_1.initDB)();
|
|
160
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
161
|
+
const timestamp = Date.now();
|
|
162
|
+
const legacySummary = await (0, channel_summary_store_js_1.getChannelSummary)(input.channelKey);
|
|
163
|
+
const previous = (await getChannelSummaryState(input.channelKey)) ??
|
|
164
|
+
(legacySummary
|
|
165
|
+
? createLegacySeedState(input.channelKey, legacySummary)
|
|
166
|
+
: createEmptyState(input.channelKey));
|
|
167
|
+
const eventIds = [];
|
|
168
|
+
const findingIds = [];
|
|
169
|
+
const eventType = input.ack.status === 'failed'
|
|
170
|
+
? 'audit_failed'
|
|
171
|
+
: input.ack.action === 'no_op'
|
|
172
|
+
? 'no_op'
|
|
173
|
+
: input.ack.action;
|
|
174
|
+
eventIds.push(await (0, event_store_js_1.appendMemoryEvent)({
|
|
175
|
+
event_type: eventType,
|
|
176
|
+
actor: 'memory_agent',
|
|
177
|
+
source_turn_id: input.turnId,
|
|
178
|
+
memory_id: input.savedMemories?.[0]?.id,
|
|
179
|
+
topic: input.topic,
|
|
180
|
+
scope_refs: input.scopeRefs,
|
|
181
|
+
reason: input.ack.reason,
|
|
182
|
+
created_at: timestamp,
|
|
183
|
+
}));
|
|
184
|
+
if (input.ack.status === 'failed') {
|
|
185
|
+
findingIds.push(await (0, finding_store_js_1.createAuditFinding)({
|
|
186
|
+
kind: 'unsupported_claim',
|
|
187
|
+
severity: 'high',
|
|
188
|
+
summary: input.ack.reason ?? `memory audit failed for ${input.topic}`,
|
|
189
|
+
evidence_refs: eventIds,
|
|
190
|
+
affected_memory_ids: input.savedMemories?.map((memory) => memory.id) ?? [],
|
|
191
|
+
recommended_action: 'consult_memory',
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
const state = reduceState(previous, {
|
|
195
|
+
topic: input.topic,
|
|
196
|
+
ack: input.ack,
|
|
197
|
+
savedMemories: input.savedMemories,
|
|
198
|
+
timestamp,
|
|
199
|
+
});
|
|
200
|
+
adapter
|
|
201
|
+
.prepare(`
|
|
202
|
+
INSERT OR REPLACE INTO channel_summary_state (
|
|
203
|
+
channel_key,
|
|
204
|
+
state_json,
|
|
205
|
+
state_hash,
|
|
206
|
+
updated_at
|
|
207
|
+
)
|
|
208
|
+
VALUES (?, ?, ?, ?)
|
|
209
|
+
`)
|
|
210
|
+
.run(input.channelKey, JSON.stringify({
|
|
211
|
+
active_topic: state.active_topic,
|
|
212
|
+
active_decisions: state.active_decisions,
|
|
213
|
+
recent_milestones: state.recent_milestones,
|
|
214
|
+
recent_audit_outcomes: state.recent_audit_outcomes,
|
|
215
|
+
}), state.state_hash, timestamp);
|
|
216
|
+
await (0, channel_summary_store_js_1.upsertChannelSummary)({
|
|
217
|
+
channelKey: input.channelKey,
|
|
218
|
+
summaryMarkdown: renderChannelSummaryMarkdown(state),
|
|
219
|
+
deltaHash: state.state_hash,
|
|
220
|
+
});
|
|
221
|
+
return { eventIds, findingIds, state };
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=channel-summary-state-store.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChannelSummaryRecord } from './types.js';
|
|
2
|
+
export declare function upsertChannelSummary(input: {
|
|
3
|
+
channelKey: string;
|
|
4
|
+
summaryMarkdown: string;
|
|
5
|
+
deltaHash?: string;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function getChannelSummary(channelKey: string): Promise<ChannelSummaryRecord | null>;
|
|
8
|
+
//# sourceMappingURL=channel-summary-store.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upsertChannelSummary = upsertChannelSummary;
|
|
4
|
+
exports.getChannelSummary = getChannelSummary;
|
|
5
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
6
|
+
function toChannelSummary(row) {
|
|
7
|
+
return {
|
|
8
|
+
channel_key: String(row.channel_key),
|
|
9
|
+
summary_markdown: String(row.summary_markdown),
|
|
10
|
+
delta_hash: typeof row.delta_hash === 'string' ? row.delta_hash : undefined,
|
|
11
|
+
updated_at: Number(row.updated_at),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async function upsertChannelSummary(input) {
|
|
15
|
+
await (0, db_manager_js_1.initDB)();
|
|
16
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
17
|
+
adapter
|
|
18
|
+
.prepare(`
|
|
19
|
+
INSERT OR REPLACE INTO channel_summaries (
|
|
20
|
+
channel_key,
|
|
21
|
+
summary_markdown,
|
|
22
|
+
delta_hash,
|
|
23
|
+
updated_at
|
|
24
|
+
)
|
|
25
|
+
VALUES (?, ?, ?, ?)
|
|
26
|
+
`)
|
|
27
|
+
.run(input.channelKey, input.summaryMarkdown, input.deltaHash ?? null, Date.now());
|
|
28
|
+
}
|
|
29
|
+
async function getChannelSummary(channelKey) {
|
|
30
|
+
await (0, db_manager_js_1.initDB)();
|
|
31
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
32
|
+
const row = adapter
|
|
33
|
+
.prepare(`
|
|
34
|
+
SELECT channel_key, summary_markdown, delta_hash, updated_at
|
|
35
|
+
FROM channel_summaries
|
|
36
|
+
WHERE channel_key = ?
|
|
37
|
+
`)
|
|
38
|
+
.get(channelKey);
|
|
39
|
+
return row ? toChannelSummary(row) : null;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=channel-summary-store.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MemoryEventRecord } from './types.js';
|
|
2
|
+
export declare function appendMemoryEvent(input: Omit<MemoryEventRecord, 'event_id'>): Promise<string>;
|
|
3
|
+
export declare function listMemoryEventsForTopic(topic: string): Promise<MemoryEventRecord[]>;
|
|
4
|
+
export declare function listRecentMemoryEvents(limit?: number): Promise<MemoryEventRecord[]>;
|
|
5
|
+
//# sourceMappingURL=event-store.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.appendMemoryEvent = appendMemoryEvent;
|
|
7
|
+
exports.listMemoryEventsForTopic = listMemoryEventsForTopic;
|
|
8
|
+
exports.listRecentMemoryEvents = listRecentMemoryEvents;
|
|
9
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
11
|
+
function deserializeEvent(row) {
|
|
12
|
+
return {
|
|
13
|
+
event_id: String(row.event_id),
|
|
14
|
+
event_type: row.event_type,
|
|
15
|
+
actor: row.actor,
|
|
16
|
+
source_turn_id: typeof row.source_turn_id === 'string' && row.source_turn_id.length > 0
|
|
17
|
+
? row.source_turn_id
|
|
18
|
+
: undefined,
|
|
19
|
+
memory_id: typeof row.memory_id === 'string' && row.memory_id.length > 0 ? row.memory_id : undefined,
|
|
20
|
+
topic: typeof row.topic === 'string' && row.topic.length > 0 ? row.topic : undefined,
|
|
21
|
+
scope_refs: JSON.parse(String(row.scope_refs)),
|
|
22
|
+
evidence_refs: typeof row.evidence_refs === 'string'
|
|
23
|
+
? JSON.parse(row.evidence_refs)
|
|
24
|
+
: undefined,
|
|
25
|
+
reason: typeof row.reason === 'string' ? row.reason : undefined,
|
|
26
|
+
created_at: Number(row.created_at),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function appendMemoryEvent(input) {
|
|
30
|
+
await (0, db_manager_js_1.initDB)();
|
|
31
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
32
|
+
const eventId = `evt_${node_crypto_1.default.randomUUID().replace(/-/g, '')}`;
|
|
33
|
+
adapter
|
|
34
|
+
.prepare(`
|
|
35
|
+
INSERT INTO memory_events (
|
|
36
|
+
event_id, event_type, actor, source_turn_id, memory_id, topic,
|
|
37
|
+
scope_refs, evidence_refs, reason, created_at
|
|
38
|
+
)
|
|
39
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
40
|
+
`)
|
|
41
|
+
.run(eventId, input.event_type, input.actor, input.source_turn_id ?? null, input.memory_id ?? null, input.topic ?? null, JSON.stringify(input.scope_refs), input.evidence_refs ? JSON.stringify(input.evidence_refs) : null, input.reason ?? null, input.created_at);
|
|
42
|
+
return eventId;
|
|
43
|
+
}
|
|
44
|
+
async function listMemoryEventsForTopic(topic) {
|
|
45
|
+
await (0, db_manager_js_1.initDB)();
|
|
46
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
47
|
+
const rows = adapter
|
|
48
|
+
.prepare(`
|
|
49
|
+
SELECT event_id, event_type, actor, source_turn_id, memory_id, topic,
|
|
50
|
+
scope_refs, evidence_refs, reason, created_at
|
|
51
|
+
FROM memory_events
|
|
52
|
+
WHERE topic = ?
|
|
53
|
+
ORDER BY created_at DESC
|
|
54
|
+
`)
|
|
55
|
+
.all(topic);
|
|
56
|
+
return rows.map(deserializeEvent);
|
|
57
|
+
}
|
|
58
|
+
async function listRecentMemoryEvents(limit = 10) {
|
|
59
|
+
await (0, db_manager_js_1.initDB)();
|
|
60
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
61
|
+
const rows = adapter
|
|
62
|
+
.prepare(`
|
|
63
|
+
SELECT event_id, event_type, actor, source_turn_id, memory_id, topic,
|
|
64
|
+
scope_refs, evidence_refs, reason, created_at
|
|
65
|
+
FROM memory_events
|
|
66
|
+
ORDER BY created_at DESC
|
|
67
|
+
LIMIT ?
|
|
68
|
+
`)
|
|
69
|
+
.all(limit);
|
|
70
|
+
return rows.map(deserializeEvent);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=event-store.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MemoryEdge, MemoryRecord } from './types.js';
|
|
2
|
+
interface EvolutionInput {
|
|
3
|
+
incoming: Pick<MemoryRecord, 'topic' | 'summary' | 'kind'>;
|
|
4
|
+
existing: Array<Pick<MemoryRecord, 'id' | 'topic' | 'summary' | 'kind'> & {
|
|
5
|
+
_semanticMatch?: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
export interface EvolutionResult {
|
|
9
|
+
edges: MemoryEdge[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve how an incoming memory relates to existing memories.
|
|
13
|
+
*
|
|
14
|
+
* Supersede rules (conservative — avoid information loss):
|
|
15
|
+
* 1. Raw → Extracted: structured fact replaces raw conversation (same topic, raw kind)
|
|
16
|
+
* 2. Same fact update: same topic + high summary overlap (≥0.6) between two extracted facts
|
|
17
|
+
*
|
|
18
|
+
* Everything else → builds_on (preserves independent facts under same topic)
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveMemoryEvolution(input: EvolutionInput): EvolutionResult;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=evolution-engine.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveMemoryEvolution = resolveMemoryEvolution;
|
|
4
|
+
const STOP_WORDS = new Set([
|
|
5
|
+
'the',
|
|
6
|
+
'is',
|
|
7
|
+
'a',
|
|
8
|
+
'an',
|
|
9
|
+
'to',
|
|
10
|
+
'in',
|
|
11
|
+
'on',
|
|
12
|
+
'of',
|
|
13
|
+
'for',
|
|
14
|
+
'and',
|
|
15
|
+
'or',
|
|
16
|
+
'but',
|
|
17
|
+
'it',
|
|
18
|
+
'we',
|
|
19
|
+
'i',
|
|
20
|
+
'this',
|
|
21
|
+
'that',
|
|
22
|
+
'with',
|
|
23
|
+
'as',
|
|
24
|
+
'at',
|
|
25
|
+
'by',
|
|
26
|
+
'from',
|
|
27
|
+
'be',
|
|
28
|
+
'are',
|
|
29
|
+
'was',
|
|
30
|
+
'were',
|
|
31
|
+
'been',
|
|
32
|
+
'has',
|
|
33
|
+
'have',
|
|
34
|
+
'had',
|
|
35
|
+
'do',
|
|
36
|
+
'does',
|
|
37
|
+
'did',
|
|
38
|
+
'will',
|
|
39
|
+
'would',
|
|
40
|
+
'can',
|
|
41
|
+
'could',
|
|
42
|
+
'should',
|
|
43
|
+
'not',
|
|
44
|
+
'no',
|
|
45
|
+
'so',
|
|
46
|
+
'if',
|
|
47
|
+
'up',
|
|
48
|
+
'out',
|
|
49
|
+
'all',
|
|
50
|
+
'its',
|
|
51
|
+
'user',
|
|
52
|
+
'assistant',
|
|
53
|
+
]);
|
|
54
|
+
const MIN_TOKEN_LENGTH = 3;
|
|
55
|
+
function summaryOverlapRatio(left, right) {
|
|
56
|
+
const tokenize = (s) => new Set(s
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.split(/[^a-z0-9_]+/)
|
|
59
|
+
.filter((t) => t.length >= MIN_TOKEN_LENGTH && !STOP_WORDS.has(t)));
|
|
60
|
+
const leftTokens = tokenize(left);
|
|
61
|
+
const rightTokens = tokenize(right);
|
|
62
|
+
if (leftTokens.size === 0 && rightTokens.size === 0) {
|
|
63
|
+
return left.toLowerCase().trim() === right.toLowerCase().trim() ? 1 : 0;
|
|
64
|
+
}
|
|
65
|
+
const smaller = leftTokens.size <= rightTokens.size ? leftTokens : rightTokens;
|
|
66
|
+
const larger = leftTokens.size > rightTokens.size ? leftTokens : rightTokens;
|
|
67
|
+
let shared = 0;
|
|
68
|
+
for (const t of smaller) {
|
|
69
|
+
if (larger.has(t))
|
|
70
|
+
shared++;
|
|
71
|
+
}
|
|
72
|
+
return shared / Math.max(smaller.size, 1);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolve how an incoming memory relates to existing memories.
|
|
76
|
+
*
|
|
77
|
+
* Supersede rules (conservative — avoid information loss):
|
|
78
|
+
* 1. Raw → Extracted: structured fact replaces raw conversation (same topic, raw kind)
|
|
79
|
+
* 2. Same fact update: same topic + high summary overlap (≥0.6) between two extracted facts
|
|
80
|
+
*
|
|
81
|
+
* Everything else → builds_on (preserves independent facts under same topic)
|
|
82
|
+
*/
|
|
83
|
+
function resolveMemoryEvolution(input) {
|
|
84
|
+
const edges = [];
|
|
85
|
+
for (const existing of input.existing) {
|
|
86
|
+
if (existing.topic === input.incoming.topic) {
|
|
87
|
+
// Rule 1: Raw → Extracted supersede (ingestConversation saves raw first, then extracted)
|
|
88
|
+
// Raw records have kind='fact' but summary starts with conversation text (role prefixes)
|
|
89
|
+
const existingIsRaw = existing.kind === 'fact' && /^(user:|assistant:)/i.test(existing.summary.trim());
|
|
90
|
+
if (existingIsRaw) {
|
|
91
|
+
edges.push({
|
|
92
|
+
from_id: 'incoming',
|
|
93
|
+
to_id: existing.id,
|
|
94
|
+
type: 'supersedes',
|
|
95
|
+
reason: 'Structured extraction replaces raw conversation',
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// Rule 2: Same fact update — supersede if summaries share meaningful overlap.
|
|
100
|
+
// Threshold 0.3 balances: "Use SQLite" → "Switch to PostgreSQL" (same topic, genuine update)
|
|
101
|
+
// vs "User's cat Luna" / "User's wedding plan" (same topic, independent facts).
|
|
102
|
+
const overlap = summaryOverlapRatio(existing.summary, input.incoming.summary);
|
|
103
|
+
if (overlap >= 0.3) {
|
|
104
|
+
edges.push({
|
|
105
|
+
from_id: 'incoming',
|
|
106
|
+
to_id: existing.id,
|
|
107
|
+
type: 'supersedes',
|
|
108
|
+
reason: `Updated fact (${(overlap * 100).toFixed(0)}% overlap)`,
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Different content under same topic → independent facts, link as builds_on
|
|
113
|
+
edges.push({
|
|
114
|
+
from_id: 'incoming',
|
|
115
|
+
to_id: existing.id,
|
|
116
|
+
type: 'builds_on',
|
|
117
|
+
reason: `Related but distinct (${(overlap * 100).toFixed(0)}% overlap)`,
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// Semantic candidates (vector similarity >= 0.82) → builds_on
|
|
122
|
+
if (existing._semanticMatch) {
|
|
123
|
+
edges.push({
|
|
124
|
+
from_id: 'incoming',
|
|
125
|
+
to_id: existing.id,
|
|
126
|
+
type: 'builds_on',
|
|
127
|
+
reason: 'Semantically similar memory detected via vector search',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { edges };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=evolution-engine.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ConversationMessage, type ExtractedMemoryUnit } from './types.js';
|
|
2
|
+
export declare function buildExtractionPrompt(messages: ConversationMessage[], existingTopics?: string[]): string;
|
|
3
|
+
export declare function parseExtractionResponse(response: string): ExtractedMemoryUnit[];
|
|
4
|
+
//# sourceMappingURL=extraction-prompt.d.ts.map
|