agim-cli 1.1.11 → 1.2.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/CHANGELOG.md +140 -0
- package/dist/cli.js +78 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/approval-bus.d.ts +18 -0
- package/dist/core/approval-bus.d.ts.map +1 -1
- package/dist/core/approval-bus.js +111 -0
- package/dist/core/approval-bus.js.map +1 -1
- package/dist/core/approval-router.d.ts.map +1 -1
- package/dist/core/approval-router.js +12 -0
- package/dist/core/approval-router.js.map +1 -1
- package/dist/core/audit-log.d.ts +39 -0
- package/dist/core/audit-log.d.ts.map +1 -1
- package/dist/core/audit-log.js +124 -0
- package/dist/core/audit-log.js.map +1 -1
- package/dist/core/boot-state.d.ts +17 -0
- package/dist/core/boot-state.d.ts.map +1 -0
- package/dist/core/boot-state.js +77 -0
- package/dist/core/boot-state.js.map +1 -0
- package/dist/core/job-recovery.d.ts +41 -1
- package/dist/core/job-recovery.d.ts.map +1 -1
- package/dist/core/job-recovery.js +216 -4
- package/dist/core/job-recovery.js.map +1 -1
- package/dist/core/memory-consolidate.d.ts +12 -0
- package/dist/core/memory-consolidate.d.ts.map +1 -0
- package/dist/core/memory-consolidate.js +242 -0
- package/dist/core/memory-consolidate.js.map +1 -0
- package/dist/core/memory-distill.d.ts +30 -0
- package/dist/core/memory-distill.d.ts.map +1 -0
- package/dist/core/memory-distill.js +213 -0
- package/dist/core/memory-distill.js.map +1 -0
- package/dist/core/memory-rpc.d.ts +11 -0
- package/dist/core/memory-rpc.d.ts.map +1 -0
- package/dist/core/memory-rpc.js +94 -0
- package/dist/core/memory-rpc.js.map +1 -0
- package/dist/core/memory-vector.d.ts +44 -0
- package/dist/core/memory-vector.d.ts.map +1 -0
- package/dist/core/memory-vector.js +360 -0
- package/dist/core/memory-vector.js.map +1 -0
- package/dist/core/memory.d.ts +140 -0
- package/dist/core/memory.d.ts.map +1 -0
- package/dist/core/memory.js +714 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/persona.d.ts +24 -0
- package/dist/core/persona.d.ts.map +1 -0
- package/dist/core/persona.js +80 -0
- package/dist/core/persona.js.map +1 -0
- package/dist/core/push-rpc.d.ts +26 -0
- package/dist/core/push-rpc.d.ts.map +1 -0
- package/dist/core/push-rpc.js +123 -0
- package/dist/core/push-rpc.js.map +1 -0
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +26 -1
- package/dist/core/router.js.map +1 -1
- package/dist/core/types.d.ts +41 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/plugins/agents/claude-code/index.d.ts +9 -0
- package/dist/plugins/agents/claude-code/index.d.ts.map +1 -1
- package/dist/plugins/agents/claude-code/index.js +37 -0
- package/dist/plugins/agents/claude-code/index.js.map +1 -1
- package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +8 -0
- package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -1
- package/dist/plugins/agents/claude-code/mcp-approval-server.js +181 -0
- package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -1
- package/dist/plugins/messengers/telegram/telegram-adapter.d.ts +5 -1
- package/dist/plugins/messengers/telegram/telegram-adapter.d.ts.map +1 -1
- package/dist/plugins/messengers/telegram/telegram-adapter.js +85 -0
- package/dist/plugins/messengers/telegram/telegram-adapter.js.map +1 -1
- package/dist/web/public/settings.html +106 -10
- package/dist/web/public/tasks.html +977 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +433 -6
- package/dist/web/server.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// memory-consolidate — daily(-ish) per-user job that rebuilds each
|
|
2
|
+
// persona_profile from accumulated facts.
|
|
3
|
+
//
|
|
4
|
+
// Why a separate pass: distillation writes raw facts append-only ("user
|
|
5
|
+
// likes coffee" might land 3 times in a week). Persona is the small
|
|
6
|
+
// always-injected summary; we don't want it to grow unboundedly. The
|
|
7
|
+
// consolidation prompt distils {facts} → a ≤300-token Chinese/English
|
|
8
|
+
// summary, dropping dupes and resolving contradictions.
|
|
9
|
+
//
|
|
10
|
+
// Agent choice:
|
|
11
|
+
// - Look at audit-log for the user's most recently used agent.
|
|
12
|
+
// - If none in the last 30 days, skip — user is inactive, no point.
|
|
13
|
+
// - Resolve agent via registry; skip + log if not registered (agent was
|
|
14
|
+
// removed from config).
|
|
15
|
+
//
|
|
16
|
+
// Throttling:
|
|
17
|
+
// - One LLM call per active user, run interval-spaced via setInterval at
|
|
18
|
+
// startup. Default 24h, env IMHUB_MEMORY_CONSOLIDATE_HOURS.
|
|
19
|
+
// - For agim instances with many users, this batches naturally — each
|
|
20
|
+
// run picks up where we left off.
|
|
21
|
+
import { logger as rootLogger } from './logger.js';
|
|
22
|
+
import { registry } from './registry.js';
|
|
23
|
+
import { listRecentFacts, upsertPersona, } from './memory.js';
|
|
24
|
+
import { isMemoryEnabled } from './persona.js';
|
|
25
|
+
const log = rootLogger.child({ component: 'memory-consolidate' });
|
|
26
|
+
const MAX_FACTS_PER_USER = 80; // cap input size to keep prompt cheap
|
|
27
|
+
const PERSONA_TARGET_CHARS = 600; // ~300 tokens give or take
|
|
28
|
+
const PROMPT_TIMEOUT_MS = 60_000;
|
|
29
|
+
function intervalMs() {
|
|
30
|
+
const raw = process.env.IMHUB_MEMORY_CONSOLIDATE_HOURS;
|
|
31
|
+
if (raw) {
|
|
32
|
+
const h = Number(raw);
|
|
33
|
+
if (Number.isFinite(h) && h > 0)
|
|
34
|
+
return h * 3600_000;
|
|
35
|
+
}
|
|
36
|
+
return 24 * 3600_000;
|
|
37
|
+
}
|
|
38
|
+
function isMemoryConsolidateEnabled() {
|
|
39
|
+
// Master toggle pairs with IMHUB_MEMORY_ENABLED but can be turned off
|
|
40
|
+
// separately for operators who want distillation but no consolidation
|
|
41
|
+
// (e.g. very long retention, manual persona edits).
|
|
42
|
+
if (!isMemoryEnabled())
|
|
43
|
+
return false;
|
|
44
|
+
const raw = (process.env.IMHUB_MEMORY_CONSOLIDATE_ENABLED || '1').toLowerCase();
|
|
45
|
+
return raw !== '0' && raw !== 'false' && raw !== 'no';
|
|
46
|
+
}
|
|
47
|
+
function buildConsolidationPrompt(facts) {
|
|
48
|
+
const lines = [];
|
|
49
|
+
for (const f of facts) {
|
|
50
|
+
const meta = [
|
|
51
|
+
f.category,
|
|
52
|
+
`conf=${f.confidence.toFixed(2)}`,
|
|
53
|
+
f.who ? `who=${f.who}` : '',
|
|
54
|
+
f.when_text ? `when=${f.when_text}` : '',
|
|
55
|
+
f.where_label ? `where=${f.where_label}` : '',
|
|
56
|
+
f.why ? `why=${f.why}` : '',
|
|
57
|
+
].filter(Boolean).join(' · ');
|
|
58
|
+
lines.push(`- [${meta}] ${f.what}`);
|
|
59
|
+
}
|
|
60
|
+
return [
|
|
61
|
+
`你是一个 persona 摘要编辑器。任务:把下面 ${facts.length} 条事实合并成**不超过 ${PERSONA_TARGET_CHARS} 个字符**的中文 persona 摘要,给未来的对话引用。`,
|
|
62
|
+
'',
|
|
63
|
+
'规则:',
|
|
64
|
+
`- 去重 / 合并相似事实("用户在杭州" + "用户在杭州工作" → 一句话)`,
|
|
65
|
+
'- 矛盾事实保留最新的,标"(目前)"',
|
|
66
|
+
'- 优先 category=profile / preference / goal,category=history 仅写最新',
|
|
67
|
+
'- 用清晰的小段落或要点列出,不要散文式叙述',
|
|
68
|
+
'- 严格 ≤ ' + PERSONA_TARGET_CHARS + ' 字符',
|
|
69
|
+
'- 输出**纯文本摘要,不要 markdown 围栏,不要任何解释**',
|
|
70
|
+
'',
|
|
71
|
+
'事实列表:',
|
|
72
|
+
...lines,
|
|
73
|
+
'',
|
|
74
|
+
'现在请输出 persona 摘要:',
|
|
75
|
+
].join('\n');
|
|
76
|
+
}
|
|
77
|
+
async function consolidateOneUser(userKey, agentName) {
|
|
78
|
+
const facts = listRecentFacts(userKey, MAX_FACTS_PER_USER);
|
|
79
|
+
if (facts.length === 0)
|
|
80
|
+
return false;
|
|
81
|
+
const agent = registry.getAgent(agentName);
|
|
82
|
+
if (!agent) {
|
|
83
|
+
log.warn({ event: 'memory.consolidate.agent_missing', agentName, userKey });
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const prompt = buildConsolidationPrompt(facts);
|
|
87
|
+
let raw = '';
|
|
88
|
+
try {
|
|
89
|
+
const gen = agent.sendPrompt(`memory-consolidate-${Date.now()}`, prompt, [], {
|
|
90
|
+
platform: 'memory-consolidate',
|
|
91
|
+
userId: userKey,
|
|
92
|
+
});
|
|
93
|
+
const deadline = Date.now() + PROMPT_TIMEOUT_MS;
|
|
94
|
+
for await (const chunk of gen) {
|
|
95
|
+
raw += chunk;
|
|
96
|
+
if (Date.now() > deadline)
|
|
97
|
+
break;
|
|
98
|
+
if (raw.length > PERSONA_TARGET_CHARS * 4)
|
|
99
|
+
break; // safety cap
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
log.warn({ event: 'memory.consolidate.agent_failed', userKey, agentName, err: String(err) });
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
let summary = raw.trim();
|
|
107
|
+
if (!summary)
|
|
108
|
+
return false;
|
|
109
|
+
// Strip optional code fences the model added despite instructions.
|
|
110
|
+
const fence = summary.match(/```[a-zA-Z]*\s*([\s\S]*?)```/);
|
|
111
|
+
if (fence)
|
|
112
|
+
summary = fence[1].trim();
|
|
113
|
+
if (summary.length > PERSONA_TARGET_CHARS * 2) {
|
|
114
|
+
// Hard truncate at 2x target to avoid runaway summaries.
|
|
115
|
+
summary = summary.slice(0, PERSONA_TARGET_CHARS * 2) + '…';
|
|
116
|
+
}
|
|
117
|
+
const ok = upsertPersona(userKey, summary);
|
|
118
|
+
if (ok) {
|
|
119
|
+
log.info({
|
|
120
|
+
event: 'memory.consolidate.upserted',
|
|
121
|
+
userKey, agentName, factsConsidered: facts.length, summaryLen: summary.length,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return ok;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Find each user_key that has facts + which agent they used last (so we
|
|
128
|
+
* call the right one for consolidation). Reads facts.user_key as the
|
|
129
|
+
* authoritative user list; cross-references audit-log for agent affinity.
|
|
130
|
+
*/
|
|
131
|
+
async function listActiveUsersWithAgent() {
|
|
132
|
+
const { default: Database } = await import('better-sqlite3');
|
|
133
|
+
const { MEMORY_DB_PATH } = await import('./memory.js');
|
|
134
|
+
const { AGIM_HOME } = await import('./agim-paths.js');
|
|
135
|
+
const { join } = await import('node:path');
|
|
136
|
+
const auditDbPath = join(AGIM_HOME, 'audit.db');
|
|
137
|
+
let memDb = null;
|
|
138
|
+
let auditDb = null;
|
|
139
|
+
try {
|
|
140
|
+
memDb = new Database(MEMORY_DB_PATH, { readonly: true });
|
|
141
|
+
auditDb = new Database(auditDbPath, { readonly: true });
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
log.warn({ event: 'memory.consolidate.db_open_failed', err: String(err) });
|
|
145
|
+
if (memDb)
|
|
146
|
+
memDb.close();
|
|
147
|
+
if (auditDb)
|
|
148
|
+
auditDb.close();
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const users = memDb.prepare(`
|
|
153
|
+
SELECT user_key, COUNT(*) AS n FROM facts GROUP BY user_key HAVING n > 0
|
|
154
|
+
`).all();
|
|
155
|
+
const out = [];
|
|
156
|
+
for (const u of users) {
|
|
157
|
+
// user_key shape = `${platform}:${userId}`. Same composite is used by
|
|
158
|
+
// audit-log's `platform` + `user_id` cols, so split & query.
|
|
159
|
+
const sep = u.user_key.indexOf(':');
|
|
160
|
+
if (sep < 0) {
|
|
161
|
+
out.push({ user_key: u.user_key, recentAgent: null });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const platform = u.user_key.slice(0, sep);
|
|
165
|
+
const userId = u.user_key.slice(sep + 1);
|
|
166
|
+
let recentAgent = null;
|
|
167
|
+
try {
|
|
168
|
+
const row = auditDb.prepare(`
|
|
169
|
+
SELECT agent FROM invocations
|
|
170
|
+
WHERE platform = ? AND user_id = ?
|
|
171
|
+
AND ts >= datetime('now', '-30 days')
|
|
172
|
+
AND agent != ''
|
|
173
|
+
ORDER BY ts DESC LIMIT 1
|
|
174
|
+
`).get(platform, userId);
|
|
175
|
+
if (row?.agent)
|
|
176
|
+
recentAgent = row.agent;
|
|
177
|
+
}
|
|
178
|
+
catch { /* fallthrough */ }
|
|
179
|
+
out.push({ user_key: u.user_key, recentAgent });
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
memDb.close();
|
|
185
|
+
auditDb.close();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* One pass: consolidate every active user. Returns { users, updated }.
|
|
190
|
+
* Safe to call manually (e.g. from a CLI / /memory consolidate command).
|
|
191
|
+
*/
|
|
192
|
+
export async function runConsolidationOnce() {
|
|
193
|
+
if (!isMemoryConsolidateEnabled())
|
|
194
|
+
return { users: 0, updated: 0 };
|
|
195
|
+
const snapshots = await listActiveUsersWithAgent();
|
|
196
|
+
let updated = 0;
|
|
197
|
+
for (const s of snapshots) {
|
|
198
|
+
if (!s.recentAgent) {
|
|
199
|
+
log.debug({ event: 'memory.consolidate.skip_inactive', userKey: s.user_key });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const ok = await consolidateOneUser(s.user_key, s.recentAgent);
|
|
204
|
+
if (ok)
|
|
205
|
+
updated++;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
log.warn({ event: 'memory.consolidate.user_failed', userKey: s.user_key, err: String(err) });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
log.info({ event: 'memory.consolidate.pass_done', users: snapshots.length, updated });
|
|
212
|
+
return { users: snapshots.length, updated };
|
|
213
|
+
}
|
|
214
|
+
// Module-level interval handle for stopMemoryConsolidator().
|
|
215
|
+
let timer = null;
|
|
216
|
+
/** Start the periodic consolidator. Idempotent; safe to call once at boot. */
|
|
217
|
+
export function startMemoryConsolidator() {
|
|
218
|
+
if (timer)
|
|
219
|
+
return;
|
|
220
|
+
const period = intervalMs();
|
|
221
|
+
log.info({ event: 'memory.consolidate.start', periodHours: period / 3600_000 });
|
|
222
|
+
// Don't run immediately at boot — wait one period so we don't compete
|
|
223
|
+
// with the user's first inbound message. Tests can override by calling
|
|
224
|
+
// runConsolidationOnce directly.
|
|
225
|
+
timer = setInterval(() => {
|
|
226
|
+
if (!isMemoryConsolidateEnabled())
|
|
227
|
+
return;
|
|
228
|
+
void runConsolidationOnce().catch((err) => {
|
|
229
|
+
log.warn({ event: 'memory.consolidate.tick_failed', err: String(err) });
|
|
230
|
+
});
|
|
231
|
+
}, period);
|
|
232
|
+
if (typeof timer === 'object' && timer && 'unref' in timer) {
|
|
233
|
+
timer.unref();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export function stopMemoryConsolidator() {
|
|
237
|
+
if (timer) {
|
|
238
|
+
clearInterval(timer);
|
|
239
|
+
timer = null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=memory-consolidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-consolidate.js","sourceRoot":"","sources":["../../src/core/memory-consolidate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AgentAdapter } from './types.js';
|
|
2
|
+
export interface DistillationInput {
|
|
3
|
+
/** The IM platform (telegram / wechat / ...). */
|
|
4
|
+
platform: string;
|
|
5
|
+
/** The IM user id (the human). Used as the memory partition key. */
|
|
6
|
+
userId: string;
|
|
7
|
+
/** The user's prompt that triggered the agent. */
|
|
8
|
+
userMessage: string;
|
|
9
|
+
/** The agent's reply text. */
|
|
10
|
+
agentReply: string;
|
|
11
|
+
/** The agent that produced the reply. Used for the extraction call. */
|
|
12
|
+
agent: AgentAdapter;
|
|
13
|
+
/** Optional: tracing id linking the extraction back to its source turn. */
|
|
14
|
+
traceId?: string;
|
|
15
|
+
/** Optional: only run distillation when the reply or message reaches a
|
|
16
|
+
* minimum length. Skips trivial exchanges like "ok" / "thanks". */
|
|
17
|
+
minLen?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Run extraction synchronously. Returns the number of facts saved (or 0).
|
|
21
|
+
* Errors are caught and logged; never thrown to the caller.
|
|
22
|
+
*/
|
|
23
|
+
export declare function runDistillation(input: DistillationInput): Promise<number>;
|
|
24
|
+
/**
|
|
25
|
+
* Fire-and-forget wrapper. Schedules runDistillation() on the next tick so
|
|
26
|
+
* the caller doesn't block. Returns immediately. Use this from router/cli
|
|
27
|
+
* post-reply hooks.
|
|
28
|
+
*/
|
|
29
|
+
export declare function scheduleDistillation(input: DistillationInput): void;
|
|
30
|
+
//# sourceMappingURL=memory-distill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-distill.d.ts","sourceRoot":"","sources":["../../src/core/memory-distill.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAQ9C,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAA;IACd,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAA;IACnB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,KAAK,EAAE,YAAY,CAAA;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;wEACoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAkFD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwF/E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAOnE"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// memory-distill — extracts long-term-worthy facts from a (user msg, agent
|
|
2
|
+
// reply) pair and writes them to memory.db. Runs asynchronously after the
|
|
3
|
+
// reply is delivered so the user never waits on distillation.
|
|
4
|
+
//
|
|
5
|
+
// Design choices (per the 2026-05-17 memory discussion):
|
|
6
|
+
//
|
|
7
|
+
// * Use the SAME agent that just produced the reply. agim is model-agnostic
|
|
8
|
+
// — assuming claude / haiku is always available would lock out operators
|
|
9
|
+
// who only have codex / opencode / copilot. Cost is the user's already-
|
|
10
|
+
// budgeted agent; quality matches their setup.
|
|
11
|
+
//
|
|
12
|
+
// * Single shot, JSON-out. No tool loop, no chat — extraction is a pure
|
|
13
|
+
// function. Keeps it cheap and predictable.
|
|
14
|
+
//
|
|
15
|
+
// * Fire-and-forget. router.ts calls scheduleDistillation() right after
|
|
16
|
+
// sink.deliver() and moves on. Errors are logged + dropped — they never
|
|
17
|
+
// surface to the user.
|
|
18
|
+
//
|
|
19
|
+
// * Confidence-based admission. Default new facts get confidence 0.6.
|
|
20
|
+
// Future consolidation can promote frequently-confirmed facts to 0.9+
|
|
21
|
+
// and demote unused ones; the v1.5 first cut just stores them.
|
|
22
|
+
//
|
|
23
|
+
// * Dedup at write time (TODO: v1.6) — for now we just append. If
|
|
24
|
+
// extraction surfaces "user lives in 杭州" three times in three turns,
|
|
25
|
+
// we'll have three rows. Consolidation cron will dedupe.
|
|
26
|
+
import { logger as rootLogger } from './logger.js';
|
|
27
|
+
import { saveFact, userKey } from './memory.js';
|
|
28
|
+
import { isMemoryEnabled } from './persona.js';
|
|
29
|
+
import { logInvocation } from './audit-log.js';
|
|
30
|
+
const log = rootLogger.child({ component: 'memory-distill' });
|
|
31
|
+
const DEFAULT_MIN_LEN = 20;
|
|
32
|
+
// Prompt sent to the agent for fact extraction. Returns JSON; we parse
|
|
33
|
+
// defensively (the LLM occasionally wraps in markdown fences).
|
|
34
|
+
function buildExtractionPrompt(userMessage, agentReply) {
|
|
35
|
+
return [
|
|
36
|
+
'你是一个事实抽取器。从下面这一轮对话中提取最多 5 条**值得长期记住的事实**,',
|
|
37
|
+
'输出严格的 JSON,结构如下,**不要任何额外文字 / markdown / 解释**:',
|
|
38
|
+
'',
|
|
39
|
+
'```json',
|
|
40
|
+
'{',
|
|
41
|
+
' "facts": [',
|
|
42
|
+
' {',
|
|
43
|
+
' "what": "用户偏好早 8 点喝咖啡", // 必填,1 句话,主语写 user/我/...都行',
|
|
44
|
+
' "category": "preference", // fact|preference|goal|history|profile',
|
|
45
|
+
' "confidence": 0.8, // 0-1,根据语境强弱',
|
|
46
|
+
' "who": "user", // 可选,事实涉及的主体',
|
|
47
|
+
' "when_text": "", // 可选',
|
|
48
|
+
' "where_label": "", // 可选',
|
|
49
|
+
' "why": "" // 可选',
|
|
50
|
+
' }',
|
|
51
|
+
' ]',
|
|
52
|
+
'}',
|
|
53
|
+
'```',
|
|
54
|
+
'',
|
|
55
|
+
'严格执行:',
|
|
56
|
+
'- **跳过寒暄 / 测试消息 / 临时性信息**(如"在不在"、"明天再说")',
|
|
57
|
+
'- 不要总结整轮对话;只挑可在未来对话里**复用**的事实',
|
|
58
|
+
'- 用户没说的事**不要编造**(confidence 必须基于实际证据)',
|
|
59
|
+
'- 如果这轮没有值得记的事实,**返回 `{"facts": []}`**',
|
|
60
|
+
'- 输出**纯 JSON,不要 markdown 围栏**',
|
|
61
|
+
'',
|
|
62
|
+
'## 用户消息',
|
|
63
|
+
userMessage.slice(0, 4000),
|
|
64
|
+
'',
|
|
65
|
+
'## Agent 回复',
|
|
66
|
+
agentReply.slice(0, 4000),
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
function tryParseFactsJson(raw) {
|
|
70
|
+
// Strip optional ```json fences and leading/trailing prose.
|
|
71
|
+
let s = raw.trim();
|
|
72
|
+
const fenceMatch = s.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
73
|
+
if (fenceMatch)
|
|
74
|
+
s = fenceMatch[1].trim();
|
|
75
|
+
// Trim everything before first `{` and after last `}`.
|
|
76
|
+
const start = s.indexOf('{');
|
|
77
|
+
const end = s.lastIndexOf('}');
|
|
78
|
+
if (start < 0 || end < 0)
|
|
79
|
+
return [];
|
|
80
|
+
s = s.slice(start, end + 1);
|
|
81
|
+
try {
|
|
82
|
+
const obj = JSON.parse(s);
|
|
83
|
+
return Array.isArray(obj.facts) ? obj.facts : [];
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function normalizeCategory(c) {
|
|
90
|
+
const v = typeof c === 'string' ? c.toLowerCase() : '';
|
|
91
|
+
if (v === 'preference' || v === 'goal' || v === 'history' || v === 'profile')
|
|
92
|
+
return v;
|
|
93
|
+
return 'fact';
|
|
94
|
+
}
|
|
95
|
+
function clampConfidence(c) {
|
|
96
|
+
const n = typeof c === 'number' ? c : Number(c);
|
|
97
|
+
if (!Number.isFinite(n))
|
|
98
|
+
return 0.6;
|
|
99
|
+
return Math.max(0, Math.min(1, n));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Run extraction synchronously. Returns the number of facts saved (or 0).
|
|
103
|
+
* Errors are caught and logged; never thrown to the caller.
|
|
104
|
+
*/
|
|
105
|
+
export async function runDistillation(input) {
|
|
106
|
+
if (!isMemoryEnabled())
|
|
107
|
+
return 0;
|
|
108
|
+
if (!input.platform || !input.userId)
|
|
109
|
+
return 0;
|
|
110
|
+
const min = input.minLen ?? DEFAULT_MIN_LEN;
|
|
111
|
+
if (input.userMessage.trim().length < min && input.agentReply.trim().length < min) {
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
const key = userKey(input.platform, input.userId);
|
|
115
|
+
const source = input.traceId ? `trace:${input.traceId}` : 'auto';
|
|
116
|
+
const traceId = input.traceId || `distill-${Date.now()}`;
|
|
117
|
+
// Single-shot extraction. We use a synthetic session id so the agent
|
|
118
|
+
// doesn't pollute the user's real conversation history.
|
|
119
|
+
const extractionPrompt = buildExtractionPrompt(input.userMessage, input.agentReply);
|
|
120
|
+
let raw = '';
|
|
121
|
+
let costAccum = 0;
|
|
122
|
+
const startedAt = Date.now();
|
|
123
|
+
let agentFailed = null;
|
|
124
|
+
try {
|
|
125
|
+
const gen = input.agent.sendPrompt(`memory-distill-${Date.now()}`, extractionPrompt, [], {
|
|
126
|
+
// platform='memory-distill' keeps distill rows separate from real IM
|
|
127
|
+
// turns in the audit view (and in Cost & Health pivots).
|
|
128
|
+
platform: 'memory-distill',
|
|
129
|
+
userId: input.userId,
|
|
130
|
+
// P1-5: account for distill cost so Cost & Health reports the true
|
|
131
|
+
// operator spend, not just user-facing turns.
|
|
132
|
+
onUsage: (delta) => {
|
|
133
|
+
if (typeof delta.costUsd === 'number' && Number.isFinite(delta.costUsd)) {
|
|
134
|
+
costAccum += delta.costUsd;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
for await (const chunk of gen) {
|
|
139
|
+
raw += chunk;
|
|
140
|
+
if (raw.length > 8000)
|
|
141
|
+
break; // safety cap; extraction output is small
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
agentFailed = err instanceof Error ? err : new Error(String(err));
|
|
146
|
+
log.warn({ event: 'memory.distill.agent_failed', err: String(err) });
|
|
147
|
+
}
|
|
148
|
+
// Audit every distill attempt — success or failure — so operators can
|
|
149
|
+
// see how often distillation runs, what it costs, and when it errors.
|
|
150
|
+
// intent='memory.distill' makes it filterable in the dashboard.
|
|
151
|
+
try {
|
|
152
|
+
logInvocation({
|
|
153
|
+
traceId,
|
|
154
|
+
userId: input.userId,
|
|
155
|
+
platform: 'memory-distill',
|
|
156
|
+
agent: input.agent.name,
|
|
157
|
+
intent: 'memory.distill',
|
|
158
|
+
promptLen: extractionPrompt.length,
|
|
159
|
+
responseLen: raw.length,
|
|
160
|
+
durationMs: Date.now() - startedAt,
|
|
161
|
+
cost: costAccum,
|
|
162
|
+
success: agentFailed === null,
|
|
163
|
+
error: agentFailed?.message,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch { /* audit best-effort */ }
|
|
167
|
+
if (agentFailed)
|
|
168
|
+
return 0;
|
|
169
|
+
if (!raw.trim()) {
|
|
170
|
+
log.debug({ event: 'memory.distill.empty_output' });
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
const extracted = tryParseFactsJson(raw);
|
|
174
|
+
if (extracted.length === 0) {
|
|
175
|
+
log.debug({ event: 'memory.distill.no_facts', rawPreview: raw.slice(0, 200) });
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
let saved = 0;
|
|
179
|
+
for (const ef of extracted) {
|
|
180
|
+
if (typeof ef.what !== 'string' || !ef.what.trim())
|
|
181
|
+
continue;
|
|
182
|
+
const id = saveFact({
|
|
183
|
+
user_key: key,
|
|
184
|
+
what: ef.what.trim().slice(0, 500),
|
|
185
|
+
who: typeof ef.who === 'string' ? ef.who.slice(0, 200) : '',
|
|
186
|
+
when_text: typeof ef.when_text === 'string' ? ef.when_text.slice(0, 200) : '',
|
|
187
|
+
where_label: typeof ef.where_label === 'string' ? ef.where_label.slice(0, 200) : '',
|
|
188
|
+
why: typeof ef.why === 'string' ? ef.why.slice(0, 500) : '',
|
|
189
|
+
category: normalizeCategory(ef.category),
|
|
190
|
+
confidence: clampConfidence(ef.confidence),
|
|
191
|
+
source,
|
|
192
|
+
});
|
|
193
|
+
if (id !== null)
|
|
194
|
+
saved++;
|
|
195
|
+
}
|
|
196
|
+
log.info({ event: 'memory.distill.done', saved, attempted: extracted.length, user_key: key });
|
|
197
|
+
return saved;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Fire-and-forget wrapper. Schedules runDistillation() on the next tick so
|
|
201
|
+
* the caller doesn't block. Returns immediately. Use this from router/cli
|
|
202
|
+
* post-reply hooks.
|
|
203
|
+
*/
|
|
204
|
+
export function scheduleDistillation(input) {
|
|
205
|
+
if (!isMemoryEnabled())
|
|
206
|
+
return;
|
|
207
|
+
setImmediate(() => {
|
|
208
|
+
void runDistillation(input).catch((err) => {
|
|
209
|
+
log.warn({ event: 'memory.distill.scheduled_failed', err: String(err) });
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=memory-distill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-distill.js","sourceRoot":"","sources":["../../src/core/memory-distill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RunContext } from './approval-bus.js';
|
|
2
|
+
export type MemoryOp = 'query' | 'save' | 'list' | 'delete';
|
|
3
|
+
export type MemoryResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
result: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
error: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function handleMemoryOp(op: string, payload: Record<string, unknown>, ctx: RunContext): Promise<MemoryResult>;
|
|
11
|
+
//# sourceMappingURL=memory-rpc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-rpc.d.ts","sourceRoot":"","sources":["../../src/core/memory-rpc.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAInD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE3D,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAWhC,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,YAAY,CAAC,CAyEvB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// memory-rpc — handles memory_query / memory_save / memory_list / memory_delete
|
|
2
|
+
// invocations relayed by the approval-bus from MCP sidecars.
|
|
3
|
+
//
|
|
4
|
+
// Same wire pattern as reminder-rpc / push-rpc. Stateless: every op reads /
|
|
5
|
+
// writes via src/core/memory.ts. Context resolution (which user owns the
|
|
6
|
+
// memory) is the caller's RunContext from the approval-bus.
|
|
7
|
+
import { logger as rootLogger } from './logger.js';
|
|
8
|
+
import { saveFact, queryFacts, queryFactsHybrid, listRecentFacts, deleteFact, userKey, } from './memory.js';
|
|
9
|
+
const log = rootLogger.child({ component: 'memory-rpc' });
|
|
10
|
+
function asString(v) {
|
|
11
|
+
return typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
12
|
+
}
|
|
13
|
+
function asNumber(v) {
|
|
14
|
+
if (typeof v !== 'number')
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!Number.isFinite(v))
|
|
17
|
+
return undefined;
|
|
18
|
+
return v;
|
|
19
|
+
}
|
|
20
|
+
export async function handleMemoryOp(op, payload, ctx) {
|
|
21
|
+
if (!ctx.userId)
|
|
22
|
+
return { ok: false, error: 'memory ops require a userId in context' };
|
|
23
|
+
const key = userKey(ctx.platform, ctx.userId);
|
|
24
|
+
try {
|
|
25
|
+
if (op === 'query') {
|
|
26
|
+
const query = asString(payload.query) ?? '';
|
|
27
|
+
const category = asString(payload.category);
|
|
28
|
+
const k = asNumber(payload.k) ?? 5;
|
|
29
|
+
// v1.6 — auto hybrid (FTS5 + vector RRF) when vector backend ready,
|
|
30
|
+
// falls back to plain FTS5 otherwise. Always async because backend
|
|
31
|
+
// may take time on first embed.
|
|
32
|
+
const r = query
|
|
33
|
+
? await queryFactsHybrid({ user_key: key, query, category, k })
|
|
34
|
+
: queryFacts({ user_key: key, category, k });
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
result: {
|
|
38
|
+
matched: r.matched,
|
|
39
|
+
facts: r.facts.map((f) => ({
|
|
40
|
+
id: f.id, what: f.what, who: f.who, when_text: f.when_text,
|
|
41
|
+
where_label: f.where_label, why: f.why,
|
|
42
|
+
category: f.category, confidence: f.confidence,
|
|
43
|
+
created_at: new Date(f.created_at * 1000).toISOString(),
|
|
44
|
+
})),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (op === 'save') {
|
|
49
|
+
const what = asString(payload.what);
|
|
50
|
+
if (!what)
|
|
51
|
+
return { ok: false, error: 'what is required (non-empty string)' };
|
|
52
|
+
const id = saveFact({
|
|
53
|
+
user_key: key,
|
|
54
|
+
what,
|
|
55
|
+
who: asString(payload.who),
|
|
56
|
+
when_text: asString(payload.when_text),
|
|
57
|
+
where_label: asString(payload.where_label),
|
|
58
|
+
why: asString(payload.why),
|
|
59
|
+
category: asString(payload.category) ?? 'fact',
|
|
60
|
+
confidence: asNumber(payload.confidence),
|
|
61
|
+
source: 'manual',
|
|
62
|
+
});
|
|
63
|
+
if (id === null)
|
|
64
|
+
return { ok: false, error: 'failed to persist fact (sqlite unavailable?)' };
|
|
65
|
+
return { ok: true, result: { id } };
|
|
66
|
+
}
|
|
67
|
+
if (op === 'list') {
|
|
68
|
+
const limit = asNumber(payload.limit) ?? 50;
|
|
69
|
+
const rows = listRecentFacts(key, limit);
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
result: rows.map((f) => ({
|
|
73
|
+
id: f.id, what: f.what, who: f.who, when_text: f.when_text,
|
|
74
|
+
where_label: f.where_label, why: f.why,
|
|
75
|
+
category: f.category, confidence: f.confidence,
|
|
76
|
+
created_at: new Date(f.created_at * 1000).toISOString(),
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (op === 'delete') {
|
|
81
|
+
const id = asNumber(payload.id);
|
|
82
|
+
if (id === undefined)
|
|
83
|
+
return { ok: false, error: 'id is required (number)' };
|
|
84
|
+
const ok = deleteFact(id, key);
|
|
85
|
+
return { ok: true, result: { deleted: ok } };
|
|
86
|
+
}
|
|
87
|
+
return { ok: false, error: `unknown memory op: ${op}` };
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
log.warn({ event: 'memory.rpc.failed', op, err: String(err) });
|
|
91
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=memory-rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-rpc.js","sourceRoot":"","sources":["../../src/core/memory-rpc.ts"],"names":[],"mappings":"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"}
|