metame-cli 1.5.10 → 1.5.12
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 +49 -6
- package/index.js +266 -72
- package/package.json +7 -3
- package/scripts/daemon-admin-commands.js +34 -0
- package/scripts/daemon-agent-commands.js +6 -2
- package/scripts/daemon-bridges.js +41 -10
- package/scripts/daemon-claude-engine.js +128 -29
- package/scripts/daemon-command-router.js +16 -0
- package/scripts/daemon-command-session-route.js +3 -1
- package/scripts/daemon-default.yaml +3 -1
- package/scripts/daemon-engine-runtime.js +1 -5
- package/scripts/daemon-message-pipeline.js +113 -44
- package/scripts/daemon-ops-commands.js +25 -11
- package/scripts/daemon-reactive-lifecycle.js +757 -76
- package/scripts/daemon-session-commands.js +3 -2
- package/scripts/daemon-session-store.js +82 -27
- package/scripts/daemon-team-dispatch.js +21 -5
- package/scripts/daemon-utils.js +3 -1
- package/scripts/daemon.js +80 -2
- package/scripts/distill.js +1 -1
- package/scripts/docs/file-transfer.md +1 -0
- package/scripts/docs/maintenance-manual.md +55 -2
- package/scripts/docs/pointer-map.md +34 -0
- package/scripts/feishu-adapter.js +25 -0
- package/scripts/hooks/intent-file-transfer.js +2 -1
- package/scripts/hooks/intent-perpetual.js +109 -0
- package/scripts/hooks/intent-research.js +112 -0
- package/scripts/intent-registry.js +4 -0
- package/scripts/memory-extract.js +29 -1
- package/scripts/memory-nightly-reflect.js +104 -0
- package/scripts/ops-mission-queue.js +258 -0
- package/scripts/ops-verifier.js +197 -0
- package/scripts/signal-capture.js +3 -3
- package/scripts/skill-evolution.js +11 -2
- package/skills/agent-browser/SKILL.md +153 -0
- package/skills/agent-reach/SKILL.md +66 -0
- package/skills/agent-reach/evolution.json +13 -0
- package/skills/deep-research/SKILL.md +77 -0
- package/skills/find-skills/SKILL.md +133 -0
- package/skills/heartbeat-task-manager/SKILL.md +63 -0
- package/skills/macos-local-orchestrator/SKILL.md +192 -0
- package/skills/macos-local-orchestrator/agents/openai.yaml +4 -0
- package/skills/macos-local-orchestrator/references/tooling-landscape.md +70 -0
- package/skills/macos-mail-calendar/SKILL.md +394 -0
- package/skills/mcp-installer/SKILL.md +138 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/README.md +72 -0
- package/skills/skill-creator/SKILL.md +96 -0
- package/skills/skill-creator/evolution.json +6 -0
- package/skills/skill-creator/references/creation-guide.md +116 -0
- package/skills/skill-creator/references/evolution-guide.md +74 -0
- package/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skill-creator/scripts/align_all.py +32 -0
- package/skills/skill-creator/scripts/auto_evolve_hook.js +247 -0
- package/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skill-creator/scripts/merge_evolution.py +70 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/setup.py +141 -0
- package/skills/skill-creator/scripts/smart_stitch.py +82 -0
- package/skills/skill-manager/SKILL.md +112 -0
- package/skills/skill-manager/scripts/delete_skill.py +31 -0
- package/skills/skill-manager/scripts/list_skills.py +61 -0
- package/skills/skill-manager/scripts/scan_and_check.py +125 -0
- package/skills/skill-manager/scripts/sync_index.py +144 -0
- package/skills/skill-manager/scripts/update_helper.py +39 -0
|
@@ -652,13 +652,30 @@ function createBridgeStarter(deps) {
|
|
|
652
652
|
async function startFeishuBridge(config, executeTaskByName) {
|
|
653
653
|
if (!config.feishu || !config.feishu.enabled) return null;
|
|
654
654
|
if (!config.feishu.app_id || !config.feishu.app_secret) {
|
|
655
|
-
log('
|
|
655
|
+
log('ERROR', 'Feishu enabled but app_id/app_secret missing — bridge will NOT start. Check ~/.metame/daemon.yaml');
|
|
656
656
|
return null;
|
|
657
657
|
}
|
|
658
658
|
|
|
659
659
|
const { createBot } = require('./feishu-adapter.js');
|
|
660
660
|
const bot = createBot(config.feishu);
|
|
661
661
|
|
|
662
|
+
// Validate credentials before starting WebSocket — fail loud, not silent
|
|
663
|
+
try {
|
|
664
|
+
const validation = await bot.validateCredentials();
|
|
665
|
+
if (!validation.ok) {
|
|
666
|
+
log('ERROR', `Feishu credential check FAILED: ${validation.error}`);
|
|
667
|
+
if (validation.isAuthError) {
|
|
668
|
+
log('ERROR', 'Feishu bridge will NOT start — fix app_id/app_secret in ~/.metame/daemon.yaml and restart daemon');
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
log('WARN', 'Feishu credential check failed (possibly network issue) — attempting to start anyway');
|
|
672
|
+
} else {
|
|
673
|
+
log('INFO', 'Feishu credentials validated OK');
|
|
674
|
+
}
|
|
675
|
+
} catch (e) {
|
|
676
|
+
log('WARN', `Feishu credential pre-check error: ${e.message} — attempting to start anyway`);
|
|
677
|
+
}
|
|
678
|
+
|
|
662
679
|
try {
|
|
663
680
|
const receiver = await bot.startReceiving(async (chatId, text, event, fileInfo, senderId) => {
|
|
664
681
|
const liveCfg = loadConfig();
|
|
@@ -750,6 +767,7 @@ function createBridgeStarter(deps) {
|
|
|
750
767
|
log('INFO', `Feishu message from ${chatId}: ${text.slice(0, 50)}`);
|
|
751
768
|
const parentId = extractFeishuReplyMessageId(event);
|
|
752
769
|
let _replyAgentKey = null;
|
|
770
|
+
let _replyMappingFound = false; // true = mapping exists (agentKey may be null = main)
|
|
753
771
|
// Load state once for the entire routing block
|
|
754
772
|
const _st = loadState();
|
|
755
773
|
if (parentId) {
|
|
@@ -758,6 +776,7 @@ function createBridgeStarter(deps) {
|
|
|
758
776
|
if (parentId) {
|
|
759
777
|
const mapped = _st.msg_sessions && _st.msg_sessions[parentId];
|
|
760
778
|
if (mapped) {
|
|
779
|
+
_replyMappingFound = true;
|
|
761
780
|
if (typeof restoreSessionFromReply === 'function') {
|
|
762
781
|
restoreSessionFromReply(chatId, mapped);
|
|
763
782
|
} else {
|
|
@@ -835,16 +854,28 @@ function createBridgeStarter(deps) {
|
|
|
835
854
|
// Bare /stop, no sticky set → fall through to handleCommand
|
|
836
855
|
}
|
|
837
856
|
|
|
838
|
-
// 0. Quoted reply → force route
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
857
|
+
// 0. Quoted reply → force route based on which agent sent the parent message.
|
|
858
|
+
// Cases:
|
|
859
|
+
// a) agentKey = known team member → route to that member (set sticky)
|
|
860
|
+
// b) agentKey = null, mapping found → user replied to main; clear sticky, route to main
|
|
861
|
+
// c) parentId present, no mapping → intent is explicit, avoid sticky; clear sticky, route to main
|
|
862
|
+
if (parentId) {
|
|
863
|
+
if (_replyAgentKey) {
|
|
864
|
+
const member = _boundProj.team.find(m => m.key === _replyAgentKey);
|
|
865
|
+
if (member) {
|
|
866
|
+
_setSticky(member.key);
|
|
867
|
+
log('INFO', `Quoted reply → force route to ${_replyAgentKey} (sticky set)`);
|
|
868
|
+
_dispatchToTeamMember(member, _boundProj, trimmedText, liveCfg, bot, chatId, executeTaskByName, acl);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
// agentKey set but not a current team member → fall through to main
|
|
872
|
+
log('INFO', `Quoted reply agentKey=${_replyAgentKey} not in team, routing to main`);
|
|
846
873
|
}
|
|
847
|
-
|
|
874
|
+
// Cases b & c: no agentKey (main agent) or stale/unknown agentKey
|
|
875
|
+
_clearSticky();
|
|
876
|
+
log('INFO', `Quoted reply → route to main (agentKey=${_replyAgentKey} mappingFound=${_replyMappingFound})`);
|
|
877
|
+
await pipeline.processMessage(chatId, trimmedText, { bot, config: liveCfg, executeTaskByName, senderId: acl.senderId, readOnly: acl.readOnly });
|
|
878
|
+
return;
|
|
848
879
|
}
|
|
849
880
|
// 1. Explicit nickname → route + set sticky
|
|
850
881
|
const teamMatch = _findTeamMember(trimmedText, _boundProj.team);
|
|
@@ -106,7 +106,15 @@ function createClaudeEngine(deps) {
|
|
|
106
106
|
}
|
|
107
107
|
// Card reuse for merge-pause: when a task is paused for message merging,
|
|
108
108
|
// save the statusMsgId so the next askClaude reuses the same card.
|
|
109
|
-
|
|
109
|
+
// Entries auto-expire via periodic sweep (60s) to prevent unbounded growth.
|
|
110
|
+
const _pausedCards = new Map(); // chatId -> { statusMsgId, cardHeader, savedAt }
|
|
111
|
+
const _PAUSED_CARD_TTL = 60000;
|
|
112
|
+
setInterval(() => {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
for (const [k, v] of _pausedCards) {
|
|
115
|
+
if (now - (v.savedAt || 0) > _PAUSED_CARD_TTL) _pausedCards.delete(k);
|
|
116
|
+
}
|
|
117
|
+
}, _PAUSED_CARD_TTL).unref();
|
|
110
118
|
|
|
111
119
|
let mentorEngine = null;
|
|
112
120
|
try { mentorEngine = require('./mentor-engine'); } catch { /* optional */ }
|
|
@@ -239,6 +247,7 @@ function createClaudeEngine(deps) {
|
|
|
239
247
|
&& !!error
|
|
240
248
|
&& (!output || !!errorCode)
|
|
241
249
|
&& failureKind !== 'user-stop'
|
|
250
|
+
&& failureKind !== 'merge-pause'
|
|
242
251
|
&& !!canRetry;
|
|
243
252
|
}
|
|
244
253
|
|
|
@@ -481,6 +490,13 @@ function createClaudeEngine(deps) {
|
|
|
481
490
|
retryPromptPrefix: '',
|
|
482
491
|
};
|
|
483
492
|
}
|
|
493
|
+
if (code === 'INTERRUPTED_MERGE_PAUSE' || lowered.includes('paused for merge')) {
|
|
494
|
+
return {
|
|
495
|
+
kind: 'merge-pause',
|
|
496
|
+
userMessage: '',
|
|
497
|
+
retryPromptPrefix: '',
|
|
498
|
+
};
|
|
499
|
+
}
|
|
484
500
|
const interrupted = (
|
|
485
501
|
lowered.includes('stopped by user')
|
|
486
502
|
|| lowered.includes('interrupted')
|
|
@@ -1275,9 +1291,9 @@ function createClaudeEngine(deps) {
|
|
|
1275
1291
|
};
|
|
1276
1292
|
const _ackBoundKey = _ackAgentMap[_ackChatIdStr] || projectKeyFromVirtualChatId(_ackChatIdStr);
|
|
1277
1293
|
const _ackBoundProj = _ackBoundKey && config.projects ? config.projects[_ackBoundKey] : null;
|
|
1278
|
-
// _ackCardHeader: non-null for
|
|
1279
|
-
let _ackCardHeader = (_ackBoundProj && _ackBoundProj.
|
|
1280
|
-
? { title: `${_ackBoundProj.icon} ${_ackBoundProj.name}`, color: _ackBoundProj.color || 'blue' }
|
|
1294
|
+
// _ackCardHeader: non-null for bound projects with a name; passed to editMessage to preserve header on streaming edits
|
|
1295
|
+
let _ackCardHeader = (_ackBoundProj && _ackBoundProj.name)
|
|
1296
|
+
? { title: `${_ackBoundProj.icon || '🤖'} ${_ackBoundProj.name}`, color: _ackBoundProj.color || 'blue' }
|
|
1281
1297
|
: null;
|
|
1282
1298
|
// Reuse card from a paused merge (same card, no new push)
|
|
1283
1299
|
const _pausedCard = _pausedCards.get(chatId);
|
|
@@ -1287,7 +1303,6 @@ function createClaudeEngine(deps) {
|
|
|
1287
1303
|
const cardAge = _pausedCard.savedAt ? Date.now() - _pausedCard.savedAt : 0;
|
|
1288
1304
|
if (cardAge > 30000) {
|
|
1289
1305
|
log('INFO', `[askClaude] Discarding stale paused card for ${chatId} (${Math.round(cardAge / 1000)}s old)`);
|
|
1290
|
-
if (_pausedCard.statusMsgId && bot.deleteMessage) bot.deleteMessage(chatId, _pausedCard.statusMsgId).catch(() => {});
|
|
1291
1306
|
} else {
|
|
1292
1307
|
statusMsgId = _pausedCard.statusMsgId;
|
|
1293
1308
|
if (_pausedCard.cardHeader) _ackCardHeader = _pausedCard.cardHeader;
|
|
@@ -1365,6 +1380,10 @@ function createClaudeEngine(deps) {
|
|
|
1365
1380
|
const sessionRaw = getSession(sessionChatId);
|
|
1366
1381
|
const boundCwd = (boundProject && boundProject.cwd) ? normalizeCwd(boundProject.cwd) : null;
|
|
1367
1382
|
const boundEngineName = (boundProject && boundProject.engine) ? normalizeEngineName(boundProject.engine) : getDefaultEngine();
|
|
1383
|
+
// effectiveCwd: single source of truth for this request's working directory.
|
|
1384
|
+
// For bound projects, config always wins over stored session cwd.
|
|
1385
|
+
// Resolved once here; all downstream createSession/spawn calls use this.
|
|
1386
|
+
let effectiveCwd = boundCwd || null;
|
|
1368
1387
|
|
|
1369
1388
|
// Engine is determined from config only — bound agent config wins, then global default.
|
|
1370
1389
|
const engineName = normalizeEngineName(
|
|
@@ -1406,6 +1425,14 @@ function createClaudeEngine(deps) {
|
|
|
1406
1425
|
let session = resolveSessionForEngine(sessionChatId, engineName) || { cwd: boundCwd || HOME, engine: engineName, id: null, started: false };
|
|
1407
1426
|
session.engine = engineName; // keep local copy for Codex resume detection below
|
|
1408
1427
|
session.logicalChatId = sessionChatId;
|
|
1428
|
+
// Finalize effectiveCwd: bound config > stored session > HOME
|
|
1429
|
+
if (!effectiveCwd) effectiveCwd = (session && session.cwd) || HOME;
|
|
1430
|
+
// Correct stored cwd if it drifted from config (e.g., stale state from prior bug)
|
|
1431
|
+
if (session.cwd !== effectiveCwd) {
|
|
1432
|
+
log('WARN', `[SessionCwd] correcting session cwd for ${sessionChatId}: ${session.cwd || 'unknown'} -> ${effectiveCwd}`);
|
|
1433
|
+
session = { ...session, cwd: effectiveCwd };
|
|
1434
|
+
await patchSessionSerialized(sessionChatId, (cur) => ({ ...cur, cwd: effectiveCwd }));
|
|
1435
|
+
}
|
|
1409
1436
|
|
|
1410
1437
|
// Warm pool: check if a persistent process is available for this session (Claude only).
|
|
1411
1438
|
// Declared early so downstream logic can skip expensive operations when reusing warm process.
|
|
@@ -1425,7 +1452,7 @@ function createClaudeEngine(deps) {
|
|
|
1425
1452
|
}
|
|
1426
1453
|
session = createSession(
|
|
1427
1454
|
sessionChatId,
|
|
1428
|
-
|
|
1455
|
+
effectiveCwd,
|
|
1429
1456
|
boundProject && boundProject.name ? boundProject.name : '',
|
|
1430
1457
|
engineName,
|
|
1431
1458
|
engineName === 'codex' ? requestedCodexPermissionProfile : undefined
|
|
@@ -1492,7 +1519,7 @@ function createClaudeEngine(deps) {
|
|
|
1492
1519
|
const resumeInspection = inspectClaudeResumeSession(session, model);
|
|
1493
1520
|
if (resumeInspection.shouldResume === false) {
|
|
1494
1521
|
log('INFO', `[ModelPin] session ${session.id.slice(0, 8)} flagged as ${resumeInspection.reason}; starting fresh Claude session`);
|
|
1495
|
-
session = createSession(sessionChatId,
|
|
1522
|
+
session = createSession(sessionChatId, effectiveCwd, boundProject && boundProject.name ? boundProject.name : '', runtime.name);
|
|
1496
1523
|
} else if (resumeInspection.modelPin) {
|
|
1497
1524
|
if (resumeInspection.modelPin !== model) {
|
|
1498
1525
|
log('INFO', `[ModelPin] resuming ${session.id.slice(0, 8)} with original model ${resumeInspection.modelPin} (configured: ${model})`);
|
|
@@ -1546,6 +1573,8 @@ function createClaudeEngine(deps) {
|
|
|
1546
1573
|
const memory = require('./memory');
|
|
1547
1574
|
|
|
1548
1575
|
// L1: NOW.md per-agent whiteboard injection(按 projectKey 隔离,防并发冲突)
|
|
1576
|
+
// One-shot: inject once then clear, same pattern as compactContext.
|
|
1577
|
+
// Prevents re-injection on daemon restart or new session for the same chat.
|
|
1549
1578
|
if (!session.started) {
|
|
1550
1579
|
try {
|
|
1551
1580
|
const nowDir = path.join(HOME, '.metame', 'memory', 'now');
|
|
@@ -1555,6 +1584,8 @@ function createClaudeEngine(deps) {
|
|
|
1555
1584
|
const nowContent = fs.readFileSync(nowPath, 'utf8').trim();
|
|
1556
1585
|
if (nowContent) {
|
|
1557
1586
|
memoryHint += `\n\n[Current task context:\n${nowContent}]`;
|
|
1587
|
+
// Clear after injection to prevent re-triggering on next session start
|
|
1588
|
+
try { fs.writeFileSync(nowPath, '', 'utf8'); } catch { /* non-critical */ }
|
|
1558
1589
|
}
|
|
1559
1590
|
}
|
|
1560
1591
|
} catch { /* non-critical */ }
|
|
@@ -1605,6 +1636,33 @@ function createClaudeEngine(deps) {
|
|
|
1605
1636
|
}
|
|
1606
1637
|
}
|
|
1607
1638
|
|
|
1639
|
+
// Inject latest nightly insight (decisions/lessons) — one-liner per file, ~100 tokens
|
|
1640
|
+
if (!session.started) {
|
|
1641
|
+
try {
|
|
1642
|
+
const reflectDirs = [
|
|
1643
|
+
path.join(HOME, '.metame', 'memory', 'decisions'),
|
|
1644
|
+
path.join(HOME, '.metame', 'memory', 'lessons'),
|
|
1645
|
+
];
|
|
1646
|
+
const reflectItems = [];
|
|
1647
|
+
for (const dir of reflectDirs) {
|
|
1648
|
+
if (!fs.existsSync(dir)) continue;
|
|
1649
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.md')).sort();
|
|
1650
|
+
const latest = files[files.length - 1];
|
|
1651
|
+
if (!latest) continue;
|
|
1652
|
+
const content = fs.readFileSync(path.join(dir, latest), 'utf8');
|
|
1653
|
+
// Extract ## headings as one-line summaries (skip frontmatter)
|
|
1654
|
+
const headings = content.match(/^## .+$/gm);
|
|
1655
|
+
if (headings && headings.length > 0) {
|
|
1656
|
+
const type = dir.endsWith('decisions') ? 'decision' : 'lesson';
|
|
1657
|
+
reflectItems.push(...headings.slice(0, 2).map(h => `- [${type}] ${h.replace(/^## /, '')}`));
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (reflectItems.length > 0) {
|
|
1661
|
+
memoryHint += `\n\n[Recent insights:\n${reflectItems.join('\n')}]`;
|
|
1662
|
+
}
|
|
1663
|
+
} catch { /* non-critical */ }
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1608
1666
|
memory.close();
|
|
1609
1667
|
} catch (e) {
|
|
1610
1668
|
if (e.code !== 'MODULE_NOT_FOUND') log('WARN', `Memory injection failed: ${e.message}`);
|
|
@@ -1621,10 +1679,16 @@ function createClaudeEngine(deps) {
|
|
|
1621
1679
|
brainDoc = brain;
|
|
1622
1680
|
const cmap = brain && brain.user_competence_map;
|
|
1623
1681
|
if (cmap && typeof cmap === 'object' && Object.keys(cmap).length > 0) {
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1682
|
+
const entries = Object.entries(cmap);
|
|
1683
|
+
const allExpert = entries.every(([, level]) => String(level).toLowerCase() === 'expert');
|
|
1684
|
+
if (allExpert) {
|
|
1685
|
+
zdpHint = `\n- User is expert-level across all domains. Skip basics, no analogies needed.`;
|
|
1686
|
+
} else {
|
|
1687
|
+
const lines = entries
|
|
1688
|
+
.map(([domain, level]) => ` ${domain}: ${level}`)
|
|
1689
|
+
.join('\n');
|
|
1690
|
+
zdpHint = `\n- User competence map (adjust explanation depth accordingly):\n${lines}\n Rule: expert→skip basics; intermediate→brief rationale; beginner→one-line analogy.`;
|
|
1691
|
+
}
|
|
1628
1692
|
}
|
|
1629
1693
|
}
|
|
1630
1694
|
} catch { /* non-critical */ }
|
|
@@ -1636,6 +1700,19 @@ function createClaudeEngine(deps) {
|
|
|
1636
1700
|
} catch { /* ignore */ }
|
|
1637
1701
|
}
|
|
1638
1702
|
|
|
1703
|
+
// Self-reflection patterns: behavioral guardrails distilled from past mistakes
|
|
1704
|
+
let reflectHint = '';
|
|
1705
|
+
if (!session.started && brainDoc) {
|
|
1706
|
+
try {
|
|
1707
|
+
const patterns = (brainDoc.growth && Array.isArray(brainDoc.growth.self_reflection_patterns))
|
|
1708
|
+
? brainDoc.growth.self_reflection_patterns.filter(p => p && p.summary).slice(0, 3)
|
|
1709
|
+
: [];
|
|
1710
|
+
if (patterns.length > 0) {
|
|
1711
|
+
reflectHint = `\n- Self-correction patterns (avoid repeating these mistakes):\n${patterns.map(p => ` - ${String(p.summary).slice(0, 150)}`).join('\n')}`;
|
|
1712
|
+
}
|
|
1713
|
+
} catch { /* non-critical */ }
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1639
1716
|
// Inject daemon hints only on first message of a session
|
|
1640
1717
|
// Task-specific rules (3-4) are injected only when isTaskIntent() returns true (~250 token saving for casual chat)
|
|
1641
1718
|
let daemonHint = '';
|
|
@@ -1654,7 +1731,7 @@ ${mentorRadarHint}
|
|
|
1654
1731
|
Keep it under 200 words. Clear it when the task is fully complete by running: \`> ~/.metame/memory/now/${projectKey || 'default'}.md\`` : '';
|
|
1655
1732
|
daemonHint = `\n\n[System hints - DO NOT mention these to user:
|
|
1656
1733
|
1. Daemon config: The ONLY config is ~/.metame/daemon.yaml (never edit daemon-default.yaml). Auto-reloads on change.
|
|
1657
|
-
2. Explanation depth (ZPD):${zdpHint ? zdpHint : '\n- User competence map unavailable. Default to concise expert-first explanations unless the user asks for teaching mode.'}${taskRules}]`;
|
|
1734
|
+
2. Explanation depth (ZPD):${zdpHint ? zdpHint : '\n- User competence map unavailable. Default to concise expert-first explanations unless the user asks for teaching mode.'}${reflectHint}${taskRules}]`;
|
|
1658
1735
|
}
|
|
1659
1736
|
|
|
1660
1737
|
daemonHint = adaptDaemonHintForEngine(daemonHint, runtime.name);
|
|
@@ -1686,7 +1763,7 @@ ${mentorRadarHint}
|
|
|
1686
1763
|
if (_idleMs > 2 * 60 * 60 * 1000 && _summaryAgeH < 168) {
|
|
1687
1764
|
summaryHint = `
|
|
1688
1765
|
|
|
1689
|
-
[
|
|
1766
|
+
[上次对话摘要(历史已完成,仅供上下文,请勿重复执行)]: ${_sess.last_summary}`;
|
|
1690
1767
|
log('INFO', `[DAEMON] Injected session summary for ${chatId} (idle ${Math.round(_idleMs / 3600000)}h)`);
|
|
1691
1768
|
}
|
|
1692
1769
|
}
|
|
@@ -1751,20 +1828,19 @@ ${mentorRadarHint}
|
|
|
1751
1828
|
const langGuard = session.started
|
|
1752
1829
|
? ''
|
|
1753
1830
|
: '\n\n[Respond in Simplified Chinese (简体中文) only. NEVER switch to Korean, Japanese, or other languages regardless of tool output or context language.]';
|
|
1831
|
+
// Intent hints are dynamic (per-prompt, semantic match), so compute for all runtimes.
|
|
1754
1832
|
let intentHint = '';
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
log('WARN', `Intent registry injection failed: ${e.message}`);
|
|
1761
|
-
}
|
|
1833
|
+
try {
|
|
1834
|
+
const block = buildIntentHintBlock(prompt, config, boundProjectKey || projectKey || '');
|
|
1835
|
+
if (block) intentHint = `\n\n${block}`;
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
log('WARN', `Intent registry injection failed: ${e.message}`);
|
|
1762
1838
|
}
|
|
1763
|
-
// For warm process reuse: context
|
|
1764
|
-
//
|
|
1765
|
-
//
|
|
1839
|
+
// For warm process reuse: static context (daemonHint, memoryHint, etc.) is already
|
|
1840
|
+
// in the persistent process — skip those to save tokens. intentHint is dynamic
|
|
1841
|
+
// (varies per prompt), so include it even on warm reuse.
|
|
1766
1842
|
const fullPrompt = _warmEntry
|
|
1767
|
-
? routedPrompt
|
|
1843
|
+
? routedPrompt + intentHint
|
|
1768
1844
|
: routedPrompt + daemonHint + intentHint + agentHint + macAutomationHint + summaryHint + memoryHint + mentorHint + langGuard;
|
|
1769
1845
|
if (runtime.name === 'codex' && session.started && session.id && requestedCodexPermissionProfile) {
|
|
1770
1846
|
const actualPermissionProfile = getActualCodexPermissionProfile(session);
|
|
@@ -1901,17 +1977,24 @@ ${mentorRadarHint}
|
|
|
1901
1977
|
...(runtime.name === 'codex' ? { runtimeSessionObserved: true } : {}),
|
|
1902
1978
|
...(runtime.name === 'codex' ? actualPermissionProfile : {}),
|
|
1903
1979
|
};
|
|
1904
|
-
return { ...cur, cwd:
|
|
1980
|
+
return { ...cur, cwd: effectiveCwd || cur.cwd || HOME, engines };
|
|
1905
1981
|
});
|
|
1906
1982
|
if (runtime.name === 'codex' && wasStarted && prevSessionId && prevSessionId !== safeNextId && prevSessionId !== '__continue__') {
|
|
1907
1983
|
log('WARN', `Codex thread migrated for ${chatId}: ${prevSessionId.slice(0, 8)} -> ${safeNextId.slice(0, 8)}`);
|
|
1908
1984
|
}
|
|
1985
|
+
// Keep card header in sync with the real session ID reported by the engine
|
|
1986
|
+
if (_ackCardHeader && _ackCardHeader._baseTitle) {
|
|
1987
|
+
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader._baseTitle}(${safeNextId.slice(0, 8)})` };
|
|
1988
|
+
}
|
|
1909
1989
|
};
|
|
1910
1990
|
|
|
1911
1991
|
// Check if user cancelled during pre-spawn phase (sentinel was marked aborted)
|
|
1912
1992
|
// Stamp session ID on card header so user can track session continuity
|
|
1993
|
+
if (_ackCardHeader) {
|
|
1994
|
+
_ackCardHeader._baseTitle = _ackCardHeader.title; // preserve original title for onSession updates
|
|
1995
|
+
}
|
|
1913
1996
|
if (session && session.id && _ackCardHeader) {
|
|
1914
|
-
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader.
|
|
1997
|
+
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader._baseTitle}(${session.id.slice(0, 8)})` };
|
|
1915
1998
|
}
|
|
1916
1999
|
|
|
1917
2000
|
const _preSentinel = activeProcesses.get(chatId);
|
|
@@ -1978,7 +2061,7 @@ ${mentorRadarHint}
|
|
|
1978
2061
|
);
|
|
1979
2062
|
session = createSession(
|
|
1980
2063
|
sessionChatId,
|
|
1981
|
-
|
|
2064
|
+
effectiveCwd,
|
|
1982
2065
|
boundProject && boundProject.name ? boundProject.name : '',
|
|
1983
2066
|
'codex',
|
|
1984
2067
|
requestedCodexPermissionProfile
|
|
@@ -2055,7 +2138,7 @@ ${mentorRadarHint}
|
|
|
2055
2138
|
if (resumeFailure.kind !== 'interrupted') {
|
|
2056
2139
|
session = createSession(
|
|
2057
2140
|
sessionChatId,
|
|
2058
|
-
|
|
2141
|
+
effectiveCwd,
|
|
2059
2142
|
boundProject && boundProject.name ? boundProject.name : '',
|
|
2060
2143
|
'codex',
|
|
2061
2144
|
requestedCodexPermissionProfile
|
|
@@ -2163,6 +2246,16 @@ ${mentorRadarHint}
|
|
|
2163
2246
|
return { ok: true };
|
|
2164
2247
|
}
|
|
2165
2248
|
|
|
2249
|
+
// Merge-pause with partial output: save card for reuse, discard partial output
|
|
2250
|
+
if (output && errorCode === 'INTERRUPTED_MERGE_PAUSE') {
|
|
2251
|
+
if (statusMsgId) {
|
|
2252
|
+
_pausedCards.set(chatId, { statusMsgId, cardHeader: _ackCardHeader, savedAt: Date.now() });
|
|
2253
|
+
if (bot.editMessage) bot.editMessage(chatId, statusMsgId, '⏸ 合并中…', _ackCardHeader).catch(() => {});
|
|
2254
|
+
log('INFO', `[askClaude] Merge-pause with partial output, saved card ${statusMsgId} for ${chatId}`);
|
|
2255
|
+
}
|
|
2256
|
+
return { ok: false, error: 'Paused for merge', errorCode };
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2166
2259
|
if (output) {
|
|
2167
2260
|
if (runtime.name === 'codex') {
|
|
2168
2261
|
_codexResumeRetryTs.delete(getCodexResumeRetryKey(chatId, 'interrupted'));
|
|
@@ -2338,6 +2431,12 @@ ${mentorRadarHint}
|
|
|
2338
2431
|
} catch { /* non-critical — memory module may not be available */ }
|
|
2339
2432
|
});
|
|
2340
2433
|
}
|
|
2434
|
+
// Speculatively save card for pipeline post-resume flush reuse.
|
|
2435
|
+
// If no follow-up arrives, the card expires (30s TTL in _pausedCards consumer).
|
|
2436
|
+
const _replyMsgId = replyMsg && replyMsg.message_id;
|
|
2437
|
+
if (_replyMsgId && _ackCardHeader) {
|
|
2438
|
+
_pausedCards.set(chatId, { statusMsgId: _replyMsgId, cardHeader: _ackCardHeader, savedAt: Date.now() });
|
|
2439
|
+
}
|
|
2341
2440
|
return { ok: !timedOut };
|
|
2342
2441
|
} else {
|
|
2343
2442
|
const errMsg = error || 'Unknown error';
|
|
@@ -2363,7 +2462,7 @@ ${mentorRadarHint}
|
|
|
2363
2462
|
if (runtime.name === 'claude' && _isSessionResumeFail) {
|
|
2364
2463
|
const _reason = errMsg.includes('already in use') ? 'locked' : _isThinkingSignatureError ? 'thinking-signature-invalid' : 'not found';
|
|
2365
2464
|
log('WARN', `Session ${session.id} unusable (${_reason}), creating new`);
|
|
2366
|
-
session = createSession(sessionChatId,
|
|
2465
|
+
session = createSession(sessionChatId, effectiveCwd, '', runtime.name);
|
|
2367
2466
|
|
|
2368
2467
|
const retryArgs = runtime.buildArgs({
|
|
2369
2468
|
model,
|
|
@@ -643,6 +643,19 @@ function createCommandRouter(deps) {
|
|
|
643
643
|
return;
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
// /btw — quick side question (read-only, concise, bypasses cooldown)
|
|
647
|
+
if (/^\/btw(\s|$)/i.test(text)) {
|
|
648
|
+
const btwQuestion = text.replace(/^\/btw\s*/i, '').trim();
|
|
649
|
+
if (!btwQuestion) {
|
|
650
|
+
await bot.sendMessage(chatId, '用法: /btw <问题>\n快速提问,不影响主会话节奏');
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const btwPrompt = `[Side question — answer concisely from existing context, no need for tools]\n\n${btwQuestion}`;
|
|
654
|
+
resetCooldown(chatId);
|
|
655
|
+
await askClaude(bot, chatId, btwPrompt, config, true, senderId);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
646
659
|
if (text.startsWith('/')) {
|
|
647
660
|
const currentModel = (config.daemon && config.daemon.model) || 'opus';
|
|
648
661
|
const currentProvider = providerMod ? providerMod.getActiveName() : 'anthropic';
|
|
@@ -664,6 +677,9 @@ function createCommandRouter(deps) {
|
|
|
664
677
|
'/agent reset — 重置当前 Agent 角色',
|
|
665
678
|
'/agent soul [repair] — 查看/修复 Agent Soul 身份层',
|
|
666
679
|
'',
|
|
680
|
+
'💬 快捷:',
|
|
681
|
+
'/btw <问题> — 快速旁白提问(只读,不打断主任务)',
|
|
682
|
+
'',
|
|
667
683
|
'📂 Session 管理:',
|
|
668
684
|
'/new [path] [name] — 新建会话',
|
|
669
685
|
'/sessions — 浏览所有最近会话',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { normalizeEngineName: _normalizeEngine } = require('./daemon-utils');
|
|
4
|
+
|
|
3
5
|
function createCommandSessionResolver(deps) {
|
|
4
6
|
const {
|
|
5
7
|
path,
|
|
@@ -11,7 +13,7 @@ function createCommandSessionResolver(deps) {
|
|
|
11
13
|
} = deps;
|
|
12
14
|
|
|
13
15
|
function normalizeEngineName(name) {
|
|
14
|
-
return
|
|
16
|
+
return _normalizeEngine(name, getDefaultEngine);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
function inferStoredEngine(rawSession) {
|
|
@@ -45,6 +45,7 @@ heartbeat:
|
|
|
45
45
|
type: script
|
|
46
46
|
command: node ~/.metame/distill.js
|
|
47
47
|
interval: 4h
|
|
48
|
+
timeout: 5m
|
|
48
49
|
precondition: "test -s ~/.metame/raw_signals.jsonl"
|
|
49
50
|
require_idle: true
|
|
50
51
|
notify: false
|
|
@@ -74,6 +75,7 @@ heartbeat:
|
|
|
74
75
|
type: script
|
|
75
76
|
command: node ~/.metame/skill-evolution.js
|
|
76
77
|
interval: 12h
|
|
78
|
+
timeout: 5m
|
|
77
79
|
precondition: "test -s ~/.metame/skill_signals.jsonl"
|
|
78
80
|
require_idle: true
|
|
79
81
|
notify: false
|
|
@@ -104,7 +106,7 @@ heartbeat:
|
|
|
104
106
|
at: "01:30"
|
|
105
107
|
require_idle: true
|
|
106
108
|
notify: false
|
|
107
|
-
enabled:
|
|
109
|
+
enabled: false
|
|
108
110
|
|
|
109
111
|
# Legacy flat tasks (no project isolation). New tasks should go under projects: above.
|
|
110
112
|
# Examples — uncomment or add your own:
|
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { execSync } = require('child_process');
|
|
7
|
+
const { normalizeEngineName } = require('./daemon-utils');
|
|
7
8
|
|
|
8
9
|
const CODEX_TOOL_MAP = Object.freeze({
|
|
9
10
|
command_execution: 'Bash',
|
|
@@ -14,11 +15,6 @@ const CODEX_TOOL_MAP = Object.freeze({
|
|
|
14
15
|
web_fetch: 'WebFetch',
|
|
15
16
|
});
|
|
16
17
|
|
|
17
|
-
function normalizeEngineName(name) {
|
|
18
|
-
const text = String(name || '').trim().toLowerCase();
|
|
19
|
-
return text === 'codex' ? 'codex' : 'claude';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
18
|
function resolveBinary(engineName, deps = {}) {
|
|
23
19
|
const engine = normalizeEngineName(engineName);
|
|
24
20
|
const home = deps.HOME || os.homedir();
|