metame-cli 1.4.12 → 1.4.13
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/index.js +205 -57
- package/package.json +2 -2
- package/scripts/daemon-admin-commands.js +365 -0
- package/scripts/daemon-agent-commands.js +467 -0
- package/scripts/daemon-agent-tools.js +256 -0
- package/scripts/daemon-bridges.js +236 -0
- package/scripts/daemon-checkpoints.js +89 -0
- package/scripts/daemon-claude-engine.js +808 -0
- package/scripts/daemon-command-router.js +395 -0
- package/scripts/daemon-default.yaml +2 -2
- package/scripts/daemon-exec-commands.js +290 -0
- package/scripts/daemon-file-browser.js +219 -0
- package/scripts/daemon-notify.js +64 -0
- package/scripts/daemon-ops-commands.js +275 -0
- package/scripts/daemon-runtime-lifecycle.js +133 -0
- package/scripts/daemon-session-commands.js +436 -0
- package/scripts/daemon-session-store.js +423 -0
- package/scripts/daemon-task-scheduler.js +539 -0
- package/scripts/daemon.js +543 -4308
- package/scripts/memory-extract.js +15 -9
- package/scripts/session-analytics.js +116 -0
- package/scripts/test_daemon.js +1407 -0
|
@@ -21,7 +21,7 @@ const HOME = os.homedir();
|
|
|
21
21
|
const LOCK_FILE = path.join(HOME, '.metame', 'memory-extract.lock');
|
|
22
22
|
|
|
23
23
|
// Atomic fact extraction prompt (local copy — distill.js no longer exports this)
|
|
24
|
-
const FACT_EXTRACTION_PROMPT =
|
|
24
|
+
const FACT_EXTRACTION_PROMPT = `你是精准的知识提取引擎。从以下会话材料中提取「值得长期记住的原子事实」。
|
|
25
25
|
|
|
26
26
|
提取类型(必须是以下之一):
|
|
27
27
|
- tech_decision(技术决策:为什么选A不选B)
|
|
@@ -53,12 +53,13 @@ const FACT_EXTRACTION_PROMPT = `你是精准的知识提取引擎。从以下会
|
|
|
53
53
|
- value长度20-200字
|
|
54
54
|
- entity用英文点号路径,value可用中文
|
|
55
55
|
- medium confidence必须有非空tags
|
|
56
|
+
- 优先引用证据里的具体锚点(文件名、命令、报错关键词);没有锚点时不要硬编
|
|
56
57
|
- 没有值得提取的事实时 facts 返回 []
|
|
57
58
|
|
|
58
59
|
只输出JSON对象,不要解释。
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
{{
|
|
61
|
+
会话材料(包含骨架 + 证据):
|
|
62
|
+
{{SESSION_INPUT}}`.trim();
|
|
62
63
|
|
|
63
64
|
const SESSION_TAGS_FILE = path.join(os.homedir(), '.metame', 'session_tags.json');
|
|
64
65
|
|
|
@@ -105,12 +106,12 @@ const VAGUE_PATTERNS = [
|
|
|
105
106
|
const ALLOWED_FLAT = new Set(['王总', 'system', 'user']);
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
|
-
* Extract atomic facts from
|
|
109
|
+
* Extract atomic facts from session skeleton + evidence via Haiku.
|
|
109
110
|
* Returns filtered fact array (may be empty).
|
|
110
111
|
*/
|
|
111
|
-
async function extractFacts(skeleton,
|
|
112
|
-
const
|
|
113
|
-
const prompt = FACT_EXTRACTION_PROMPT.replace('{{
|
|
112
|
+
async function extractFacts(skeleton, evidence, distillEnv) {
|
|
113
|
+
const sessionInput = JSON.stringify({ skeleton, evidence }, null, 2).slice(0, 4500);
|
|
114
|
+
const prompt = FACT_EXTRACTION_PROMPT.replace('{{SESSION_INPUT}}', sessionInput);
|
|
114
115
|
|
|
115
116
|
let raw;
|
|
116
117
|
try {
|
|
@@ -120,7 +121,7 @@ async function extractFacts(skeleton, sessionSummary, distillEnv) {
|
|
|
120
121
|
]);
|
|
121
122
|
} catch (e) {
|
|
122
123
|
console.log(`[memory-extract] Haiku call failed: ${e.message} | code:${e.code} killed:${e.killed} stdout:${String(e.stdout || '').slice(0, 100)} stderr:${String(e.stderr || '').slice(0, 100)}`);
|
|
123
|
-
return [];
|
|
124
|
+
return { facts: [], session_name: "未命名会话" };
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
let parsed;
|
|
@@ -219,7 +220,12 @@ async function run() {
|
|
|
219
220
|
continue;
|
|
220
221
|
}
|
|
221
222
|
|
|
222
|
-
|
|
223
|
+
let evidence = null;
|
|
224
|
+
try {
|
|
225
|
+
evidence = sessionAnalytics.extractEvidence(session.path, 3000);
|
|
226
|
+
} catch { /* non-fatal */ }
|
|
227
|
+
|
|
228
|
+
const { facts, session_name } = await extractFacts(skeleton, evidence, distillEnv);
|
|
223
229
|
|
|
224
230
|
if (facts.length > 0) {
|
|
225
231
|
const { saved, skipped, superseded } = memory.saveFacts(
|
|
@@ -233,6 +233,121 @@ function extractSkeleton(jsonlPath) {
|
|
|
233
233
|
return skeleton;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Extract compact evidence from a session JSONL for memory extraction.
|
|
238
|
+
* Returns { user_messages, tool_traces, key_results, file_anchors }.
|
|
239
|
+
*/
|
|
240
|
+
function extractEvidence(jsonlPath, budget = 3000) {
|
|
241
|
+
const content = fs.readFileSync(jsonlPath, 'utf8');
|
|
242
|
+
const lines = content.split('\n');
|
|
243
|
+
|
|
244
|
+
const totalBudget = Math.max(600, budget);
|
|
245
|
+
const userBudget = Math.floor(totalBudget / 3);
|
|
246
|
+
const toolBudget = Math.floor(totalBudget / 3);
|
|
247
|
+
const resultBudget = totalBudget - userBudget - toolBudget;
|
|
248
|
+
|
|
249
|
+
const evidence = {
|
|
250
|
+
user_messages: [],
|
|
251
|
+
tool_traces: [],
|
|
252
|
+
key_results: [],
|
|
253
|
+
file_anchors: [],
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const seen = {
|
|
257
|
+
user: new Set(),
|
|
258
|
+
tool: new Set(),
|
|
259
|
+
result: new Set(),
|
|
260
|
+
file: new Set(),
|
|
261
|
+
};
|
|
262
|
+
const used = { user: 0, tool: 0, result: 0 };
|
|
263
|
+
|
|
264
|
+
const addWithBudget = (bucket, key, text, maxChars) => {
|
|
265
|
+
if (!text || !text.trim()) return;
|
|
266
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
267
|
+
if (!normalized || seen[key].has(normalized)) return;
|
|
268
|
+
const room = maxChars - used[key];
|
|
269
|
+
if (room <= 0) return;
|
|
270
|
+
const clipped = normalized.slice(0, room);
|
|
271
|
+
if (clipped.length < 12) return;
|
|
272
|
+
bucket.push(clipped);
|
|
273
|
+
seen[key].add(normalized);
|
|
274
|
+
used[key] += clipped.length;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
for (const line of lines) {
|
|
278
|
+
if (!line.trim()) continue;
|
|
279
|
+
if (!line.includes('"type"')) continue;
|
|
280
|
+
|
|
281
|
+
let entry;
|
|
282
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
283
|
+
|
|
284
|
+
// User raw messages (exclude tool_result wrappers)
|
|
285
|
+
if (entry.type === 'user' && entry.message && entry.message.content) {
|
|
286
|
+
const msg = entry.message.content;
|
|
287
|
+
if (typeof msg === 'string') {
|
|
288
|
+
addWithBudget(evidence.user_messages, 'user', msg, userBudget);
|
|
289
|
+
} else if (Array.isArray(msg)) {
|
|
290
|
+
for (const item of msg) {
|
|
291
|
+
if (item && item.type === 'text' && item.text) {
|
|
292
|
+
addWithBudget(evidence.user_messages, 'user', item.text, userBudget);
|
|
293
|
+
} else if (item && item.type === 'tool_result' && item.is_error) {
|
|
294
|
+
const toolText = typeof item.content === 'string'
|
|
295
|
+
? item.content
|
|
296
|
+
: Array.isArray(item.content)
|
|
297
|
+
? item.content.map(c => (typeof c === 'string' ? c : c && c.text ? c.text : '')).join(' ')
|
|
298
|
+
: '';
|
|
299
|
+
addWithBudget(evidence.key_results, 'result', `tool_result error: ${toolText.slice(0, 120)}`, resultBudget);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (entry.type === 'assistant' && entry.message && Array.isArray(entry.message.content)) {
|
|
306
|
+
for (const item of entry.message.content) {
|
|
307
|
+
if (!item || item.type !== 'tool_use') continue;
|
|
308
|
+
const name = item.name || 'unknown';
|
|
309
|
+
const input = item.input || {};
|
|
310
|
+
|
|
311
|
+
if ((name === 'Write' || name === 'Edit') && typeof input.file_path === 'string') {
|
|
312
|
+
const base = path.basename(input.file_path);
|
|
313
|
+
const trace = `${name} ${base}`;
|
|
314
|
+
addWithBudget(evidence.tool_traces, 'tool', trace, toolBudget);
|
|
315
|
+
if (base && !seen.file.has(base)) {
|
|
316
|
+
evidence.file_anchors.push(base);
|
|
317
|
+
seen.file.add(base);
|
|
318
|
+
}
|
|
319
|
+
} else if (name === 'Bash' && typeof input.command === 'string') {
|
|
320
|
+
const cmd = input.command.replace(/\s+/g, ' ').trim();
|
|
321
|
+
const trace = `Bash ${cmd.slice(0, 120)}`;
|
|
322
|
+
addWithBudget(evidence.tool_traces, 'tool', trace, toolBudget);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// tool_result can appear as standalone event in some transcripts
|
|
328
|
+
if (entry.type === 'tool_result') {
|
|
329
|
+
const result = entry.message || {};
|
|
330
|
+
const isError = !!result.is_error;
|
|
331
|
+
const snippet = typeof result.content === 'string'
|
|
332
|
+
? result.content
|
|
333
|
+
: Array.isArray(result.content)
|
|
334
|
+
? result.content.map(c => (typeof c === 'string' ? c : c && c.text ? c.text : '')).join(' ')
|
|
335
|
+
: '';
|
|
336
|
+
if (isError) {
|
|
337
|
+
addWithBudget(evidence.key_results, 'result', `tool_result error: ${snippet.slice(0, 120)}`, resultBudget);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Tight caps keep payload small and predictable
|
|
343
|
+
evidence.user_messages = evidence.user_messages.slice(0, 8);
|
|
344
|
+
evidence.tool_traces = evidence.tool_traces.slice(0, 12);
|
|
345
|
+
evidence.key_results = evidence.key_results.slice(0, 6);
|
|
346
|
+
evidence.file_anchors = evidence.file_anchors.slice(0, 12);
|
|
347
|
+
|
|
348
|
+
return evidence;
|
|
349
|
+
}
|
|
350
|
+
|
|
236
351
|
/**
|
|
237
352
|
* Format skeleton as a compact one-liner for injection into the distill prompt.
|
|
238
353
|
* Target: ~60 tokens.
|
|
@@ -467,6 +582,7 @@ module.exports = {
|
|
|
467
582
|
findAllUnanalyzedSessions,
|
|
468
583
|
findAllUnextractedSessions,
|
|
469
584
|
extractSkeleton,
|
|
585
|
+
extractEvidence,
|
|
470
586
|
formatForPrompt,
|
|
471
587
|
formatGoalContext,
|
|
472
588
|
summarizeSession,
|