metame-cli 1.4.33 → 1.5.0
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 +187 -48
- package/index.js +148 -9
- package/package.json +6 -3
- package/scripts/daemon-admin-commands.js +254 -9
- package/scripts/daemon-agent-commands.js +64 -6
- package/scripts/daemon-agent-tools.js +26 -5
- package/scripts/daemon-bridges.js +110 -20
- package/scripts/daemon-claude-engine.js +704 -268
- package/scripts/daemon-command-router.js +24 -8
- package/scripts/daemon-default.yaml +28 -4
- package/scripts/daemon-engine-runtime.js +275 -0
- package/scripts/daemon-exec-commands.js +10 -4
- package/scripts/daemon-notify.js +37 -1
- package/scripts/daemon-runtime-lifecycle.js +2 -1
- package/scripts/daemon-session-commands.js +52 -4
- package/scripts/daemon-session-store.js +2 -1
- package/scripts/daemon-task-scheduler.js +87 -28
- package/scripts/daemon-user-acl.js +26 -9
- package/scripts/daemon.js +81 -17
- package/scripts/distill.js +323 -18
- package/scripts/docs/agent-guide.md +12 -0
- package/scripts/docs/maintenance-manual.md +119 -0
- package/scripts/docs/pointer-map.md +88 -0
- package/scripts/feishu-adapter.js +6 -1
- package/scripts/hooks/stop-session-capture.js +243 -0
- package/scripts/memory-extract.js +100 -5
- package/scripts/memory-nightly-reflect.js +196 -11
- package/scripts/memory.js +134 -3
- package/scripts/mentor-engine.js +405 -0
- package/scripts/platform.js +2 -0
- package/scripts/providers.js +169 -21
- package/scripts/schema.js +12 -0
- package/scripts/session-analytics.js +245 -12
- package/scripts/skill-changelog.js +245 -0
- package/scripts/skill-evolution.js +288 -5
- package/scripts/usage-classifier.js +1 -1
- package/scripts/daemon-admin-commands.test.js +0 -333
- package/scripts/daemon-task-envelope.test.js +0 -59
- package/scripts/daemon-task-scheduler.test.js +0 -106
- package/scripts/reliability-core.test.js +0 -280
- package/scripts/skill-evolution.test.js +0 -113
- package/scripts/task-board.test.js +0 -83
- package/scripts/test_daemon.js +0 -1407
- package/scripts/utils.test.js +0 -192
package/index.js
CHANGED
|
@@ -88,7 +88,7 @@ function syncDirFiles(srcDir, destDir, { fileList, chmod } = {}) {
|
|
|
88
88
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
89
89
|
// IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
|
|
90
90
|
const scriptsDir = path.join(__dirname, 'scripts');
|
|
91
|
-
const BUNDLED_BASE_SCRIPTS = ['platform.js', 'signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', '
|
|
91
|
+
const BUNDLED_BASE_SCRIPTS = ['platform.js', 'signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'daemon.js', 'telegram-adapter.js', 'feishu-adapter.js', 'daemon-default.yaml', 'providers.js', 'session-analytics.js', 'resolve-yaml.js', 'utils.js', 'skill-evolution.js', 'memory.js', 'memory-extract.js', 'memory-search.js', 'memory-write.js', 'memory-gc.js', 'qmd-client.js', 'session-summarize.js', 'mentor-engine.js', 'check-macos-control-capabilities.sh', 'usage-classifier.js', 'task-board.js', 'memory-nightly-reflect.js', 'memory-index.js', 'skill-changelog.js'];
|
|
92
92
|
const DAEMON_MODULE_SCRIPTS = (() => {
|
|
93
93
|
try {
|
|
94
94
|
return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
|
|
@@ -124,6 +124,8 @@ if (scriptsUpdated) {
|
|
|
124
124
|
syncDirFiles(path.join(__dirname, 'scripts', 'docs'), path.join(METAME_DIR, 'docs'));
|
|
125
125
|
// Bin: CLI tools (dispatch_to etc.)
|
|
126
126
|
syncDirFiles(path.join(__dirname, 'scripts', 'bin'), path.join(METAME_DIR, 'bin'), { chmod: 0o755 });
|
|
127
|
+
// Hooks: Claude Code event hooks (Stop, PostToolUse, etc.)
|
|
128
|
+
syncDirFiles(path.join(__dirname, 'scripts', 'hooks'), path.join(METAME_DIR, 'hooks'));
|
|
127
129
|
|
|
128
130
|
// ---------------------------------------------------------
|
|
129
131
|
// Deploy bundled skills to ~/.claude/skills/
|
|
@@ -163,6 +165,29 @@ if (fs.existsSync(bundledSkillsDir)) {
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
|
|
168
|
+
// Ensure ~/.codex/skills and ~/.agents/skills are symlinks to ~/.claude/skills
|
|
169
|
+
// This keeps skill evolution unified across all engines.
|
|
170
|
+
for (const altDir of [
|
|
171
|
+
path.join(HOME_DIR, '.codex', 'skills'),
|
|
172
|
+
path.join(HOME_DIR, '.agents', 'skills'),
|
|
173
|
+
]) {
|
|
174
|
+
try {
|
|
175
|
+
const parentDir = path.dirname(altDir);
|
|
176
|
+
if (!fs.existsSync(parentDir)) continue; // engine not installed, skip
|
|
177
|
+
const stat = fs.lstatSync(altDir);
|
|
178
|
+
if (stat.isSymbolicLink()) continue; // already a symlink, good
|
|
179
|
+
// Physical directory exists — replace with symlink
|
|
180
|
+
fs.rmSync(altDir, { recursive: true, force: true });
|
|
181
|
+
fs.symlinkSync(CLAUDE_SKILLS_DIR, altDir);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
if (e.code === 'ENOENT') {
|
|
184
|
+
// Directory doesn't exist — create symlink
|
|
185
|
+
try { fs.symlinkSync(CLAUDE_SKILLS_DIR, altDir); } catch { /* non-fatal */ }
|
|
186
|
+
}
|
|
187
|
+
// Other errors: non-fatal, skip
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
166
191
|
// Load daemon config for local launch flags
|
|
167
192
|
let daemonCfg = {};
|
|
168
193
|
try {
|
|
@@ -231,6 +256,8 @@ function ensureHookInstalled() {
|
|
|
231
256
|
entry.hooks?.some(h => h.command && h.command.includes('signal-capture.js'))
|
|
232
257
|
);
|
|
233
258
|
|
|
259
|
+
let modified = false;
|
|
260
|
+
|
|
234
261
|
if (!stillInstalled) {
|
|
235
262
|
if (!settings.hooks) settings.hooks = {};
|
|
236
263
|
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
@@ -241,9 +268,33 @@ function ensureHookInstalled() {
|
|
|
241
268
|
command: hookCommand
|
|
242
269
|
}]
|
|
243
270
|
});
|
|
271
|
+
modified = true;
|
|
272
|
+
console.log(`${icon("hook")} MetaMe: Signal capture hook installed.`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Ensure Stop hook (session-logger + tool-failure capture) is installed
|
|
276
|
+
const stopHookScript = path.join(METAME_DIR, 'hooks', 'stop-session-capture.js').replace(/\\/g, '/');
|
|
277
|
+
const stopHookCommand = `node "${stopHookScript}"`;
|
|
278
|
+
const stopHookInstalled = (settings.hooks?.Stop || []).some(entry =>
|
|
279
|
+
entry.hooks?.some(h => h.command && h.command.includes('stop-session-capture.js'))
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
if (!stopHookInstalled) {
|
|
283
|
+
if (!settings.hooks) settings.hooks = {};
|
|
284
|
+
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
285
|
+
|
|
286
|
+
settings.hooks.Stop.push({
|
|
287
|
+
hooks: [{
|
|
288
|
+
type: 'command',
|
|
289
|
+
command: stopHookCommand
|
|
290
|
+
}]
|
|
291
|
+
});
|
|
292
|
+
modified = true;
|
|
293
|
+
console.log(`${icon("hook")} MetaMe: Stop session capture hook installed.`);
|
|
294
|
+
}
|
|
244
295
|
|
|
296
|
+
if (modified) {
|
|
245
297
|
fs.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2), 'utf8');
|
|
246
|
-
console.log(`${icon("hook")} MetaMe: Signal capture hook installed.`);
|
|
247
298
|
}
|
|
248
299
|
} catch (e) {
|
|
249
300
|
// Non-fatal: hook install failure shouldn't block launch
|
|
@@ -743,7 +794,8 @@ const GLOBAL_MARKER_END = '<!-- METAME-GLOBAL:END -->';
|
|
|
743
794
|
// Build dynamic Agent dispatch table from daemon.yaml projects.
|
|
744
795
|
// Only include agents whose cwd actually exists on disk — test/stale agents
|
|
745
796
|
// with deleted paths are automatically excluded, no manual cleanup needed.
|
|
746
|
-
|
|
797
|
+
// The table is written to ~/.metame/docs/dispatch-table.md (NOT inlined into CLAUDE.md).
|
|
798
|
+
const DISPATCH_TABLE_PATH = path.join(METAME_DIR, 'docs', 'dispatch-table.md');
|
|
747
799
|
try {
|
|
748
800
|
const daemonYamlPath = path.join(os.homedir(), '.metame', 'daemon.yaml');
|
|
749
801
|
if (fs.existsSync(daemonYamlPath)) {
|
|
@@ -752,13 +804,29 @@ try {
|
|
|
752
804
|
const rows = Object.entries(projects)
|
|
753
805
|
.filter(([, p]) => {
|
|
754
806
|
if (!p || !p.name || !p.cwd) return false;
|
|
755
|
-
// Expand ~ to home directory
|
|
756
807
|
const expandedCwd = String(p.cwd).replace(/^~/, os.homedir());
|
|
757
808
|
return fs.existsSync(expandedCwd);
|
|
758
809
|
})
|
|
759
810
|
.map(([key, p]) => `| \`${key}\` | ${p.name} |`);
|
|
760
811
|
if (rows.length > 0) {
|
|
761
|
-
|
|
812
|
+
const tableContent = [
|
|
813
|
+
'# Agent Dispatch 路由表',
|
|
814
|
+
'',
|
|
815
|
+
'> 自动生成,来源:daemon.yaml。勿手动编辑。',
|
|
816
|
+
'',
|
|
817
|
+
'| project_key | 昵称 |',
|
|
818
|
+
'|-------------|------|',
|
|
819
|
+
...rows,
|
|
820
|
+
'',
|
|
821
|
+
'## 使用方法',
|
|
822
|
+
'```bash',
|
|
823
|
+
'~/.metame/bin/dispatch_to [--new] <project_key> "内容"',
|
|
824
|
+
'```',
|
|
825
|
+
'`--new` 强制新建会话(用户说"新开会话"时加此参数)。',
|
|
826
|
+
'新增 Agent:`/agent bind <名称> <工作目录>`',
|
|
827
|
+
].join('\n') + '\n';
|
|
828
|
+
fs.mkdirSync(path.dirname(DISPATCH_TABLE_PATH), { recursive: true });
|
|
829
|
+
fs.writeFileSync(DISPATCH_TABLE_PATH, tableContent);
|
|
762
830
|
}
|
|
763
831
|
}
|
|
764
832
|
} catch { /* daemon.yaml missing or invalid — skip dispatch table */ }
|
|
@@ -771,11 +839,11 @@ const KERNEL_BODY = PROTOCOL_NORMAL
|
|
|
771
839
|
|
|
772
840
|
const CAPABILITY_SECTIONS = [
|
|
773
841
|
'## Agent Dispatch',
|
|
774
|
-
|
|
775
|
-
'新增 Agent:`/agent bind <名称> <工作目录>`',
|
|
842
|
+
'识别到"告诉X/让X/通知X"等转发意图时 → 先 `cat ~/.metame/docs/dispatch-table.md` 获取路由表(昵称→project_key),再执行转发。不要凭记忆猜测昵称对应关系。',
|
|
776
843
|
'',
|
|
777
844
|
'## Agent 创建与管理',
|
|
778
845
|
'用户问创建/管理/绑定 Agent 时 → 先 `cat ~/.metame/docs/agent-guide.md` 再回答。',
|
|
846
|
+
'用户问代码结构/升级进度/脚本入口时 → 先 `cat ~/.metame/docs/pointer-map.md` 再回答。',
|
|
779
847
|
'',
|
|
780
848
|
'## 手机端文件交互',
|
|
781
849
|
'用户要文件("发给我"/"发过来"/"导出")→ 先 `cat ~/.metame/docs/file-transfer.md` 再执行。',
|
|
@@ -882,7 +950,37 @@ try {
|
|
|
882
950
|
}
|
|
883
951
|
} catch { /* non-fatal */ }
|
|
884
952
|
|
|
953
|
+
// Skill evolution status
|
|
954
|
+
try {
|
|
955
|
+
const skillChangelog = require('./scripts/skill-changelog');
|
|
956
|
+
const skillCount = skillChangelog.countInstalledSkills();
|
|
957
|
+
const lastSession = skillChangelog.getLastSessionStart();
|
|
958
|
+
const recentChanges = skillChangelog.getRecentChanges(lastSession);
|
|
959
|
+
|
|
960
|
+
if (recentChanges.length === 0) {
|
|
961
|
+
console.log(`${icon("tool")} Skills: ${skillCount} installed · 无新变更`);
|
|
962
|
+
} else {
|
|
963
|
+
const evolved = recentChanges.filter(c => c.action === 'evolved');
|
|
964
|
+
const others = recentChanges.filter(c => c.action !== 'evolved');
|
|
965
|
+
const parts = [`${skillCount} installed`];
|
|
966
|
+
if (evolved.length > 0) parts.push(`${evolved.length} evolved since last session`);
|
|
967
|
+
if (others.length > 0) parts.push(`${others.length} other event${others.length > 1 ? 's' : ''}`);
|
|
968
|
+
console.log(`${icon("tool")} Skills: ${parts.join(' · ')}`);
|
|
969
|
+
|
|
970
|
+
// Show up to 3 details
|
|
971
|
+
const shown = recentChanges.slice(0, 3);
|
|
972
|
+
for (const c of shown) {
|
|
973
|
+
const actionIcon = skillChangelog.getActionIcon(c.action);
|
|
974
|
+
console.log(` ${actionIcon} ${c.skill || 'system'}: ${c.summary}`);
|
|
975
|
+
}
|
|
976
|
+
if (recentChanges.length > 3) {
|
|
977
|
+
console.log(` +${recentChanges.length - 3} more`);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
885
980
|
|
|
981
|
+
// Write session start marker for next time
|
|
982
|
+
skillChangelog.writeSessionStart();
|
|
983
|
+
} catch { /* non-fatal */ }
|
|
886
984
|
|
|
887
985
|
// ---------------------------------------------------------
|
|
888
986
|
// 4.9 AUTO-UPDATE CHECK (non-blocking)
|
|
@@ -1806,7 +1904,12 @@ WantedBy=default.target
|
|
|
1806
1904
|
}
|
|
1807
1905
|
|
|
1808
1906
|
// Unknown subcommand
|
|
1809
|
-
console.log(`${icon("book")} MetaMe
|
|
1907
|
+
console.log(`${icon("book")} MetaMe Commands:`);
|
|
1908
|
+
console.log(" metame — launch Claude with MetaMe init");
|
|
1909
|
+
console.log(" metame codex [args] — launch Codex with MetaMe init");
|
|
1910
|
+
console.log(" metame continue — resume latest session");
|
|
1911
|
+
console.log("");
|
|
1912
|
+
console.log(`${icon("book")} Daemon Commands:`);
|
|
1810
1913
|
console.log(" metame start — start background daemon");
|
|
1811
1914
|
console.log(" metame stop — stop daemon");
|
|
1812
1915
|
console.log(" metame status — show status & budget");
|
|
@@ -1824,7 +1927,43 @@ WantedBy=default.target
|
|
|
1824
1927
|
}
|
|
1825
1928
|
|
|
1826
1929
|
// ---------------------------------------------------------
|
|
1827
|
-
// 5.8
|
|
1930
|
+
// 5.8 CODEX — launch Codex with MetaMe initialization
|
|
1931
|
+
// ---------------------------------------------------------
|
|
1932
|
+
const isCodex = process.argv[2] === 'codex';
|
|
1933
|
+
if (isCodex) {
|
|
1934
|
+
// spawn() resolves PATH automatically; error event handles missing binary
|
|
1935
|
+
const codexBin = 'codex';
|
|
1936
|
+
|
|
1937
|
+
// Build codex args: remaining user args after 'codex'
|
|
1938
|
+
const codexUserArgs = process.argv.slice(3);
|
|
1939
|
+
let codexArgs;
|
|
1940
|
+
if (codexUserArgs.length === 0) {
|
|
1941
|
+
// Interactive mode: `codex --full-auto`
|
|
1942
|
+
codexArgs = ['--full-auto'];
|
|
1943
|
+
} else {
|
|
1944
|
+
// Non-interactive: `codex exec --full-auto <user args>`
|
|
1945
|
+
codexArgs = ['exec', '--full-auto', ...codexUserArgs];
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
const codexChild = spawn(codexBin, codexArgs, {
|
|
1949
|
+
stdio: 'inherit',
|
|
1950
|
+
cwd: process.cwd(),
|
|
1951
|
+
env: { ...process.env, METAME_ACTIVE_SESSION: 'true' },
|
|
1952
|
+
});
|
|
1953
|
+
|
|
1954
|
+
codexChild.on('error', () => {
|
|
1955
|
+
console.error(`\n${icon("fail")} Error: Could not launch 'codex'.`);
|
|
1956
|
+
console.error(" Please install: npm install -g @openai/codex");
|
|
1957
|
+
});
|
|
1958
|
+
codexChild.on('close', (code) => process.exit(code || 0));
|
|
1959
|
+
|
|
1960
|
+
// Background distillation
|
|
1961
|
+
spawnDistillBackground();
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// ---------------------------------------------------------
|
|
1966
|
+
// 5.9 CONTINUE/SYNC — resume latest session from terminal
|
|
1828
1967
|
// ---------------------------------------------------------
|
|
1829
1968
|
// Usage: exit Claude first, then run `metame continue` from terminal.
|
|
1830
1969
|
// Finds the most recent session and launches Claude with --resume.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
|
-
"scripts/"
|
|
11
|
+
"scripts/",
|
|
12
|
+
"!scripts/*.test.js",
|
|
13
|
+
"!scripts/test_daemon.js",
|
|
14
|
+
"!scripts/hooks/test-*.js"
|
|
12
15
|
],
|
|
13
16
|
"scripts": {
|
|
14
17
|
"test": "node --test scripts/*.test.js",
|
|
15
18
|
"start": "node index.js",
|
|
16
|
-
"sync:plugin": "cp scripts/platform.js scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-command-router.js scripts/daemon-user-acl.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/daemon-task-envelope.js scripts/task-board.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/usage-classifier.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-extract.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/skill-evolution.js scripts/check-macos-control-capabilities.sh plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
19
|
+
"sync:plugin": "cp scripts/platform.js scripts/schema.js scripts/pending-traits.js scripts/signal-capture.js scripts/distill.js scripts/daemon.js scripts/daemon-agent-commands.js scripts/daemon-session-commands.js scripts/daemon-admin-commands.js scripts/daemon-exec-commands.js scripts/daemon-ops-commands.js scripts/daemon-session-store.js scripts/daemon-checkpoints.js scripts/daemon-bridges.js scripts/daemon-file-browser.js scripts/daemon-runtime-lifecycle.js scripts/daemon-notify.js scripts/daemon-claude-engine.js scripts/daemon-engine-runtime.js scripts/daemon-command-router.js scripts/daemon-user-acl.js scripts/daemon-agent-tools.js scripts/daemon-task-scheduler.js scripts/daemon-task-envelope.js scripts/task-board.js scripts/telegram-adapter.js scripts/feishu-adapter.js scripts/daemon-default.yaml scripts/providers.js scripts/utils.js scripts/usage-classifier.js scripts/resolve-yaml.js scripts/memory.js scripts/memory-write.js scripts/memory-extract.js scripts/memory-search.js scripts/memory-gc.js scripts/memory-nightly-reflect.js scripts/memory-index.js scripts/qmd-client.js scripts/session-summarize.js scripts/session-analytics.js scripts/mentor-engine.js scripts/skill-evolution.js scripts/skill-changelog.js scripts/check-macos-control-capabilities.sh plugin/scripts/ && echo '✅ Plugin scripts synced'",
|
|
17
20
|
"sync:readme": "node scripts/sync-readme.js",
|
|
18
21
|
"restart:daemon": "node index.js stop 2>/dev/null; sleep 1; node index.js start 2>/dev/null || echo '⚠️ Daemon not running or restart failed'",
|
|
19
22
|
"precommit": "npm run sync:plugin && npm run restart:daemon"
|
|
@@ -5,6 +5,9 @@ const {
|
|
|
5
5
|
CORE_USAGE_CATEGORIES,
|
|
6
6
|
USAGE_CATEGORY_LABEL,
|
|
7
7
|
} = require('./usage-classifier');
|
|
8
|
+
const { IS_WIN } = require('./platform');
|
|
9
|
+
let mentorEngine = null;
|
|
10
|
+
try { mentorEngine = require('./mentor-engine'); } catch { /* optional */ }
|
|
8
11
|
|
|
9
12
|
function createAdminCommandHandler(deps) {
|
|
10
13
|
const {
|
|
@@ -30,6 +33,8 @@ function createAdminCommandHandler(deps) {
|
|
|
30
33
|
getMessageQueue,
|
|
31
34
|
loadState,
|
|
32
35
|
saveState,
|
|
36
|
+
getDefaultEngine = () => 'claude',
|
|
37
|
+
setDefaultEngine = () => {},
|
|
33
38
|
} = deps;
|
|
34
39
|
|
|
35
40
|
function resolveProjectKey(targetName, projects) {
|
|
@@ -92,6 +97,61 @@ function createAdminCommandHandler(deps) {
|
|
|
92
97
|
return 'unspecified';
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
function modeFromLevel(level) {
|
|
101
|
+
const n = Number(level);
|
|
102
|
+
if (!Number.isFinite(n)) return 'gentle';
|
|
103
|
+
if (n >= 8) return 'intense';
|
|
104
|
+
if (n >= 4) return 'active';
|
|
105
|
+
return 'gentle';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseDistillModelIntent(input) {
|
|
109
|
+
const text = String(input || '').trim();
|
|
110
|
+
if (!text || text.startsWith('/')) return null;
|
|
111
|
+
if (!/(蒸馏|distill|提炼|提纯)/i.test(text)) return null;
|
|
112
|
+
const setVerb = '(?:改成|改为|设为|设置|切到|切换到|换成|改用|使用|用|set|switch|use)';
|
|
113
|
+
if (!(new RegExp(setVerb, 'i')).test(text)) return null;
|
|
114
|
+
|
|
115
|
+
const explicitModel = text.match(new RegExp(`(?:蒸馏模型|模型|distill\\s*model|model)\\s*(?:${setVerb}|to|is)?\\s*[::]?\\s*([a-zA-Z0-9._-]{2,80})`, 'i'));
|
|
116
|
+
if (explicitModel) return { model: explicitModel[1] };
|
|
117
|
+
|
|
118
|
+
if (/(蒸馏模型|模型|distill\s*model|model)/i.test(text)) {
|
|
119
|
+
const quotedModel = text.match(/[“"'「]([a-zA-Z0-9._-]{2,80})[”"'」]/);
|
|
120
|
+
if (quotedModel) return { model: quotedModel[1] };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const knownToken = text.match(new RegExp(`${setVerb}\\s*(?:为|成|到|to)?\\s*[::]?\\s*(gpt-5\\.1-codex-mini|gpt-5-mini|haiku|sonnet|opus|5\\.1mini|5mini|codex-mini)\\b`, 'i'));
|
|
124
|
+
if (knownToken) return { model: knownToken[1] };
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function ensureMentorConfig(cfg) {
|
|
130
|
+
if (!cfg.daemon) cfg.daemon = {};
|
|
131
|
+
if (!cfg.daemon.mentor || typeof cfg.daemon.mentor !== 'object') {
|
|
132
|
+
cfg.daemon.mentor = {};
|
|
133
|
+
}
|
|
134
|
+
const mentor = cfg.daemon.mentor;
|
|
135
|
+
if (typeof mentor.enabled !== 'boolean') mentor.enabled = false;
|
|
136
|
+
if (!Number.isFinite(Number(mentor.friction_level))) mentor.friction_level = 3;
|
|
137
|
+
if (!mentor.mode || !['gentle', 'active', 'intense'].includes(String(mentor.mode))) {
|
|
138
|
+
mentor.mode = modeFromLevel(mentor.friction_level);
|
|
139
|
+
}
|
|
140
|
+
if (!Array.isArray(mentor.exclude_agents)) mentor.exclude_agents = ['personal', 'xianyu'];
|
|
141
|
+
if (!Array.isArray(mentor.emotion_keywords_extra)) mentor.emotion_keywords_extra = [];
|
|
142
|
+
return mentor;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function hasCli(execSyncFn, bin) {
|
|
146
|
+
try {
|
|
147
|
+
const cmd = process.platform === 'win32' ? `where ${bin}` : `which ${bin}`;
|
|
148
|
+
execSyncFn(cmd, { encoding: 'utf8' });
|
|
149
|
+
return true;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
95
155
|
async function handleAdminCommand(ctx) {
|
|
96
156
|
const { bot, chatId, text } = ctx;
|
|
97
157
|
const state = ctx.state || {};
|
|
@@ -195,17 +255,69 @@ function createAdminCommandHandler(deps) {
|
|
|
195
255
|
return { handled: true, config };
|
|
196
256
|
}
|
|
197
257
|
|
|
258
|
+
// /skill-evo approve <id> — approve a workflow_proposal and dispatch skill creation
|
|
259
|
+
const approveMatch = arg.match(/^approve\s+(\S+)$/i);
|
|
260
|
+
if (approveMatch) {
|
|
261
|
+
const id = approveMatch[1];
|
|
262
|
+
// Find the queue item (search both pending and notified states)
|
|
263
|
+
const item = skillEvolution.listQueueItems({ status: ['pending', 'notified'], limit: 200 })
|
|
264
|
+
.find(i => i.id === id && i.type === 'workflow_proposal');
|
|
265
|
+
if (!item) {
|
|
266
|
+
await bot.sendMessage(chatId, `❌ 未找到 workflow_proposal: ${id}`);
|
|
267
|
+
return { handled: true, config };
|
|
268
|
+
}
|
|
269
|
+
// Build skill-creator prefilled prompt
|
|
270
|
+
const toolsSig = (item.tools_signature || []).join(', ');
|
|
271
|
+
const prefilledPrompt = [
|
|
272
|
+
'/skill-creator',
|
|
273
|
+
`创建一个新技能,自动化以下工作流:`,
|
|
274
|
+
`工作流模式: ${item.search_hint || item.reason}`,
|
|
275
|
+
toolsSig ? `常用工具: ${toolsSig}` : '',
|
|
276
|
+
item.example_prompt ? `用户示例: "${item.example_prompt}"` : '',
|
|
277
|
+
`该技能应封装这个多步工作流为单一可调用技能。`,
|
|
278
|
+
].filter(Boolean).join('\n');
|
|
279
|
+
// Dispatch to metame agent for skill creation (async — must not block event loop)
|
|
280
|
+
try {
|
|
281
|
+
const HOME = require('os').homedir();
|
|
282
|
+
const dispatchBin = require('path').join(HOME, '.metame', 'bin', 'dispatch_to');
|
|
283
|
+
const { execFile } = require('child_process');
|
|
284
|
+
const { promisify } = require('util');
|
|
285
|
+
const execFileAsync = promisify(execFile);
|
|
286
|
+
// dispatch_to is a Node.js script; on Windows shebang resolution is unavailable,
|
|
287
|
+
// so invoke via node explicitly for cross-platform safety
|
|
288
|
+
const cmd = IS_WIN ? process.execPath : dispatchBin;
|
|
289
|
+
const cmdArgs = IS_WIN ? [dispatchBin, 'metame', prefilledPrompt] : ['metame', prefilledPrompt];
|
|
290
|
+
await execFileAsync(cmd, cmdArgs, { encoding: 'utf8', timeout: 15000 });
|
|
291
|
+
// Mark installed only after successful dispatch
|
|
292
|
+
skillEvolution.resolveQueueItemById(id, 'installed');
|
|
293
|
+
await bot.sendMessage(chatId, `✅ 已派发给 Jarvis 创建技能,完成后会通知你\n工作流: ${item.search_hint || item.reason}`);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
// Dispatch failed — don't mark installed, keep in queue
|
|
296
|
+
await bot.sendMessage(chatId, `⚠️ 自动派发失败: ${e.message}\n提案仍在队列中,可重试: /skill-evo approve ${id}`);
|
|
297
|
+
}
|
|
298
|
+
return { handled: true, config };
|
|
299
|
+
}
|
|
300
|
+
|
|
198
301
|
const dismissMatch = arg.match(/^(?:dismiss|skip|ignored?)\s+(\S+)$/i);
|
|
199
302
|
if (dismissMatch) {
|
|
200
303
|
const id = dismissMatch[1];
|
|
304
|
+
// Check if this is a workflow_proposal — if so, reset the sketch
|
|
305
|
+
const item = skillEvolution.listQueueItems({ status: ['pending', 'notified'], limit: 200 })
|
|
306
|
+
.find(i => i.id === id);
|
|
201
307
|
const ok = skillEvolution.resolveQueueItemById
|
|
202
308
|
? skillEvolution.resolveQueueItemById(id, 'dismissed')
|
|
203
309
|
: false;
|
|
310
|
+
// Reset workflow sketch so it can re-accumulate
|
|
311
|
+
if (ok && item && item.type === 'workflow_proposal' && item.workflow_sketch_id) {
|
|
312
|
+
if (skillEvolution.resetWorkflowSketch) {
|
|
313
|
+
skillEvolution.resetWorkflowSketch(item.workflow_sketch_id);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
204
316
|
await bot.sendMessage(chatId, ok ? `✅ 已标记 dismissed: ${id}` : `❌ 未找到可处理项: ${id}`);
|
|
205
317
|
return { handled: true, config };
|
|
206
318
|
}
|
|
207
319
|
|
|
208
|
-
await bot.sendMessage(chatId, '用法: /skill-evo list | /skill-evo done <id> | /skill-evo dismiss <id>');
|
|
320
|
+
await bot.sendMessage(chatId, '用法: /skill-evo list | /skill-evo done <id> | /skill-evo dismiss <id> | /skill-evo approve <id>');
|
|
209
321
|
return { handled: true, config };
|
|
210
322
|
}
|
|
211
323
|
|
|
@@ -635,6 +747,68 @@ function createAdminCommandHandler(deps) {
|
|
|
635
747
|
return { handled: true, config };
|
|
636
748
|
}
|
|
637
749
|
|
|
750
|
+
if (text === '/mentor' || text.startsWith('/mentor ')) {
|
|
751
|
+
try {
|
|
752
|
+
backupConfig();
|
|
753
|
+
const cfg = yaml.load(fs.readFileSync(CONFIG_FILE, 'utf8')) || {};
|
|
754
|
+
const mentorCfg = ensureMentorConfig(cfg);
|
|
755
|
+
const arg = text.slice('/mentor'.length).trim();
|
|
756
|
+
|
|
757
|
+
if (!arg || arg === 'status') {
|
|
758
|
+
const status = mentorEngine && typeof mentorEngine.getRuntimeStatus === 'function'
|
|
759
|
+
? mentorEngine.getRuntimeStatus()
|
|
760
|
+
: { debt_count: 0, cooldown_remaining_ms: 0 };
|
|
761
|
+
const mode = String(mentorCfg.mode || modeFromLevel(mentorCfg.friction_level));
|
|
762
|
+
const level = Number(mentorCfg.friction_level || 0);
|
|
763
|
+
const cooldownSec = Math.ceil((Number(status.cooldown_remaining_ms) || 0) / 1000);
|
|
764
|
+
const lines = [
|
|
765
|
+
`Mentor: ${mentorCfg.enabled ? 'ON' : 'OFF'}`,
|
|
766
|
+
`Mode: ${mode}`,
|
|
767
|
+
`Friction level: ${level}`,
|
|
768
|
+
`Debts: ${status.debt_count || 0}`,
|
|
769
|
+
`Emotion cooldown: ${cooldownSec > 0 ? `${cooldownSec}s` : '0s'}`,
|
|
770
|
+
'Zone: n/a (runtime)',
|
|
771
|
+
];
|
|
772
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
773
|
+
return { handled: true, config };
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (arg === 'on' || arg === 'off') {
|
|
777
|
+
mentorCfg.enabled = arg === 'on';
|
|
778
|
+
writeConfigSafe(cfg);
|
|
779
|
+
config = loadConfig();
|
|
780
|
+
await bot.sendMessage(chatId, mentorCfg.enabled
|
|
781
|
+
? '✅ Mentor mode enabled.'
|
|
782
|
+
: '✅ Mentor mode disabled.');
|
|
783
|
+
return { handled: true, config };
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const mLevel = arg.match(/^level\s+(-?\d{1,2})$/i);
|
|
787
|
+
if (mLevel) {
|
|
788
|
+
let level = Number(mLevel[1]);
|
|
789
|
+
if (!Number.isFinite(level)) level = 3;
|
|
790
|
+
level = Math.max(0, Math.min(10, Math.floor(level)));
|
|
791
|
+
mentorCfg.friction_level = level;
|
|
792
|
+
mentorCfg.mode = modeFromLevel(level);
|
|
793
|
+
writeConfigSafe(cfg);
|
|
794
|
+
config = loadConfig();
|
|
795
|
+
await bot.sendMessage(chatId, `✅ Mentor level set to ${level} (${mentorCfg.mode}).`);
|
|
796
|
+
return { handled: true, config };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
await bot.sendMessage(chatId, [
|
|
800
|
+
'用法:',
|
|
801
|
+
'/mentor on',
|
|
802
|
+
'/mentor off',
|
|
803
|
+
'/mentor level <0-10>',
|
|
804
|
+
'/mentor status',
|
|
805
|
+
].join('\n'));
|
|
806
|
+
} catch (e) {
|
|
807
|
+
await bot.sendMessage(chatId, `❌ Mentor command failed: ${e.message}`);
|
|
808
|
+
}
|
|
809
|
+
return { handled: true, config };
|
|
810
|
+
}
|
|
811
|
+
|
|
638
812
|
if (text === '/reload') {
|
|
639
813
|
if (global._metameReload) {
|
|
640
814
|
const r = global._metameReload();
|
|
@@ -727,6 +901,10 @@ function createAdminCommandHandler(deps) {
|
|
|
727
901
|
const validModels = ['sonnet', 'opus', 'haiku'];
|
|
728
902
|
const checks = [];
|
|
729
903
|
let issues = 0;
|
|
904
|
+
const activeProvider = providerMod && typeof providerMod.getActiveName === 'function'
|
|
905
|
+
? providerMod.getActiveName()
|
|
906
|
+
: 'anthropic';
|
|
907
|
+
const isCustomProvider = activeProvider !== 'anthropic';
|
|
730
908
|
|
|
731
909
|
let cfg = null;
|
|
732
910
|
try {
|
|
@@ -738,21 +916,34 @@ function createAdminCommandHandler(deps) {
|
|
|
738
916
|
}
|
|
739
917
|
|
|
740
918
|
const m = (cfg && cfg.daemon && cfg.daemon.model) || 'opus';
|
|
741
|
-
|
|
919
|
+
const modelOk = isCustomProvider
|
|
920
|
+
? /^[a-zA-Z0-9._-]{2,80}$/.test(String(m || '').trim())
|
|
921
|
+
: validModels.includes(m);
|
|
922
|
+
if (modelOk) {
|
|
742
923
|
checks.push(`✅ 模型: ${m}`);
|
|
743
924
|
} else {
|
|
744
|
-
checks.push(`❌ 模型: ${m} (无效)`);
|
|
925
|
+
checks.push(`❌ 模型: ${m} (${isCustomProvider ? '格式无效' : '无效'})`);
|
|
745
926
|
issues++;
|
|
746
927
|
}
|
|
747
928
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
929
|
+
const hasClaude = hasCli(execSync, 'claude');
|
|
930
|
+
const hasCodex = hasCli(execSync, 'codex');
|
|
931
|
+
checks.push(hasClaude ? '✅ Claude CLI' : '⚠️ Claude CLI 未找到');
|
|
932
|
+
checks.push(hasCodex ? '✅ Codex CLI' : '⚠️ Codex CLI 未找到');
|
|
933
|
+
|
|
934
|
+
const currentEngine = getDefaultEngine() === 'codex' ? 'codex' : 'claude';
|
|
935
|
+
if (currentEngine === 'claude' && !hasClaude) {
|
|
936
|
+
checks.push('❌ 当前默认引擎是 claude,但 Claude CLI 不可用');
|
|
937
|
+
issues++;
|
|
938
|
+
}
|
|
939
|
+
if (currentEngine === 'codex' && !hasCodex) {
|
|
940
|
+
checks.push('❌ 当前默认引擎是 codex,但 Codex CLI 不可用');
|
|
753
941
|
issues++;
|
|
754
942
|
}
|
|
755
943
|
|
|
944
|
+
checks.push(`✅ 默认引擎: ${currentEngine}`);
|
|
945
|
+
checks.push(`✅ Provider: ${activeProvider}${isCustomProvider ? ' (custom)' : ''}`);
|
|
946
|
+
|
|
756
947
|
const bakFile = CONFIG_FILE + '.bak';
|
|
757
948
|
const hasBak = fs.existsSync(bakFile);
|
|
758
949
|
checks.push(hasBak ? '✅ 有备份' : '⚠️ 无备份');
|
|
@@ -867,10 +1058,64 @@ function createAdminCommandHandler(deps) {
|
|
|
867
1058
|
return { handled: true, config };
|
|
868
1059
|
}
|
|
869
1060
|
|
|
1061
|
+
// /engine [name] — show or switch default engine (claude/codex)
|
|
1062
|
+
if (text === '/engine' || text.startsWith('/engine ')) {
|
|
1063
|
+
const arg = text.slice('/engine'.length).trim().toLowerCase();
|
|
1064
|
+
if (!arg) {
|
|
1065
|
+
const cur = getDefaultEngine();
|
|
1066
|
+
const distill = providerMod ? providerMod.getDistillModel() : '(unknown)';
|
|
1067
|
+
await bot.sendMessage(chatId, `🔧 当前引擎: ${cur}\n🧪 蒸馏模型: ${distill}\n\n用法: /engine claude 或 /engine codex`);
|
|
1068
|
+
return { handled: true, config };
|
|
1069
|
+
}
|
|
1070
|
+
if (arg !== 'claude' && arg !== 'codex') {
|
|
1071
|
+
await bot.sendMessage(chatId, `❌ 不支持的引擎: ${arg}\n可选: claude, codex`);
|
|
1072
|
+
return { handled: true, config };
|
|
1073
|
+
}
|
|
1074
|
+
setDefaultEngine(arg);
|
|
1075
|
+
const distill = providerMod ? providerMod.getDistillModel() : '(unknown)';
|
|
1076
|
+
await bot.sendMessage(chatId, `✅ 默认引擎: ${arg}\n🧪 蒸馏模型已同步: ${distill}`);
|
|
1077
|
+
return { handled: true, config };
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// /distill-model [name] — show or update distill model
|
|
1081
|
+
if (text === '/distill-model' || text.startsWith('/distill-model ')) {
|
|
1082
|
+
if (!providerMod || typeof providerMod.getDistillModel !== 'function' || typeof providerMod.setDistillModel !== 'function') {
|
|
1083
|
+
await bot.sendMessage(chatId, '❌ Distill model config is not available.');
|
|
1084
|
+
return { handled: true, config };
|
|
1085
|
+
}
|
|
1086
|
+
const arg = text.slice('/distill-model'.length).trim();
|
|
1087
|
+
if (!arg) {
|
|
1088
|
+
await bot.sendMessage(chatId, `🧪 当前蒸馏模型: ${providerMod.getDistillModel()}\n用法: /distill-model <model>\n示例: /distill-model gpt-5.1-codex-mini`);
|
|
1089
|
+
return { handled: true, config };
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
providerMod.setDistillModel(arg);
|
|
1093
|
+
await bot.sendMessage(chatId, `✅ 蒸馏模型已更新为: ${providerMod.getDistillModel()}`);
|
|
1094
|
+
} catch (e) {
|
|
1095
|
+
await bot.sendMessage(chatId, `❌ 设置失败: ${e.message}`);
|
|
1096
|
+
}
|
|
1097
|
+
return { handled: true, config };
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const nlDistillIntent = parseDistillModelIntent(text);
|
|
1101
|
+
if (nlDistillIntent) {
|
|
1102
|
+
if (!providerMod || typeof providerMod.setDistillModel !== 'function' || typeof providerMod.getDistillModel !== 'function') {
|
|
1103
|
+
await bot.sendMessage(chatId, '❌ Distill model config is not available.');
|
|
1104
|
+
return { handled: true, config };
|
|
1105
|
+
}
|
|
1106
|
+
try {
|
|
1107
|
+
providerMod.setDistillModel(nlDistillIntent.model);
|
|
1108
|
+
await bot.sendMessage(chatId, `✅ 已按自然语言请求更新蒸馏模型: ${providerMod.getDistillModel()}`);
|
|
1109
|
+
} catch (e) {
|
|
1110
|
+
await bot.sendMessage(chatId, `❌ 设置失败: ${e.message}`);
|
|
1111
|
+
}
|
|
1112
|
+
return { handled: true, config };
|
|
1113
|
+
}
|
|
1114
|
+
|
|
870
1115
|
return { handled: false, config };
|
|
871
1116
|
}
|
|
872
1117
|
|
|
873
|
-
return { handleAdminCommand };
|
|
1118
|
+
return { handleAdminCommand, _private: { parseDistillModelIntent } };
|
|
874
1119
|
}
|
|
875
1120
|
|
|
876
1121
|
module.exports = { createAdminCommandHandler };
|