metame-cli 1.6.3 → 1.6.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
CHANGED
|
@@ -20,10 +20,18 @@ MetaMe is an AI that lives on your machine — remembers how you think, stays on
|
|
|
20
20
|
|
|
21
21
|
No cloud. Your machine, your data.
|
|
22
22
|
|
|
23
|
+
**macOS / Linux:**
|
|
24
|
+
|
|
23
25
|
```bash
|
|
24
26
|
curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
**Windows (PowerShell):**
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
irm https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.ps1 | iex
|
|
33
|
+
```
|
|
34
|
+
|
|
27
35
|
---
|
|
28
36
|
|
|
29
37
|
> ### 🚀 v1.5.10 — Perpetual Task Engine & Agent Soul Layer
|
|
@@ -256,9 +264,11 @@ MetaMe is the orchestration layer. Claude Code and Codex are the engines. You in
|
|
|
256
264
|
| **Both** | `npm install -g @anthropic-ai/claude-code @openai/codex metame-cli` | `claude` + `codex login` |
|
|
257
265
|
| **Claude plugin** (no npm) | `claude plugin install github:Yaron9/MetaMe/plugin` | `claude` |
|
|
258
266
|
|
|
259
|
-
> **No Node.js?**
|
|
267
|
+
> **No Node.js?** One-liner installers handle everything (Node + MetaMe):
|
|
268
|
+
> - macOS / Linux: `curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash`
|
|
269
|
+
> - Windows (PowerShell): `irm https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.ps1 | iex` — uses winget when available, otherwise downloads the LTS MSI.
|
|
260
270
|
>
|
|
261
|
-
> **Windows?** Use PowerShell
|
|
271
|
+
> **Windows?** Use PowerShell or CMD natively. WSL has proxy and path issues.
|
|
262
272
|
|
|
263
273
|
### Setup (3 minutes)
|
|
264
274
|
|
package/package.json
CHANGED
|
@@ -1931,6 +1931,9 @@ function createClaudeEngine(deps) {
|
|
|
1931
1931
|
log('WARN', `Codex thread migrated for ${chatId}: ${prevSessionId.slice(0, 8)} -> ${safeNextId.slice(0, 8)}`);
|
|
1932
1932
|
}
|
|
1933
1933
|
// Keep card header in sync with the real session ID reported by the engine
|
|
1934
|
+
if (!_ackCardHeader && bot.sendCard) {
|
|
1935
|
+
_ackCardHeader = { title: `📎 ${safeNextId.slice(0, 8)}`, color: 'grey' };
|
|
1936
|
+
}
|
|
1934
1937
|
if (_ackCardHeader && _ackCardHeader._baseTitle) {
|
|
1935
1938
|
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader._baseTitle}(${safeNextId.slice(0, 8)})` };
|
|
1936
1939
|
}
|
|
@@ -1941,7 +1944,11 @@ function createClaudeEngine(deps) {
|
|
|
1941
1944
|
if (_ackCardHeader) {
|
|
1942
1945
|
_ackCardHeader._baseTitle = _ackCardHeader.title; // preserve original title for onSession updates
|
|
1943
1946
|
}
|
|
1944
|
-
|
|
1947
|
+
// For non-bound-project chats: create a minimal header to display session ID (Feishu cards)
|
|
1948
|
+
if (!_ackCardHeader && bot.sendCard && session && session.id) {
|
|
1949
|
+
_ackCardHeader = { title: `📎 ${session.id.slice(0, 8)}`, color: 'grey' };
|
|
1950
|
+
}
|
|
1951
|
+
if (session && session.id && _ackCardHeader && _ackCardHeader._baseTitle) {
|
|
1945
1952
|
_ackCardHeader = { ..._ackCardHeader, title: `${_ackCardHeader._baseTitle}(${session.id.slice(0, 8)})` };
|
|
1946
1953
|
}
|
|
1947
1954
|
|
|
@@ -2336,13 +2343,15 @@ function createClaudeEngine(deps) {
|
|
|
2336
2343
|
log('DEBUG', `[REPLY:${chatId}] sendCard done msgId=${replyMsg && replyMsg.message_id}`);
|
|
2337
2344
|
} else {
|
|
2338
2345
|
log('DEBUG', `[REPLY:${chatId}] sending sendMarkdown`);
|
|
2339
|
-
|
|
2346
|
+
const _textSessionSuffix = session && session.id ? `\n\n📎 ${session.id.slice(0, 8)}` : '';
|
|
2347
|
+
replyMsg = await bot.sendMarkdown(chatId, cleanOutput + _textSessionSuffix);
|
|
2340
2348
|
log('DEBUG', `[REPLY:${chatId}] sendMarkdown done msgId=${replyMsg && replyMsg.message_id}`);
|
|
2341
2349
|
}
|
|
2342
2350
|
}
|
|
2343
2351
|
} catch (sendErr) {
|
|
2344
2352
|
log('WARN', `sendCard/sendMarkdown failed (${sendErr.message}), falling back to sendMessage`);
|
|
2345
|
-
|
|
2353
|
+
const _textSessionSuffix = session && session.id ? `\n\n📎 ${session.id.slice(0, 8)}` : '';
|
|
2354
|
+
try { replyMsg = await bot.sendMessage(chatId, cleanOutput + _textSessionSuffix); } catch (e2) {
|
|
2346
2355
|
log('ERROR', `sendMessage fallback also failed: ${e2.message}`);
|
|
2347
2356
|
}
|
|
2348
2357
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { resolveEngineModel } = require('./daemon-engine-runtime');
|
|
4
|
-
const { createAgentIntentHandler } = require('./daemon-agent-intent');
|
|
5
4
|
const { rawChatId: extractOriginalChatId, isThreadChatId } = require('./core/thread-chat-id');
|
|
6
5
|
const { createWikiCommandHandler } = require('./daemon-wiki');
|
|
7
6
|
|
|
@@ -26,9 +25,9 @@ function createCommandRouter(deps) {
|
|
|
26
25
|
activeProcesses,
|
|
27
26
|
pipeline, // message pipeline — used for interrupt/clearQueue
|
|
28
27
|
log,
|
|
29
|
-
agentTools,
|
|
28
|
+
agentTools: _agentTools,
|
|
30
29
|
pendingAgentFlows,
|
|
31
|
-
pendingActivations,
|
|
30
|
+
pendingActivations: _pendingActivations,
|
|
32
31
|
agentFlowTtlMs,
|
|
33
32
|
getDefaultEngine,
|
|
34
33
|
getDb, // optional — () → DatabaseSync (for wiki commands)
|
|
@@ -42,7 +41,7 @@ function createCommandRouter(deps) {
|
|
|
42
41
|
return Number.isFinite(num) && num > 0 ? num : (10 * 60 * 1000);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
function
|
|
44
|
+
function _hasFreshPendingFlow(flowKey) {
|
|
46
45
|
if (!pendingAgentFlows) return false;
|
|
47
46
|
const flow = pendingAgentFlows.get(flowKey);
|
|
48
47
|
if (!flow) return false;
|
|
@@ -217,7 +216,7 @@ function createCommandRouter(deps) {
|
|
|
217
216
|
return null;
|
|
218
217
|
}
|
|
219
218
|
|
|
220
|
-
function
|
|
219
|
+
function _getBoundProjectForChat(chatId, cfg) {
|
|
221
220
|
const map = {
|
|
222
221
|
...(cfg.telegram ? cfg.telegram.chat_agent_map : {}),
|
|
223
222
|
...(cfg.feishu ? cfg.feishu.chat_agent_map : {}),
|
|
@@ -356,18 +355,9 @@ function createCommandRouter(deps) {
|
|
|
356
355
|
});
|
|
357
356
|
}
|
|
358
357
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
attachOrCreateSession,
|
|
363
|
-
normalizeCwd,
|
|
364
|
-
getDefaultEngine,
|
|
365
|
-
loadConfig,
|
|
366
|
-
getBoundProjectForChat,
|
|
367
|
-
log,
|
|
368
|
-
pendingActivations,
|
|
369
|
-
hasFreshPendingFlow,
|
|
370
|
-
});
|
|
358
|
+
// Agent intent classification removed — now handled by agent-management skill.
|
|
359
|
+
// tryHandleAgentIntent was previously created here via createAgentIntentHandler().
|
|
360
|
+
// Explicit /agent commands still work through handleAgentCommand above.
|
|
371
361
|
|
|
372
362
|
async function handleCommand(bot, chatId, text, config, executeTaskByName, senderId = null, readOnly = false, _meta = {}) {
|
|
373
363
|
if (text && !text.startsWith('/chatid') && !text.startsWith('/myid')) log('INFO', `CMD [${String(chatId).slice(-8)}]: ${text.slice(0, 80)}`);
|
|
@@ -593,9 +583,8 @@ function createCommandRouter(deps) {
|
|
|
593
583
|
return;
|
|
594
584
|
}
|
|
595
585
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
586
|
+
// Agent intent classification removed — now handled by agent-management skill
|
|
587
|
+
// via Claude's semantic understanding instead of daemon-level regex matching.
|
|
599
588
|
}
|
|
600
589
|
|
|
601
590
|
const daemonCfg = (config && config.daemon) || {};
|
|
@@ -165,8 +165,10 @@ function setupRuntimeWatchers(deps) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
let reloadDebounce = null;
|
|
168
|
+
let suppressNextWatch = false; // guard: skip watcher re-trigger after .bak restore
|
|
168
169
|
fs.watchFile(CONFIG_FILE, { interval: 2000 }, (curr, prev) => {
|
|
169
170
|
if (curr.mtimeMs === prev.mtimeMs) return;
|
|
171
|
+
if (suppressNextWatch) { suppressNextWatch = false; return; }
|
|
170
172
|
if (reloadDebounce) clearTimeout(reloadDebounce);
|
|
171
173
|
reloadDebounce = setTimeout(() => {
|
|
172
174
|
log('INFO', 'daemon.yaml changed on disk — auto-reloading config');
|
|
@@ -176,6 +178,27 @@ function setupRuntimeWatchers(deps) {
|
|
|
176
178
|
adminNotifyFn(`🔄 Config auto-reloaded. ${r.tasks} heartbeat tasks active.`).catch(() => { });
|
|
177
179
|
} else {
|
|
178
180
|
log('ERROR', `Auto-reload failed: ${r.error}`);
|
|
181
|
+
// Attempt auto-restore from backup
|
|
182
|
+
const bakFile = CONFIG_FILE + '.bak';
|
|
183
|
+
try {
|
|
184
|
+
if (!fs.existsSync(bakFile)) {
|
|
185
|
+
log('WARN', 'No .bak file available for auto-restore');
|
|
186
|
+
} else {
|
|
187
|
+
suppressNextWatch = true; // prevent watcher re-trigger from our own copyFileSync
|
|
188
|
+
fs.copyFileSync(bakFile, CONFIG_FILE);
|
|
189
|
+
log('INFO', 'Auto-restored daemon.yaml from .bak');
|
|
190
|
+
const r2 = reloadConfig();
|
|
191
|
+
if (r2.success) {
|
|
192
|
+
log('INFO', `Restored config reload OK: ${r2.tasks} tasks`);
|
|
193
|
+
adminNotifyFn(`⚠️ daemon.yaml 解析失败,已从备份恢复。错误: ${r.error}`).catch(() => { });
|
|
194
|
+
} else {
|
|
195
|
+
log('ERROR', `Backup config also invalid: ${r2.error}. Manual intervention required.`);
|
|
196
|
+
adminNotifyFn(`🚨 daemon.yaml 和备份均无法解析,请手动修复。`).catch(() => { });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (restoreErr) {
|
|
200
|
+
log('ERROR', `Auto-restore from .bak failed: ${restoreErr.message}`);
|
|
201
|
+
}
|
|
179
202
|
}
|
|
180
203
|
}, 1000);
|
|
181
204
|
});
|
package/scripts/daemon.js
CHANGED
|
@@ -259,7 +259,10 @@ function loadConfig() {
|
|
|
259
259
|
function writeConfigSafe(nextConfig) {
|
|
260
260
|
const tmpFile = `${CONFIG_FILE}.tmp.${process.pid}.${Date.now()}`;
|
|
261
261
|
try {
|
|
262
|
-
|
|
262
|
+
const dumped = yaml.dump(nextConfig, { lineWidth: -1 });
|
|
263
|
+
// Validate: round-trip parse before committing to disk
|
|
264
|
+
yaml.load(dumped);
|
|
265
|
+
fs.writeFileSync(tmpFile, dumped, 'utf8');
|
|
263
266
|
fs.renameSync(tmpFile, CONFIG_FILE);
|
|
264
267
|
} catch (e) {
|
|
265
268
|
try { if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile); } catch { }
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-management
|
|
3
|
+
description: >
|
|
4
|
+
MetaMe Agent lifecycle management — create, bind, list, edit, unbind agents.
|
|
5
|
+
TRIGGER when: user explicitly requests creating a new agent, binding/unbinding
|
|
6
|
+
an agent to a chat, listing agents, editing agent roles, resetting agents, or
|
|
7
|
+
managing agent soul/identity. Keywords: "新建agent", "创建智能体", "绑定agent",
|
|
8
|
+
"解绑", "agent列表", "/agent", "创建工作区".
|
|
9
|
+
DO NOT TRIGGER when: user is DISCUSSING agents conceptually, talking ABOUT the
|
|
10
|
+
agent system, reporting bugs about agents, or mentioning "agent" in passing
|
|
11
|
+
without an actionable request. If the message is about agent architecture,
|
|
12
|
+
design, code, or features — that is NOT a trigger.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Agent Management
|
|
16
|
+
|
|
17
|
+
Manage MetaMe agents through `/agent` slash commands. Never edit `daemon.yaml` directly.
|
|
18
|
+
|
|
19
|
+
## Intent Discrimination (Critical)
|
|
20
|
+
|
|
21
|
+
Before acting, classify the user's message:
|
|
22
|
+
|
|
23
|
+
**ACTION** — user wants you to DO something with agents right now:
|
|
24
|
+
- "帮我创建一个agent负责代码审查"
|
|
25
|
+
- "给这个群绑定一个agent"
|
|
26
|
+
- "列出所有agent"
|
|
27
|
+
- "把当前agent解绑"
|
|
28
|
+
- "修改agent的角色为后端专家"
|
|
29
|
+
|
|
30
|
+
**DISCUSSION** — user is talking ABOUT agents, not requesting action:
|
|
31
|
+
- "创建agent的功能应该做成skill" (talking about the feature)
|
|
32
|
+
- "agent intent误触发了" (reporting a bug)
|
|
33
|
+
- "我觉得agent管理可以更优雅" (design discussion)
|
|
34
|
+
- "新建agent的流程需要改进" (meta-discussion)
|
|
35
|
+
|
|
36
|
+
**Rule: when in doubt, treat as DISCUSSION.** Only proceed with agent operations when intent is unambiguous. If uncertain, ask: "你是想让我现在创建一个agent,还是在讨论agent功能?"
|
|
37
|
+
|
|
38
|
+
## Command Reference
|
|
39
|
+
|
|
40
|
+
All operations use daemon-handled slash commands sent as regular messages:
|
|
41
|
+
|
|
42
|
+
| Command | Purpose |
|
|
43
|
+
|---------|---------|
|
|
44
|
+
| `/agent list` | List all configured agents with status |
|
|
45
|
+
| `/agent bind <name> <cwd>` | Create/bind agent to current chat |
|
|
46
|
+
| `/agent new` | Start interactive creation wizard |
|
|
47
|
+
| `/agent new clone` | Clone current agent to a new workspace |
|
|
48
|
+
| `/agent new team` | Create multi-member team workspace |
|
|
49
|
+
| `/agent edit <description>` | Merge role description into CLAUDE.md |
|
|
50
|
+
| `/agent reset` | Clear agent role section from CLAUDE.md |
|
|
51
|
+
| `/agent unbind` | Unbind agent from current chat |
|
|
52
|
+
| `/agent soul` | View current soul/identity |
|
|
53
|
+
| `/agent soul repair` | Repair soul layer files |
|
|
54
|
+
| `/agent soul edit <text>` | Overwrite SOUL.md content |
|
|
55
|
+
| `/activate` | Activate a pending agent in a new chat |
|
|
56
|
+
|
|
57
|
+
## Workflows
|
|
58
|
+
|
|
59
|
+
### Create Agent (One-Shot)
|
|
60
|
+
|
|
61
|
+
When the user provides enough info (name + purpose), skip the wizard:
|
|
62
|
+
|
|
63
|
+
1. Derive: agent name, workspace directory, role description, engine
|
|
64
|
+
2. Default workspace: `~/AGI/<agent-name>/` (use forward slashes on Mac, backslashes on Windows)
|
|
65
|
+
3. Send: `/agent bind <name> <cwd>` — this creates the project, registers in daemon.yaml, and binds to current chat
|
|
66
|
+
4. If user wants a role description, follow up with: `/agent edit <description>`
|
|
67
|
+
5. If user wants a SEPARATE Feishu chat for the agent, use `/agent new` wizard instead
|
|
68
|
+
|
|
69
|
+
### Create Agent (With Dedicated Chat)
|
|
70
|
+
|
|
71
|
+
When the user wants the agent in its own Feishu group:
|
|
72
|
+
|
|
73
|
+
1. Send: `/agent new` — starts the interactive wizard
|
|
74
|
+
2. The wizard will ask for: directory, name, description
|
|
75
|
+
3. Answer each wizard prompt based on user's requirements
|
|
76
|
+
4. After creation, the system auto-creates a Feishu chat and binds it (if permissions allow)
|
|
77
|
+
5. If auto-chat fails, tell user to `/activate` in the new chat within 30 minutes
|
|
78
|
+
|
|
79
|
+
### Quick Operations
|
|
80
|
+
|
|
81
|
+
- **List**: Send `/agent list` — shows all agents with bound status
|
|
82
|
+
- **Unbind**: Send `/agent unbind` — removes current chat binding
|
|
83
|
+
- **Edit role**: Send `/agent edit <full description>` — merges into workspace CLAUDE.md
|
|
84
|
+
- **Reset**: Send `/agent reset` — clears the Agent Role section
|
|
85
|
+
|
|
86
|
+
## Strict Chat Constraint
|
|
87
|
+
|
|
88
|
+
If the current chat is already bound to a specific agent (a "strict chat" — it has an entry
|
|
89
|
+
in `chat_agent_map`), then **do NOT** use `/agent bind`, `/agent unbind`, or `/activate` here.
|
|
90
|
+
These would break the fixed routing. Safe operations in strict chats: `/agent list`,
|
|
91
|
+
`/agent new` (creates elsewhere), `/agent edit`, `/agent soul`.
|
|
92
|
+
|
|
93
|
+
If the user wants to bind/unbind, tell them to do it in the target chat or create a new chat.
|
|
94
|
+
|
|
95
|
+
## Constraints
|
|
96
|
+
|
|
97
|
+
- All commands are sent as plain text messages — the daemon intercepts and handles them
|
|
98
|
+
- Never directly write to `daemon.yaml` or `~/.metame/` files for agent config
|
|
99
|
+
- YAML paths on Windows: always use single quotes to avoid escape issues (`'D:\path'` not `"D:\path"`)
|
|
100
|
+
- Agent names should be short, ASCII-safe identifiers (Chinese names work for display)
|
|
101
|
+
- One chat can only be bound to one agent at a time
|