metame-cli 1.4.33 → 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 +187 -48
- 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 +704 -268
- 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 +87 -28
- 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
|
@@ -28,8 +28,26 @@ function createAgentCommandHandler(deps) {
|
|
|
28
28
|
attachOrCreateSession,
|
|
29
29
|
agentFlowTtlMs,
|
|
30
30
|
agentBindTtlMs,
|
|
31
|
+
getDefaultEngine = () => 'claude',
|
|
31
32
|
} = deps;
|
|
32
33
|
|
|
34
|
+
function normalizeEngineName(name) {
|
|
35
|
+
const n = String(name || '').trim().toLowerCase();
|
|
36
|
+
return n === 'codex' ? 'codex' : getDefaultEngine();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function inferEngineByCwd(cfg, cwd) {
|
|
40
|
+
if (!cfg || !cfg.projects || !cwd) return null;
|
|
41
|
+
const targetCwd = normalizeCwd(cwd);
|
|
42
|
+
for (const proj of Object.values(cfg.projects || {})) {
|
|
43
|
+
if (!proj || !proj.cwd) continue;
|
|
44
|
+
if (normalizeCwd(proj.cwd) === targetCwd) {
|
|
45
|
+
return normalizeEngineName(proj.engine);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
// Pending activations have no TTL — they persist until consumed.
|
|
34
52
|
// The creating chatId is stored to prevent self-activation.
|
|
35
53
|
|
|
@@ -127,7 +145,12 @@ function createAgentCommandHandler(deps) {
|
|
|
127
145
|
const action = res.data.isNewProject ? '绑定成功' : '重新绑定';
|
|
128
146
|
const displayCwd = String(res.data.cwd || '').replace(HOME, '~');
|
|
129
147
|
if (res.data.cwd && typeof attachOrCreateSession === 'function') {
|
|
130
|
-
attachOrCreateSession(
|
|
148
|
+
attachOrCreateSession(
|
|
149
|
+
chatId,
|
|
150
|
+
normalizeCwd(res.data.cwd),
|
|
151
|
+
p.name || agentName || res.data.projectKey || '',
|
|
152
|
+
p.engine || getDefaultEngine()
|
|
153
|
+
);
|
|
131
154
|
}
|
|
132
155
|
await bot.sendMessage(chatId, `${icon} ${p.name || agentName} ${action}\n目录: ${displayCwd}`);
|
|
133
156
|
return { ok: true, data: res.data };
|
|
@@ -140,7 +163,7 @@ function createAgentCommandHandler(deps) {
|
|
|
140
163
|
}
|
|
141
164
|
const fallbackCwd = (fallback.data && fallback.data.cwd) || agentCwd;
|
|
142
165
|
if (fallbackCwd && typeof attachOrCreateSession === 'function') {
|
|
143
|
-
attachOrCreateSession(chatId, normalizeCwd(fallbackCwd), agentName || '');
|
|
166
|
+
attachOrCreateSession(chatId, normalizeCwd(fallbackCwd), agentName || '', getDefaultEngine());
|
|
144
167
|
}
|
|
145
168
|
return {
|
|
146
169
|
ok: true,
|
|
@@ -163,9 +186,9 @@ function createAgentCommandHandler(deps) {
|
|
|
163
186
|
|
|
164
187
|
async function createAgentViaUnifiedApi(chatId, name, dir, roleDesc, opts = {}) {
|
|
165
188
|
// Default: skip binding the creating chat — let the target group activate via /activate
|
|
166
|
-
const { skipChatBinding = true } = opts;
|
|
189
|
+
const { skipChatBinding = true, engine = null } = opts;
|
|
167
190
|
if (agentTools && typeof agentTools.createNewWorkspaceAgent === 'function') {
|
|
168
|
-
const res = await agentTools.createNewWorkspaceAgent(name, dir, roleDesc, chatId, { skipChatBinding });
|
|
191
|
+
const res = await agentTools.createNewWorkspaceAgent(name, dir, roleDesc, chatId, { skipChatBinding, engine });
|
|
169
192
|
if (res.ok && skipChatBinding && res.data && res.data.projectKey) {
|
|
170
193
|
storePendingActivation(res.data.projectKey, name, res.data.cwd, chatId);
|
|
171
194
|
}
|
|
@@ -273,10 +296,14 @@ function createAgentCommandHandler(deps) {
|
|
|
273
296
|
const cwd = fullMatch.projectPath || (getSession(chatId) && getSession(chatId).cwd) || HOME;
|
|
274
297
|
|
|
275
298
|
const state2 = loadState();
|
|
299
|
+
const cfgForEngine = loadConfig();
|
|
300
|
+
const engineByTargetCwd = inferEngineByCwd(cfgForEngine, cwd);
|
|
301
|
+
const currentEngine = normalizeEngineName(state2.sessions[chatId] && state2.sessions[chatId].engine);
|
|
276
302
|
state2.sessions[chatId] = {
|
|
277
303
|
id: sessionId,
|
|
278
304
|
cwd,
|
|
279
305
|
started: true,
|
|
306
|
+
engine: engineByTargetCwd || currentEngine,
|
|
280
307
|
};
|
|
281
308
|
saveState(state2);
|
|
282
309
|
const name = fullMatch.customTitle;
|
|
@@ -485,7 +512,36 @@ function createAgentCommandHandler(deps) {
|
|
|
485
512
|
}
|
|
486
513
|
}
|
|
487
514
|
}
|
|
488
|
-
// No pending activation
|
|
515
|
+
// No pending activation — fall back to scanning daemon.yaml for unbound projects
|
|
516
|
+
const allBoundKeys = new Set(Object.values({
|
|
517
|
+
...(cfg.telegram ? cfg.telegram.chat_agent_map : {}),
|
|
518
|
+
...(cfg.feishu ? cfg.feishu.chat_agent_map : {}),
|
|
519
|
+
}));
|
|
520
|
+
const unboundProjects = Object.entries(cfg.projects || {})
|
|
521
|
+
.filter(([key, p]) => p && p.cwd && !allBoundKeys.has(key))
|
|
522
|
+
.map(([key, p]) => ({ key, name: p.name || key, cwd: p.cwd, icon: p.icon || '🤖' }));
|
|
523
|
+
|
|
524
|
+
if (unboundProjects.length === 1) {
|
|
525
|
+
// Exactly one unbound project — auto-bind using project KEY (not display name)
|
|
526
|
+
// to ensure toProjectKey() resolves to the correct existing key in daemon.yaml
|
|
527
|
+
const proj = unboundProjects[0];
|
|
528
|
+
const bindRes2 = await bindViaUnifiedApi(bot, chatId, proj.key, proj.cwd);
|
|
529
|
+
if (bindRes2.ok) pendingActivations && pendingActivations.delete(proj.key);
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (unboundProjects.length > 1) {
|
|
534
|
+
// Multiple unbound projects — show pick list using project keys
|
|
535
|
+
const lines = ['请选择要激活的 Agent:', ''];
|
|
536
|
+
for (const p of unboundProjects) {
|
|
537
|
+
lines.push(`${p.icon} ${p.name} → \`/agent bind ${p.key} ${p.cwd}\``);
|
|
538
|
+
}
|
|
539
|
+
lines.push('\n发送对应命令即可绑定此群。');
|
|
540
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Truly nothing to activate
|
|
489
545
|
await bot.sendMessage(chatId,
|
|
490
546
|
'没有待激活的 Agent。\n\n如果已创建过 Agent,直接用:\n`/agent bind <名称> <目录>`\n即可绑定,不需要重新创建。'
|
|
491
547
|
);
|
|
@@ -517,4 +573,6 @@ function createAgentCommandHandler(deps) {
|
|
|
517
573
|
return { handleAgentCommand };
|
|
518
574
|
}
|
|
519
575
|
|
|
520
|
-
module.exports = {
|
|
576
|
+
module.exports = {
|
|
577
|
+
createAgentCommandHandler,
|
|
578
|
+
};
|
|
@@ -31,13 +31,17 @@ function createAgentTools(deps) {
|
|
|
31
31
|
return (String(agentName || '').replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase() || String(chatId));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function normalizeEngine(engine) {
|
|
35
|
+
return String(engine || '').trim().toLowerCase() === 'codex' ? 'codex' : 'claude';
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
function ensureAdapterConfig(cfg, adapterKey) {
|
|
35
39
|
if (!cfg[adapterKey]) cfg[adapterKey] = {};
|
|
36
40
|
if (!cfg[adapterKey].allowed_chat_ids) cfg[adapterKey].allowed_chat_ids = [];
|
|
37
41
|
if (!cfg[adapterKey].chat_agent_map) cfg[adapterKey].chat_agent_map = {};
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
async function bindAgentToChat(chatId, agentName, workspaceDir, { force = false } = {}) {
|
|
44
|
+
async function bindAgentToChat(chatId, agentName, workspaceDir, { force = false, engine = null } = {}) {
|
|
41
45
|
try {
|
|
42
46
|
const safeName = sanitizeText(agentName, 120);
|
|
43
47
|
if (!safeName) return { ok: false, error: 'agentName is required' };
|
|
@@ -49,6 +53,7 @@ function createAgentTools(deps) {
|
|
|
49
53
|
|
|
50
54
|
const projectKey = toProjectKey(safeName, chatId);
|
|
51
55
|
let resolvedDir = resolveWorkspaceDir(workspaceDir);
|
|
56
|
+
const normalizedEngine = engine ? normalizeEngine(engine) : null;
|
|
52
57
|
|
|
53
58
|
if (!resolvedDir) {
|
|
54
59
|
const existing = cfg.projects[projectKey];
|
|
@@ -80,7 +85,12 @@ function createAgentTools(deps) {
|
|
|
80
85
|
cfg[adapterKey].chat_agent_map[String(chatId)] = projectKey;
|
|
81
86
|
const existed = !!cfg.projects[projectKey];
|
|
82
87
|
if (!existed) {
|
|
83
|
-
cfg.projects[projectKey] = {
|
|
88
|
+
cfg.projects[projectKey] = {
|
|
89
|
+
name: safeName,
|
|
90
|
+
cwd: resolvedDir,
|
|
91
|
+
nicknames: [safeName],
|
|
92
|
+
...(normalizedEngine === 'codex' ? { engine: 'codex' } : {}),
|
|
93
|
+
};
|
|
84
94
|
} else {
|
|
85
95
|
const nicknames = Array.isArray(cfg.projects[projectKey].nicknames)
|
|
86
96
|
? cfg.projects[projectKey].nicknames
|
|
@@ -91,6 +101,7 @@ function createAgentTools(deps) {
|
|
|
91
101
|
name: safeName,
|
|
92
102
|
cwd: resolvedDir,
|
|
93
103
|
nicknames,
|
|
104
|
+
...(normalizedEngine === 'codex' ? { engine: 'codex' } : {}),
|
|
94
105
|
};
|
|
95
106
|
}
|
|
96
107
|
|
|
@@ -175,8 +186,9 @@ ${safeDelta}
|
|
|
175
186
|
}
|
|
176
187
|
}
|
|
177
188
|
|
|
178
|
-
async function createNewWorkspaceAgent(agentName, workspaceDir, roleDescription, chatId, { skipChatBinding = false } = {}) {
|
|
189
|
+
async function createNewWorkspaceAgent(agentName, workspaceDir, roleDescription, chatId, { skipChatBinding = false, engine = null } = {}) {
|
|
179
190
|
let bindData;
|
|
191
|
+
const normalizedEngine = engine ? normalizeEngine(engine) : null;
|
|
180
192
|
|
|
181
193
|
if (skipChatBinding) {
|
|
182
194
|
// Create the project entry without touching chat_agent_map
|
|
@@ -192,7 +204,16 @@ ${safeDelta}
|
|
|
192
204
|
const projectKey = toProjectKey(safeName, chatId);
|
|
193
205
|
const existed = !!cfg.projects[projectKey];
|
|
194
206
|
if (!existed) {
|
|
195
|
-
cfg.projects[projectKey] = {
|
|
207
|
+
cfg.projects[projectKey] = {
|
|
208
|
+
name: safeName,
|
|
209
|
+
cwd: resolvedDir,
|
|
210
|
+
nicknames: [safeName],
|
|
211
|
+
...(normalizedEngine === 'codex' ? { engine: 'codex' } : {}),
|
|
212
|
+
};
|
|
213
|
+
writeConfigSafe(cfg);
|
|
214
|
+
backupConfig();
|
|
215
|
+
} else if (normalizedEngine === 'codex') {
|
|
216
|
+
cfg.projects[projectKey] = { ...cfg.projects[projectKey], engine: 'codex' };
|
|
196
217
|
writeConfigSafe(cfg);
|
|
197
218
|
backupConfig();
|
|
198
219
|
}
|
|
@@ -204,7 +225,7 @@ ${safeDelta}
|
|
|
204
225
|
project: cfg.projects[projectKey],
|
|
205
226
|
};
|
|
206
227
|
} else {
|
|
207
|
-
const bindResult = await bindAgentToChat(chatId, agentName, workspaceDir);
|
|
228
|
+
const bindResult = await bindAgentToChat(chatId, agentName, workspaceDir, { engine: normalizedEngine });
|
|
208
229
|
if (!bindResult.ok) return bindResult;
|
|
209
230
|
bindData = bindResult.data;
|
|
210
231
|
}
|
|
@@ -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
|
|