metame-cli 1.4.34 → 1.5.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/README.md +146 -32
- package/index.js +148 -9
- package/package.json +6 -3
- package/scripts/daemon-admin-commands.js +254 -9
- package/scripts/daemon-agent-commands.js +64 -6
- package/scripts/daemon-agent-tools.js +26 -5
- package/scripts/daemon-bridges.js +110 -20
- package/scripts/daemon-claude-engine.js +698 -239
- package/scripts/daemon-command-router.js +24 -8
- package/scripts/daemon-default.yaml +28 -4
- package/scripts/daemon-engine-runtime.js +275 -0
- package/scripts/daemon-exec-commands.js +10 -4
- package/scripts/daemon-notify.js +37 -1
- package/scripts/daemon-runtime-lifecycle.js +2 -1
- package/scripts/daemon-session-commands.js +52 -4
- package/scripts/daemon-session-store.js +2 -1
- package/scripts/daemon-task-scheduler.js +68 -38
- package/scripts/daemon-user-acl.js +26 -9
- package/scripts/daemon.js +81 -17
- package/scripts/distill.js +323 -18
- package/scripts/docs/agent-guide.md +12 -0
- package/scripts/docs/maintenance-manual.md +119 -0
- package/scripts/docs/pointer-map.md +88 -0
- package/scripts/feishu-adapter.js +6 -1
- package/scripts/hooks/stop-session-capture.js +243 -0
- package/scripts/memory-extract.js +100 -5
- package/scripts/memory-nightly-reflect.js +196 -11
- package/scripts/memory.js +134 -3
- package/scripts/mentor-engine.js +405 -0
- package/scripts/platform.js +2 -0
- package/scripts/providers.js +169 -21
- package/scripts/schema.js +12 -0
- package/scripts/session-analytics.js +245 -12
- package/scripts/skill-changelog.js +245 -0
- package/scripts/skill-evolution.js +288 -5
- package/scripts/usage-classifier.js +1 -1
- package/scripts/daemon-admin-commands.test.js +0 -333
- package/scripts/daemon-task-envelope.test.js +0 -59
- package/scripts/daemon-task-scheduler.test.js +0 -106
- package/scripts/reliability-core.test.js +0 -280
- package/scripts/skill-evolution.test.js +0 -113
- package/scripts/task-board.test.js +0 -83
- package/scripts/test_daemon.js +0 -1407
- package/scripts/utils.test.js +0 -192
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
let userAcl = null;
|
|
4
|
+
try { userAcl = require('./daemon-user-acl'); } catch { /* optional */ }
|
|
5
|
+
|
|
3
6
|
function createBridgeStarter(deps) {
|
|
4
7
|
const {
|
|
5
8
|
fs,
|
|
@@ -15,6 +18,52 @@ function createBridgeStarter(deps) {
|
|
|
15
18
|
pendingActivations, // optional — used to show smart activation hint
|
|
16
19
|
} = deps;
|
|
17
20
|
|
|
21
|
+
async function sendAclReply(bot, chatId, text) {
|
|
22
|
+
if (!text) return;
|
|
23
|
+
try {
|
|
24
|
+
if (bot.sendMarkdown) await bot.sendMarkdown(chatId, text);
|
|
25
|
+
else await bot.sendMessage(chatId, text.replace(/[*_`]/g, ''));
|
|
26
|
+
} catch { /* non-fatal */ }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeSenderId(senderId) {
|
|
30
|
+
if (senderId === undefined || senderId === null) return null;
|
|
31
|
+
const text = String(senderId).trim();
|
|
32
|
+
return text || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function applyUserAcl({ bot, chatId, text, config, senderId, bypassAcl }) {
|
|
36
|
+
const trimmed = String(text || '').trim();
|
|
37
|
+
const normalizedSenderId = normalizeSenderId(senderId);
|
|
38
|
+
if (!trimmed || bypassAcl || !userAcl) {
|
|
39
|
+
return { blocked: false, readOnly: false, senderId: normalizedSenderId };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let userCtx;
|
|
43
|
+
try {
|
|
44
|
+
userCtx = userAcl.resolveUserCtx(normalizedSenderId, config || {});
|
|
45
|
+
} catch {
|
|
46
|
+
return { blocked: false, readOnly: false, senderId: normalizedSenderId };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const userCmd = userAcl.handleUserCommand(trimmed, userCtx);
|
|
50
|
+
if (userCmd && userCmd.handled) {
|
|
51
|
+
await sendAclReply(bot, chatId, userCmd.reply);
|
|
52
|
+
return { blocked: true, readOnly: !!userCtx.readOnly, senderId: normalizedSenderId };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const publicCmds = Array.isArray(userAcl.PUBLIC_COMMANDS) ? userAcl.PUBLIC_COMMANDS : [];
|
|
56
|
+
const isPublic = publicCmds.includes(trimmed.toLowerCase());
|
|
57
|
+
const action = userAcl.classifyCommandAction(trimmed);
|
|
58
|
+
const allowed = isPublic || (typeof userCtx.can === 'function' && userCtx.can(action));
|
|
59
|
+
if (!allowed) {
|
|
60
|
+
await sendAclReply(bot, chatId, `⚠️ 当前权限不足(角色: ${userCtx.role})\n命令类型: ${action}\n请联系管理员授权。`);
|
|
61
|
+
return { blocked: true, readOnly: true, senderId: normalizedSenderId };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { blocked: false, readOnly: !!userCtx.readOnly, senderId: normalizedSenderId };
|
|
65
|
+
}
|
|
66
|
+
|
|
18
67
|
// Returns the best pending activation for a given chatId (excludes self-created)
|
|
19
68
|
function getPendingActivationForChat(chatId) {
|
|
20
69
|
if (!pendingActivations || pendingActivations.size === 0) return null;
|
|
@@ -67,12 +116,26 @@ function createBridgeStarter(deps) {
|
|
|
67
116
|
if (update.callback_query) {
|
|
68
117
|
const cb = update.callback_query;
|
|
69
118
|
const chatId = cb.message && cb.message.chat.id;
|
|
119
|
+
const senderId = cb.from && cb.from.id ? String(cb.from.id) : null;
|
|
70
120
|
bot.answerCallback(cb.id).catch(() => { });
|
|
71
121
|
if (chatId && cb.data) {
|
|
72
122
|
const liveCfg = loadConfig();
|
|
73
123
|
const allowedIds = (liveCfg.telegram && liveCfg.telegram.allowed_chat_ids) || [];
|
|
74
124
|
if (!allowedIds.includes(chatId)) continue;
|
|
75
|
-
|
|
125
|
+
const isBindCmd = cb.data.startsWith('/agent bind')
|
|
126
|
+
|| cb.data.startsWith('/agent-bind-dir')
|
|
127
|
+
|| cb.data.startsWith('/browse bind')
|
|
128
|
+
|| cb.data === '/activate';
|
|
129
|
+
const acl = await applyUserAcl({
|
|
130
|
+
bot,
|
|
131
|
+
chatId,
|
|
132
|
+
text: cb.data,
|
|
133
|
+
config: liveCfg,
|
|
134
|
+
senderId,
|
|
135
|
+
bypassAcl: !allowedIds.includes(chatId) && !!isBindCmd,
|
|
136
|
+
});
|
|
137
|
+
if (acl.blocked) continue;
|
|
138
|
+
handleCommand(bot, chatId, cb.data, liveCfg, executeTaskByName, acl.senderId, acl.readOnly).catch(e => {
|
|
76
139
|
log('ERROR', `Telegram callback handler error: ${e.message}`);
|
|
77
140
|
});
|
|
78
141
|
}
|
|
@@ -83,6 +146,7 @@ function createBridgeStarter(deps) {
|
|
|
83
146
|
|
|
84
147
|
const msg = update.message;
|
|
85
148
|
const chatId = msg.chat.id;
|
|
149
|
+
const senderId = msg.from && msg.from.id ? String(msg.from.id) : null;
|
|
86
150
|
|
|
87
151
|
const liveCfg = loadConfig();
|
|
88
152
|
const allowedIds = (liveCfg.telegram && liveCfg.telegram.allowed_chat_ids) || [];
|
|
@@ -93,7 +157,8 @@ function createBridgeStarter(deps) {
|
|
|
93
157
|
|| trimmedText.startsWith('/browse bind')
|
|
94
158
|
|| trimmedText === '/activate'
|
|
95
159
|
);
|
|
96
|
-
|
|
160
|
+
const isAllowedChat = allowedIds.includes(chatId);
|
|
161
|
+
if (!isAllowedChat && !isBindCmd) {
|
|
97
162
|
log('WARN', `Rejected message from unauthorized chat: ${chatId}`);
|
|
98
163
|
bot.sendMessage(chatId, unauthorizedMsg(chatId)).catch(() => {});
|
|
99
164
|
continue;
|
|
@@ -108,6 +173,15 @@ function createBridgeStarter(deps) {
|
|
|
108
173
|
const fileId = msg.document ? msg.document.file_id : msg.photo[msg.photo.length - 1].file_id;
|
|
109
174
|
const fileName = msg.document ? msg.document.file_name : `photo_${Date.now()}.jpg`;
|
|
110
175
|
const caption = msg.caption || '';
|
|
176
|
+
const acl = await applyUserAcl({
|
|
177
|
+
bot,
|
|
178
|
+
chatId,
|
|
179
|
+
text: caption || '[file-upload]',
|
|
180
|
+
config: liveCfg,
|
|
181
|
+
senderId,
|
|
182
|
+
bypassAcl: !isAllowedChat && !!isBindCmd,
|
|
183
|
+
});
|
|
184
|
+
if (acl.blocked) continue;
|
|
111
185
|
|
|
112
186
|
const session = getSession(chatId);
|
|
113
187
|
const cwd = session?.cwd || HOME;
|
|
@@ -123,7 +197,7 @@ function createBridgeStarter(deps) {
|
|
|
123
197
|
? `User uploaded a file to the project: ${destPath}\nUser says: "${caption}"`
|
|
124
198
|
: `User uploaded a file to the project: ${destPath}\nAcknowledge receipt. Only read the file if the user asks you to.`;
|
|
125
199
|
|
|
126
|
-
handleCommand(bot, chatId, prompt, liveCfg, executeTaskByName).catch(e => {
|
|
200
|
+
handleCommand(bot, chatId, prompt, liveCfg, executeTaskByName, acl.senderId, acl.readOnly).catch(e => {
|
|
127
201
|
log('ERROR', `Telegram file handler error: ${e.message}`);
|
|
128
202
|
});
|
|
129
203
|
} catch (err) {
|
|
@@ -134,7 +208,17 @@ function createBridgeStarter(deps) {
|
|
|
134
208
|
}
|
|
135
209
|
|
|
136
210
|
if (msg.text) {
|
|
137
|
-
|
|
211
|
+
const text = msg.text.trim();
|
|
212
|
+
const acl = await applyUserAcl({
|
|
213
|
+
bot,
|
|
214
|
+
chatId,
|
|
215
|
+
text,
|
|
216
|
+
config: liveCfg,
|
|
217
|
+
senderId,
|
|
218
|
+
bypassAcl: !isAllowedChat && !!isBindCmd,
|
|
219
|
+
});
|
|
220
|
+
if (acl.blocked) continue;
|
|
221
|
+
handleCommand(bot, chatId, text, liveCfg, executeTaskByName, acl.senderId, acl.readOnly).catch(e => {
|
|
138
222
|
log('ERROR', `Telegram handler error: ${e.message}`);
|
|
139
223
|
});
|
|
140
224
|
}
|
|
@@ -182,27 +266,24 @@ function createBridgeStarter(deps) {
|
|
|
182
266
|
|| trimmedText.startsWith('/browse bind')
|
|
183
267
|
|| trimmedText === '/activate'
|
|
184
268
|
);
|
|
185
|
-
|
|
269
|
+
const isAllowedChat = allowedIds.includes(chatId);
|
|
270
|
+
if (!isAllowedChat && !isBindCmd) {
|
|
186
271
|
log('WARN', `Feishu: rejected message from ${chatId}`);
|
|
187
272
|
const msg = unauthorizedMsg(chatId);
|
|
188
273
|
(bot.sendMarkdown ? bot.sendMarkdown(chatId, msg) : bot.sendMessage(chatId, msg)).catch(() => {});
|
|
189
274
|
return;
|
|
190
275
|
}
|
|
191
276
|
|
|
192
|
-
const operatorIds = (liveCfg.feishu && liveCfg.feishu.operator_ids) || [];
|
|
193
|
-
if (operatorIds.length > 0 && senderId && !operatorIds.includes(senderId) && !isBindCmd) {
|
|
194
|
-
log('INFO', `Feishu: read-only message from non-operator ${senderId} in ${chatId}: ${(text || '').slice(0, 50)}`);
|
|
195
|
-
if (text && text.startsWith('/')) {
|
|
196
|
-
await (bot.sendMarkdown ? bot.sendMarkdown(chatId, '⚠️ 该操作需要授权,请联系管理员。') : bot.sendMessage(chatId, '⚠️ 该操作需要授权,请联系管理员。'));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (text) {
|
|
200
|
-
await handleCommand(bot, chatId, text, liveCfg, executeTaskByName, senderId, true);
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
277
|
if (fileInfo && fileInfo.fileKey) {
|
|
278
|
+
const acl = await applyUserAcl({
|
|
279
|
+
bot,
|
|
280
|
+
chatId,
|
|
281
|
+
text: text || '[file-upload]',
|
|
282
|
+
config: liveCfg,
|
|
283
|
+
senderId,
|
|
284
|
+
bypassAcl: !isAllowedChat && !!isBindCmd,
|
|
285
|
+
});
|
|
286
|
+
if (acl.blocked) return;
|
|
206
287
|
log('INFO', `Feishu file from ${chatId}: ${fileInfo.fileName} (key: ${fileInfo.fileKey}, msgId: ${fileInfo.messageId}, type: ${fileInfo.msgType})`);
|
|
207
288
|
const session = getSession(chatId);
|
|
208
289
|
const cwd = session?.cwd || HOME;
|
|
@@ -218,7 +299,7 @@ function createBridgeStarter(deps) {
|
|
|
218
299
|
? `User uploaded a file to the project: ${destPath}\nUser says: "${text}"`
|
|
219
300
|
: `User uploaded a file to the project: ${destPath}\nAcknowledge receipt. Only read the file if the user asks you to.`;
|
|
220
301
|
|
|
221
|
-
await handleCommand(bot, chatId, prompt, liveCfg, executeTaskByName);
|
|
302
|
+
await handleCommand(bot, chatId, prompt, liveCfg, executeTaskByName, acl.senderId, acl.readOnly);
|
|
222
303
|
} catch (err) {
|
|
223
304
|
log('ERROR', `Feishu file download failed: ${err.message}`);
|
|
224
305
|
await bot.sendMessage(chatId, `❌ Download failed: ${err.message}`);
|
|
@@ -227,6 +308,15 @@ function createBridgeStarter(deps) {
|
|
|
227
308
|
}
|
|
228
309
|
|
|
229
310
|
if (text) {
|
|
311
|
+
const acl = await applyUserAcl({
|
|
312
|
+
bot,
|
|
313
|
+
chatId,
|
|
314
|
+
text,
|
|
315
|
+
config: liveCfg,
|
|
316
|
+
senderId,
|
|
317
|
+
bypassAcl: !isAllowedChat && !!isBindCmd,
|
|
318
|
+
});
|
|
319
|
+
if (acl.blocked) return;
|
|
230
320
|
log('INFO', `Feishu message from ${chatId}: ${text.slice(0, 50)}`);
|
|
231
321
|
const parentId = event?.message?.parent_id;
|
|
232
322
|
if (parentId) {
|
|
@@ -238,7 +328,7 @@ function createBridgeStarter(deps) {
|
|
|
238
328
|
log('INFO', `Session restored via reply: ${mapped.id.slice(0, 8)} (${path.basename(mapped.cwd)})`);
|
|
239
329
|
}
|
|
240
330
|
}
|
|
241
|
-
await handleCommand(bot, chatId, text, liveCfg, executeTaskByName, senderId);
|
|
331
|
+
await handleCommand(bot, chatId, text, liveCfg, executeTaskByName, acl.senderId, acl.readOnly);
|
|
242
332
|
}
|
|
243
333
|
});
|
|
244
334
|
|