metame-cli 1.5.21 → 1.5.23
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 +3 -2
- package/index.js +25 -24
- package/package.json +1 -1
- package/scripts/agent-intent-shared.js +111 -0
- package/scripts/daemon-agent-commands.js +160 -354
- package/scripts/daemon-agent-intent.js +282 -0
- package/scripts/daemon-agent-lifecycle.js +243 -0
- package/scripts/daemon-agent-workflow.js +295 -0
- package/scripts/daemon-bridges.js +18 -5
- package/scripts/daemon-claude-engine.js +74 -75
- package/scripts/daemon-command-router.js +24 -294
- package/scripts/daemon-prompt-context.js +127 -0
- package/scripts/daemon-reactive-lifecycle.js +138 -6
- package/scripts/daemon-session-commands.js +16 -2
- package/scripts/daemon-session-store.js +104 -0
- package/scripts/daemon-team-workflow.js +146 -0
- package/scripts/daemon.js +14 -3
- package/scripts/docs/hook-config.md +41 -21
- package/scripts/docs/maintenance-manual.md +2 -2
- package/scripts/docs/orphan-files-review.md +1 -1
- package/scripts/docs/pointer-map.md +2 -2
- package/scripts/hooks/intent-agent-capability.js +51 -0
- package/scripts/hooks/intent-doc-router.js +23 -11
- package/scripts/hooks/intent-memory-recall.js +1 -3
- package/scripts/hooks/intent-team-dispatch.js +1 -1
- package/scripts/intent-registry.js +78 -14
- package/scripts/ops-mission-queue.js +101 -36
- package/scripts/ops-reactive-bootstrap.js +86 -0
- package/scripts/resolve-yaml.js +3 -0
- package/scripts/runtime-bootstrap.js +77 -0
- package/scripts/hooks/intent-engine.js +0 -75
- package/scripts/hooks/team-context.js +0 -143
|
@@ -30,6 +30,7 @@ function createSessionCommandHandler(deps) {
|
|
|
30
30
|
sessionRichLabel,
|
|
31
31
|
buildSessionCardElements,
|
|
32
32
|
getSessionRecentContext,
|
|
33
|
+
getSessionRecentDialogue,
|
|
33
34
|
getDefaultEngine = () => 'claude',
|
|
34
35
|
} = deps;
|
|
35
36
|
|
|
@@ -430,11 +431,24 @@ function createSessionCommandHandler(deps) {
|
|
|
430
431
|
const recentCtx = typeof getSessionRecentContext === 'function'
|
|
431
432
|
? getSessionRecentContext(target.sessionId)
|
|
432
433
|
: null;
|
|
434
|
+
const recentDialogue = typeof getSessionRecentDialogue === 'function'
|
|
435
|
+
? getSessionRecentDialogue(target.sessionId, 4)
|
|
436
|
+
: null;
|
|
433
437
|
const title = target.customTitle || target.summary || target.sessionId.slice(0, 8);
|
|
434
438
|
const lines = [`▶️ Resumed: ${title}`];
|
|
435
439
|
if (attached.cwd) lines.push(`📁 ${path.basename(attached.cwd)}`);
|
|
436
|
-
if (
|
|
437
|
-
|
|
440
|
+
if (Array.isArray(recentDialogue) && recentDialogue.length > 0) {
|
|
441
|
+
lines.push('');
|
|
442
|
+
lines.push('最近对话:');
|
|
443
|
+
recentDialogue.forEach((item) => {
|
|
444
|
+
const marker = item.role === 'assistant' ? '🤖' : '👤';
|
|
445
|
+
const text = String(item.text || '').replace(/\n/g, ' ').slice(0, 120);
|
|
446
|
+
if (text) lines.push(`${marker} ${text}`);
|
|
447
|
+
});
|
|
448
|
+
} else {
|
|
449
|
+
if (recentCtx && recentCtx.lastUser) lines.push(`👤 ${String(recentCtx.lastUser).replace(/\n/g, ' ').slice(0, 80)}`);
|
|
450
|
+
if (recentCtx && recentCtx.lastAssistant) lines.push(`🤖 ${String(recentCtx.lastAssistant).replace(/\n/g, ' ').slice(0, 80)}`);
|
|
451
|
+
}
|
|
438
452
|
await bot.sendMessage(chatId, lines.join('\n'));
|
|
439
453
|
return true;
|
|
440
454
|
}
|
|
@@ -352,6 +352,29 @@ function createSessionStore(deps) {
|
|
|
352
352
|
return '';
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
function extractRecentClaudeDialogueFromLines(lines, maxMessages = 4) {
|
|
356
|
+
const collected = [];
|
|
357
|
+
for (const line of lines) {
|
|
358
|
+
if (!line) continue;
|
|
359
|
+
try {
|
|
360
|
+
const d = JSON.parse(line);
|
|
361
|
+
if (d.type === 'user' && d.message && d.userType !== 'internal') {
|
|
362
|
+
const raw = _extractMessageText(d);
|
|
363
|
+
if (raw.length > 2) {
|
|
364
|
+
collected.push({ role: 'user', text: raw.slice(0, 160) });
|
|
365
|
+
}
|
|
366
|
+
} else if (d.type === 'assistant' && d.message) {
|
|
367
|
+
const raw = _extractMessageText(d);
|
|
368
|
+
if (raw.length > 2) {
|
|
369
|
+
collected.push({ role: 'assistant', text: raw.slice(0, 160) });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} catch { /* skip */ }
|
|
373
|
+
if (collected.length >= maxMessages) break;
|
|
374
|
+
}
|
|
375
|
+
return collected.reverse();
|
|
376
|
+
}
|
|
377
|
+
|
|
355
378
|
function scanClaudeSessions() {
|
|
356
379
|
try {
|
|
357
380
|
if (!fs.existsSync(CLAUDE_PROJECTS_DIR)) return [];
|
|
@@ -638,6 +661,59 @@ function createSessionStore(deps) {
|
|
|
638
661
|
}
|
|
639
662
|
}
|
|
640
663
|
|
|
664
|
+
function parseCodexSessionRecentDialogue(sessionFile, maxMessages = 4) {
|
|
665
|
+
try {
|
|
666
|
+
if (!sessionFile || !fs.existsSync(sessionFile)) return [];
|
|
667
|
+
const lines = fs.readFileSync(sessionFile, 'utf8').split('\n').filter(Boolean);
|
|
668
|
+
const items = [];
|
|
669
|
+
let pendingAssistant = '';
|
|
670
|
+
|
|
671
|
+
function pushDialogueItem(role, text) {
|
|
672
|
+
const clean = String(text || '').trim();
|
|
673
|
+
if (!clean) return;
|
|
674
|
+
const clipped = clean.slice(0, 160);
|
|
675
|
+
const prev = items[items.length - 1];
|
|
676
|
+
if (prev && prev.role === role) {
|
|
677
|
+
prev.text = clipped;
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
items.push({ role, text: clipped });
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
for (const line of lines) {
|
|
684
|
+
let entry;
|
|
685
|
+
try {
|
|
686
|
+
entry = JSON.parse(line);
|
|
687
|
+
} catch {
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (entry.type === 'response_item' && entry.payload && entry.payload.type === 'message') {
|
|
691
|
+
const role = String(entry.payload.role || '').toLowerCase();
|
|
692
|
+
if (role !== 'user' && role !== 'assistant') continue;
|
|
693
|
+
const text = stripCodexInjectedHints(extractCodexMessageText(entry.payload.content || entry.payload));
|
|
694
|
+
if (!text) continue;
|
|
695
|
+
if (role === 'user') {
|
|
696
|
+
if (pendingAssistant) {
|
|
697
|
+
pushDialogueItem('assistant', pendingAssistant);
|
|
698
|
+
pendingAssistant = '';
|
|
699
|
+
}
|
|
700
|
+
pushDialogueItem('user', text);
|
|
701
|
+
} else {
|
|
702
|
+
pendingAssistant = '';
|
|
703
|
+
pushDialogueItem('assistant', text);
|
|
704
|
+
}
|
|
705
|
+
} else if (entry.type === 'event_msg' && entry.payload && entry.payload.type === 'agent_message') {
|
|
706
|
+
const text = stripCodexInjectedHints(extractCodexMessageText(entry.payload.message));
|
|
707
|
+
if (text) pendingAssistant = text;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (pendingAssistant) pushDialogueItem('assistant', pendingAssistant);
|
|
711
|
+
return items.slice(-maxMessages);
|
|
712
|
+
} catch {
|
|
713
|
+
return [];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
641
717
|
function enrichCodexSession(session) {
|
|
642
718
|
if (!session || session._enriched) return session;
|
|
643
719
|
try {
|
|
@@ -1173,6 +1249,32 @@ function createSessionStore(deps) {
|
|
|
1173
1249
|
} catch { return null; }
|
|
1174
1250
|
}
|
|
1175
1251
|
|
|
1252
|
+
function getSessionRecentDialogue(sessionId, maxMessages = 4) {
|
|
1253
|
+
try {
|
|
1254
|
+
const limit = Math.max(1, Math.min(Number(maxMessages) || 4, 8));
|
|
1255
|
+
const sessionFile = findSessionFile(sessionId);
|
|
1256
|
+
if (sessionFile) {
|
|
1257
|
+
const stat = fs.statSync(sessionFile);
|
|
1258
|
+
const tailSize = Math.min(262144, stat.size);
|
|
1259
|
+
const buf = Buffer.alloc(tailSize);
|
|
1260
|
+
const fd = fs.openSync(sessionFile, 'r');
|
|
1261
|
+
try {
|
|
1262
|
+
fs.readSync(fd, buf, 0, tailSize, stat.size - tailSize);
|
|
1263
|
+
} finally {
|
|
1264
|
+
fs.closeSync(fd);
|
|
1265
|
+
}
|
|
1266
|
+
const lines = buf.toString('utf8').split('\n').reverse();
|
|
1267
|
+
const dialogue = extractRecentClaudeDialogueFromLines(lines, limit);
|
|
1268
|
+
return dialogue.length ? dialogue : null;
|
|
1269
|
+
}
|
|
1270
|
+
const codexFile = findCodexSessionFile(sessionId);
|
|
1271
|
+
const dialogue = parseCodexSessionRecentDialogue(codexFile, limit);
|
|
1272
|
+
return dialogue.length ? dialogue : null;
|
|
1273
|
+
} catch {
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1176
1278
|
function markSessionStarted(chatId, engine) {
|
|
1177
1279
|
const state = loadState();
|
|
1178
1280
|
const s = state.sessions[chatId];
|
|
@@ -1368,6 +1470,7 @@ function createSessionStore(deps) {
|
|
|
1368
1470
|
writeSessionName,
|
|
1369
1471
|
markSessionStarted,
|
|
1370
1472
|
getSessionRecentContext,
|
|
1473
|
+
getSessionRecentDialogue,
|
|
1371
1474
|
isEngineSessionValid,
|
|
1372
1475
|
getCodexSessionSandboxProfile,
|
|
1373
1476
|
getCodexSessionPermissionMode,
|
|
@@ -1378,6 +1481,7 @@ function createSessionStore(deps) {
|
|
|
1378
1481
|
stripCodexInjectedHints,
|
|
1379
1482
|
looksLikeInternalCodexPrompt,
|
|
1380
1483
|
parseCodexSessionPreview,
|
|
1484
|
+
parseCodexSessionRecentDialogue,
|
|
1381
1485
|
buildPendingStateSessions,
|
|
1382
1486
|
},
|
|
1383
1487
|
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const VALID_TEAM_COLORS = ['green', 'yellow', 'red', 'blue', 'purple', 'orange', 'pink', 'indigo'];
|
|
4
|
+
|
|
5
|
+
function parseTeamMembers(input, teamName) {
|
|
6
|
+
const memberLines = String(input || '').split(/[,,\n]/).filter(line => line.trim());
|
|
7
|
+
const members = [];
|
|
8
|
+
|
|
9
|
+
for (const line of memberLines) {
|
|
10
|
+
const parts = line.trim().split(':');
|
|
11
|
+
const name = parts[0] && parts[0].trim();
|
|
12
|
+
if (!name) continue;
|
|
13
|
+
|
|
14
|
+
const icon = (parts[1] && parts[1].trim()) || '🤖';
|
|
15
|
+
const rawColor = parts[2] && parts[2].trim().toLowerCase();
|
|
16
|
+
const color = VALID_TEAM_COLORS.includes(rawColor)
|
|
17
|
+
? rawColor
|
|
18
|
+
: VALID_TEAM_COLORS[members.length % VALID_TEAM_COLORS.length];
|
|
19
|
+
|
|
20
|
+
members.push({
|
|
21
|
+
key: name,
|
|
22
|
+
name: `${teamName} · ${name}`,
|
|
23
|
+
icon,
|
|
24
|
+
color,
|
|
25
|
+
nicknames: [name],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return members;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findParentProjectKey({ projects, dirPath, normalizeCwd }) {
|
|
33
|
+
if (!projects || !dirPath) return null;
|
|
34
|
+
const targetDir = normalizeCwd(dirPath);
|
|
35
|
+
|
|
36
|
+
for (const [projKey, proj] of Object.entries(projects)) {
|
|
37
|
+
if (normalizeCwd(proj && proj.cwd ? proj.cwd : '') === targetDir) {
|
|
38
|
+
return projKey;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureTeamMemberWorkspace({ fs, path, execSync, teamDir, teamName, member }) {
|
|
46
|
+
const memberDir = path.join(teamDir, member.key);
|
|
47
|
+
if (!fs.existsSync(memberDir)) fs.mkdirSync(memberDir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
const claudeMdPath = path.join(memberDir, 'CLAUDE.md');
|
|
50
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
51
|
+
fs.writeFileSync(claudeMdPath, `# ${member.name}\n\n(团队成员:${teamName})\n`, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
if (typeof execSync === 'function') execSync('git init -q', { cwd: memberDir, stdio: 'ignore' });
|
|
56
|
+
} catch {
|
|
57
|
+
// Git init is a best-effort enhancement for checkpoints.
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
...member,
|
|
62
|
+
cwd: memberDir,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function registerTeamMembers({
|
|
67
|
+
cfg,
|
|
68
|
+
parentProjectKey,
|
|
69
|
+
members,
|
|
70
|
+
writeConfigSafe,
|
|
71
|
+
backupConfig,
|
|
72
|
+
}) {
|
|
73
|
+
if (!parentProjectKey || !cfg.projects || !cfg.projects[parentProjectKey]) return null;
|
|
74
|
+
|
|
75
|
+
const proj = cfg.projects[parentProjectKey];
|
|
76
|
+
if (!Array.isArray(proj.team)) proj.team = [];
|
|
77
|
+
|
|
78
|
+
for (const member of members) {
|
|
79
|
+
if (proj.team.some(existing => existing && existing.key === member.key)) continue;
|
|
80
|
+
proj.team.push({
|
|
81
|
+
key: member.key,
|
|
82
|
+
name: member.name,
|
|
83
|
+
icon: member.icon,
|
|
84
|
+
color: member.color,
|
|
85
|
+
cwd: member.cwd,
|
|
86
|
+
nicknames: member.nicknames,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof writeConfigSafe === 'function') writeConfigSafe(cfg);
|
|
91
|
+
if (typeof backupConfig === 'function') backupConfig();
|
|
92
|
+
return parentProjectKey;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createTeamWorkspace({
|
|
96
|
+
fs,
|
|
97
|
+
path,
|
|
98
|
+
execSync,
|
|
99
|
+
dirPath,
|
|
100
|
+
teamName,
|
|
101
|
+
members,
|
|
102
|
+
loadConfig,
|
|
103
|
+
normalizeCwd,
|
|
104
|
+
writeConfigSafe,
|
|
105
|
+
backupConfig,
|
|
106
|
+
HOME,
|
|
107
|
+
}) {
|
|
108
|
+
const teamDir = path.join(dirPath, 'team');
|
|
109
|
+
if (!fs.existsSync(teamDir)) fs.mkdirSync(teamDir, { recursive: true });
|
|
110
|
+
|
|
111
|
+
const createdMembers = members.map((member) => ensureTeamMemberWorkspace({
|
|
112
|
+
fs,
|
|
113
|
+
path,
|
|
114
|
+
execSync,
|
|
115
|
+
teamDir,
|
|
116
|
+
teamName,
|
|
117
|
+
member,
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const cfg = typeof loadConfig === 'function' ? loadConfig() : { projects: {} };
|
|
121
|
+
const parentProjectKey = findParentProjectKey({
|
|
122
|
+
projects: cfg.projects,
|
|
123
|
+
dirPath,
|
|
124
|
+
normalizeCwd,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
registerTeamMembers({
|
|
128
|
+
cfg,
|
|
129
|
+
parentProjectKey,
|
|
130
|
+
members: createdMembers,
|
|
131
|
+
writeConfigSafe,
|
|
132
|
+
backupConfig,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
teamDir,
|
|
137
|
+
parentProjectKey,
|
|
138
|
+
memberLines: createdMembers.map((member) => `${member.icon} ${member.key}: ${member.cwd.replace(HOME, '~')}`),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
VALID_TEAM_COLORS,
|
|
144
|
+
parseTeamMembers,
|
|
145
|
+
createTeamWorkspace,
|
|
146
|
+
};
|
package/scripts/daemon.js
CHANGED
|
@@ -36,9 +36,11 @@ const fs = require('fs');
|
|
|
36
36
|
const path = require('path');
|
|
37
37
|
const os = require('os');
|
|
38
38
|
const { execSync, execFileSync, execFile, spawn } = require('child_process');
|
|
39
|
+
const { bootstrapRuntimeModulePaths } = require('./runtime-bootstrap');
|
|
39
40
|
|
|
40
41
|
const HOME = os.homedir();
|
|
41
42
|
const METAME_DIR = path.join(HOME, '.metame');
|
|
43
|
+
bootstrapRuntimeModulePaths(METAME_DIR);
|
|
42
44
|
const CONFIG_FILE = path.join(METAME_DIR, 'daemon.yaml');
|
|
43
45
|
const STATE_FILE = path.join(METAME_DIR, 'daemon_state.json');
|
|
44
46
|
const PID_FILE = path.join(METAME_DIR, 'daemon.pid');
|
|
@@ -507,6 +509,7 @@ function createNullBot(onOutput) {
|
|
|
507
509
|
deleteMessage: async () => { },
|
|
508
510
|
sendFile: noop,
|
|
509
511
|
downloadFile: noop,
|
|
512
|
+
notifyFinalOutput: async (text) => { if (onOutput) onOutput({ body: text, final: true }); return { message_id: '_virtual' }; },
|
|
510
513
|
};
|
|
511
514
|
}
|
|
512
515
|
|
|
@@ -610,6 +613,7 @@ function createStreamForwardBot(realBot, chatId, onOutput = null, opts = {}) {
|
|
|
610
613
|
deleteMessage: async (_, msgId) => { await waitUntilReady(); return realBot.deleteMessage(chatId, msgId); },
|
|
611
614
|
sendFile: async (_, filePath, caption) => { await waitUntilReady(); return realBot.sendFile(chatId, filePath, caption); },
|
|
612
615
|
downloadFile: async (...args) => realBot.downloadFile(...args),
|
|
616
|
+
notifyFinalOutput: async (text) => { if (onOutput) onOutput({ body: text, final: true }); return { message_id: '_virtual' }; },
|
|
613
617
|
};
|
|
614
618
|
}
|
|
615
619
|
|
|
@@ -897,10 +901,11 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
897
901
|
|
|
898
902
|
let _taskFinalized = false;
|
|
899
903
|
const outputHandler = (output) => {
|
|
904
|
+
const isFinalOutput = !!(output && typeof output === 'object' && output.final);
|
|
900
905
|
const outStr = typeof output === 'object' ? (output.body || JSON.stringify(output)) : String(output);
|
|
901
906
|
const displayOut = envelope ? appendTeamTaskResumeHint(outStr, envelope.task_id, envelope.scope_id) : outStr;
|
|
902
907
|
log('INFO', `Dispatch output from ${targetProject}: ${outStr.slice(0, 200)}`);
|
|
903
|
-
if (envelope && taskBoard && !_taskFinalized && outStr.trim().length > 2) {
|
|
908
|
+
if (!isFinalOutput && envelope && taskBoard && !_taskFinalized && outStr.trim().length > 2) {
|
|
904
909
|
const status = inferTaskStatusFromOutput(outStr);
|
|
905
910
|
const artifacts = extractArtifactPaths(outStr);
|
|
906
911
|
const update = {
|
|
@@ -916,9 +921,9 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
916
921
|
});
|
|
917
922
|
_taskFinalized = true;
|
|
918
923
|
}
|
|
919
|
-
if (replyFn && outStr.trim().length > 2) {
|
|
924
|
+
if (!isFinalOutput && replyFn && outStr.trim().length > 2) {
|
|
920
925
|
replyFn(displayOut);
|
|
921
|
-
} else if (!replyFn && fullMsg.callback && fullMsg.from && config) {
|
|
926
|
+
} else if (!isFinalOutput && !replyFn && fullMsg.callback && fullMsg.from && config) {
|
|
922
927
|
// Write result to sender's inbox before dispatching callback
|
|
923
928
|
try {
|
|
924
929
|
const inboxDir = path.join(os.homedir(), '.metame', 'memory', 'inbox', fullMsg.from);
|
|
@@ -954,6 +959,7 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
954
959
|
|
|
955
960
|
// ── Reactive lifecycle hook ──
|
|
956
961
|
try {
|
|
962
|
+
if (!isFinalOutput) return;
|
|
957
963
|
handleReactiveOutput(targetProject, outStr, loadConfig(), {
|
|
958
964
|
log,
|
|
959
965
|
loadState,
|
|
@@ -1761,6 +1767,7 @@ const {
|
|
|
1761
1767
|
sessionLabel,
|
|
1762
1768
|
sessionRichLabel,
|
|
1763
1769
|
getSessionRecentContext,
|
|
1770
|
+
getSessionRecentDialogue,
|
|
1764
1771
|
buildSessionCardElements,
|
|
1765
1772
|
getSession,
|
|
1766
1773
|
getSessionForEngine,
|
|
@@ -2010,6 +2017,8 @@ const { handleSessionCommand } = createSessionCommandHandler({
|
|
|
2010
2017
|
loadSessionTags,
|
|
2011
2018
|
sessionRichLabel,
|
|
2012
2019
|
buildSessionCardElements,
|
|
2020
|
+
getSessionRecentContext,
|
|
2021
|
+
getSessionRecentDialogue,
|
|
2013
2022
|
sessionLabel,
|
|
2014
2023
|
getDefaultEngine,
|
|
2015
2024
|
});
|
|
@@ -2047,6 +2056,7 @@ const { spawnClaudeAsync, askClaude } = createClaudeEngine({
|
|
|
2047
2056
|
findSessionFile,
|
|
2048
2057
|
listRecentSessions,
|
|
2049
2058
|
getSessionRecentContext,
|
|
2059
|
+
getSessionRecentDialogue,
|
|
2050
2060
|
isEngineSessionValid,
|
|
2051
2061
|
getCodexSessionSandboxProfile,
|
|
2052
2062
|
getCodexSessionPermissionMode,
|
|
@@ -2118,6 +2128,7 @@ const { handleAgentCommand } = createAgentCommandHandler({
|
|
|
2118
2128
|
loadSessionTags,
|
|
2119
2129
|
sessionRichLabel,
|
|
2120
2130
|
getSessionRecentContext,
|
|
2131
|
+
getSessionRecentDialogue,
|
|
2121
2132
|
pendingBinds,
|
|
2122
2133
|
pendingAgentFlows,
|
|
2123
2134
|
pendingTeamFlows,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MetaMe
|
|
1
|
+
# MetaMe Intent 配置手册
|
|
2
2
|
|
|
3
3
|
> 自动部署到 `~/.metame/docs/hook-config.md`。源文件:`scripts/docs/hook-config.md`。只编辑 `scripts/`,不要直接改 `~/.metame/`。
|
|
4
4
|
|
|
@@ -8,15 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
UserPromptSubmit (每轮用户输入)
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
└── signal-capture.js → 捕获用户偏好信号(写文件,不注入)
|
|
12
|
+
|
|
13
|
+
Daemon runtime (每轮真正发给引擎前)
|
|
14
|
+
└── intent-registry.js → 意图检测 + 按需拼接提示块
|
|
13
15
|
|
|
14
16
|
Stop (每轮结束)
|
|
15
17
|
└── stop-session-capture.js → session 事件日志 + 工具失败捕获
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
`scripts/intent-registry.js` 是单一维护源,负责调用各意图模块并返回提示块。
|
|
19
|
-
|
|
21
|
+
daemon 在运行时直接复用同一 registry,Claude / Codex 共用这一条注入路径。
|
|
20
22
|
零匹配 → 零输出(不浪费 token)。
|
|
21
23
|
|
|
22
24
|
---
|
|
@@ -25,13 +27,20 @@ Stop (每轮结束)
|
|
|
25
27
|
|
|
26
28
|
| 模块 key | 文件 | 触发条件 | 注入内容 |
|
|
27
29
|
|---------|------|---------|---------|
|
|
30
|
+
| `agent_capability` | `intent-agent-capability.js` | 明确的 Agent 创建/绑定/激活/分身/团队/角色/Soul 管理语境 | 当前真实支持的 `/agent ...` `/activate` 能力提示 |
|
|
28
31
|
| `team_dispatch` | `intent-team-dispatch.js` | 检测到"告诉/让/发给 + 成员名"等联络意图 | `dispatch_to` 命令提示(仅匹配成员) |
|
|
29
32
|
| `ops_assist` | `intent-ops-assist.js` | 回退/日志/重启/gc/状态 相关语境 | `/undo` `/restart` `/logs` `/gc` `/status` 命令提示 |
|
|
30
|
-
| `task_create` | `intent-task-create.js` | 定时/提醒/每天X点 等调度语境 | `/task-add` 命令用法提示 |
|
|
31
33
|
| `file_transfer` | `intent-file-transfer.js` | "发给我/发过来/导出" 等文件传输语境 | `[[FILE:...]]` 协议 + 收发规则 |
|
|
32
34
|
| `weixin_bridge` | `intent-weixin-bridge.js` | "帮我绑定微信/配置微信桥接/开启微信接入/开始微信扫码登录" 等明确桥接语境 | 开启 `weixin.enabled` + `/weixin` 绑定流程提示 |
|
|
33
|
-
| `memory_recall` | `intent-memory-recall.js` | "上次/之前/还记得" 等跨会话回忆语境 | `memory-search.js`
|
|
34
|
-
| `doc_router` | `intent-doc-router.js` | "
|
|
35
|
+
| `memory_recall` | `intent-memory-recall.js` | "上次/之前/还记得" 等跨会话回忆语境 | `memory-search.js` CLI 召回提示 |
|
|
36
|
+
| `doc_router` | `intent-doc-router.js` | "代码结构/脚本入口"、"hook/intent 配置" 等文档导向语境 | 统一 doc-router 文档指引 |
|
|
37
|
+
| `perpetual` | `intent-perpetual.js` | 永续/reactive 任务语境 | `dispatch_to` / `/status perpetual` 等永续协议提示 |
|
|
38
|
+
| `research` | `intent-research.js` | `paper_rev` 项目下的研究语境 | 研究方法与文件落点提示 |
|
|
39
|
+
|
|
40
|
+
注入策略:
|
|
41
|
+
- 零匹配 → 零输出
|
|
42
|
+
- 多模块命中时按优先级裁剪,默认最多注入 2 个 hint、总长不超过约 1200 字符
|
|
43
|
+
- `doc_router` 只作为 fallback;只要已有真实能力提示命中,就不再额外塞文档路由
|
|
35
44
|
---
|
|
36
45
|
|
|
37
46
|
## 开关控制
|
|
@@ -40,13 +49,13 @@ Stop (每轮结束)
|
|
|
40
49
|
|
|
41
50
|
```yaml
|
|
42
51
|
hooks:
|
|
52
|
+
agent_capability: true
|
|
43
53
|
team_dispatch: true # 改为 false 可禁用
|
|
44
54
|
ops_assist: true
|
|
45
|
-
task_create: false # 禁用任务调度提示
|
|
46
55
|
weixin_bridge: true # 默认开启;只匹配明确的微信配置/绑定语境
|
|
47
56
|
```
|
|
48
57
|
|
|
49
|
-
改完立即生效(
|
|
58
|
+
改完立即生效(daemon 每次发请求前都会读取)。
|
|
50
59
|
**不需要重启 daemon。**
|
|
51
60
|
|
|
52
61
|
---
|
|
@@ -70,19 +79,20 @@ module.exports = function detect<Name>(prompt, config, projectKey) {
|
|
|
70
79
|
};
|
|
71
80
|
```
|
|
72
81
|
|
|
73
|
-
2. **注册到 intent-
|
|
82
|
+
2. **注册到 intent-registry.js**:
|
|
74
83
|
|
|
75
84
|
```js
|
|
76
|
-
// scripts/
|
|
85
|
+
// scripts/intent-registry.js
|
|
77
86
|
const INTENT_MODULES = {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
your_name:
|
|
87
|
+
agent_capability: require('./hooks/intent-agent-capability'),
|
|
88
|
+
team_dispatch: require('./hooks/intent-team-dispatch'),
|
|
89
|
+
ops_assist: require('./hooks/intent-ops-assist'),
|
|
90
|
+
your_name: require('./hooks/intent-<name>'), // ← 加这行
|
|
82
91
|
};
|
|
83
92
|
|
|
84
93
|
const DEFAULTS = {
|
|
85
94
|
// ...
|
|
95
|
+
agent_capability: true,
|
|
86
96
|
your_name: true, // ← 加这行(默认开启)
|
|
87
97
|
};
|
|
88
98
|
```
|
|
@@ -96,11 +106,14 @@ hooks:
|
|
|
96
106
|
your_name: true
|
|
97
107
|
```
|
|
98
108
|
|
|
99
|
-
4. **部署**:`node index.js
|
|
109
|
+
4. **部署**:`node index.js`
|
|
100
110
|
|
|
101
111
|
5. **验证**:
|
|
102
112
|
```bash
|
|
103
|
-
|
|
113
|
+
node - <<'EOF'
|
|
114
|
+
const { buildIntentHintBlock } = require('./scripts/intent-registry');
|
|
115
|
+
console.log(buildIntentHintBlock('触发词', {}, ''));
|
|
116
|
+
EOF
|
|
104
117
|
```
|
|
105
118
|
|
|
106
119
|
---
|
|
@@ -109,7 +122,16 @@ echo '{"prompt":"触发词"}' | node ~/.metame/hooks/intent-engine.js
|
|
|
109
122
|
|
|
110
123
|
```bash
|
|
111
124
|
# 测试某个 prompt 是否触发意图
|
|
112
|
-
|
|
125
|
+
node - <<'EOF'
|
|
126
|
+
const { buildIntentHintBlock } = require('./scripts/intent-registry');
|
|
127
|
+
console.log(buildIntentHintBlock('告诉工匠去做这个', {
|
|
128
|
+
projects: {
|
|
129
|
+
business: {
|
|
130
|
+
team: [{ key: 'builder', name: '工匠', nicknames: ['工匠'] }],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}, 'business'));
|
|
134
|
+
EOF
|
|
113
135
|
|
|
114
136
|
# 查看当前已注册的 hooks
|
|
115
137
|
python3 -c "
|
|
@@ -128,9 +150,7 @@ for k, v in s.get('hooks', {}).items():
|
|
|
128
150
|
|
|
129
151
|
| 文件 | 说明 |
|
|
130
152
|
|------|------|
|
|
131
|
-
| `scripts/intent-registry.js` | 共享意图注册表(
|
|
132
|
-
| `scripts/hooks/intent-engine.js` | Claude hook adapter(源文件) |
|
|
133
|
-
| `~/.metame/hooks/intent-engine.js` | 部署副本(copy) |
|
|
153
|
+
| `scripts/intent-registry.js` | 共享意图注册表(daemon 运行时唯一入口) |
|
|
134
154
|
| `scripts/hooks/intent-*.js` | 各意图模块(源文件) |
|
|
135
155
|
| `~/.metame/daemon.yaml` | 用户配置(包含 `hooks:` 开关) |
|
|
136
156
|
| `~/.claude/settings.json` | Claude Code hook 注册表 |
|
|
@@ -179,7 +179,7 @@ feishu:
|
|
|
179
179
|
|
|
180
180
|
在手机端(飞书/Telegram)发送以下任一方式触发创建向导:
|
|
181
181
|
|
|
182
|
-
- 自然语言:`创建团队`、`新建工作组`、`建个团队` 等(`
|
|
182
|
+
- 自然语言:`创建团队`、`新建工作组`、`建个团队` 等(`daemon-agent-intent.js` 统一识别并路由)
|
|
183
183
|
- 命令:`/agent new team`
|
|
184
184
|
|
|
185
185
|
向导分三步,全部在 `daemon-agent-commands.js` 中实现:
|
|
@@ -354,7 +354,7 @@ Claude 看到 hook 注入:
|
|
|
354
354
|
| `daemon-admin-commands.js` | `/dispatch peers` 查看配置 + `/dispatch to peer:project` 手动派发 |
|
|
355
355
|
| `scripts/bin/dispatch_to` | 支持 `peer:project` 格式 → 写 `remote-pending.jsonl` |
|
|
356
356
|
| `daemon-team-dispatch.js` | `buildTeamRosterHint()` 为远端成员生成 `peer:key` 格式命令 |
|
|
357
|
-
| `hooks/team-
|
|
357
|
+
| `hooks/intent-team-dispatch.js` | daemon 意图注入远端 `peer:key` dispatch 命令 |
|
|
358
358
|
|
|
359
359
|
### 管理命令
|
|
360
360
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|------|--------|------|
|
|
14
14
|
| `session-analytics.js` | daemon-claude-engine, distill, memory-extract | 会话分析核心库 |
|
|
15
15
|
| `mentor-engine.js` | daemon-claude-engine, daemon-admin-commands | AI 导师引擎 |
|
|
16
|
-
| `intent-registry.js` | daemon-claude-engine
|
|
16
|
+
| `intent-registry.js` | daemon-claude-engine | 意图识别注册表(daemon 运行时注入) |
|
|
17
17
|
| `daemon-command-session-route.js` | daemon-exec-commands, daemon-ops-commands | 会话路由解析 |
|
|
18
18
|
| `daemon-siri-bridge.js` | daemon-bridges.js | Siri HTTP 桥接 |
|
|
19
19
|
| `daemon-siri-imessage.js` | daemon-siri-bridge.js | iMessage 数据库读取 |
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
- `scripts/daemon-agent-tools.js`
|
|
54
54
|
- 关键点:自然语言提取 `codex` 关键词;默认 `claude` 不写 `engine` 字段,仅 `codex` 持久化 `engine: codex`;
|
|
55
55
|
`bindAgentToChat()` 自动调用 `ensureAgentMetadata()` 建立 soul 层;
|
|
56
|
-
`
|
|
56
|
+
`daemon-agent-intent.js` 统一处理 Agent/团队自然语言入口(含负样本过滤、Windows 路径识别、显式动作优先)
|
|
57
57
|
|
|
58
58
|
- 会话命令与兼容边界:
|
|
59
59
|
- `scripts/daemon-exec-commands.js`
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
按昵称解析到远端 member 时自动走 `sendRemoteDispatch`
|
|
114
114
|
|
|
115
115
|
- Intent Hook:
|
|
116
|
-
- `scripts/hooks/team-
|
|
116
|
+
- `scripts/hooks/intent-team-dispatch.js`
|
|
117
117
|
- 关键点:检测通信意图 → 注入 dispatch_to 命令提示;远端成员自动带 `peer:key` 前缀
|
|
118
118
|
|
|
119
119
|
## Mentor Mode(Step 1-4)定位
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { classifyAgentIntent } = require('../agent-intent-shared');
|
|
4
|
+
|
|
5
|
+
function buildLines(action) {
|
|
6
|
+
switch (action) {
|
|
7
|
+
case 'create':
|
|
8
|
+
return [
|
|
9
|
+
'- 创建并绑定当前群:直接用自然语言说“给这个群创建一个 Agent,目录是 ~/repo”',
|
|
10
|
+
'- 创建待激活 Agent:说“创建一个 codex agent,目录是 ~/repo”,再去目标群发 `/activate`',
|
|
11
|
+
];
|
|
12
|
+
case 'bind':
|
|
13
|
+
return [
|
|
14
|
+
'- 绑定现有 Agent:`/agent bind <名称> <目录>`',
|
|
15
|
+
'- 也可直接说“给这个群绑定一个 Agent,目录是 ~/repo”',
|
|
16
|
+
];
|
|
17
|
+
case 'list':
|
|
18
|
+
return ['- 查看已配置 Agent:`/agent list`'];
|
|
19
|
+
case 'unbind':
|
|
20
|
+
return ['- 解绑当前群:`/agent unbind`'];
|
|
21
|
+
case 'edit_role':
|
|
22
|
+
return ['- 修改当前 Agent 角色:`/agent edit <描述>`,或直接用自然语言说“把当前 agent 角色改成 ...”'];
|
|
23
|
+
case 'reset':
|
|
24
|
+
return ['- 清空当前 Agent 的角色定义:`/agent reset`'];
|
|
25
|
+
case 'soul':
|
|
26
|
+
return [
|
|
27
|
+
'- 查看当前 Soul:`/agent soul`',
|
|
28
|
+
'- 修复 Soul 文件:`/agent soul repair`',
|
|
29
|
+
'- 覆盖编辑 Soul:`/agent soul edit <内容>`',
|
|
30
|
+
];
|
|
31
|
+
case 'activate':
|
|
32
|
+
return ['- 在新群完成绑定:进入目标群发送 `/activate`'];
|
|
33
|
+
case 'wizard_clone':
|
|
34
|
+
return ['- 创建当前 Agent 的分身:`/agent new clone`'];
|
|
35
|
+
case 'wizard_team':
|
|
36
|
+
return ['- 创建团队工作区:`/agent new team`'];
|
|
37
|
+
case 'agent_doc':
|
|
38
|
+
return ['- Agent 配置/管理文档:先看 `~/.metame/docs/agent-guide.md`'];
|
|
39
|
+
default:
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = function detectAgentCapability(prompt) {
|
|
45
|
+
const intent = classifyAgentIntent(prompt);
|
|
46
|
+
if (!intent) return null;
|
|
47
|
+
|
|
48
|
+
const lines = buildLines(intent.action);
|
|
49
|
+
if (lines.length === 0) return null;
|
|
50
|
+
return ['[Agent 能力提示]', ...lines].join('\n');
|
|
51
|
+
};
|