metame-cli 1.3.20 โ 1.3.22
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 +115 -646
- package/package.json +1 -1
- package/scripts/daemon.js +400 -86
- package/scripts/distill.js +15 -0
- package/scripts/feishu-adapter.js +24 -13
- package/scripts/skill-evolution.js +792 -0
- package/scripts/test_daemon.js +3818 -0
package/scripts/daemon.js
CHANGED
|
@@ -26,6 +26,10 @@ const PID_FILE = path.join(METAME_DIR, 'daemon.pid');
|
|
|
26
26
|
const LOG_FILE = path.join(METAME_DIR, 'daemon.log');
|
|
27
27
|
const BRAIN_FILE = path.join(HOME, '.claude_profile.yaml');
|
|
28
28
|
|
|
29
|
+
// Skill evolution module (hot path + cold path)
|
|
30
|
+
let skillEvolution = null;
|
|
31
|
+
try { skillEvolution = require('./skill-evolution'); } catch { /* graceful fallback */ }
|
|
32
|
+
|
|
29
33
|
// ---------------------------------------------------------
|
|
30
34
|
// SKILL ROUTING (keyword โ /skillname prefix, like metame-desktop)
|
|
31
35
|
// ---------------------------------------------------------
|
|
@@ -121,7 +125,7 @@ function loadConfig() {
|
|
|
121
125
|
|
|
122
126
|
function backupConfig() {
|
|
123
127
|
const bak = CONFIG_FILE + '.bak';
|
|
124
|
-
try { fs.copyFileSync(CONFIG_FILE, bak); } catch {}
|
|
128
|
+
try { fs.copyFileSync(CONFIG_FILE, bak); } catch { }
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
function restoreConfig() {
|
|
@@ -132,7 +136,7 @@ function restoreConfig() {
|
|
|
132
136
|
// Preserve security-critical fields from current config (chat IDs, agent map)
|
|
133
137
|
// so a /fix never loses manually-added channels
|
|
134
138
|
let curCfg = {};
|
|
135
|
-
try { curCfg = yaml.load(fs.readFileSync(CONFIG_FILE, 'utf8')) || {}; } catch {}
|
|
139
|
+
try { curCfg = yaml.load(fs.readFileSync(CONFIG_FILE, 'utf8')) || {}; } catch { }
|
|
136
140
|
for (const adapter of ['feishu', 'telegram']) {
|
|
137
141
|
if (curCfg[adapter] && bakCfg[adapter]) {
|
|
138
142
|
const curIds = curCfg[adapter].allowed_chat_ids || [];
|
|
@@ -559,6 +563,25 @@ function startHeartbeat(config, notifyFn) {
|
|
|
559
563
|
}
|
|
560
564
|
}
|
|
561
565
|
}
|
|
566
|
+
|
|
567
|
+
// Skill evolution: check queue and notify user of actionable items
|
|
568
|
+
if (skillEvolution) {
|
|
569
|
+
try {
|
|
570
|
+
const notifications = skillEvolution.checkEvolutionQueue();
|
|
571
|
+
for (const item of notifications) {
|
|
572
|
+
let msg = '';
|
|
573
|
+
if (item.type === 'skill_gap') {
|
|
574
|
+
msg = `๐งฌ *ๆ่ฝ็ผบๅฃๆฃๆต*\n${item.reason}`;
|
|
575
|
+
if (item.search_hint) msg += `\nๆ็ดขๅปบ่ฎฎ: \`${item.search_hint}\``;
|
|
576
|
+
} else if (item.type === 'skill_fix') {
|
|
577
|
+
msg = `๐ง *ๆ่ฝ้่ฆไฟฎๅค*\nๆ่ฝ \`${item.skill_name}\` ${item.reason}`;
|
|
578
|
+
} else if (item.type === 'user_complaint') {
|
|
579
|
+
msg = `โ ๏ธ *ๆ่ฝๅ้ฆ*\nๆ่ฝ \`${item.skill_name}\` ๆถๅฐ็จๆทๅ้ฆ\n${item.reason}`;
|
|
580
|
+
}
|
|
581
|
+
if (msg && notifyFn) notifyFn(msg);
|
|
582
|
+
}
|
|
583
|
+
} catch (e) { log('WARN', `Skill evolution queue check failed: ${e.message}`); }
|
|
584
|
+
}
|
|
562
585
|
}, checkIntervalSec * 1000);
|
|
563
586
|
|
|
564
587
|
return timer;
|
|
@@ -601,7 +624,7 @@ async function startTelegramBridge(config, executeTaskByName) {
|
|
|
601
624
|
if (update.callback_query) {
|
|
602
625
|
const cb = update.callback_query;
|
|
603
626
|
const chatId = cb.message && cb.message.chat.id;
|
|
604
|
-
bot.answerCallback(cb.id).catch(() => {});
|
|
627
|
+
bot.answerCallback(cb.id).catch(() => { });
|
|
605
628
|
if (chatId && cb.data) {
|
|
606
629
|
const allowedIds = (loadConfig().telegram && loadConfig().telegram.allowed_chat_ids) || [];
|
|
607
630
|
if (!allowedIds.includes(chatId)) continue;
|
|
@@ -619,9 +642,10 @@ async function startTelegramBridge(config, executeTaskByName) {
|
|
|
619
642
|
const chatId = msg.chat.id;
|
|
620
643
|
|
|
621
644
|
// Security: check whitelist (empty = deny all) โ read live config to support hot-reload
|
|
622
|
-
// Exception: /bind
|
|
645
|
+
// Exception: /bind and /agent bind/new are allowed from any chat so users can self-register new groups
|
|
623
646
|
const allowedIds = (loadConfig().telegram && loadConfig().telegram.allowed_chat_ids) || [];
|
|
624
|
-
const
|
|
647
|
+
const trimmedText = msg.text && msg.text.trim();
|
|
648
|
+
const isBindCmd = trimmedText && (trimmedText.startsWith('/agent bind') || trimmedText.startsWith('/agent new'));
|
|
625
649
|
if (!allowedIds.includes(chatId) && !isBindCmd) {
|
|
626
650
|
log('WARN', `Rejected message from unauthorized chat: ${chatId}`);
|
|
627
651
|
continue;
|
|
@@ -697,10 +721,10 @@ async function startTelegramBridge(config, executeTaskByName) {
|
|
|
697
721
|
}
|
|
698
722
|
|
|
699
723
|
// โโ Timing constants โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
700
|
-
const CLAUDE_COOLDOWN_MS
|
|
701
|
-
const STATUS_THROTTLE_MS
|
|
724
|
+
const CLAUDE_COOLDOWN_MS = 10000; // 10s between Claude calls per chat
|
|
725
|
+
const STATUS_THROTTLE_MS = 3000; // Min 3s between streaming status updates
|
|
702
726
|
const FALLBACK_THROTTLE_MS = 8000; // 8s between fallback status updates
|
|
703
|
-
const DEDUP_TTL_MS
|
|
727
|
+
const DEDUP_TTL_MS = 60000; // Feishu message dedup window (60s)
|
|
704
728
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
705
729
|
|
|
706
730
|
// Rate limiter for /ask and /run โ prevents rapid-fire Claude calls
|
|
@@ -795,7 +819,7 @@ async function sendDirPicker(bot, chatId, mode, title) {
|
|
|
795
819
|
* - Shows up to 12 subdirs per page with pagination
|
|
796
820
|
*/
|
|
797
821
|
async function sendBrowse(bot, chatId, mode, dirPath, title, page = 0) {
|
|
798
|
-
const cmd = mode === 'new' ? '/new' : mode === 'bind' ? '/bind-dir' : '/cd';
|
|
822
|
+
const cmd = mode === 'new' ? '/new' : mode === 'bind' ? '/bind-dir' : mode === 'agent-new' ? '/agent-dir' : '/cd';
|
|
799
823
|
const PAGE_SIZE = 10;
|
|
800
824
|
try {
|
|
801
825
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -943,6 +967,52 @@ async function sendDirListing(bot, chatId, baseDir, arg) {
|
|
|
943
967
|
}
|
|
944
968
|
}
|
|
945
969
|
|
|
970
|
+
/**
|
|
971
|
+
* ๆบ่ฝๅๅนถ Agent ่ง่ฒๆ่ฟฐๅฐ CLAUDE.md
|
|
972
|
+
* ๅฆๆ็ฎๅฝไธญๆฒกๆ CLAUDE.md๏ผ็ดๆฅๅๅปบ๏ผๅฆๅ่ฐ็จ Claude ๅๅนถใ
|
|
973
|
+
*/
|
|
974
|
+
async function mergeAgentRole(cwd, description) {
|
|
975
|
+
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
976
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
977
|
+
// ็ดๆฅๅๅปบ๏ผๆ ้่ฐ Claude
|
|
978
|
+
const content = `## Agent ่ง่ฒ\n\n${description}\n`;
|
|
979
|
+
fs.writeFileSync(claudeMdPath, content, 'utf8');
|
|
980
|
+
return { created: true };
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const existing = fs.readFileSync(claudeMdPath, 'utf8');
|
|
984
|
+
const prompt = `็ฐๆ CLAUDE.md ๅ
ๅฎน๏ผ
|
|
985
|
+
---
|
|
986
|
+
${existing}
|
|
987
|
+
---
|
|
988
|
+
|
|
989
|
+
็จๆทไธบ่ฟไธช Agent ๅฎไน็่ง่ฒๅ่่ดฃ๏ผ
|
|
990
|
+
"${description}"
|
|
991
|
+
|
|
992
|
+
่ฏทๅฐ็จๆทๆๅพๅๅนถ่ฟ CLAUDE.md๏ผ
|
|
993
|
+
1. ๆพๅฐ็ฐๆ่ง่ฒ/่่ดฃ็ธๅ
ณ็ซ ่ โ ๆดๆฐๆฟๆข
|
|
994
|
+
2. ๆฒกๆไธๅฑ็ซ ่ไฝๆ็ธๅ
ณๅ
ๅฎน โ ๅๅนถ่ฟๅป
|
|
995
|
+
3. ๅฎๅ
จๆฒกๆ็ธๅ
ณๅ
ๅฎน โ ๅจๆไปถๆ้กถ้จๆฐๅข ## Agent ่ง่ฒ section
|
|
996
|
+
4. ่พๅบๅฎๆด CLAUDE.md ๅ
ๅฎน๏ผไฟๆๅๆๅ
ถไปๅ
ๅฎนไธๅ
|
|
997
|
+
5. ไฟๆ็ฎๆด๏ผ็ฆๆญข้ๅค
|
|
998
|
+
|
|
999
|
+
็ดๆฅ่พๅบๅฎๆด CLAUDE.md ๅ
ๅฎน๏ผไธ่ฆๅ ไปปไฝ่งฃ้ๆไปฃ็ ๅๆ ่ฎฐใ`;
|
|
1000
|
+
|
|
1001
|
+
const claudeArgs = ['-p', '--output-format', 'text', '--max-turns', '1'];
|
|
1002
|
+
const { output, error } = await spawnClaudeAsync(claudeArgs, prompt, HOME, 60000);
|
|
1003
|
+
if (error || !output) {
|
|
1004
|
+
return { error: error || 'ๅๅนถๅคฑ่ดฅ' };
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
let cleanOutput = output.trim();
|
|
1008
|
+
if (cleanOutput.startsWith('```')) {
|
|
1009
|
+
cleanOutput = cleanOutput.replace(/^```[a-zA-Z]*\n/, '').replace(/\n```$/, '');
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
fs.writeFileSync(claudeMdPath, cleanOutput, 'utf8');
|
|
1013
|
+
return { merged: true };
|
|
1014
|
+
}
|
|
1015
|
+
|
|
946
1016
|
/**
|
|
947
1017
|
* Unified command handler โ shared by Telegram & Feishu
|
|
948
1018
|
*/
|
|
@@ -1009,48 +1079,11 @@ async function handleCommand(bot, chatId, text, config, executeTaskByName, sende
|
|
|
1009
1079
|
}
|
|
1010
1080
|
|
|
1011
1081
|
// --- /bind <name> [cwd]: register this chat as a dedicated agent channel ---
|
|
1012
|
-
// With cwd: /bind ๅฐ็พ ~/ โ bind immediately
|
|
1013
|
-
// Without cwd: /bind ๆๆ โ show directory picker
|
|
1014
|
-
if (text.startsWith('/bind ') || text === '/bind') {
|
|
1015
|
-
const args = text.slice(5).trim();
|
|
1016
|
-
const parts = args.split(/\s+/);
|
|
1017
|
-
const agentName = parts[0];
|
|
1018
|
-
const agentCwd = parts.slice(1).join(' ');
|
|
1019
|
-
|
|
1020
|
-
if (!agentName) {
|
|
1021
|
-
await bot.sendMessage(chatId, '็จๆณ: /bind <ๅ็งฐ> [ๅทฅไฝ็ฎๅฝ]\nไพ: /bind ๅฐ็พ ~/\nๆ: /bind ๆๆ (ๅผนๅบ็ฎๅฝ้ๆฉ)');
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
if (!agentCwd) {
|
|
1026
|
-
// No cwd given โ show directory picker
|
|
1027
|
-
pendingBinds.set(String(chatId), agentName);
|
|
1028
|
-
await sendDirPicker(bot, chatId, 'bind', `ไธบใ${agentName}ใ้ๆฉๅทฅไฝ็ฎๅฝ:`);
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
await doBindAgent(bot, chatId, agentName, agentCwd);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// --- /bind-dir <path>: called by directory picker to complete a pending bind ---
|
|
1037
|
-
if (text.startsWith('/bind-dir ')) {
|
|
1038
|
-
const dirPath = expandPath(text.slice(10).trim());
|
|
1039
|
-
const agentName = pendingBinds.get(String(chatId));
|
|
1040
|
-
if (!agentName) {
|
|
1041
|
-
await bot.sendMessage(chatId, 'โ ๆฒกๆๅพ
ๅฎๆ็ /bind๏ผ่ฏท้ๆฐๅ้ /bind <ๅ็งฐ>');
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
pendingBinds.delete(String(chatId));
|
|
1045
|
-
await doBindAgent(bot, chatId, agentName, dirPath);
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
1082
|
|
|
1049
1083
|
// --- chat_agent_map: auto-switch agent based on dedicated chatId ---
|
|
1050
1084
|
// Configure in daemon.yaml: feishu.chat_agent_map or telegram.chat_agent_map
|
|
1051
1085
|
// e.g. chat_agent_map: { "oc_xxx": "personal", "oc_yyy": "metame" }
|
|
1052
|
-
const chatAgentMap = (config.feishu
|
|
1053
|
-
(config.telegram && config.telegram.chat_agent_map) || {};
|
|
1086
|
+
const chatAgentMap = { ...(config.telegram ? config.telegram.chat_agent_map : {}), ...(config.feishu ? config.feishu.chat_agent_map : {}) };
|
|
1054
1087
|
const mappedKey = chatAgentMap[String(chatId)];
|
|
1055
1088
|
if (mappedKey && config.projects && config.projects[mappedKey]) {
|
|
1056
1089
|
const proj = config.projects[mappedKey];
|
|
@@ -1088,8 +1121,7 @@ async function handleCommand(bot, chatId, text, config, executeTaskByName, sende
|
|
|
1088
1121
|
if (!arg) {
|
|
1089
1122
|
// In a dedicated agent group, use the agent's bound cwd directly
|
|
1090
1123
|
const newCfg = loadConfig();
|
|
1091
|
-
const agentMap = (newCfg.feishu
|
|
1092
|
-
(newCfg.telegram && newCfg.telegram.chat_agent_map) || {};
|
|
1124
|
+
const agentMap = { ...(newCfg.telegram ? newCfg.telegram.chat_agent_map : {}), ...(newCfg.feishu ? newCfg.feishu.chat_agent_map : {}) };
|
|
1093
1125
|
const boundKey = agentMap[String(chatId)];
|
|
1094
1126
|
const boundProj = boundKey && newCfg.projects && newCfg.projects[boundKey];
|
|
1095
1127
|
if (boundProj && boundProj.cwd) {
|
|
@@ -1342,21 +1374,224 @@ async function handleCommand(bot, chatId, text, config, executeTaskByName, sende
|
|
|
1342
1374
|
return;
|
|
1343
1375
|
}
|
|
1344
1376
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1377
|
+
// โโโ /agent ๅฝไปคไฝ็ณป โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1378
|
+
// /agent bind <ๅ็งฐ> [็ฎๅฝ] โ ๆๅฝๅ็พค็ปๅฎไธบไธๅฑ agent ้ข้
|
|
1379
|
+
// /agent list โ ๆฅ็ๆๆๅทฒ้
็ฝฎ็ agent
|
|
1380
|
+
// /agent new โ ๅคๆญฅๅๅฏผๆฐๅปบ agent
|
|
1381
|
+
// /agent edit โ ็ผ่พๅฝๅ agent ็ CLAUDE.md ่ง่ฒๅฎไน
|
|
1382
|
+
// /agent reset โ ๅ ้คๅฝๅ agent ็่ง่ฒ section
|
|
1383
|
+
// /agent โ ๅผนๅบ agent ๅๆข้ๆฉๅจ๏ผๆ ๅๆฐ๏ผ
|
|
1384
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1385
|
+
|
|
1386
|
+
// ๅค็ /agent new ๅคๆญฅๅๅฏผ็ถๆๆบไธญ็ๆๆฌ่พๅ
ฅ๏ผname/desc ๆญฅ้ชค๏ผ
|
|
1387
|
+
{
|
|
1388
|
+
const flow = pendingAgentFlows.get(String(chatId));
|
|
1389
|
+
if (flow && flow.step === 'name' && text && !text.startsWith('/')) {
|
|
1390
|
+
// ๆญฅ้ชค2: ็จๆทๅๅคไบ Agent ๅ็งฐ
|
|
1391
|
+
flow.name = text.trim();
|
|
1392
|
+
flow.step = 'desc';
|
|
1393
|
+
pendingAgentFlows.set(String(chatId), flow);
|
|
1394
|
+
await bot.sendMessage(chatId, `ๅฅฝ็๏ผAgent ๅ็งฐๆฏใ${flow.name}ใ\n\n่ฏทๆ่ฟฐ่ฟไธช Agent ็่ง่ฒๅ่่ดฃ๏ผ็จ่ช็ถ่ฏญ่จ๏ผ๏ผ`);
|
|
1350
1395
|
return;
|
|
1351
1396
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
const
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1397
|
+
if (flow && flow.step === 'desc' && text && !text.startsWith('/')) {
|
|
1398
|
+
// ๆญฅ้ชค3: ็จๆทๅๅคไบ่ง่ฒๆ่ฟฐ
|
|
1399
|
+
pendingAgentFlows.delete(String(chatId));
|
|
1400
|
+
const { dir, name } = flow;
|
|
1401
|
+
const description = text.trim();
|
|
1402
|
+
await bot.sendMessage(chatId, `โณ ๆญฃๅจ้
็ฝฎ Agentใ${name}ใ๏ผ็จ็ญ...`);
|
|
1403
|
+
try {
|
|
1404
|
+
// a. ๅๅ
ฅ config๏ผprojects ้ๆฐๅขๆก็ฎ๏ผๅนถ็ปๅฎๅฝๅ็พค
|
|
1405
|
+
await doBindAgent(bot, chatId, name, dir);
|
|
1406
|
+
// b. ๆบ่ฝๅๅนถ CLAUDE.md
|
|
1407
|
+
const mergeResult = await mergeAgentRole(dir, description);
|
|
1408
|
+
if (mergeResult.error) {
|
|
1409
|
+
await bot.sendMessage(chatId, `โ ๏ธ CLAUDE.md ๅๅนถๅคฑ่ดฅ: ${mergeResult.error}๏ผๅ
ถไป้
็ฝฎๅทฒไฟๅญ`);
|
|
1410
|
+
} else if (mergeResult.created) {
|
|
1411
|
+
await bot.sendMessage(chatId, `๐ ๅทฒๅๅปบ CLAUDE.md ๅนถๅๅ
ฅ่ง่ฒๅฎไน`);
|
|
1412
|
+
} else {
|
|
1413
|
+
await bot.sendMessage(chatId, `๐ ๅทฒๅฐ่ง่ฒๅฎไนๅๅนถ่ฟ็ฐๆ CLAUDE.md`);
|
|
1414
|
+
}
|
|
1415
|
+
} catch (e) {
|
|
1416
|
+
await bot.sendMessage(chatId, `โ ๅๅปบ Agent ๅคฑ่ดฅ: ${e.message}`);
|
|
1417
|
+
}
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// /agent edit ็ถๆๆบ๏ผ็ญๅพ
็จๆท่พๅ
ฅไฟฎๆนๆๅพ
|
|
1423
|
+
{
|
|
1424
|
+
const editFlow = pendingAgentFlows.get(String(chatId) + ':edit');
|
|
1425
|
+
if (editFlow && text && !text.startsWith('/')) {
|
|
1426
|
+
pendingAgentFlows.delete(String(chatId) + ':edit');
|
|
1427
|
+
const { cwd } = editFlow;
|
|
1428
|
+
await bot.sendMessage(chatId, 'โณ ๆญฃๅจๆดๆฐ CLAUDE.md...');
|
|
1429
|
+
const mergeResult = await mergeAgentRole(cwd, text.trim());
|
|
1430
|
+
if (mergeResult.error) {
|
|
1431
|
+
await bot.sendMessage(chatId, `โ ๆดๆฐๅคฑ่ดฅ: ${mergeResult.error}`);
|
|
1432
|
+
} else {
|
|
1433
|
+
await bot.sendMessage(chatId, 'โ
CLAUDE.md ๅทฒๆดๆฐ');
|
|
1434
|
+
}
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
if (text === '/agent' || text.startsWith('/agent ')) {
|
|
1440
|
+
const agentArg = text === '/agent' ? '' : text.slice(7).trim();
|
|
1441
|
+
const agentParts = agentArg.split(/\s+/);
|
|
1442
|
+
const agentSub = agentParts[0]; // bind / list / new / edit / reset / ''
|
|
1443
|
+
|
|
1444
|
+
// /agent bind <ๅ็งฐ> [็ฎๅฝ] โ ๆฟไปฃๆง็ /bind
|
|
1445
|
+
if (agentSub === 'bind') {
|
|
1446
|
+
const bindName = agentParts[1];
|
|
1447
|
+
const bindCwd = agentParts.slice(2).join(' ');
|
|
1448
|
+
if (!bindName) {
|
|
1449
|
+
await bot.sendMessage(chatId, '็จๆณ: /agent bind <ๅ็งฐ> [ๅทฅไฝ็ฎๅฝ]\nไพ: /agent bind ๅฐ็พ ~/\nๆ: /agent bind ๆๆ (ๅผนๅบ็ฎๅฝ้ๆฉ)');
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (!bindCwd) {
|
|
1453
|
+
pendingBinds.set(String(chatId), bindName);
|
|
1454
|
+
await sendDirPicker(bot, chatId, 'bind', `ไธบใ${bindName}ใ้ๆฉๅทฅไฝ็ฎๅฝ:`);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
await doBindAgent(bot, chatId, bindName, expandPath(bindCwd));
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// /agent list โ ๆฅ็ๆๆๅทฒ้
็ฝฎ็ agent
|
|
1462
|
+
if (agentSub === 'list') {
|
|
1463
|
+
const cfg = loadConfig();
|
|
1464
|
+
const projects = cfg.projects || {};
|
|
1465
|
+
const entries = Object.entries(projects).filter(([, p]) => p.cwd);
|
|
1466
|
+
if (entries.length === 0) {
|
|
1467
|
+
await bot.sendMessage(chatId, 'ๆๆ ๅทฒ้
็ฝฎ็ Agentใ\nไฝฟ็จ /agent new ๅๅปบ๏ผๆ /agent bind <ๅ็งฐ> ็ปๅฎ็ฎๅฝใ');
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
// ๆพๅบๅฝๅ็พค็ปๅฎ็ agent
|
|
1471
|
+
const agentMap = { ...(cfg.telegram ? cfg.telegram.chat_agent_map : {}), ...(cfg.feishu ? cfg.feishu.chat_agent_map : {}) };
|
|
1472
|
+
const boundKey = agentMap[String(chatId)];
|
|
1473
|
+
const lines = ['๐ ๅทฒ้
็ฝฎ็ Agent๏ผ', ''];
|
|
1474
|
+
for (const [key, p] of entries) {
|
|
1475
|
+
const icon = p.icon || '๐ค';
|
|
1476
|
+
const name = p.name || key;
|
|
1477
|
+
const displayCwd = (p.cwd || '').replace(HOME, '~');
|
|
1478
|
+
const bound = key === boundKey ? ' โ ๅฝๅ' : '';
|
|
1479
|
+
lines.push(`${icon} ${name}${bound}`);
|
|
1480
|
+
lines.push(` ็ฎๅฝ: ${displayCwd}`);
|
|
1481
|
+
lines.push(` Key: ${key}`);
|
|
1482
|
+
lines.push('');
|
|
1483
|
+
}
|
|
1484
|
+
await bot.sendMessage(chatId, lines.join('\n').trimEnd());
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// /agent new โ ๅคๆญฅๅๅฏผๆฐๅปบ agent
|
|
1489
|
+
if (agentSub === 'new') {
|
|
1490
|
+
pendingAgentFlows.set(String(chatId), { step: 'dir' });
|
|
1491
|
+
await sendBrowse(bot, chatId, 'agent-new', HOME, 'ๆญฅ้ชค1/3๏ผ้ๆฉ่ฟไธช Agent ็ๅทฅไฝ็ฎๅฝ');
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// /agent edit โ ็ผ่พๅฝๅ agent ็ CLAUDE.md ่ง่ฒๅฎไน
|
|
1496
|
+
if (agentSub === 'edit') {
|
|
1497
|
+
const cfg = loadConfig();
|
|
1498
|
+
const agentMap = { ...(cfg.telegram ? cfg.telegram.chat_agent_map : {}), ...(cfg.feishu ? cfg.feishu.chat_agent_map : {}) };
|
|
1499
|
+
const boundKey = agentMap[String(chatId)];
|
|
1500
|
+
const boundProj = boundKey && cfg.projects && cfg.projects[boundKey];
|
|
1501
|
+
if (!boundProj || !boundProj.cwd) {
|
|
1502
|
+
await bot.sendMessage(chatId, 'โ ๅฝๅ็พคๆช็ปๅฎ Agent๏ผ่ฏทๅ
ไฝฟ็จ /agent bind ๆ /agent new');
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
const cwd = normalizeCwd(boundProj.cwd);
|
|
1506
|
+
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
1507
|
+
let currentContent = '๏ผCLAUDE.md ไธๅญๅจ๏ผ';
|
|
1508
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
1509
|
+
currentContent = fs.readFileSync(claudeMdPath, 'utf8');
|
|
1510
|
+
// ๅชๅฑ็คบๅ 500 ๅญ็ฌฆ
|
|
1511
|
+
if (currentContent.length > 500) {
|
|
1512
|
+
currentContent = currentContent.slice(0, 500) + '\n...(ๅทฒๆชๆญ)';
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
pendingAgentFlows.set(String(chatId) + ':edit', { cwd });
|
|
1516
|
+
await bot.sendMessage(chatId, `๐ ๅฝๅ CLAUDE.md ๅ
ๅฎน:\n\`\`\`\n${currentContent}\n\`\`\`\n\n่ฏทๆ่ฟฐไฝ ๆณๅ็ไฟฎๆน๏ผ็จ่ช็ถ่ฏญ่จ๏ผไพๅฆ๏ผใๆ่ง่ฒๆนๆๅ็ซฏๅทฅ็จๅธ๏ผไธๆณจ Pythonใ๏ผ๏ผ`);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// /agent reset โ ๅ ้ค CLAUDE.md ้็่ง่ฒ section
|
|
1521
|
+
if (agentSub === 'reset') {
|
|
1522
|
+
const cfg = loadConfig();
|
|
1523
|
+
const agentMap = { ...(cfg.telegram ? cfg.telegram.chat_agent_map : {}), ...(cfg.feishu ? cfg.feishu.chat_agent_map : {}) };
|
|
1524
|
+
const boundKey = agentMap[String(chatId)];
|
|
1525
|
+
const boundProj = boundKey && cfg.projects && cfg.projects[boundKey];
|
|
1526
|
+
if (!boundProj || !boundProj.cwd) {
|
|
1527
|
+
await bot.sendMessage(chatId, 'โ ๅฝๅ็พคๆช็ปๅฎ Agent๏ผ่ฏทๅ
ไฝฟ็จ /agent bind ๆ /agent new');
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const cwd = normalizeCwd(boundProj.cwd);
|
|
1531
|
+
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
1532
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
1533
|
+
await bot.sendMessage(chatId, 'โ ๏ธ CLAUDE.md ไธๅญๅจ๏ผๆ ้้็ฝฎ');
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
let content = fs.readFileSync(claudeMdPath, 'utf8');
|
|
1537
|
+
// ็จๆญฃๅๅ ้ค ## Agent ่ง่ฒ section๏ผๅฐไธไธไธช ## ๆๆไปถๆซๅฐพ๏ผ
|
|
1538
|
+
content = content.replace(/(?:^|\n)## Agent ่ง่ฒ\n[\s\S]*?(?=\n## |$)/, '').trimStart();
|
|
1539
|
+
// ๅฆๆๆฒกๅน้
ๅฐ๏ผ็ปๅบๆ็คบ
|
|
1540
|
+
if (content === fs.readFileSync(claudeMdPath, 'utf8').trimStart()) {
|
|
1541
|
+
await bot.sendMessage(chatId, 'โ ๏ธ ๆชๆพๅฐใ## Agent ่ง่ฒใsection๏ผCLAUDE.md ๆชไฟฎๆน');
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
fs.writeFileSync(claudeMdPath, content, 'utf8');
|
|
1545
|
+
await bot.sendMessage(chatId, 'โ
ๅทฒๅ ้ค่ง่ฒ section๏ผ่ฏท้ๆฐๅ้่ง่ฒๆ่ฟฐ๏ผ/agent edit ๆ /agent new๏ผ');
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// /agent๏ผๆ ๅๆฐ๏ผโ ๅผนๅบ agent ๅๆข้ๆฉๅจ
|
|
1550
|
+
{
|
|
1551
|
+
const projects = config.projects || {};
|
|
1552
|
+
const entries = Object.entries(projects).filter(([, p]) => p.cwd);
|
|
1553
|
+
if (entries.length === 0) {
|
|
1554
|
+
await bot.sendMessage(chatId, 'ๆๆ ๅทฒ้
็ฝฎ็ Agentใ\nไฝฟ็จ /agent new ๆฐๅปบ๏ผๆ /agent bind <ๅ็งฐ> ็ปๅฎ็ฎๅฝใ');
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
const currentSession = getSession(chatId);
|
|
1558
|
+
const currentCwd = currentSession?.cwd ? path.resolve(expandPath(currentSession.cwd)) : null;
|
|
1559
|
+
const buttons = entries.map(([key, p]) => {
|
|
1560
|
+
const projCwd = normalizeCwd(p.cwd);
|
|
1561
|
+
const active = currentCwd && path.resolve(projCwd) === currentCwd ? ' โ' : '';
|
|
1562
|
+
return [{ text: `${p.icon || '๐ค'} ${p.name || key}${active}`, callback_data: `/cd ${projCwd}` }];
|
|
1563
|
+
});
|
|
1564
|
+
await bot.sendButtons(chatId, 'ๅๆขๅฏน่ฏๅฏน่ฑก', buttons);
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// --- /bind-dir <path>: /agent bind ็ฎๅฝ้ๆฉๅจ็ๅ
้จๅ่ฐ ---
|
|
1570
|
+
if (text.startsWith('/bind-dir ')) {
|
|
1571
|
+
const dirPath = expandPath(text.slice(10).trim());
|
|
1572
|
+
const agentName = pendingBinds.get(String(chatId));
|
|
1573
|
+
if (!agentName) {
|
|
1574
|
+
await bot.sendMessage(chatId, 'โ ๆฒกๆๅพ
ๅฎๆ็ /agent bind๏ผ่ฏท้ๆฐๅ้');
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
pendingBinds.delete(String(chatId));
|
|
1578
|
+
await doBindAgent(bot, chatId, agentName, dirPath);
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// --- /agent-dir <path>: /agent new ๅๅฏผ็็ฎๅฝ้ๆฉๅ่ฐ ---
|
|
1583
|
+
if (text.startsWith('/agent-dir ')) {
|
|
1584
|
+
const dirPath = expandPath(text.slice(11).trim());
|
|
1585
|
+
const flow = pendingAgentFlows.get(String(chatId));
|
|
1586
|
+
if (!flow || flow.step !== 'dir') {
|
|
1587
|
+
await bot.sendMessage(chatId, 'โ ๆฒกๆๅพ
ๅฎๆ็ /agent new๏ผ่ฏท้ๆฐๅ้ /agent new');
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
flow.dir = dirPath;
|
|
1591
|
+
flow.step = 'name';
|
|
1592
|
+
pendingAgentFlows.set(String(chatId), flow);
|
|
1593
|
+
const displayPath = dirPath.replace(HOME, '~');
|
|
1594
|
+
await bot.sendMessage(chatId, `โ ๅทฒ้ๆฉ็ฎๅฝ๏ผ${displayPath}\n\nๆญฅ้ชค2/3๏ผ็ป่ฟไธช Agent ่ตทไธชๅๅญ๏ผ`);
|
|
1360
1595
|
return;
|
|
1361
1596
|
}
|
|
1362
1597
|
|
|
@@ -1583,7 +1818,7 @@ async function handleCommand(bot, chatId, text, config, executeTaskByName, sende
|
|
|
1583
1818
|
if (text === '/budget') {
|
|
1584
1819
|
const limit = (config.budget && config.budget.daily_limit) || 50000;
|
|
1585
1820
|
const used = state.budget.tokens_used;
|
|
1586
|
-
await bot.sendMessage(chatId, `Budget: ${used}/${limit} tokens (${((used/limit)*100).toFixed(1)}%)`);
|
|
1821
|
+
await bot.sendMessage(chatId, `Budget: ${used}/${limit} tokens (${((used / limit) * 100).toFixed(1)}%)`);
|
|
1587
1822
|
return;
|
|
1588
1823
|
}
|
|
1589
1824
|
|
|
@@ -1890,7 +2125,7 @@ async function handleCommand(bot, chatId, text, config, executeTaskByName, sende
|
|
|
1890
2125
|
break; // Found a message before checkpoint, stop
|
|
1891
2126
|
}
|
|
1892
2127
|
}
|
|
1893
|
-
} catch {}
|
|
2128
|
+
} catch { }
|
|
1894
2129
|
}
|
|
1895
2130
|
if (cutIdx > 0) {
|
|
1896
2131
|
const kept = lines.slice(0, cutIdx);
|
|
@@ -2131,8 +2366,13 @@ async function handleCommand(bot, chatId, text, config, executeTaskByName, sende
|
|
|
2131
2366
|
'/last โ ็ปง็ปญ็ต่ไธๆ่ฟ็ๅฏน่ฏ',
|
|
2132
2367
|
'/cd last โ ๅๅฐ็ต่ๆ่ฟ็้กน็ฎ็ฎๅฝ',
|
|
2133
2368
|
'',
|
|
2134
|
-
'๐ค Agent
|
|
2135
|
-
'/agent โ
|
|
2369
|
+
'๐ค Agent ็ฎก็:',
|
|
2370
|
+
'/agent โ ๅๆข Agent',
|
|
2371
|
+
'/agent new โ ๅๅฏผๆฐๅปบ Agent',
|
|
2372
|
+
'/agent bind <ๅ็งฐ> [็ฎๅฝ] โ ็ปๅฎๅฝๅ็พค',
|
|
2373
|
+
'/agent list โ ๆฅ็ๆๆ Agent',
|
|
2374
|
+
'/agent edit โ ็ผ่พๅฝๅ Agent ่ง่ฒ',
|
|
2375
|
+
'/agent reset โ ้็ฝฎๅฝๅ Agent ่ง่ฒ',
|
|
2136
2376
|
'',
|
|
2137
2377
|
'๐ Session ็ฎก็:',
|
|
2138
2378
|
'/new [path] [name] โ ๆฐๅปบไผ่ฏ',
|
|
@@ -2209,7 +2449,7 @@ async function handleCommand(bot, chatId, text, config, executeTaskByName, sende
|
|
|
2209
2449
|
await bot.sendMessage(chatId, 'Daily token budget exceeded.');
|
|
2210
2450
|
return;
|
|
2211
2451
|
}
|
|
2212
|
-
await askClaude(bot, chatId, text, config);
|
|
2452
|
+
await askClaude(bot, chatId, text, config, readOnly);
|
|
2213
2453
|
}
|
|
2214
2454
|
|
|
2215
2455
|
// ---------------------------------------------------------
|
|
@@ -2520,6 +2760,8 @@ function spawnClaudeAsync(args, input, cwd, timeoutMs = 300000) {
|
|
|
2520
2760
|
const timer = setTimeout(() => {
|
|
2521
2761
|
killed = true;
|
|
2522
2762
|
child.kill('SIGTERM');
|
|
2763
|
+
// Fix: escalate to SIGKILL if SIGTERM is ignored
|
|
2764
|
+
setTimeout(() => { try { child.kill('SIGKILL'); } catch { } }, 5000);
|
|
2523
2765
|
}, timeoutMs);
|
|
2524
2766
|
|
|
2525
2767
|
child.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
@@ -2580,9 +2822,49 @@ const CONTENT_EXTENSIONS = new Set([
|
|
|
2580
2822
|
// Active Claude processes per chat (for /stop)
|
|
2581
2823
|
const activeProcesses = new Map(); // chatId -> { child, aborted }
|
|
2582
2824
|
|
|
2825
|
+
// Fix3: persist child PIDs so next daemon startup can kill orphans
|
|
2826
|
+
const ACTIVE_PIDS_FILE = path.join(HOME, '.metame', 'active_claude_pids.json');
|
|
2827
|
+
function saveActivePids() {
|
|
2828
|
+
try {
|
|
2829
|
+
const pids = {};
|
|
2830
|
+
for (const [chatId, proc] of activeProcesses) {
|
|
2831
|
+
if (proc.child && proc.child.pid) pids[chatId] = proc.child.pid;
|
|
2832
|
+
}
|
|
2833
|
+
fs.writeFileSync(ACTIVE_PIDS_FILE, JSON.stringify(pids), 'utf8');
|
|
2834
|
+
} catch { }
|
|
2835
|
+
}
|
|
2836
|
+
function getProcessName(pid) {
|
|
2837
|
+
try {
|
|
2838
|
+
return execSync(`ps -p ${pid} -o comm=`, { encoding: 'utf8', timeout: 2000 }).trim();
|
|
2839
|
+
} catch { return null; }
|
|
2840
|
+
}
|
|
2841
|
+
function killOrphanPids() {
|
|
2842
|
+
try {
|
|
2843
|
+
if (!fs.existsSync(ACTIVE_PIDS_FILE)) return;
|
|
2844
|
+
const pids = JSON.parse(fs.readFileSync(ACTIVE_PIDS_FILE, 'utf8'));
|
|
2845
|
+
for (const [chatId, pid] of Object.entries(pids)) {
|
|
2846
|
+
try {
|
|
2847
|
+
// Safety: only kill if PID still belongs to a claude process (prevent PID reuse accidents)
|
|
2848
|
+
const comm = getProcessName(pid);
|
|
2849
|
+
if (!comm || !comm.includes('claude')) {
|
|
2850
|
+
log('WARN', `Skipping PID ${pid} (chatId: ${chatId}): process is "${comm}", not claude`);
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
process.kill(pid, 'SIGKILL');
|
|
2854
|
+
log('INFO', `Killed orphan claude PID ${pid} (chatId: ${chatId})`);
|
|
2855
|
+
} catch { }
|
|
2856
|
+
}
|
|
2857
|
+
fs.unlinkSync(ACTIVE_PIDS_FILE);
|
|
2858
|
+
} catch { }
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2583
2861
|
// Pending /bind flows: waiting for user to pick a directory
|
|
2584
2862
|
const pendingBinds = new Map(); // chatId -> agentName
|
|
2585
2863
|
|
|
2864
|
+
// Pending /agent new ๅคๆญฅๅๅฏผ็ถๆๆบ
|
|
2865
|
+
// chatId -> { step: 'dir'|'name'|'desc', dir: string, name: string }
|
|
2866
|
+
const pendingAgentFlows = new Map();
|
|
2867
|
+
|
|
2586
2868
|
// Message queue for messages received while a task is running
|
|
2587
2869
|
const messageQueue = new Map(); // chatId -> { messages: string[], notified: false }
|
|
2588
2870
|
|
|
@@ -2694,6 +2976,7 @@ function spawnClaudeStreaming(args, input, cwd, onStatus, timeoutMs = 600000, ch
|
|
|
2694
2976
|
// Track active process for /stop
|
|
2695
2977
|
if (chatId) {
|
|
2696
2978
|
activeProcesses.set(chatId, { child, aborted: false });
|
|
2979
|
+
saveActivePids(); // Fix3: persist PID to disk
|
|
2697
2980
|
}
|
|
2698
2981
|
|
|
2699
2982
|
let buffer = '';
|
|
@@ -2703,10 +2986,13 @@ function spawnClaudeStreaming(args, input, cwd, onStatus, timeoutMs = 600000, ch
|
|
|
2703
2986
|
let lastStatusTime = 0;
|
|
2704
2987
|
const STATUS_THROTTLE = STATUS_THROTTLE_MS;
|
|
2705
2988
|
const writtenFiles = []; // Track files created/modified by Write tool
|
|
2989
|
+
const toolUsageLog = []; // Track all tool invocations for skill evolution
|
|
2706
2990
|
|
|
2707
2991
|
const timer = setTimeout(() => {
|
|
2708
2992
|
killed = true;
|
|
2709
2993
|
child.kill('SIGTERM');
|
|
2994
|
+
// Fix: escalate to SIGKILL if SIGTERM is ignored
|
|
2995
|
+
setTimeout(() => { try { child.kill('SIGKILL'); } catch { } }, 5000);
|
|
2710
2996
|
}, timeoutMs);
|
|
2711
2997
|
|
|
2712
2998
|
child.stdout.on('data', (data) => {
|
|
@@ -2735,6 +3021,13 @@ function spawnClaudeStreaming(args, input, cwd, onStatus, timeoutMs = 600000, ch
|
|
|
2735
3021
|
if (block.type === 'tool_use') {
|
|
2736
3022
|
const toolName = block.name || 'Tool';
|
|
2737
3023
|
|
|
3024
|
+
// Track tool usage for skill evolution
|
|
3025
|
+
const toolEntry = { tool: toolName };
|
|
3026
|
+
if (toolName === 'Skill' && block.input?.skill) toolEntry.skill = block.input.skill;
|
|
3027
|
+
else if (block.input?.command) toolEntry.context = block.input.command.slice(0, 50);
|
|
3028
|
+
else if (block.input?.file_path) toolEntry.context = path.basename(block.input.file_path);
|
|
3029
|
+
if (toolUsageLog.length < 50) toolUsageLog.push(toolEntry);
|
|
3030
|
+
|
|
2738
3031
|
// Track files written by Write tool
|
|
2739
3032
|
if (toolName === 'Write' && block.input?.file_path) {
|
|
2740
3033
|
const filePath = block.input.file_path;
|
|
@@ -2799,7 +3092,7 @@ function spawnClaudeStreaming(args, input, cwd, onStatus, timeoutMs = 600000, ch
|
|
|
2799
3092
|
: `${displayEmoji} ${displayName}...`;
|
|
2800
3093
|
|
|
2801
3094
|
if (onStatus) {
|
|
2802
|
-
onStatus(status).catch(() => {});
|
|
3095
|
+
onStatus(status).catch(() => { });
|
|
2803
3096
|
}
|
|
2804
3097
|
}
|
|
2805
3098
|
}
|
|
@@ -2834,23 +3127,23 @@ function spawnClaudeStreaming(args, input, cwd, onStatus, timeoutMs = 600000, ch
|
|
|
2834
3127
|
// Clean up active process tracking
|
|
2835
3128
|
const proc = chatId ? activeProcesses.get(chatId) : null;
|
|
2836
3129
|
const wasAborted = proc && proc.aborted;
|
|
2837
|
-
if (chatId) activeProcesses.delete(chatId);
|
|
3130
|
+
if (chatId) { activeProcesses.delete(chatId); saveActivePids(); } // Fix3
|
|
2838
3131
|
|
|
2839
3132
|
if (wasAborted) {
|
|
2840
|
-
resolve({ output: finalResult || null, error: 'Stopped by user', files: writtenFiles });
|
|
3133
|
+
resolve({ output: finalResult || null, error: 'Stopped by user', files: writtenFiles, toolUsageLog });
|
|
2841
3134
|
} else if (killed) {
|
|
2842
|
-
resolve({ output: finalResult || null, error: 'Timeout: Claude took too long', files: writtenFiles });
|
|
3135
|
+
resolve({ output: finalResult || null, error: 'Timeout: Claude took too long', files: writtenFiles, toolUsageLog });
|
|
2843
3136
|
} else if (code !== 0) {
|
|
2844
|
-
resolve({ output: finalResult || null, error: stderr || `Exit code ${code}`, files: writtenFiles });
|
|
3137
|
+
resolve({ output: finalResult || null, error: stderr || `Exit code ${code}`, files: writtenFiles, toolUsageLog });
|
|
2845
3138
|
} else {
|
|
2846
|
-
resolve({ output: finalResult || '', error: null, files: writtenFiles });
|
|
3139
|
+
resolve({ output: finalResult || '', error: null, files: writtenFiles, toolUsageLog });
|
|
2847
3140
|
}
|
|
2848
3141
|
});
|
|
2849
3142
|
|
|
2850
3143
|
child.on('error', (err) => {
|
|
2851
3144
|
clearTimeout(timer);
|
|
2852
|
-
if (chatId) activeProcesses.delete(chatId);
|
|
2853
|
-
resolve({ output: null, error: err.message, files: [] });
|
|
3145
|
+
if (chatId) { activeProcesses.delete(chatId); saveActivePids(); } // Fix3
|
|
3146
|
+
resolve({ output: null, error: err.message, files: [], toolUsageLog: [] });
|
|
2854
3147
|
});
|
|
2855
3148
|
|
|
2856
3149
|
// Write input and close stdin
|
|
@@ -2897,7 +3190,7 @@ function lazyDistill() {
|
|
|
2897
3190
|
* Shared ask logic โ full Claude Code session (stateful, with tools)
|
|
2898
3191
|
* Now uses spawn (async) instead of execSync to allow parallel requests.
|
|
2899
3192
|
*/
|
|
2900
|
-
async function askClaude(bot, chatId, prompt, config) {
|
|
3193
|
+
async function askClaude(bot, chatId, prompt, config, readOnly = false) {
|
|
2901
3194
|
log('INFO', `askClaude for ${chatId}: ${prompt.slice(0, 50)}`);
|
|
2902
3195
|
// Trigger background distill on first message / every 4h
|
|
2903
3196
|
try { lazyDistill(); } catch { /* non-fatal */ }
|
|
@@ -2909,9 +3202,9 @@ async function askClaude(bot, chatId, prompt, config) {
|
|
|
2909
3202
|
} catch (e) {
|
|
2910
3203
|
log('ERROR', `Failed to send ack to ${chatId}: ${e.message}`);
|
|
2911
3204
|
}
|
|
2912
|
-
await bot.sendTyping(chatId).catch(() => {});
|
|
3205
|
+
await bot.sendTyping(chatId).catch(() => { });
|
|
2913
3206
|
const typingTimer = setInterval(() => {
|
|
2914
|
-
bot.sendTyping(chatId).catch(() => {});
|
|
3207
|
+
bot.sendTyping(chatId).catch(() => { });
|
|
2915
3208
|
}, 4000);
|
|
2916
3209
|
|
|
2917
3210
|
// Agent nickname routing: "่ดพ็ปดๆฏ" / "ๅฐ็พ๏ผๅธฎๆ..." โ switch project session
|
|
@@ -3037,11 +3330,23 @@ async function askClaude(bot, chatId, prompt, config) {
|
|
|
3037
3330
|
} catch { /* ignore status update failures */ }
|
|
3038
3331
|
};
|
|
3039
3332
|
|
|
3040
|
-
const { output, error, files } = await spawnClaudeStreaming(args, fullPrompt, session.cwd, onStatus, 600000, chatId);
|
|
3333
|
+
const { output, error, files, toolUsageLog } = await spawnClaudeStreaming(args, fullPrompt, session.cwd, onStatus, 600000, chatId);
|
|
3041
3334
|
clearInterval(typingTimer);
|
|
3335
|
+
|
|
3336
|
+
// Skill evolution: capture signal + hot path heuristic check
|
|
3337
|
+
if (skillEvolution) {
|
|
3338
|
+
try {
|
|
3339
|
+
const signal = skillEvolution.extractSkillSignal(fullPrompt, output, error, files, session.cwd, toolUsageLog);
|
|
3340
|
+
if (signal) {
|
|
3341
|
+
skillEvolution.appendSkillSignal(signal);
|
|
3342
|
+
skillEvolution.checkHotEvolution(signal);
|
|
3343
|
+
}
|
|
3344
|
+
} catch (e) { log('WARN', `Skill evolution signal capture failed: ${e.message}`); }
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3042
3347
|
// Clean up status message
|
|
3043
3348
|
if (statusMsgId && bot.deleteMessage) {
|
|
3044
|
-
bot.deleteMessage(chatId, statusMsgId).catch(() => {});
|
|
3349
|
+
bot.deleteMessage(chatId, statusMsgId).catch(() => { });
|
|
3045
3350
|
}
|
|
3046
3351
|
|
|
3047
3352
|
// When Claude completes with no text output (pure tool work), send a done notice
|
|
@@ -3113,7 +3418,7 @@ async function askClaude(bot, chatId, prompt, config) {
|
|
|
3113
3418
|
|
|
3114
3419
|
// Auto-name: if this was the first message and session has no name, generate one
|
|
3115
3420
|
if (wasNew && !getSessionName(session.id)) {
|
|
3116
|
-
autoNameSession(chatId, session.id, prompt, session.cwd).catch(() => {});
|
|
3421
|
+
autoNameSession(chatId, session.id, prompt, session.cwd).catch(() => { });
|
|
3117
3422
|
}
|
|
3118
3423
|
} else {
|
|
3119
3424
|
const errMsg = error || 'Unknown error';
|
|
@@ -3184,10 +3489,11 @@ async function startFeishuBridge(config, executeTaskByName) {
|
|
|
3184
3489
|
try {
|
|
3185
3490
|
const receiver = await bot.startReceiving(async (chatId, text, event, fileInfo, senderId) => {
|
|
3186
3491
|
// Security: check whitelist (empty = deny all) โ read live config to support hot-reload
|
|
3187
|
-
// Exception: /bind
|
|
3492
|
+
// Exception: /bind and /agent bind/new are allowed from any chat so users can self-register new groups
|
|
3188
3493
|
const liveCfg = loadConfig();
|
|
3189
3494
|
const allowedIds = (liveCfg.feishu && liveCfg.feishu.allowed_chat_ids) || [];
|
|
3190
|
-
const
|
|
3495
|
+
const trimmedText = text && text.trim();
|
|
3496
|
+
const isBindCmd = trimmedText && (trimmedText.startsWith('/agent bind') || trimmedText.startsWith('/agent new'));
|
|
3191
3497
|
if (!allowedIds.includes(chatId) && !isBindCmd) {
|
|
3192
3498
|
log('WARN', `Feishu: rejected message from ${chatId}`);
|
|
3193
3499
|
return;
|
|
@@ -3284,7 +3590,7 @@ function killExistingDaemon() {
|
|
|
3284
3590
|
} catch {
|
|
3285
3591
|
// Process doesn't exist or already dead
|
|
3286
3592
|
}
|
|
3287
|
-
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
3593
|
+
try { fs.unlinkSync(PID_FILE); } catch { }
|
|
3288
3594
|
}
|
|
3289
3595
|
|
|
3290
3596
|
function writePid() {
|
|
@@ -3346,6 +3652,7 @@ async function main() {
|
|
|
3346
3652
|
saveState(state);
|
|
3347
3653
|
|
|
3348
3654
|
log('INFO', `MetaMe daemon started (PID: ${process.pid})`);
|
|
3655
|
+
killOrphanPids(); // Fix3: kill any claude processes left by previous daemon
|
|
3349
3656
|
|
|
3350
3657
|
// Task executor lookup (always reads fresh config)
|
|
3351
3658
|
function executeTaskByName(name) {
|
|
@@ -3427,7 +3734,7 @@ async function main() {
|
|
|
3427
3734
|
const r = reloadConfig();
|
|
3428
3735
|
if (r.success) {
|
|
3429
3736
|
log('INFO', `Auto-reload OK: ${r.tasks} tasks`);
|
|
3430
|
-
notifyFn(`๐ Config auto-reloaded. ${r.tasks} heartbeat tasks active.`).catch(() => {});
|
|
3737
|
+
notifyFn(`๐ Config auto-reloaded. ${r.tasks} heartbeat tasks active.`).catch(() => { });
|
|
3431
3738
|
} else {
|
|
3432
3739
|
log('ERROR', `Auto-reload failed: ${r.error}`);
|
|
3433
3740
|
}
|
|
@@ -3456,7 +3763,7 @@ async function main() {
|
|
|
3456
3763
|
|
|
3457
3764
|
// Notify once on startup (single message, no duplicates)
|
|
3458
3765
|
await sleep(1500); // Let polling settle
|
|
3459
|
-
await notifyFn('โ
Daemon ready.').catch(() => {});
|
|
3766
|
+
await notifyFn('โ
Daemon ready.').catch(() => { });
|
|
3460
3767
|
|
|
3461
3768
|
// Graceful shutdown
|
|
3462
3769
|
const shutdown = () => {
|
|
@@ -3466,6 +3773,13 @@ async function main() {
|
|
|
3466
3773
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
3467
3774
|
if (telegramBridge) telegramBridge.stop();
|
|
3468
3775
|
if (feishuBridge) feishuBridge.stop();
|
|
3776
|
+
// Fix1: kill all tracked claude child processes before exiting
|
|
3777
|
+
for (const [cid, proc] of activeProcesses) {
|
|
3778
|
+
try { proc.child.kill('SIGKILL'); } catch { }
|
|
3779
|
+
log('INFO', `Shutdown: killed claude child for chatId ${cid}`);
|
|
3780
|
+
}
|
|
3781
|
+
activeProcesses.clear();
|
|
3782
|
+
try { if (fs.existsSync(ACTIVE_PIDS_FILE)) fs.unlinkSync(ACTIVE_PIDS_FILE); } catch { }
|
|
3469
3783
|
cleanPid();
|
|
3470
3784
|
const s = loadState();
|
|
3471
3785
|
s.pid = null;
|