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?** Run `curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash` — it installs Node + MetaMe for you.
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/CMD natively. WSL has proxy and path issues.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metame-cli",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
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": {
@@ -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
- if (session && session.id && _ackCardHeader) {
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
- replyMsg = await bot.sendMarkdown(chatId, cleanOutput);
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
- try { replyMsg = await bot.sendMessage(chatId, cleanOutput); } catch (e2) {
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 hasFreshPendingFlow(flowKey) {
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 getBoundProjectForChat(chatId, cfg) {
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
- const tryHandleAgentIntent = createAgentIntentHandler({
360
- agentTools,
361
- handleAgentCommand,
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
- if (await tryHandleAgentIntent(bot, chatId, text, config, senderId)) {
597
- return;
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
- fs.writeFileSync(tmpFile, yaml.dump(nextConfig, { lineWidth: -1 }), 'utf8');
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