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
|
@@ -26,6 +26,7 @@ function createCommandRouter(deps) {
|
|
|
26
26
|
pendingAgentFlows,
|
|
27
27
|
pendingActivations,
|
|
28
28
|
agentFlowTtlMs,
|
|
29
|
+
getDefaultEngine,
|
|
29
30
|
} = deps;
|
|
30
31
|
|
|
31
32
|
function resolveFlowTtlMs() {
|
|
@@ -178,6 +179,13 @@ function createCommandRouter(deps) {
|
|
|
178
179
|
return '';
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
function inferAgentEngineFromText(input) {
|
|
183
|
+
const text = String(input || '').trim().toLowerCase();
|
|
184
|
+
if (!text) return null;
|
|
185
|
+
if (/\bcodex\b/.test(text) || /柯德|科德/.test(text)) return 'codex';
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
181
189
|
function isLikelyDirectAgentAction(input) {
|
|
182
190
|
const text = String(input || '').trim();
|
|
183
191
|
return /^(?:请|帮我|麻烦|给我|给这个群|给当前群|在这个群|把这个群|把当前群|将这个群|这个群|当前群|本群|群里|我想|我要|我需要|创建|新建|新增|搞一个|加一个|create|bind|绑定|列出|查看|显示|有哪些|解绑|取消绑定|断开绑定|修改|调整)/i.test(text);
|
|
@@ -435,14 +443,19 @@ function createCommandRouter(deps) {
|
|
|
435
443
|
}
|
|
436
444
|
const agentName = deriveAgentName(input, workspaceDir);
|
|
437
445
|
const roleDelta = deriveCreateRoleDelta(input);
|
|
446
|
+
const inferredEngine = inferAgentEngineFromText(input);
|
|
438
447
|
// Always skip binding creating chat — new group activates via /activate
|
|
439
|
-
const res = await agentTools.createNewWorkspaceAgent(agentName, workspaceDir, roleDelta, chatId, {
|
|
448
|
+
const res = await agentTools.createNewWorkspaceAgent(agentName, workspaceDir, roleDelta, chatId, {
|
|
449
|
+
skipChatBinding: true,
|
|
450
|
+
engine: inferredEngine,
|
|
451
|
+
});
|
|
440
452
|
if (!res.ok) {
|
|
441
453
|
await bot.sendMessage(chatId, `❌ 创建 Agent 失败: ${res.error}`);
|
|
442
454
|
return true;
|
|
443
455
|
}
|
|
444
456
|
const data = res.data || {};
|
|
445
457
|
const projName = projectNameFromResult(data, agentName);
|
|
458
|
+
const engineTip = data.project && data.project.engine ? `\n引擎: ${data.project.engine}` : '';
|
|
446
459
|
if (data.projectKey && pendingActivations) {
|
|
447
460
|
pendingActivations.set(data.projectKey, {
|
|
448
461
|
agentKey: data.projectKey, agentName: projName, cwd: data.cwd,
|
|
@@ -450,7 +463,7 @@ function createCommandRouter(deps) {
|
|
|
450
463
|
});
|
|
451
464
|
}
|
|
452
465
|
await bot.sendMessage(chatId,
|
|
453
|
-
`✅ Agent「${projName}」已创建\n目录: ${data.cwd || '(未知)'}\n\n` +
|
|
466
|
+
`✅ Agent「${projName}」已创建\n目录: ${data.cwd || '(未知)'}${engineTip}\n\n` +
|
|
454
467
|
`**下一步**: 在新群里发送 \`/activate\` 完成绑定(30分钟内有效)`
|
|
455
468
|
);
|
|
456
469
|
return true;
|
|
@@ -458,14 +471,15 @@ function createCommandRouter(deps) {
|
|
|
458
471
|
|
|
459
472
|
if (wantsBind) {
|
|
460
473
|
const agentName = deriveAgentName(input, workspaceDir);
|
|
461
|
-
const
|
|
474
|
+
const inferredEngine = inferAgentEngineFromText(input);
|
|
475
|
+
const res = await agentTools.bindAgentToChat(chatId, agentName, workspaceDir || null, { engine: inferredEngine });
|
|
462
476
|
if (!res.ok) {
|
|
463
477
|
await bot.sendMessage(chatId, `❌ 绑定失败: ${res.error}`);
|
|
464
478
|
return true;
|
|
465
479
|
}
|
|
466
480
|
const data = res.data || {};
|
|
467
481
|
const projName = projectNameFromResult(data, agentName);
|
|
468
|
-
if (data.cwd) attachOrCreateSession(chatId, normalizeCwd(data.cwd), projName);
|
|
482
|
+
if (data.cwd) attachOrCreateSession(chatId, normalizeCwd(data.cwd), projName, (data.project && data.project.engine) || getDefaultEngine());
|
|
469
483
|
await bot.sendMessage(chatId, `✅ 已绑定 Agent\n名称: ${projName}\n目录: ${data.cwd || '(未知)'}`);
|
|
470
484
|
return true;
|
|
471
485
|
}
|
|
@@ -504,8 +518,10 @@ function createCommandRouter(deps) {
|
|
|
504
518
|
const proj = config.projects[mappedKey];
|
|
505
519
|
const projCwd = normalizeCwd(proj.cwd);
|
|
506
520
|
const cur = loadState().sessions?.[chatId];
|
|
507
|
-
|
|
508
|
-
|
|
521
|
+
const curEngine = String((cur && cur.engine) || getDefaultEngine()).toLowerCase();
|
|
522
|
+
const projEngine = String((proj && proj.engine) || getDefaultEngine()).toLowerCase();
|
|
523
|
+
if (!cur || cur.cwd !== projCwd || curEngine !== projEngine) {
|
|
524
|
+
attachOrCreateSession(chatId, projCwd, proj.name || mappedKey, proj.engine || getDefaultEngine());
|
|
509
525
|
}
|
|
510
526
|
}
|
|
511
527
|
|
|
@@ -564,7 +580,7 @@ function createCommandRouter(deps) {
|
|
|
564
580
|
'/undo <hash> — 回退到指定 git checkpoint',
|
|
565
581
|
'/quit — 结束会话,重新加载 MCP/配置',
|
|
566
582
|
'',
|
|
567
|
-
`⚙️ /model [${currentModel}] /provider [${currentProvider}] /status /tasks /run /budget /reload`,
|
|
583
|
+
`⚙️ /model [${currentModel}] /engine [${getDefaultEngine()}] /provider [${currentProvider}] /distill-model /status /tasks /run /budget /reload /mentor`,
|
|
568
584
|
'🧩 /TeamTask create <agent> <目标> [--scope <id>] · /TeamTask · /TeamTask <id>',
|
|
569
585
|
'🧠 /memory — 记忆统计 · /memory <关键词> — 搜索事实',
|
|
570
586
|
'🧬 /skill-evo — 查看/处理技能演化队列',
|
|
@@ -604,7 +620,7 @@ function createCommandRouter(deps) {
|
|
|
604
620
|
if (quickAgent && !quickAgent.rest) {
|
|
605
621
|
const { key, proj } = quickAgent;
|
|
606
622
|
const projCwd = normalizeCwd(proj.cwd);
|
|
607
|
-
attachOrCreateSession(chatId, projCwd, proj.name || key);
|
|
623
|
+
attachOrCreateSession(chatId, projCwd, proj.name || key, proj.engine || getDefaultEngine());
|
|
608
624
|
log('INFO', `Agent switch via nickname: ${key} (${projCwd})`);
|
|
609
625
|
await bot.sendMessage(chatId, `${proj.icon || '🤖'} ${proj.name || key} 在线`);
|
|
610
626
|
return;
|
|
@@ -33,22 +33,31 @@ projects:
|
|
|
33
33
|
|
|
34
34
|
heartbeat:
|
|
35
35
|
tasks:
|
|
36
|
-
# 认知蒸馏:有偏好信号才触发,
|
|
36
|
+
# 认知蒸馏:有偏好信号才触发,2小时冷却,仅在用户闲置时执行
|
|
37
37
|
- name: cognitive-distill
|
|
38
38
|
type: script
|
|
39
39
|
command: node ~/.metame/distill.js
|
|
40
|
-
interval:
|
|
40
|
+
interval: 2h
|
|
41
41
|
precondition: "test -s ~/.metame/raw_signals.jsonl"
|
|
42
42
|
require_idle: true
|
|
43
43
|
notify: false
|
|
44
44
|
enabled: true
|
|
45
45
|
|
|
46
|
-
# 记忆提取:扫描未分析 session,提取长期事实和会话标签,4
|
|
46
|
+
# 记忆提取:扫描未分析 session,提取长期事实和会话标签,4小时冷却
|
|
47
47
|
- name: memory-extract
|
|
48
48
|
type: script
|
|
49
49
|
command: node ~/.metame/memory-extract.js
|
|
50
50
|
interval: 4h
|
|
51
|
-
timeout:
|
|
51
|
+
timeout: 1800
|
|
52
|
+
require_idle: true
|
|
53
|
+
notify: false
|
|
54
|
+
enabled: true
|
|
55
|
+
|
|
56
|
+
# 记忆垃圾回收:每天 02:00 清理过期/重复记忆
|
|
57
|
+
- name: memory-gc
|
|
58
|
+
type: script
|
|
59
|
+
command: node ~/.metame/memory-gc.js
|
|
60
|
+
at: "02:00"
|
|
52
61
|
require_idle: true
|
|
53
62
|
notify: false
|
|
54
63
|
enabled: true
|
|
@@ -63,6 +72,15 @@ heartbeat:
|
|
|
63
72
|
notify: false
|
|
64
73
|
enabled: true
|
|
65
74
|
|
|
75
|
+
# 自我反思:每天 23:00 扫描纠错信号,提炼成长模式
|
|
76
|
+
- name: self-reflect
|
|
77
|
+
type: script
|
|
78
|
+
command: node ~/.metame/self-reflect.js
|
|
79
|
+
at: "23:00"
|
|
80
|
+
require_idle: true
|
|
81
|
+
notify: false
|
|
82
|
+
enabled: true
|
|
83
|
+
|
|
66
84
|
# 夜间记忆蒸馏:每天 01:00 提炼热区事实为决策与经验文档
|
|
67
85
|
- name: nightly-reflect
|
|
68
86
|
type: script
|
|
@@ -134,3 +152,9 @@ daemon:
|
|
|
134
152
|
# Mobile users can't click "allow" — so we pre-authorize everything.
|
|
135
153
|
# Security relies on allowed_chat_ids whitelist, not tool restrictions.
|
|
136
154
|
dangerously_skip_permissions: true
|
|
155
|
+
mentor:
|
|
156
|
+
enabled: false
|
|
157
|
+
friction_level: 3
|
|
158
|
+
mode: gentle
|
|
159
|
+
exclude_agents: [personal, xianyu]
|
|
160
|
+
emotion_keywords_extra: []
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const CODEX_TOOL_MAP = Object.freeze({
|
|
9
|
+
command_execution: 'Bash',
|
|
10
|
+
file_change: 'Write',
|
|
11
|
+
file_read: 'Read',
|
|
12
|
+
mcp_tool_call: 'MCP',
|
|
13
|
+
web_search: 'WebSearch',
|
|
14
|
+
web_fetch: 'WebFetch',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function normalizeEngineName(name) {
|
|
18
|
+
const text = String(name || '').trim().toLowerCase();
|
|
19
|
+
return text === 'codex' ? 'codex' : 'claude';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveBinary(engineName, deps = {}) {
|
|
23
|
+
const engine = normalizeEngineName(engineName);
|
|
24
|
+
const home = deps.HOME || os.homedir();
|
|
25
|
+
const fsMod = deps.fs || fs;
|
|
26
|
+
const pathMod = deps.path || path;
|
|
27
|
+
const execSyncFn = deps.execSync || execSync;
|
|
28
|
+
|
|
29
|
+
const key = engine === 'codex' ? 'codex' : 'claude';
|
|
30
|
+
const cmd = process.platform === 'win32' ? `where ${key}` : `which ${key} 2>/dev/null`;
|
|
31
|
+
try {
|
|
32
|
+
const resolved = execSyncFn(cmd, { encoding: 'utf8', timeout: 3000 }).trim().split('\n')[0];
|
|
33
|
+
if (resolved) return resolved;
|
|
34
|
+
} catch { /* fallback */ }
|
|
35
|
+
|
|
36
|
+
const candidates = engine === 'codex'
|
|
37
|
+
? [
|
|
38
|
+
pathMod.join(home, '.local', 'bin', 'codex'),
|
|
39
|
+
'/usr/local/bin/codex',
|
|
40
|
+
'/opt/homebrew/bin/codex',
|
|
41
|
+
]
|
|
42
|
+
: [
|
|
43
|
+
pathMod.join(home, '.local', 'bin', 'claude'),
|
|
44
|
+
pathMod.join(home, '.npm-global', 'bin', 'claude'),
|
|
45
|
+
'/usr/local/bin/claude',
|
|
46
|
+
'/opt/homebrew/bin/claude',
|
|
47
|
+
];
|
|
48
|
+
for (const p of candidates) {
|
|
49
|
+
if (fsMod.existsSync(p)) return p;
|
|
50
|
+
}
|
|
51
|
+
return key;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ENGINE_DISTILL_MAP = Object.freeze({
|
|
55
|
+
claude: 'haiku',
|
|
56
|
+
codex: 'gpt-5.1-codex-mini',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function detectDefaultEngine(deps = {}) {
|
|
60
|
+
for (const engine of ['claude', 'codex']) {
|
|
61
|
+
const bin = resolveBinary(engine, deps);
|
|
62
|
+
if (bin !== engine) return engine; // resolveBinary found a real path
|
|
63
|
+
}
|
|
64
|
+
return 'claude'; // ultimate fallback
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function classifyEngineError(text) {
|
|
68
|
+
const msg = String(text || '').trim();
|
|
69
|
+
if (!msg) return null;
|
|
70
|
+
if (/(auth|unauthorized|login|api key|authentication|permission denied|forbidden|401|403)/i.test(msg)) {
|
|
71
|
+
return {
|
|
72
|
+
code: 'AUTH_REQUIRED',
|
|
73
|
+
message: '认证失败,请先执行 `codex login`(或配置 OPENAI_API_KEY)后重试。',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (/(rate.?limit|too many requests|quota|429)/i.test(msg)) {
|
|
77
|
+
return {
|
|
78
|
+
code: 'RATE_LIMIT',
|
|
79
|
+
message: '请求频率或配额受限,请稍后重试。',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
code: 'EXEC_FAILURE',
|
|
84
|
+
message: msg,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseJsonLine(line) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(line);
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseClaudeStreamEvent(line) {
|
|
97
|
+
const raw = parseJsonLine(line);
|
|
98
|
+
if (!raw || typeof raw !== 'object') return [];
|
|
99
|
+
|
|
100
|
+
const out = [];
|
|
101
|
+
if (raw.type === 'assistant' && raw.message && Array.isArray(raw.message.content)) {
|
|
102
|
+
for (const block of raw.message.content) {
|
|
103
|
+
if (!block) continue;
|
|
104
|
+
if (block.type === 'text' && block.text) {
|
|
105
|
+
out.push({ type: 'text', text: String(block.text), raw });
|
|
106
|
+
} else if (block.type === 'tool_use') {
|
|
107
|
+
out.push({
|
|
108
|
+
type: 'tool_use',
|
|
109
|
+
toolName: block.name || 'Tool',
|
|
110
|
+
toolInput: block.input || {},
|
|
111
|
+
raw,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (raw.type === 'result') {
|
|
117
|
+
if (raw.result) out.push({ type: 'text', text: String(raw.result), raw });
|
|
118
|
+
out.push({ type: 'done', usage: raw.usage || null, raw });
|
|
119
|
+
}
|
|
120
|
+
if (raw.type === 'content_block_start' || raw.type === 'content_block_delta') {
|
|
121
|
+
out.push({ type: 'tool_result', raw });
|
|
122
|
+
}
|
|
123
|
+
if (raw.type === 'error') {
|
|
124
|
+
const classified = classifyEngineError(raw.error || raw.message || '');
|
|
125
|
+
if (classified) out.push({ type: 'error', ...classified, raw });
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseCodexStreamEvent(line) {
|
|
131
|
+
const raw = parseJsonLine(line);
|
|
132
|
+
if (!raw || typeof raw !== 'object') return [];
|
|
133
|
+
|
|
134
|
+
const out = [];
|
|
135
|
+
if (raw.type === 'thread.started' && raw.thread_id) {
|
|
136
|
+
out.push({ type: 'session', sessionId: String(raw.thread_id), raw });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if ((raw.type === 'item.started' || raw.type === 'item.completed') && raw.item && raw.item.type) {
|
|
140
|
+
const itemType = String(raw.item.type);
|
|
141
|
+
const mapped = CODEX_TOOL_MAP[itemType] || itemType;
|
|
142
|
+
if (mapped && mapped !== 'reasoning' && itemType !== 'agent_message') {
|
|
143
|
+
if (raw.type === 'item.started') {
|
|
144
|
+
out.push({
|
|
145
|
+
type: 'tool_use',
|
|
146
|
+
toolName: mapped,
|
|
147
|
+
toolInput: {
|
|
148
|
+
command: raw.item.command || '',
|
|
149
|
+
file_path: raw.item.path || raw.item.file_path || '',
|
|
150
|
+
},
|
|
151
|
+
raw,
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
out.push({ type: 'tool_result', toolName: mapped, raw });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (raw.type === 'item.completed' && itemType === 'agent_message' && raw.item.text) {
|
|
158
|
+
out.push({ type: 'text', text: String(raw.item.text), raw });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (raw.type === 'turn.completed') {
|
|
163
|
+
out.push({ type: 'done', usage: raw.usage || null, raw });
|
|
164
|
+
}
|
|
165
|
+
if (raw.type === 'error') {
|
|
166
|
+
const classified = classifyEngineError(raw.error || raw.message || '');
|
|
167
|
+
if (classified) out.push({ type: 'error', ...classified, raw });
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildClaudeArgs(options = {}) {
|
|
173
|
+
const { model = 'opus', readOnly = false, daemonCfg = {}, session = {} } = options;
|
|
174
|
+
const args = ['-p', '--model', model];
|
|
175
|
+
if (readOnly) {
|
|
176
|
+
const readOnlyTools = ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch', 'Task'];
|
|
177
|
+
for (const tool of readOnlyTools) args.push('--allowedTools', tool);
|
|
178
|
+
} else if (daemonCfg.dangerously_skip_permissions) {
|
|
179
|
+
args.push('--dangerously-skip-permissions');
|
|
180
|
+
} else {
|
|
181
|
+
for (const tool of (daemonCfg.session_allowed_tools || [])) args.push('--allowedTools', tool);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (session.id === '__continue__') {
|
|
185
|
+
args.push('--continue');
|
|
186
|
+
} else if (session.started && session.id) {
|
|
187
|
+
args.push('--resume', session.id);
|
|
188
|
+
} else if (session.id) {
|
|
189
|
+
args.push('--session-id', session.id);
|
|
190
|
+
}
|
|
191
|
+
return args;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildCodexArgs(options = {}) {
|
|
195
|
+
const { model = 'gpt-5-codex', readOnly = false, daemonCfg = {}, session = {}, cwd } = options;
|
|
196
|
+
const args = (session && session.started && session.id && session.id !== '__continue__')
|
|
197
|
+
? ['exec', 'resume', session.id]
|
|
198
|
+
: ['exec'];
|
|
199
|
+
|
|
200
|
+
args.push('--json', '--skip-git-repo-check');
|
|
201
|
+
if (model) args.push('-m', model);
|
|
202
|
+
if (cwd) args.push('-C', cwd);
|
|
203
|
+
|
|
204
|
+
if (readOnly) {
|
|
205
|
+
args.push('-s', 'read-only');
|
|
206
|
+
} else if (daemonCfg.dangerously_skip_permissions) {
|
|
207
|
+
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
208
|
+
} else {
|
|
209
|
+
args.push('--full-auto');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// "-" means prompt is read from stdin.
|
|
213
|
+
args.push('-');
|
|
214
|
+
return args;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function createEngineRuntimeFactory(deps = {}) {
|
|
218
|
+
const home = deps.HOME || os.homedir();
|
|
219
|
+
const claudeBin = deps.CLAUDE_BIN || resolveBinary('claude', { ...deps, HOME: home });
|
|
220
|
+
const codexBin = deps.CODEX_BIN || resolveBinary('codex', { ...deps, HOME: home });
|
|
221
|
+
const getActiveProviderEnv = typeof deps.getActiveProviderEnv === 'function'
|
|
222
|
+
? deps.getActiveProviderEnv
|
|
223
|
+
: (() => ({}));
|
|
224
|
+
|
|
225
|
+
return function getEngineRuntime(engineName) {
|
|
226
|
+
const engine = normalizeEngineName(engineName);
|
|
227
|
+
if (engine === 'codex') {
|
|
228
|
+
return {
|
|
229
|
+
name: 'codex',
|
|
230
|
+
binary: codexBin,
|
|
231
|
+
defaultModel: 'gpt-5-codex',
|
|
232
|
+
stdinBehavior: 'write-and-close',
|
|
233
|
+
killSignal: 'SIGTERM',
|
|
234
|
+
timeouts: { idleMs: 10 * 60 * 1000, toolMs: 25 * 60 * 1000, ceilingMs: 60 * 60 * 1000 },
|
|
235
|
+
buildArgs: buildCodexArgs,
|
|
236
|
+
buildEnv: ({ metameProject = '' } = {}) => ({ ...process.env, METAME_PROJECT: metameProject }),
|
|
237
|
+
parseStreamEvent: parseCodexStreamEvent,
|
|
238
|
+
classifyError: classifyEngineError,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
name: 'claude',
|
|
243
|
+
binary: claudeBin,
|
|
244
|
+
defaultModel: 'opus',
|
|
245
|
+
stdinBehavior: 'write-and-close',
|
|
246
|
+
killSignal: 'SIGTERM',
|
|
247
|
+
timeouts: { idleMs: 5 * 60 * 1000, toolMs: 25 * 60 * 1000, ceilingMs: 60 * 60 * 1000 },
|
|
248
|
+
buildArgs: buildClaudeArgs,
|
|
249
|
+
buildEnv: ({ metameProject = '' } = {}) => ({
|
|
250
|
+
...(() => {
|
|
251
|
+
const env = { ...process.env, ...getActiveProviderEnv(), METAME_PROJECT: metameProject };
|
|
252
|
+
delete env.CLAUDECODE;
|
|
253
|
+
return env;
|
|
254
|
+
})(),
|
|
255
|
+
}),
|
|
256
|
+
parseStreamEvent: parseClaudeStreamEvent,
|
|
257
|
+
classifyError: classifyEngineError,
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = {
|
|
263
|
+
createEngineRuntimeFactory,
|
|
264
|
+
normalizeEngineName,
|
|
265
|
+
resolveBinary,
|
|
266
|
+
detectDefaultEngine,
|
|
267
|
+
ENGINE_DISTILL_MAP,
|
|
268
|
+
_private: {
|
|
269
|
+
classifyEngineError,
|
|
270
|
+
parseClaudeStreamEvent,
|
|
271
|
+
parseCodexStreamEvent,
|
|
272
|
+
buildClaudeArgs,
|
|
273
|
+
buildCodexArgs,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
@@ -209,8 +209,9 @@ function createExecCommandHandler(deps) {
|
|
|
209
209
|
const proc = activeProcesses.get(chatId);
|
|
210
210
|
if (proc && proc.child) {
|
|
211
211
|
proc.aborted = true;
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
const signal = proc.killSignal || 'SIGTERM';
|
|
213
|
+
try { process.kill(-proc.child.pid, signal); } catch { proc.child.kill(signal); }
|
|
214
|
+
await bot.sendMessage(chatId, '⏹ Stopping current engine task...');
|
|
214
215
|
} else {
|
|
215
216
|
await bot.sendMessage(chatId, 'No active task to stop.');
|
|
216
217
|
}
|
|
@@ -228,7 +229,8 @@ function createExecCommandHandler(deps) {
|
|
|
228
229
|
const proc = activeProcesses.get(chatId);
|
|
229
230
|
if (proc && proc.child) {
|
|
230
231
|
proc.aborted = true;
|
|
231
|
-
|
|
232
|
+
const signal = proc.killSignal || 'SIGTERM';
|
|
233
|
+
try { process.kill(-proc.child.pid, signal); } catch { proc.child.kill(signal); }
|
|
232
234
|
}
|
|
233
235
|
const session = getSession(chatId);
|
|
234
236
|
const name = session ? getSessionName(session.id) : null;
|
|
@@ -244,6 +246,10 @@ function createExecCommandHandler(deps) {
|
|
|
244
246
|
await bot.sendMessage(chatId, '❌ No active session to compact.');
|
|
245
247
|
return true;
|
|
246
248
|
}
|
|
249
|
+
if (String(session.engine || '').toLowerCase() === 'codex') {
|
|
250
|
+
await bot.sendMessage(chatId, '⚠️ Codex 会话暂不支持 /compact,请继续在同一会话里对话。');
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
247
253
|
await bot.sendMessage(chatId, '🗜 Compacting session...');
|
|
248
254
|
|
|
249
255
|
// Step 1: Read conversation from JSONL (fast, no Claude needed)
|
|
@@ -316,7 +322,7 @@ function createExecCommandHandler(deps) {
|
|
|
316
322
|
// Step 4: Create new session with the summary
|
|
317
323
|
const model = daemonCfg.model || 'opus';
|
|
318
324
|
const oldName = getSessionName(session.id);
|
|
319
|
-
const newSession = createSession(chatId, session.cwd, oldName ? oldName + ' (compacted)' : '');
|
|
325
|
+
const newSession = createSession(chatId, session.cwd, oldName ? oldName + ' (compacted)' : '', session.engine || 'claude');
|
|
320
326
|
const initArgs = ['-p', '--session-id', newSession.id, '--model', model];
|
|
321
327
|
if (daemonCfg.dangerously_skip_permissions) initArgs.push('--dangerously-skip-permissions');
|
|
322
328
|
const preamble = buildProfilePreamble();
|
package/scripts/daemon-notify.js
CHANGED
|
@@ -58,7 +58,43 @@ function createNotifier(deps) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Send only to personal (non-agent-bound) chat IDs.
|
|
63
|
+
* Agent-bound group chats (those in chat_agent_map) are excluded.
|
|
64
|
+
* Falls back to fsIds[0] if no personal chats are found.
|
|
65
|
+
* Used for system notifications that should not spam every agent group.
|
|
66
|
+
*/
|
|
67
|
+
async function notifyPersonal(message) {
|
|
68
|
+
const config = getConfig();
|
|
69
|
+
const { telegramBridge, feishuBridge } = getBridges();
|
|
70
|
+
|
|
71
|
+
if (feishuBridge && feishuBridge.bot) {
|
|
72
|
+
const chatAgentMap = (config.feishu && config.feishu.chat_agent_map) || {};
|
|
73
|
+
const fsIds = (config.feishu && config.feishu.allowed_chat_ids) || [];
|
|
74
|
+
// Personal chats = allowed IDs not bound to any agent
|
|
75
|
+
const personalIds = fsIds.filter(id => !chatAgentMap[id]);
|
|
76
|
+
const targetIds = personalIds.length > 0 ? personalIds : fsIds.slice(0, 1);
|
|
77
|
+
for (const chatId of targetIds) {
|
|
78
|
+
try { await feishuBridge.bot.sendMessage(chatId, message); } catch (e) {
|
|
79
|
+
log('ERROR', `Feishu personal notify failed ${chatId}: ${e.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (telegramBridge && telegramBridge.bot) {
|
|
85
|
+
const tgAgentMap = (config.telegram && config.telegram.chat_agent_map) || {};
|
|
86
|
+
const tgIds = (config.telegram && config.telegram.allowed_chat_ids) || [];
|
|
87
|
+
const personalIds = tgIds.filter(id => !tgAgentMap[id]);
|
|
88
|
+
const targetIds = personalIds.length > 0 ? personalIds : tgIds.slice(0, 1);
|
|
89
|
+
for (const chatId of targetIds) {
|
|
90
|
+
try { await telegramBridge.bot.sendMarkdown(chatId, message); } catch (e) {
|
|
91
|
+
log('ERROR', `Telegram personal notify failed ${chatId}: ${e.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { notify, notifyAdmin, notifyPersonal };
|
|
62
98
|
}
|
|
63
99
|
|
|
64
100
|
module.exports = { createNotifier };
|
|
@@ -50,6 +50,7 @@ function setupRuntimeWatchers(deps) {
|
|
|
50
50
|
log,
|
|
51
51
|
notifyFn,
|
|
52
52
|
adminNotifyFn,
|
|
53
|
+
notifyPersonalFn,
|
|
53
54
|
activeProcesses,
|
|
54
55
|
getConfig,
|
|
55
56
|
setConfig,
|
|
@@ -68,7 +69,7 @@ function setupRuntimeWatchers(deps) {
|
|
|
68
69
|
refreshLogMaxSize(newConfig);
|
|
69
70
|
const timer = getHeartbeatTimer();
|
|
70
71
|
if (timer) clearInterval(timer);
|
|
71
|
-
setHeartbeatTimer(startHeartbeat(newConfig, notifyFn));
|
|
72
|
+
setHeartbeatTimer(startHeartbeat(newConfig, notifyFn, notifyPersonalFn));
|
|
72
73
|
const { general, project } = getAllTasks(newConfig);
|
|
73
74
|
const totalCount = general.length + project.length;
|
|
74
75
|
log('INFO', `Config reloaded: ${totalCount} tasks (${project.length} in projects)`);
|