metame-cli 1.5.21 → 1.5.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 +3 -2
- package/index.js +7 -23
- package/package.json +1 -1
- package/scripts/agent-intent-shared.js +111 -0
- package/scripts/daemon-agent-commands.js +150 -353
- 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-team-workflow.js +146 -0
- package/scripts/daemon.js +7 -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/hooks/intent-engine.js +0 -75
- package/scripts/hooks/team-context.js +0 -143
|
@@ -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
|
@@ -507,6 +507,7 @@ function createNullBot(onOutput) {
|
|
|
507
507
|
deleteMessage: async () => { },
|
|
508
508
|
sendFile: noop,
|
|
509
509
|
downloadFile: noop,
|
|
510
|
+
notifyFinalOutput: async (text) => { if (onOutput) onOutput({ body: text, final: true }); return { message_id: '_virtual' }; },
|
|
510
511
|
};
|
|
511
512
|
}
|
|
512
513
|
|
|
@@ -610,6 +611,7 @@ function createStreamForwardBot(realBot, chatId, onOutput = null, opts = {}) {
|
|
|
610
611
|
deleteMessage: async (_, msgId) => { await waitUntilReady(); return realBot.deleteMessage(chatId, msgId); },
|
|
611
612
|
sendFile: async (_, filePath, caption) => { await waitUntilReady(); return realBot.sendFile(chatId, filePath, caption); },
|
|
612
613
|
downloadFile: async (...args) => realBot.downloadFile(...args),
|
|
614
|
+
notifyFinalOutput: async (text) => { if (onOutput) onOutput({ body: text, final: true }); return { message_id: '_virtual' }; },
|
|
613
615
|
};
|
|
614
616
|
}
|
|
615
617
|
|
|
@@ -897,10 +899,11 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
897
899
|
|
|
898
900
|
let _taskFinalized = false;
|
|
899
901
|
const outputHandler = (output) => {
|
|
902
|
+
const isFinalOutput = !!(output && typeof output === 'object' && output.final);
|
|
900
903
|
const outStr = typeof output === 'object' ? (output.body || JSON.stringify(output)) : String(output);
|
|
901
904
|
const displayOut = envelope ? appendTeamTaskResumeHint(outStr, envelope.task_id, envelope.scope_id) : outStr;
|
|
902
905
|
log('INFO', `Dispatch output from ${targetProject}: ${outStr.slice(0, 200)}`);
|
|
903
|
-
if (envelope && taskBoard && !_taskFinalized && outStr.trim().length > 2) {
|
|
906
|
+
if (!isFinalOutput && envelope && taskBoard && !_taskFinalized && outStr.trim().length > 2) {
|
|
904
907
|
const status = inferTaskStatusFromOutput(outStr);
|
|
905
908
|
const artifacts = extractArtifactPaths(outStr);
|
|
906
909
|
const update = {
|
|
@@ -916,9 +919,9 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
916
919
|
});
|
|
917
920
|
_taskFinalized = true;
|
|
918
921
|
}
|
|
919
|
-
if (replyFn && outStr.trim().length > 2) {
|
|
922
|
+
if (!isFinalOutput && replyFn && outStr.trim().length > 2) {
|
|
920
923
|
replyFn(displayOut);
|
|
921
|
-
} else if (!replyFn && fullMsg.callback && fullMsg.from && config) {
|
|
924
|
+
} else if (!isFinalOutput && !replyFn && fullMsg.callback && fullMsg.from && config) {
|
|
922
925
|
// Write result to sender's inbox before dispatching callback
|
|
923
926
|
try {
|
|
924
927
|
const inboxDir = path.join(os.homedir(), '.metame', 'memory', 'inbox', fullMsg.from);
|
|
@@ -954,6 +957,7 @@ function dispatchTask(targetProject, message, config, replyFn, streamOptions = n
|
|
|
954
957
|
|
|
955
958
|
// ── Reactive lifecycle hook ──
|
|
956
959
|
try {
|
|
960
|
+
if (!isFinalOutput) return;
|
|
957
961
|
handleReactiveOutput(targetProject, outStr, loadConfig(), {
|
|
958
962
|
log,
|
|
959
963
|
loadState,
|
|
@@ -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
|
+
};
|
|
@@ -12,16 +12,18 @@
|
|
|
12
12
|
|
|
13
13
|
const { createDocRoute } = require('./doc-router');
|
|
14
14
|
|
|
15
|
+
const AGENT_DOC_PATTERNS = [
|
|
16
|
+
/(?:agent|智能体|机器人|bot).{0,12}(文档|手册|说明|guide)/i,
|
|
17
|
+
/(?:怎么|如何|手册|文档|说明).{0,12}(配置|管理|使用).{0,12}(agent|智能体|机器人|bot)/i,
|
|
18
|
+
/(?:agent|智能体|机器人|bot).{0,12}(怎么|如何).{0,12}(配置|管理|使用)/i,
|
|
19
|
+
];
|
|
20
|
+
|
|
15
21
|
const routes = [
|
|
16
22
|
createDocRoute({
|
|
17
|
-
patterns:
|
|
18
|
-
|
|
19
|
-
/(?:agent|bot|智能体).{0,8}(?:创建|新建|添加|注册|绑定|配置|管理)/i,
|
|
20
|
-
/\b(?:create|add|register|bind|manage|setup|configure)\s+(?:an?\s+)?agent\b/i,
|
|
21
|
-
],
|
|
22
|
-
title: 'Agent 管理提示',
|
|
23
|
+
patterns: AGENT_DOC_PATTERNS,
|
|
24
|
+
title: 'Agent 文档提示',
|
|
23
25
|
docPath: '~/.metame/docs/agent-guide.md',
|
|
24
|
-
summary: '
|
|
26
|
+
summary: 'Agent 配置/管理/使用说明',
|
|
25
27
|
}),
|
|
26
28
|
createDocRoute({
|
|
27
29
|
patterns: [
|
|
@@ -34,8 +36,8 @@ const routes = [
|
|
|
34
36
|
}),
|
|
35
37
|
createDocRoute({
|
|
36
38
|
patterns: [
|
|
37
|
-
/(?:hook|intent|意图).{0,10}(
|
|
38
|
-
/(
|
|
39
|
+
/(?:hook|intent|意图).{0,10}(?:配置|设置|开关|新增|添加|修改|怎么配|怎么设置|怎么改|原理)/i,
|
|
40
|
+
/(?:配置|设置|开关|新增|添加|修改|原理).{0,10}(?:hook|intent|意图)/i,
|
|
39
41
|
/intent.?engine/i,
|
|
40
42
|
/意图引擎|意图模块/,
|
|
41
43
|
],
|
|
@@ -45,10 +47,20 @@ const routes = [
|
|
|
45
47
|
}),
|
|
46
48
|
];
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
function hasExplicitDocIntent(prompt) {
|
|
51
|
+
const text = String(prompt || '').trim();
|
|
52
|
+
if (!text) return false;
|
|
53
|
+
return AGENT_DOC_PATTERNS.some((pattern) => pattern.test(text)) ||
|
|
54
|
+
/(?:文档|手册|说明|guide|readme)/i.test(text);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function detectDocRouter(prompt) {
|
|
49
58
|
const hints = routes
|
|
50
59
|
.map((detect) => detect(prompt))
|
|
51
60
|
.filter(Boolean);
|
|
52
61
|
|
|
53
62
|
return hints.length ? hints.join('\n') : null;
|
|
54
|
-
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = detectDocRouter;
|
|
66
|
+
module.exports.hasExplicitDocIntent = hasExplicitDocIntent;
|
|
@@ -17,8 +17,6 @@ const RECALL_PATTERNS = [
|
|
|
17
17
|
/之前.{0,4}(?:说过|讨论过|聊过|提到过|商量过|做过的)/,
|
|
18
18
|
// "还记得/记不记得" — asking if AI remembers (exclude "你记得" which is often imperative)
|
|
19
19
|
/(?:还记得|记不记得|记得吗)/,
|
|
20
|
-
// "之前那个/上次那个" — referencing past artifacts
|
|
21
|
-
/(?:之前|上次|前几天)那个/,
|
|
22
20
|
// English recall patterns
|
|
23
21
|
/\b(?:last time|previously|remember when|do you remember|earlier we)\b/i,
|
|
24
22
|
];
|
|
@@ -31,6 +29,6 @@ module.exports = function detectMemoryRecall(prompt) {
|
|
|
31
29
|
'- 搜索记忆: `node ~/.metame/memory-search.js "关键词1" "keyword2"`',
|
|
32
30
|
'- 一次传 3-4 个关键词(中文+英文+函数名)',
|
|
33
31
|
'- `--facts` 只搜事实,`--sessions` 只搜会话',
|
|
34
|
-
'-
|
|
32
|
+
'- 不要假设工作区里存在 `./memory` 模块;优先走 `memory-search.js` CLI 做召回',
|
|
35
33
|
].join('\n');
|
|
36
34
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Team Dispatch Intent Module
|
|
5
5
|
*
|
|
6
6
|
* Detects communication intent towards team members in the prompt.
|
|
7
|
-
*
|
|
7
|
+
* Daemon-side intent module for team dispatch hints.
|
|
8
8
|
*
|
|
9
9
|
* @param {string} prompt - sanitized user prompt
|
|
10
10
|
* @param {object} config - daemon.yaml config
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const detectDocRouter = require('./hooks/intent-doc-router');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Shared intent registry for all MetaMe runtime adapters.
|
|
5
7
|
*
|
|
@@ -8,9 +10,9 @@
|
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
const DEFAULTS = Object.freeze({
|
|
13
|
+
agent_capability: true,
|
|
11
14
|
team_dispatch: true,
|
|
12
15
|
ops_assist: true,
|
|
13
|
-
task_create: true,
|
|
14
16
|
file_transfer: true,
|
|
15
17
|
weixin_bridge: true,
|
|
16
18
|
memory_recall: true,
|
|
@@ -20,17 +22,54 @@ const DEFAULTS = Object.freeze({
|
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
const INTENT_MODULES = Object.freeze({
|
|
25
|
+
agent_capability: {
|
|
26
|
+
detect: require('./hooks/intent-agent-capability'),
|
|
27
|
+
priority: 100,
|
|
28
|
+
},
|
|
23
29
|
team_dispatch: require('./hooks/intent-team-dispatch'),
|
|
24
|
-
ops_assist:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
ops_assist: {
|
|
31
|
+
detect: require('./hooks/intent-ops-assist'),
|
|
32
|
+
priority: 80,
|
|
33
|
+
},
|
|
34
|
+
file_transfer: {
|
|
35
|
+
detect: require('./hooks/intent-file-transfer'),
|
|
36
|
+
priority: 95,
|
|
37
|
+
},
|
|
38
|
+
weixin_bridge: {
|
|
39
|
+
detect: require('./hooks/intent-weixin-bridge'),
|
|
40
|
+
priority: 90,
|
|
41
|
+
},
|
|
42
|
+
memory_recall: {
|
|
43
|
+
detect: require('./hooks/intent-memory-recall'),
|
|
44
|
+
priority: 70,
|
|
45
|
+
},
|
|
46
|
+
doc_router: {
|
|
47
|
+
detect: detectDocRouter,
|
|
48
|
+
priority: 10,
|
|
49
|
+
fallbackOnly: true,
|
|
50
|
+
},
|
|
51
|
+
perpetual: {
|
|
52
|
+
detect: require('./hooks/intent-perpetual'),
|
|
53
|
+
priority: 60,
|
|
54
|
+
},
|
|
55
|
+
research: {
|
|
56
|
+
detect: require('./hooks/intent-research'),
|
|
57
|
+
priority: 55,
|
|
58
|
+
},
|
|
32
59
|
});
|
|
33
60
|
|
|
61
|
+
const DEFAULT_MAX_HINTS = 2;
|
|
62
|
+
const DEFAULT_MAX_HINT_CHARS = 1200;
|
|
63
|
+
|
|
64
|
+
function normalizeIntentModule(entry) {
|
|
65
|
+
if (typeof entry === 'function') return { detect: entry, priority: 50, fallbackOnly: false };
|
|
66
|
+
return {
|
|
67
|
+
detect: entry.detect,
|
|
68
|
+
priority: Number.isFinite(entry.priority) ? entry.priority : 50,
|
|
69
|
+
fallbackOnly: !!entry.fallbackOnly,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
34
73
|
function resolveEnabledIntents(config = {}) {
|
|
35
74
|
const hooksCfg = (config.hooks && typeof config.hooks === 'object') ? config.hooks : {};
|
|
36
75
|
return { ...DEFAULTS, ...hooksCfg };
|
|
@@ -42,18 +81,43 @@ function collectIntentHints(prompt, config = {}, projectKey = '') {
|
|
|
42
81
|
|
|
43
82
|
const enabled = resolveEnabledIntents(config);
|
|
44
83
|
const hints = [];
|
|
45
|
-
for (const [key,
|
|
84
|
+
for (const [key, rawEntry] of Object.entries(INTENT_MODULES)) {
|
|
46
85
|
if (enabled[key] === false) continue;
|
|
47
|
-
const
|
|
86
|
+
const entry = normalizeIntentModule(rawEntry);
|
|
87
|
+
const hint = entry.detect(text, config, projectKey);
|
|
48
88
|
if (hint) hints.push({ key, hint });
|
|
49
89
|
}
|
|
50
90
|
return hints;
|
|
51
91
|
}
|
|
52
92
|
|
|
53
93
|
function buildIntentHintBlock(prompt, config = {}, projectKey = '') {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
const maxHints = Number.isInteger(config && config.intent_max_hints) && config.intent_max_hints > 0
|
|
95
|
+
? config.intent_max_hints
|
|
96
|
+
: DEFAULT_MAX_HINTS;
|
|
97
|
+
const maxChars = Number.isInteger(config && config.intent_max_hint_chars) && config.intent_max_hint_chars > 0
|
|
98
|
+
? config.intent_max_hint_chars
|
|
99
|
+
: DEFAULT_MAX_HINT_CHARS;
|
|
100
|
+
|
|
101
|
+
let hits = collectIntentHints(prompt, config, projectKey)
|
|
102
|
+
.map((item) => ({ ...item, ...normalizeIntentModule(INTENT_MODULES[item.key]) }));
|
|
103
|
+
|
|
104
|
+
if (hits.some(item => !item.fallbackOnly) && !(typeof detectDocRouter.hasExplicitDocIntent === 'function' && detectDocRouter.hasExplicitDocIntent(prompt))) {
|
|
105
|
+
hits = hits.filter(item => !item.fallbackOnly);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
hits.sort((a, b) => b.priority - a.priority);
|
|
109
|
+
|
|
110
|
+
const selected = [];
|
|
111
|
+
let usedChars = 0;
|
|
112
|
+
for (const item of hits) {
|
|
113
|
+
if (selected.length >= maxHints) break;
|
|
114
|
+
const nextChars = usedChars + item.hint.length;
|
|
115
|
+
if (selected.length > 0 && nextChars > maxChars) continue;
|
|
116
|
+
selected.push(item);
|
|
117
|
+
usedChars = nextChars;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return selected.map(item => item.hint).join('\n\n');
|
|
57
121
|
}
|
|
58
122
|
|
|
59
123
|
module.exports = {
|