metame-cli 1.5.2 → 1.5.4
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 +90 -17
- package/index.js +76 -25
- package/package.json +1 -1
- package/scripts/bin/dispatch_to +167 -90
- package/scripts/daemon-admin-commands.js +225 -24
- package/scripts/daemon-agent-commands.js +263 -8
- package/scripts/daemon-bridges.js +395 -6
- package/scripts/daemon-claude-engine.js +749 -582
- package/scripts/daemon-command-router.js +104 -0
- package/scripts/daemon-default.yaml +9 -4
- package/scripts/daemon-engine-runtime.js +33 -2
- package/scripts/daemon-exec-commands.js +8 -5
- package/scripts/daemon-file-browser.js +1 -0
- package/scripts/daemon-remote-dispatch.js +82 -0
- package/scripts/daemon-runtime-lifecycle.js +87 -0
- package/scripts/daemon-session-commands.js +19 -11
- package/scripts/daemon-session-store.js +26 -8
- package/scripts/daemon-task-scheduler.js +2 -2
- package/scripts/daemon.js +363 -8
- package/scripts/daemon.yaml +356 -0
- package/scripts/distill.js +35 -16
- package/scripts/docs/agent-guide.md +36 -3
- package/scripts/docs/hook-config.md +131 -0
- package/scripts/docs/maintenance-manual.md +214 -3
- package/scripts/docs/pointer-map.md +60 -5
- package/scripts/feishu-adapter.js +127 -58
- package/scripts/hooks/hook-utils.js +61 -0
- package/scripts/hooks/intent-agent-manage.js +50 -0
- package/scripts/hooks/intent-engine.js +103 -0
- package/scripts/hooks/intent-file-transfer.js +51 -0
- package/scripts/hooks/intent-hook-config.js +28 -0
- package/scripts/hooks/intent-memory-recall.js +35 -0
- package/scripts/hooks/intent-ops-assist.js +54 -0
- package/scripts/hooks/intent-task-create.js +35 -0
- package/scripts/hooks/intent-team-dispatch.js +106 -0
- package/scripts/hooks/team-context.js +143 -0
- package/scripts/memory-extract.js +1 -1
- package/scripts/memory-nightly-reflect.js +109 -43
- package/scripts/memory-write.js +21 -4
- package/scripts/memory.js +55 -17
- package/scripts/publish-public.sh +24 -35
- package/scripts/qmd-client.js +1 -1
- package/scripts/signal-capture.js +14 -0
- package/scripts/team-dispatch.js +176 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MetaMe Hook Utilities — Shared helpers for Claude Code hooks
|
|
5
|
+
*
|
|
6
|
+
* Provides prompt sanitization and internal prompt detection used by
|
|
7
|
+
* multiple hooks to avoid triggering on daemon-injected system content.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Daemon-injected blocks that should be stripped before intent analysis
|
|
11
|
+
const BLOCK_PATTERNS = [
|
|
12
|
+
/<!--\s*FACTS:START\s*-->[\s\S]*?<!--\s*FACTS:END\s*-->/gi,
|
|
13
|
+
/<!--\s*MEMORY:START\s*-->[\s\S]*?<!--\s*MEMORY:END\s*-->/gi,
|
|
14
|
+
/\[System hints - DO NOT mention these to user:[\s\S]*?\]/gi,
|
|
15
|
+
/\[Mac automation policy - do NOT expose this block:[\s\S]*?\]/gi,
|
|
16
|
+
/\[Task notification\][\s\S]*?(?=\n{2,}|$)/gi,
|
|
17
|
+
/<task-notification\b[\s\S]*?<\/task-notification>/gi,
|
|
18
|
+
/<task-notification\b[\s\S]*$/gi,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Patterns that identify internal/system prompts (daemon subprocesses)
|
|
22
|
+
const INTERNAL_PATTERNS = [
|
|
23
|
+
/You are a MetaMe cognitive profile distiller/i,
|
|
24
|
+
/You are a metacognition pattern detector/i,
|
|
25
|
+
/你是精准的知识提取引擎/,
|
|
26
|
+
/RECALLED LONG-TERM FACTS \(context only/i,
|
|
27
|
+
/\[System hints - DO NOT mention these to user:/i,
|
|
28
|
+
/\[Mac automation policy - do NOT expose this block:/i,
|
|
29
|
+
/MANDATORY FIRST ACTION: The user has not been calibrated yet/i,
|
|
30
|
+
/<!--\s*FACTS:START\s*-->/i,
|
|
31
|
+
/<!--\s*MEMORY:START\s*-->/i,
|
|
32
|
+
/\[Task notification\]/i,
|
|
33
|
+
/<task-notification\b/i,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Strip daemon-injected blocks from prompt text.
|
|
38
|
+
* Returns clean user payload or empty string.
|
|
39
|
+
* @param {string} text
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function sanitizePrompt(text) {
|
|
43
|
+
let s = String(text || '');
|
|
44
|
+
for (const re of BLOCK_PATTERNS) {
|
|
45
|
+
s = s.replace(re, ' ');
|
|
46
|
+
}
|
|
47
|
+
return s.trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns true if the text matches a known internal/system prompt template.
|
|
52
|
+
* These come from daemon subprocesses (distill, memory-extract, skill-evolution).
|
|
53
|
+
* @param {string} text
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
function isInternalPrompt(text) {
|
|
57
|
+
const s = String(text || '');
|
|
58
|
+
return INTERNAL_PATTERNS.some(re => re.test(s));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { sanitizePrompt, isInternalPrompt };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Management Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects when the user asks about creating, managing, or binding agents,
|
|
7
|
+
* or asks about code structure / upgrade progress.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} prompt
|
|
10
|
+
* @returns {string|null}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const AGENT_MANAGE_PATTERNS = [
|
|
14
|
+
// Creating / binding / managing agents
|
|
15
|
+
/(?:创建|新建|添加|注册|绑定|配置|管理).{0,8}(?:agent|机器人|bot|智能体)/i,
|
|
16
|
+
/(?:agent|bot|智能体).{0,8}(?:创建|新建|添加|注册|绑定|配置|管理)/i,
|
|
17
|
+
// English
|
|
18
|
+
/\b(?:create|add|register|bind|manage|setup|configure)\s+(?:an?\s+)?agent\b/i,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const CODE_STRUCTURE_PATTERNS = [
|
|
22
|
+
// Code structure / upgrade / script entry questions
|
|
23
|
+
/(?:代码结构|脚本入口|升级进度|模块关系|文件结构)/,
|
|
24
|
+
/(?:pointer.?map|架构图|入口文件)/i,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
module.exports = function detectAgentManage(prompt) {
|
|
28
|
+
const isManage = AGENT_MANAGE_PATTERNS.some(re => re.test(prompt));
|
|
29
|
+
const isStructure = CODE_STRUCTURE_PATTERNS.some(re => re.test(prompt));
|
|
30
|
+
|
|
31
|
+
if (!isManage && !isStructure) return null;
|
|
32
|
+
|
|
33
|
+
const hints = [];
|
|
34
|
+
|
|
35
|
+
if (isManage) {
|
|
36
|
+
hints.push(
|
|
37
|
+
'[Agent 管理提示]',
|
|
38
|
+
'- 创建/管理/绑定 Agent → 先 `cat ~/.metame/docs/agent-guide.md` 获取完整流程',
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isStructure) {
|
|
43
|
+
hints.push(
|
|
44
|
+
'[代码结构提示]',
|
|
45
|
+
'- 代码结构/脚本入口/升级进度 → 先 `cat ~/.metame/docs/pointer-map.md` 获取索引',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return hints.join('\n');
|
|
50
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MetaMe Intent Engine — Unified UserPromptSubmit Hook
|
|
5
|
+
*
|
|
6
|
+
* Config-driven intent dispatcher. Replaces the standalone team-context.js hook.
|
|
7
|
+
* Each intent module detects a specific pattern and returns a hint string or null.
|
|
8
|
+
* Only injects an additionalSystemPrompt when at least one intent fires.
|
|
9
|
+
*
|
|
10
|
+
* Enabled intents are controlled via daemon.yaml `hooks:` section:
|
|
11
|
+
*
|
|
12
|
+
* hooks:
|
|
13
|
+
* team_dispatch: true # team member communication hints (default: on)
|
|
14
|
+
* ops_assist: true # /undo /restart /logs etc. hints (default: on)
|
|
15
|
+
* task_create: true # task scheduling hints (default: on)
|
|
16
|
+
*
|
|
17
|
+
* Set any key to false to disable that intent module.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const os = require('os');
|
|
25
|
+
|
|
26
|
+
const METAME_DIR = path.join(os.homedir(), '.metame');
|
|
27
|
+
const { sanitizePrompt, isInternalPrompt } = require('./hook-utils');
|
|
28
|
+
|
|
29
|
+
// Default: all intents enabled unless explicitly set to false in daemon.yaml
|
|
30
|
+
const DEFAULTS = {
|
|
31
|
+
team_dispatch: true,
|
|
32
|
+
ops_assist: true,
|
|
33
|
+
task_create: true,
|
|
34
|
+
file_transfer: true,
|
|
35
|
+
memory_recall: true,
|
|
36
|
+
agent_manage: true,
|
|
37
|
+
hook_config: true,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Intent registry — loaded lazily so startup is fast even if a module has issues
|
|
41
|
+
const INTENT_MODULES = {
|
|
42
|
+
team_dispatch: './intent-team-dispatch',
|
|
43
|
+
ops_assist: './intent-ops-assist',
|
|
44
|
+
task_create: './intent-task-create',
|
|
45
|
+
file_transfer: './intent-file-transfer',
|
|
46
|
+
memory_recall: './intent-memory-recall',
|
|
47
|
+
agent_manage: './intent-agent-manage',
|
|
48
|
+
hook_config: './intent-hook-config',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function exit() { process.exit(0); }
|
|
52
|
+
|
|
53
|
+
let raw = '';
|
|
54
|
+
process.stdin.setEncoding('utf8');
|
|
55
|
+
process.stdin.on('data', c => { raw += c; });
|
|
56
|
+
process.stdin.on('end', () => {
|
|
57
|
+
try { run(JSON.parse(raw)); } catch { exit(); }
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function run(data) {
|
|
61
|
+
// Internal daemon subprocesses set this env flag — never inject hints into them
|
|
62
|
+
if (process.env.METAME_INTERNAL_PROMPT === '1') return exit();
|
|
63
|
+
|
|
64
|
+
const projectKey = process.env.METAME_PROJECT || '';
|
|
65
|
+
const rawPrompt = (data.prompt || data.user_prompt || '').trim();
|
|
66
|
+
if (!rawPrompt) return exit();
|
|
67
|
+
|
|
68
|
+
// Strip daemon-injected blocks, then bail if this is a system prompt
|
|
69
|
+
if (isInternalPrompt(rawPrompt)) return exit();
|
|
70
|
+
const prompt = sanitizePrompt(rawPrompt);
|
|
71
|
+
if (!prompt) return exit();
|
|
72
|
+
|
|
73
|
+
// Load daemon.yaml config (graceful: intents that don't need config still run)
|
|
74
|
+
let config = {};
|
|
75
|
+
try {
|
|
76
|
+
const yaml = require('../resolve-yaml');
|
|
77
|
+
config = yaml.load(fs.readFileSync(path.join(METAME_DIR, 'daemon.yaml'), 'utf8')) || {};
|
|
78
|
+
} catch { /* proceed with defaults */ }
|
|
79
|
+
|
|
80
|
+
// Merge daemon.yaml hooks section with defaults
|
|
81
|
+
const hooksCfg = (config.hooks && typeof config.hooks === 'object') ? config.hooks : {};
|
|
82
|
+
const enabled = { ...DEFAULTS, ...hooksCfg };
|
|
83
|
+
|
|
84
|
+
// Run each enabled intent module, collect non-null hints
|
|
85
|
+
const hints = [];
|
|
86
|
+
for (const [key, modulePath] of Object.entries(INTENT_MODULES)) {
|
|
87
|
+
if (enabled[key] === false) continue;
|
|
88
|
+
try {
|
|
89
|
+
const detect = require(modulePath);
|
|
90
|
+
const result = detect(prompt, config, projectKey);
|
|
91
|
+
if (result) hints.push(result);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
process.stderr.write(`[intent-engine] ${key}: ${e.message}\n`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (hints.length === 0) return exit();
|
|
98
|
+
|
|
99
|
+
process.stdout.write(JSON.stringify({
|
|
100
|
+
hookSpecificOutput: { additionalSystemPrompt: hints.join('\n\n') },
|
|
101
|
+
}));
|
|
102
|
+
exit();
|
|
103
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* File Transfer Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects when the user wants to send/receive files via phone.
|
|
7
|
+
* Injects the [[FILE:...]] protocol hint on demand.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} prompt
|
|
10
|
+
* @returns {string|null}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const SEND_PATTERNS = [
|
|
14
|
+
// "发给我/发过来/发我/传给我" — user wants a file sent to phone
|
|
15
|
+
/(?:发给我|发过来|发我|传给我|传过来|发到手机|发手机上)/,
|
|
16
|
+
// "导出给我/导出一下/导出文件" — export intent (require suffix to avoid "导出函数" etc.)
|
|
17
|
+
/导出(?:给我|一下|文件|到手机)/,
|
|
18
|
+
// English patterns
|
|
19
|
+
/\b(?:send|share)\s+(?:me|to\s+(?:my\s+)?phone)\b/i,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const RECEIVE_PATTERNS = [
|
|
23
|
+
// User mentions uploading or sending files to the system
|
|
24
|
+
/(?:我发|给你|传给你|上传).{0,8}(?:文件|图片|截图|照片|图)/,
|
|
25
|
+
/upload\/\S+/i,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
module.exports = function detectFileTransfer(prompt) {
|
|
29
|
+
const isSend = SEND_PATTERNS.some(re => re.test(prompt));
|
|
30
|
+
const isReceive = RECEIVE_PATTERNS.some(re => re.test(prompt));
|
|
31
|
+
|
|
32
|
+
if (!isSend && !isReceive) return null;
|
|
33
|
+
|
|
34
|
+
const hints = ['[文件传输提示]'];
|
|
35
|
+
|
|
36
|
+
if (isSend) {
|
|
37
|
+
hints.push(
|
|
38
|
+
'- **发送文件到手机**:在回复末尾加 `[[FILE:/absolute/path]]`,daemon 自动发送',
|
|
39
|
+
'- 多个文件用多个 `[[FILE:...]]` 标记',
|
|
40
|
+
'- **不要读取文件内容再复述**,直接用标记发送(省 token)',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isReceive) {
|
|
45
|
+
hints.push(
|
|
46
|
+
'- **接收文件**:用户发的图片/文件自动存到当前项目 `upload/` 目录,用 Read 查看',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return hints.join('\n');
|
|
51
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook Config Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects when the user asks about hook/intent engine configuration.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} prompt
|
|
9
|
+
* @returns {string|null}
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const HOOK_PATTERNS = [
|
|
13
|
+
// Hook / intent configuration
|
|
14
|
+
/(?:hook|intent|意图).{0,10}(?:配置|设置|开关|新增|添加|修改|怎么配|怎么设置|怎么改)/i,
|
|
15
|
+
/(?:配置|设置|开关|新增|添加|修改).{0,10}(?:hook|intent|意图)/i,
|
|
16
|
+
// Specific intent engine topics
|
|
17
|
+
/intent.?engine/i,
|
|
18
|
+
/意图引擎|意图模块/,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
module.exports = function detectHookConfig(prompt) {
|
|
22
|
+
if (!HOOK_PATTERNS.some(re => re.test(prompt))) return null;
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
'[Intent Engine 配置提示]',
|
|
26
|
+
'- Hook/Intent 配置操作 → 先 `cat ~/.metame/docs/hook-config.md` 获取完整手册',
|
|
27
|
+
].join('\n');
|
|
28
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Memory Recall Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects cross-session memory recall intent (user references past conversations).
|
|
7
|
+
* Injects memory-search command hint on demand.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} prompt
|
|
10
|
+
* @returns {string|null}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const RECALL_PATTERNS = [
|
|
14
|
+
// "上次/之前/前几天" — referencing past conversations (tight window to avoid current-session refs)
|
|
15
|
+
/(?:上次|前几天|上周|前阵子).{0,6}(?:说|讨论|聊|提到|做|改|写|搞|弄|处理|商量)/,
|
|
16
|
+
// "之前" needs tighter pairing — "之前写了个函数" is current-session, not recall
|
|
17
|
+
/之前.{0,4}(?:说过|讨论过|聊过|提到过|商量过|做过的)/,
|
|
18
|
+
// "还记得/记不记得" — asking if AI remembers (exclude "你记得" which is often imperative)
|
|
19
|
+
/(?:还记得|记不记得|记得吗)/,
|
|
20
|
+
// "之前那个/上次那个" — referencing past artifacts
|
|
21
|
+
/(?:之前|上次|前几天)那个/,
|
|
22
|
+
// English recall patterns
|
|
23
|
+
/\b(?:last time|previously|remember when|do you remember|earlier we)\b/i,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
module.exports = function detectMemoryRecall(prompt) {
|
|
27
|
+
if (!RECALL_PATTERNS.some(re => re.test(prompt))) return null;
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
'[跨会话记忆提示]',
|
|
31
|
+
'- 搜索记忆: `node ~/.metame/memory-search.js "关键词1" "keyword2"`',
|
|
32
|
+
'- 一次传 3-4 个关键词(中文+英文+函数名)',
|
|
33
|
+
'- `--facts` 只搜事实,`--sessions` 只搜会话',
|
|
34
|
+
].join('\n');
|
|
35
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ops Assist Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects operational context and injects relevant command hints.
|
|
7
|
+
* Uses specific patterns to avoid noise — only fires when the user
|
|
8
|
+
* is clearly asking about operational topics.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} prompt
|
|
11
|
+
* @returns {string|null}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const OPS_INTENTS = [
|
|
15
|
+
{
|
|
16
|
+
// User explicitly wants to revert/undo code changes.
|
|
17
|
+
// Removed "上一步" (too generic: "上一步的逻辑是什么" is not undo).
|
|
18
|
+
// \b only used around ASCII keywords; Chinese patterns need no word boundary.
|
|
19
|
+
pattern: /(回退|撤销|恢复上一版|回到上一版|回滚)|\b(undo|git reset)\b/i,
|
|
20
|
+
hint: '`/undo` 撤销最近改动 | `/undo <hash>` 回到指定 checkpoint',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
// Save a progress checkpoint — requires explicit "save/create" intent.
|
|
24
|
+
// Removed bare \bcheckpoint\b: fires on any technical mention of the word.
|
|
25
|
+
pattern: /保存.{0,5}(进度|checkpoint|快照|存档)|存个档|打个checkpoint/i,
|
|
26
|
+
hint: '`/checkpoint` 保存当前进度快照',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
// Daemon restart
|
|
30
|
+
pattern: /(重启|restart).{0,10}(daemon|服务|后台)|daemon.{0,10}(挂了|不响应|崩了|没反应|失联)/i,
|
|
31
|
+
hint: '`/restart` 重启 MetaMe daemon',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
// Explicitly asking to view logs
|
|
35
|
+
pattern: /(查看|看看|看下|打开).{0,10}(日志|logs?)|(日志|logs?).{0,10}(在哪|怎么看|如何查)|\b(show|view|check)\s+logs?\b/i,
|
|
36
|
+
hint: '`/logs` 查看最近运行日志',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
// Memory/session cleanup
|
|
40
|
+
pattern: /\bgc\b|垃圾回收|清理.{0,5}(缓存|session|内存)|内存.{0,5}(清理|释放|满了|不够)/i,
|
|
41
|
+
hint: '`/gc` 清理过期 session / 释放内存',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
// System status check
|
|
45
|
+
pattern: /(系统|daemon|服务|agent).{0,5}(状态|运行情况|是否正常|健康状况)|\b(status|health)\b.{0,10}(check|查看)/i,
|
|
46
|
+
hint: '`/status` 查看当前系统状态',
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
module.exports = function detectOpsAssist(prompt) {
|
|
51
|
+
const hits = OPS_INTENTS.filter(({ pattern }) => pattern.test(prompt));
|
|
52
|
+
if (hits.length === 0) return null;
|
|
53
|
+
return ['[运维提示]', ...hits.map(h => `- ${h.hint}`)].join('\n');
|
|
54
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Task Create Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects scheduling/reminder intent and injects task creation hints.
|
|
7
|
+
* Requires explicit scheduling language to avoid false positives.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} prompt
|
|
10
|
+
* @returns {string|null}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const TASK_PATTERNS = [
|
|
14
|
+
// Explicit recurring schedule: 每天/每小时/... + action
|
|
15
|
+
/(每天|每小时|每周|每月|每.{1,4}分钟).{0,20}(提醒|通知|运行|执行|做|检查)/,
|
|
16
|
+
// "提醒我" must be paired with time/frequency context to avoid false positives
|
|
17
|
+
// e.g. "提醒我明天九点" ✓ / "提醒我这个要注意" ✗
|
|
18
|
+
/提醒我.{0,15}(每天|每周|每小时|[0-90-9]|明天|后天|下周|点钟|分钟|小时)|\bremind me\b.{0,20}(every|daily|at \d|\btomorrow\b)|\bset.{0,5}reminder\b/i,
|
|
19
|
+
// Scheduling keywords
|
|
20
|
+
/定时任务|\bheartbeat.{0,5}task\b|\bcron job\b/i,
|
|
21
|
+
// "每天X点提醒" pattern
|
|
22
|
+
/每天.{0,10}[0-90-9点时].{0,10}(提醒|通知|叫我)/,
|
|
23
|
+
// "下次别忘了 / 帮我记住 + 周期性语境"
|
|
24
|
+
/下次.{0,8}(别忘|记得|提醒)|帮我.{0,5}记住.{0,10}(每|定期|以后)/,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
module.exports = function detectTaskCreate(prompt) {
|
|
28
|
+
if (!TASK_PATTERNS.some(re => re.test(prompt))) return null;
|
|
29
|
+
return [
|
|
30
|
+
'[任务调度提示]',
|
|
31
|
+
'- 定时任务: `/task-add "描述" --at "09:00" --every day`',
|
|
32
|
+
'- 一次性提醒: `/task-add "描述" --once --in "30m"`',
|
|
33
|
+
'- 查看任务列表: `/tasks`',
|
|
34
|
+
].join('\n');
|
|
35
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Team Dispatch Intent Module
|
|
5
|
+
*
|
|
6
|
+
* Detects communication intent towards team members in the prompt.
|
|
7
|
+
* Extracted from team-context.js — same logic, pure function interface.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} prompt - sanitized user prompt
|
|
10
|
+
* @param {object} config - daemon.yaml config
|
|
11
|
+
* @param {string} projectKey - METAME_PROJECT env value
|
|
12
|
+
* @returns {string|null} - hint string or null if no intent detected
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
// ── Communication intent patterns ─────────────────────────────────────────────
|
|
19
|
+
// Three structural patterns:
|
|
20
|
+
// A) verb → name: 告诉工匠 / 发给builder / 通知乙
|
|
21
|
+
// B) name → verb: 工匠你来 / builder帮我 / 乙去做
|
|
22
|
+
// C) prep + name: 和工匠讨论 / 跟builder说 / 与乙沟通
|
|
23
|
+
|
|
24
|
+
const BEFORE_NAME_ZH = ['告诉', '通知', '让', '叫', '派', '交给', '转给', '发给', '联系', '找', '请', '问', '发消息给'];
|
|
25
|
+
const BEFORE_NAME_EN = ['tell', 'ask', 'notify', 'send to', 'assign to', 'delegate to', 'message', 'ping', 'contact'];
|
|
26
|
+
const AFTER_NAME_ZH = ['你来', '来做', '去做', '帮我', '帮忙', '负责', '处理', '跟进'];
|
|
27
|
+
const AFTER_NAME_EN = ['help', 'do this', 'handle', 'take care', 'follow up'];
|
|
28
|
+
const PREP_ZH = ['和', '跟', '与'];
|
|
29
|
+
const DISC_EN = ['discuss with', 'talk to', 'chat with', 'coordinate with', 'sync with', 'work with'];
|
|
30
|
+
|
|
31
|
+
function escapeRe(s) {
|
|
32
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hasCommIntent(text, nickname) {
|
|
36
|
+
const t = text.toLowerCase();
|
|
37
|
+
const n = escapeRe(nickname.toLowerCase());
|
|
38
|
+
|
|
39
|
+
// A) verb → name (within 15 chars)
|
|
40
|
+
for (const v of BEFORE_NAME_ZH) {
|
|
41
|
+
if (new RegExp(`${v}.{0,15}${n}`, 'u').test(t)) return true;
|
|
42
|
+
}
|
|
43
|
+
for (const v of BEFORE_NAME_EN) {
|
|
44
|
+
if (new RegExp(`${v}.{0,20}${n}`, 'i').test(t)) return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// B) name → verb (within 8 chars)
|
|
48
|
+
for (const v of AFTER_NAME_ZH) {
|
|
49
|
+
if (new RegExp(`${n}.{0,8}${v}`, 'u').test(t)) return true;
|
|
50
|
+
}
|
|
51
|
+
for (const v of AFTER_NAME_EN) {
|
|
52
|
+
if (new RegExp(`${n}.{0,10}${v}`, 'i').test(t)) return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// C) prep + name (和工匠/跟builder — prep within 3 chars before name)
|
|
56
|
+
for (const p of PREP_ZH) {
|
|
57
|
+
if (new RegExp(`${p}.{0,3}${n}`, 'u').test(t)) return true;
|
|
58
|
+
}
|
|
59
|
+
for (const v of DISC_EN) {
|
|
60
|
+
if (new RegExp(`${v}.{0,20}${n}`, 'i').test(t)) return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = function detectTeamDispatch(prompt, config, projectKey) {
|
|
67
|
+
if (!config || !config.projects || !projectKey) return null;
|
|
68
|
+
|
|
69
|
+
const dispatchBin = path.join(os.homedir(), '.metame', 'bin', 'dispatch_to');
|
|
70
|
+
|
|
71
|
+
// Collect all reachable team members (siblings + parent)
|
|
72
|
+
const allMembers = [];
|
|
73
|
+
for (const [parentKey, parent] of Object.entries(config.projects)) {
|
|
74
|
+
if (!Array.isArray(parent.team)) continue;
|
|
75
|
+
for (const member of parent.team) {
|
|
76
|
+
if (member.key === projectKey) continue; // skip self
|
|
77
|
+
allMembers.push({ member, isParent: false });
|
|
78
|
+
}
|
|
79
|
+
// Allow dispatching to parent project itself (escalation)
|
|
80
|
+
if (parent.team.some(m => m.key === projectKey)) {
|
|
81
|
+
allMembers.push({
|
|
82
|
+
member: { key: parentKey, name: parent.name, nicknames: parent.nicknames },
|
|
83
|
+
isParent: true,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (allMembers.length === 0) return null;
|
|
89
|
+
|
|
90
|
+
// Match only members with communication intent in this prompt
|
|
91
|
+
const hits = allMembers.filter(({ member }) => {
|
|
92
|
+
const nicks = [member.key, member.name, ...(Array.isArray(member.nicknames) ? member.nicknames : [])].filter(Boolean);
|
|
93
|
+
return nicks.some(n => hasCommIntent(prompt, n));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (hits.length === 0) return null;
|
|
97
|
+
|
|
98
|
+
const lines = hits.map(({ member, isParent }) => {
|
|
99
|
+
const label = isParent
|
|
100
|
+
? `${member.key}(${member.name || member.key}, 向上汇报)`
|
|
101
|
+
: `${member.key}(${member.name || member.key})`;
|
|
102
|
+
return `- ${label}: \`${dispatchBin} --from ${projectKey} ${member.key} "消息"\``;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return ['[团队联络提示]', '以下成员可通过 dispatch_to 联络:', ...lines].join('\n');
|
|
106
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MetaMe Team Context Hook — UserPromptSubmit
|
|
4
|
+
*
|
|
5
|
+
* Detects communication intent towards team members in the prompt.
|
|
6
|
+
* If found, injects targeted dispatch_to hint(s) for only those members.
|
|
7
|
+
* Zero injection when no communication intent detected → zero wasted tokens.
|
|
8
|
+
*
|
|
9
|
+
* Triggers when the prompt contains:
|
|
10
|
+
* - A communication verb (告诉/让/发给/和...讨论/...) near a member nickname
|
|
11
|
+
* - Or a member nickname in a communication context
|
|
12
|
+
*/
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
const METAME_DIR = path.join(os.homedir(), '.metame');
|
|
20
|
+
|
|
21
|
+
// ── Communication intent patterns ────────────────────────────────────────────
|
|
22
|
+
// Three structural patterns:
|
|
23
|
+
// A) verb → name: 告诉工匠 / 发给builder / 通知乙
|
|
24
|
+
// B) name → verb: 工匠你来 / builder帮我 / 乙去做
|
|
25
|
+
// C) prep + name: 和工匠讨论 / 跟builder说 / 与乙沟通 (name between prep and verb)
|
|
26
|
+
|
|
27
|
+
const BEFORE_NAME_ZH = ['告诉', '通知', '让', '叫', '派', '交给', '转给', '发给', '联系', '找', '请', '问', '发消息给'];
|
|
28
|
+
const BEFORE_NAME_EN = ['tell', 'ask', 'notify', 'send to', 'assign to', 'delegate to', 'message', 'ping', 'contact'];
|
|
29
|
+
const AFTER_NAME_ZH = ['你来', '来做', '去做', '帮我', '帮忙', '负责', '处理', '跟进'];
|
|
30
|
+
const AFTER_NAME_EN = ['help', 'do this', 'handle', 'take care', 'follow up'];
|
|
31
|
+
// Prepositions that introduce a discussion partner (name follows immediately)
|
|
32
|
+
const PREP_ZH = ['和', '跟', '与'];
|
|
33
|
+
const DISC_EN = ['discuss with', 'talk to', 'chat with', 'coordinate with', 'sync with', 'work with'];
|
|
34
|
+
|
|
35
|
+
function hasCommIntent(text, nickname) {
|
|
36
|
+
const t = text.toLowerCase();
|
|
37
|
+
const n = nickname.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
38
|
+
|
|
39
|
+
// A) verb → name (within 15 chars)
|
|
40
|
+
for (const v of BEFORE_NAME_ZH) {
|
|
41
|
+
if (new RegExp(`${v}.{0,15}${n}`, 'u').test(t)) return true;
|
|
42
|
+
}
|
|
43
|
+
for (const v of BEFORE_NAME_EN) {
|
|
44
|
+
if (new RegExp(`${v}.{0,20}${n}`, 'i').test(t)) return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// B) name → verb (within 8 chars)
|
|
48
|
+
for (const v of AFTER_NAME_ZH) {
|
|
49
|
+
if (new RegExp(`${n}.{0,8}${v}`, 'u').test(t)) return true;
|
|
50
|
+
}
|
|
51
|
+
for (const v of AFTER_NAME_EN) {
|
|
52
|
+
if (new RegExp(`${n}.{0,10}${v}`, 'i').test(t)) return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// C) prep + name (和工匠/跟builder — prep within 3 chars before name)
|
|
56
|
+
for (const p of PREP_ZH) {
|
|
57
|
+
if (new RegExp(`${p}.{0,3}${n}`, 'u').test(t)) return true;
|
|
58
|
+
}
|
|
59
|
+
for (const v of DISC_EN) {
|
|
60
|
+
if (new RegExp(`${v}.{0,20}${n}`, 'i').test(t)) return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
67
|
+
function exit() { process.exit(0); }
|
|
68
|
+
|
|
69
|
+
let raw = '';
|
|
70
|
+
process.stdin.setEncoding('utf8');
|
|
71
|
+
process.stdin.on('data', c => { raw += c; });
|
|
72
|
+
process.stdin.on('end', () => {
|
|
73
|
+
try { run(JSON.parse(raw)); } catch { exit(); }
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function run(data) {
|
|
77
|
+
const projectKey = process.env.METAME_PROJECT;
|
|
78
|
+
if (!projectKey || process.env.METAME_INTERNAL_PROMPT === '1') return exit();
|
|
79
|
+
|
|
80
|
+
const prompt = (data.prompt || data.user_prompt || '').trim();
|
|
81
|
+
if (!prompt) return exit();
|
|
82
|
+
|
|
83
|
+
// Load config
|
|
84
|
+
let config;
|
|
85
|
+
try {
|
|
86
|
+
const yaml = require('../resolve-yaml');
|
|
87
|
+
config = yaml.load(fs.readFileSync(path.join(METAME_DIR, 'daemon.yaml'), 'utf8'));
|
|
88
|
+
} catch { return exit(); }
|
|
89
|
+
|
|
90
|
+
if (!config || !config.projects) return exit();
|
|
91
|
+
|
|
92
|
+
// Collect all team members across all projects (caller may be top-level or member)
|
|
93
|
+
const allMembers = [];
|
|
94
|
+
for (const [parentKey, parent] of Object.entries(config.projects)) {
|
|
95
|
+
if (!Array.isArray(parent.team)) continue;
|
|
96
|
+
for (const member of parent.team) {
|
|
97
|
+
if (member.key === projectKey) continue; // skip self
|
|
98
|
+
allMembers.push({ member, parentKey, parent });
|
|
99
|
+
}
|
|
100
|
+
// Also allow dispatching to parent project itself (escalation)
|
|
101
|
+
if (parent.team.some(m => m.key === projectKey)) {
|
|
102
|
+
allMembers.push({
|
|
103
|
+
member: { key: parentKey, name: parent.name, nicknames: parent.nicknames },
|
|
104
|
+
parentKey,
|
|
105
|
+
parent,
|
|
106
|
+
isParent: true,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (allMembers.length === 0) return exit();
|
|
112
|
+
|
|
113
|
+
const dispatchBin = path.join(METAME_DIR, 'bin', 'dispatch_to');
|
|
114
|
+
|
|
115
|
+
// Find members with communication intent in this prompt
|
|
116
|
+
const hits = [];
|
|
117
|
+
for (const { member, isParent } of allMembers) {
|
|
118
|
+
const nicks = [member.key, member.name, ...(Array.isArray(member.nicknames) ? member.nicknames : [])].filter(Boolean);
|
|
119
|
+
const matched = nicks.some(n => hasCommIntent(prompt, n));
|
|
120
|
+
if (matched) hits.push({ member, isParent });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (hits.length === 0) return exit();
|
|
124
|
+
|
|
125
|
+
// Build targeted hint for matched members only
|
|
126
|
+
const lines = hits.map(({ member, isParent }) => {
|
|
127
|
+
const target = member.peer ? `${member.peer}:${member.key}` : member.key;
|
|
128
|
+
const location = member.peer ? ` [远端:${member.peer}]` : '';
|
|
129
|
+
const label = isParent ? `${member.key}(${member.name || member.key}, 向上汇报)` : `${member.key}(${member.name || member.key}${location})`;
|
|
130
|
+
return `- ${label}: \`${dispatchBin} --from ${projectKey} ${target} "消息"\``;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const hint = [
|
|
134
|
+
`[团队联络提示]`,
|
|
135
|
+
`以下成员可通过 dispatch_to 联络:`,
|
|
136
|
+
...lines,
|
|
137
|
+
].join('\n');
|
|
138
|
+
|
|
139
|
+
process.stdout.write(JSON.stringify({
|
|
140
|
+
hookSpecificOutput: { additionalSystemPrompt: hint },
|
|
141
|
+
}));
|
|
142
|
+
exit();
|
|
143
|
+
}
|
|
@@ -276,7 +276,7 @@ async function run() {
|
|
|
276
276
|
let distillEnv = {};
|
|
277
277
|
try { distillEnv = buildDistillEnv(); } catch { }
|
|
278
278
|
|
|
279
|
-
const sessions = sessionAnalytics.findAllUnextractedSessions(
|
|
279
|
+
const sessions = sessionAnalytics.findAllUnextractedSessions(3);
|
|
280
280
|
if (sessions.length === 0) {
|
|
281
281
|
console.log('[memory-extract] No unanalyzed sessions found.');
|
|
282
282
|
memory.close();
|