metame-cli 1.4.15 → 1.4.18
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 +9 -6
- package/index.js +12 -5
- package/package.json +2 -2
- package/scripts/check-macos-control-capabilities.sh +77 -0
- package/scripts/daemon-admin-commands.js +441 -12
- package/scripts/daemon-admin-commands.test.js +333 -0
- package/scripts/daemon-claude-engine.js +71 -22
- package/scripts/daemon-command-router.js +242 -3
- package/scripts/daemon-default.yaml +10 -3
- package/scripts/daemon-exec-commands.js +248 -13
- package/scripts/daemon-task-envelope.js +143 -0
- package/scripts/daemon-task-envelope.test.js +59 -0
- package/scripts/daemon-task-scheduler.js +216 -24
- package/scripts/daemon-task-scheduler.test.js +106 -0
- package/scripts/daemon.js +374 -26
- package/scripts/distill.js +184 -34
- package/scripts/memory-extract.js +13 -5
- package/scripts/memory.js +239 -60
- package/scripts/providers.js +1 -1
- package/scripts/reliability-core.test.js +268 -0
- package/scripts/session-analytics.js +123 -35
- package/scripts/signal-capture.js +171 -11
- package/scripts/skill-evolution.js +288 -38
- package/scripts/skill-evolution.test.js +107 -0
- package/scripts/task-board.js +398 -0
- package/scripts/task-board.test.js +83 -0
- package/scripts/usage-classifier.js +139 -0
- package/scripts/utils.js +107 -0
- package/scripts/utils.test.js +61 -1
|
@@ -13,6 +13,14 @@ const path = require('path');
|
|
|
13
13
|
const os = require('os');
|
|
14
14
|
|
|
15
15
|
const BUFFER_FILE = path.join(os.homedir(), '.metame', 'raw_signals.jsonl');
|
|
16
|
+
const OVERFLOW_FILE = path.join(os.homedir(), '.metame', 'raw_signals.overflow.jsonl');
|
|
17
|
+
const LOCK_FILE = path.join(os.homedir(), '.metame', 'raw_signals.lock');
|
|
18
|
+
const LOCK_RETRY_MAX = 80;
|
|
19
|
+
const LOCK_RETRY_MS = 8;
|
|
20
|
+
const LOCK_STALE_MS = 30 * 1000;
|
|
21
|
+
const MAX_BUFFER_LINES = 300;
|
|
22
|
+
const MAX_CAPTURE_CHARS = 1600;
|
|
23
|
+
const ABSOLUTE_MAX_CAPTURE_CHARS = 6000;
|
|
16
24
|
|
|
17
25
|
// === CONFIDENCE PATTERNS ===
|
|
18
26
|
|
|
@@ -33,6 +41,90 @@ const CORRECTION_EN = /(no,? I meant|that's not what I|you misunderstood|wrong.+
|
|
|
33
41
|
const META_ZH = /我(发现|意识到|觉得|反思|总结|复盘)|想错了|换个(思路|方向|方案)|回头(想想|看看)|之前的(方案|思路|方向).*(不行|不对|有问题)|我的(问题|毛病|习惯)是|下次(应该|要|得)/;
|
|
34
42
|
const META_EN = /(I realize|looking back|on reflection|my (mistake|problem|habit) is|let me rethink|wrong approach|next time I should)/i;
|
|
35
43
|
|
|
44
|
+
// Internal/system prompts must never enter cognition signal buffer.
|
|
45
|
+
const INTERNAL_PROMPT_PATTERNS = [
|
|
46
|
+
/You are a MetaMe cognitive profile distiller/i,
|
|
47
|
+
/You are a metacognition pattern detector/i,
|
|
48
|
+
/你是精准的知识提取引擎/,
|
|
49
|
+
/RECALLED LONG-TERM FACTS \(context only/i,
|
|
50
|
+
/\[System hints - DO NOT mention these to user:/i,
|
|
51
|
+
/\[Mac automation policy - do NOT expose this block:/i,
|
|
52
|
+
/MANDATORY FIRST ACTION: The user has not been calibrated yet/i,
|
|
53
|
+
/<\!--\s*FACTS:START\s*-->/i,
|
|
54
|
+
/<\!--\s*MEMORY:START\s*-->/i,
|
|
55
|
+
/\[Task notification\]/i,
|
|
56
|
+
/<task-notification\b/i,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
function sanitizeMetaMePrompt(text) {
|
|
60
|
+
let prompt = String(text || '');
|
|
61
|
+
if (!prompt) return '';
|
|
62
|
+
|
|
63
|
+
// Remove daemon-injected RAG blocks
|
|
64
|
+
prompt = prompt.replace(/<!--\s*FACTS:START\s*-->[\s\S]*?<!--\s*FACTS:END\s*-->/gi, ' ');
|
|
65
|
+
prompt = prompt.replace(/<!--\s*MEMORY:START\s*-->[\s\S]*?<!--\s*MEMORY:END\s*-->/gi, ' ');
|
|
66
|
+
|
|
67
|
+
// Remove daemon/system internal hint blocks
|
|
68
|
+
prompt = prompt.replace(/\[System hints - DO NOT mention these to user:[\s\S]*?\]/gi, ' ');
|
|
69
|
+
prompt = prompt.replace(/\[Mac automation policy - do NOT expose this block:[\s\S]*?\]/gi, ' ');
|
|
70
|
+
prompt = prompt.replace(/\[Task notification\][\s\S]*?(?=\n{2,}|$)/gi, ' ');
|
|
71
|
+
prompt = prompt.replace(/<task-notification\b[\s\S]*?<\/task-notification>/gi, ' ');
|
|
72
|
+
prompt = prompt.replace(/<task-notification\b[\s\S]*$/gi, ' ');
|
|
73
|
+
|
|
74
|
+
return prompt.trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isInternalPrompt(text) {
|
|
78
|
+
const prompt = String(text || '');
|
|
79
|
+
if (!prompt) return false;
|
|
80
|
+
return INTERNAL_PROMPT_PATTERNS.some((re) => re.test(prompt));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sleep(ms) {
|
|
84
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function withBufferLock(fn) {
|
|
88
|
+
const dir = path.dirname(BUFFER_FILE);
|
|
89
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
let acquired = false;
|
|
92
|
+
for (let i = 0; i < LOCK_RETRY_MAX; i++) {
|
|
93
|
+
try {
|
|
94
|
+
const fd = fs.openSync(LOCK_FILE, 'wx');
|
|
95
|
+
fs.writeSync(fd, process.pid.toString());
|
|
96
|
+
fs.closeSync(fd);
|
|
97
|
+
acquired = true;
|
|
98
|
+
break;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
if (e.code !== 'EEXIST') throw e;
|
|
101
|
+
try {
|
|
102
|
+
const age = Date.now() - fs.statSync(LOCK_FILE).mtimeMs;
|
|
103
|
+
if (age > LOCK_STALE_MS) {
|
|
104
|
+
fs.unlinkSync(LOCK_FILE);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
} catch { /* lock released by another process */ }
|
|
108
|
+
sleep(LOCK_RETRY_MS);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!acquired) return false;
|
|
113
|
+
try {
|
|
114
|
+
fn();
|
|
115
|
+
return true;
|
|
116
|
+
} finally {
|
|
117
|
+
try { fs.unlinkSync(LOCK_FILE); } catch { /* non-fatal */ }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function writeBufferAtomically(lines) {
|
|
122
|
+
const tmp = BUFFER_FILE + `.tmp.${process.pid}`;
|
|
123
|
+
const content = lines.length > 0 ? (lines.join('\n') + '\n') : '';
|
|
124
|
+
fs.writeFileSync(tmp, content, 'utf8');
|
|
125
|
+
fs.renameSync(tmp, BUFFER_FILE);
|
|
126
|
+
}
|
|
127
|
+
|
|
36
128
|
// Read JSON from stdin
|
|
37
129
|
let input = '';
|
|
38
130
|
process.stdin.setEncoding('utf8');
|
|
@@ -40,7 +132,23 @@ process.stdin.on('data', (chunk) => { input += chunk; });
|
|
|
40
132
|
process.stdin.on('end', () => {
|
|
41
133
|
try {
|
|
42
134
|
const data = JSON.parse(input);
|
|
43
|
-
|
|
135
|
+
let prompt = (data.prompt || '').trim();
|
|
136
|
+
|
|
137
|
+
// Internal Claude subprocesses (distill/memory extract/skill evolution) set this flag.
|
|
138
|
+
if (process.env.METAME_INTERNAL_PROMPT === '1') {
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Strip daemon/system injected wrappers first; keep user payload if present.
|
|
143
|
+
prompt = sanitizeMetaMePrompt(prompt);
|
|
144
|
+
if (!prompt) {
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Belt-and-suspenders: filter known internal prompt templates.
|
|
149
|
+
if (isInternalPrompt(prompt)) {
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
44
152
|
|
|
45
153
|
// === LAYER 0: Metacognitive bypass — always capture self-reflection ===
|
|
46
154
|
const isMeta = META_ZH.test(prompt) || META_EN.test(prompt);
|
|
@@ -55,6 +163,14 @@ process.stdin.on('end', () => {
|
|
|
55
163
|
process.exit(0);
|
|
56
164
|
}
|
|
57
165
|
|
|
166
|
+
// Hard cap to prevent giant prompt pastes from poisoning distill budget.
|
|
167
|
+
if (prompt.length > ABSOLUTE_MAX_CAPTURE_CHARS) {
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
if (prompt.length > MAX_CAPTURE_CHARS) {
|
|
171
|
+
prompt = prompt.slice(0, MAX_CAPTURE_CHARS);
|
|
172
|
+
}
|
|
173
|
+
|
|
58
174
|
// Skip messages that are purely code or file paths
|
|
59
175
|
if (!isMeta && /^(```|\/[\w/]+\.\w+$)/.test(prompt)) {
|
|
60
176
|
process.exit(0);
|
|
@@ -113,18 +229,62 @@ process.stdin.on('end', () => {
|
|
|
113
229
|
cwd: data.cwd || null
|
|
114
230
|
};
|
|
115
231
|
|
|
116
|
-
// Append
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.split('\n').filter(l => l.trim());
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
232
|
+
// Append/update with process-level lock to avoid concurrent read-modify-write loss.
|
|
233
|
+
const locked = withBufferLock(() => {
|
|
234
|
+
let existingLines = [];
|
|
235
|
+
try {
|
|
236
|
+
existingLines = fs.readFileSync(BUFFER_FILE, 'utf8').split('\n').filter(l => l.trim());
|
|
237
|
+
} catch {
|
|
238
|
+
// File doesn't exist yet, that's fine
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Drain overflow written during prior lock-contention periods.
|
|
242
|
+
// unlink AFTER writeBufferAtomically succeeds — crash-safe ordering.
|
|
243
|
+
let overflowDrained = false;
|
|
244
|
+
try {
|
|
245
|
+
const overflowLines = fs.readFileSync(OVERFLOW_FILE, 'utf8').split('\n').filter(Boolean);
|
|
246
|
+
if (overflowLines.length > 0) {
|
|
247
|
+
existingLines = existingLines.concat(overflowLines);
|
|
248
|
+
overflowDrained = true;
|
|
249
|
+
}
|
|
250
|
+
} catch { /* no overflow file — normal case */ }
|
|
124
251
|
|
|
125
|
-
|
|
252
|
+
// Opportunistic hygiene: remove old internal/system lines if any slipped in historically.
|
|
253
|
+
existingLines = existingLines.filter((line) => {
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(line);
|
|
256
|
+
const p = String(parsed && parsed.prompt ? parsed.prompt : '');
|
|
257
|
+
if (!p) return false;
|
|
258
|
+
if (p.length > ABSOLUTE_MAX_CAPTURE_CHARS) return false;
|
|
259
|
+
if (isInternalPrompt(p)) return false;
|
|
260
|
+
return true;
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
126
265
|
|
|
127
|
-
|
|
266
|
+
existingLines.push(JSON.stringify(entry));
|
|
267
|
+
if (existingLines.length > MAX_BUFFER_LINES) {
|
|
268
|
+
existingLines = existingLines.slice(-MAX_BUFFER_LINES);
|
|
269
|
+
}
|
|
270
|
+
writeBufferAtomically(existingLines);
|
|
271
|
+
// Unlink overflow only after main file is safely written.
|
|
272
|
+
if (overflowDrained) {
|
|
273
|
+
try { fs.unlinkSync(OVERFLOW_FILE); } catch { /* already gone */ }
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (!locked) {
|
|
278
|
+
// Last-resort fallback: write to overflow side-file so the next lock-holder
|
|
279
|
+
// can drain, clean, and cap it — never bypassing buffer rules.
|
|
280
|
+
// Guard against unbounded growth: drop entry if overflow already at cap.
|
|
281
|
+
fs.mkdirSync(path.dirname(OVERFLOW_FILE), { recursive: true });
|
|
282
|
+
try {
|
|
283
|
+
const ofLines = fs.readFileSync(OVERFLOW_FILE, 'utf8').split('\n').filter(Boolean);
|
|
284
|
+
if (ofLines.length >= MAX_BUFFER_LINES) return; // shed load
|
|
285
|
+
} catch { /* overflow file doesn't exist yet */ }
|
|
286
|
+
fs.appendFileSync(OVERFLOW_FILE, JSON.stringify(entry) + '\n', 'utf8');
|
|
287
|
+
}
|
|
128
288
|
|
|
129
289
|
} catch {
|
|
130
290
|
// Silently ignore parse errors — never block the user's workflow
|