metame-cli 1.4.18 → 1.4.20
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 +124 -38
- package/index.js +39 -1
- package/package.json +2 -2
- package/scripts/daemon-admin-commands.js +86 -4
- package/scripts/daemon-agent-commands.js +91 -62
- package/scripts/daemon-agent-tools.js +49 -12
- package/scripts/daemon-bridges.js +26 -6
- package/scripts/daemon-claude-engine.js +111 -32
- package/scripts/daemon-command-router.js +32 -15
- package/scripts/daemon-default.yaml +18 -0
- package/scripts/daemon-exec-commands.js +6 -12
- package/scripts/daemon-file-browser.js +6 -5
- package/scripts/daemon-runtime-lifecycle.js +19 -5
- package/scripts/daemon-session-store.js +176 -41
- package/scripts/daemon-task-scheduler.js +30 -29
- package/scripts/daemon-user-acl.js +399 -0
- package/scripts/daemon.js +43 -6
- package/scripts/distill.js +11 -12
- package/scripts/memory-gc.js +239 -0
- package/scripts/memory-index.js +103 -0
- package/scripts/memory-nightly-reflect.js +299 -0
- package/scripts/memory-write.js +192 -0
- package/scripts/memory.js +144 -6
- package/scripts/schema.js +30 -9
- package/scripts/self-reflect.js +121 -5
- package/scripts/session-analytics.js +9 -10
- package/scripts/task-board.js +9 -3
- package/scripts/telegram-adapter.js +77 -9
package/README.md
CHANGED
|
@@ -14,25 +14,31 @@
|
|
|
14
14
|
<a href="./README.md">English</a> | <a href="./README中文版.md">中文</a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
|
-
> **
|
|
17
|
+
> **Your digital twin. Lives on your Mac.**
|
|
18
18
|
|
|
19
|
-
MetaMe
|
|
19
|
+
MetaMe is an AI that lives on your machine — remembers how you think, stays online 24/7, and takes commands from your phone via Telegram or Feishu. Not in the cloud. In your computer.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
No cloud. Your machine, your data.
|
|
22
22
|
|
|
23
|
+
**macOS / Linux / Windows WSL — same command:**
|
|
24
|
+
```bash
|
|
25
|
+
curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Already have Node.js ≥ 18:**
|
|
23
29
|
```bash
|
|
24
30
|
npm install -g metame-cli && metame
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
---
|
|
28
34
|
|
|
29
|
-
> ### 🚀 v1.4.
|
|
35
|
+
> ### 🚀 v1.4.19 — Multi-User ACL + Session Context Preview
|
|
30
36
|
>
|
|
31
|
-
>
|
|
32
|
-
> - **
|
|
33
|
-
> - **
|
|
34
|
-
> - **
|
|
35
|
-
> - **Unix Socket IPC
|
|
37
|
+
> - **Multi-user permission system**: role-based ACL (admin / member / stranger) — share your bots with teammates without giving them full access. Manage users with `/user` commands.
|
|
38
|
+
> - **Session context preview**: `/resume` and `/sessions` now show the last message snippet so you know exactly what to pick up.
|
|
39
|
+
> - **Team Task protocol**: multi-agent task board for cross-agent collaboration. Agents can dispatch and track tasks across workspaces.
|
|
40
|
+
> - **Layered Memory Architecture**: three-layer memory (long-term facts, session summaries, session index) — all automatic.
|
|
41
|
+
> - **Unix Socket IPC**: dispatch latency <100ms.
|
|
36
42
|
>
|
|
37
43
|
> Zero configuration. It just works.
|
|
38
44
|
|
|
@@ -151,7 +157,21 @@ Chain skills into multi-step workflows — research → write → publish — fu
|
|
|
151
157
|
prompt: "Publish it"
|
|
152
158
|
```
|
|
153
159
|
|
|
154
|
-
Task options
|
|
160
|
+
**Task options:**
|
|
161
|
+
|
|
162
|
+
| Option | Description |
|
|
163
|
+
|--------|-------------|
|
|
164
|
+
| `at` | Fixed-time trigger, e.g. `"09:30"` (local time) |
|
|
165
|
+
| `days` | Day filter, e.g. `"weekdays"`, `[mon, wed, fri]` |
|
|
166
|
+
| `interval` | Interval trigger, e.g. `"4h"`, `"30m"` |
|
|
167
|
+
| `require_idle` | Skip if you're active; retry on next heartbeat tick |
|
|
168
|
+
| `precondition` | Shell guard — skip task if command returns non-zero (zero tokens consumed) |
|
|
169
|
+
| `notify` | Push result to phone when done |
|
|
170
|
+
| `model` | Override model, e.g. `"sonnet"`, `"haiku"` |
|
|
171
|
+
| `cwd` | Working directory for the task |
|
|
172
|
+
| `timeout` | Max run time |
|
|
173
|
+
|
|
174
|
+
> **Scheduled tasks require system registration.** Run `metame daemon install-launchd` and tasks fire on schedule even with the screen locked or the lid closed — as long as the Mac is on.
|
|
155
175
|
|
|
156
176
|
### 5. Skills That Evolve Themselves
|
|
157
177
|
|
|
@@ -173,6 +193,13 @@ Task fails → skill-scout finds a skill → installs → retries → succeeds
|
|
|
173
193
|
|
|
174
194
|
## Quick Start
|
|
175
195
|
|
|
196
|
+
**macOS / Linux / Windows WSL:**
|
|
197
|
+
```bash
|
|
198
|
+
curl -fsSL https://raw.githubusercontent.com/Yaron9/MetaMe/main/install.sh | bash
|
|
199
|
+
```
|
|
200
|
+
> Same command everywhere. The script detects your OS and uses Homebrew (macOS) or apt/dnf/pacman (Linux/WSL) to install Node.js automatically.
|
|
201
|
+
|
|
202
|
+
**Already have Node.js ≥ 18:**
|
|
176
203
|
```bash
|
|
177
204
|
npm install -g metame-cli && metame
|
|
178
205
|
```
|
|
@@ -184,15 +211,43 @@ npm install -g metame-cli && metame
|
|
|
184
211
|
| 1. Install & profile | `metame` | First run: cognitive interview → builds `~/.claude_profile.yaml` |
|
|
185
212
|
| 2. Connect phone | Follow the setup wizard | Bot token + app credentials → `~/.metame/daemon.yaml` |
|
|
186
213
|
| 3. Start daemon | `metame start` | Background daemon launches, bot goes online |
|
|
187
|
-
| 4.
|
|
214
|
+
| 4. Register with system | macOS: `metame daemon install-launchd` · WSL/Linux: see below | Always-on, crash recovery |
|
|
215
|
+
|
|
216
|
+
> **What does system registration mean?**
|
|
217
|
+
> Once registered, MetaMe runs in the background automatically — screen locked, lid closed, woken from sleep — as long as the machine is on. Scheduled tasks fire on time. No terminal window needed.
|
|
218
|
+
|
|
219
|
+
**WSL2 / Linux — register with systemd:**
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cat > ~/.config/systemd/user/metame.service << 'EOF'
|
|
223
|
+
[Unit]
|
|
224
|
+
Description=MetaMe Daemon
|
|
225
|
+
After=network.target
|
|
226
|
+
|
|
227
|
+
[Service]
|
|
228
|
+
ExecStart=/usr/bin/env metame start
|
|
229
|
+
Restart=on-failure
|
|
230
|
+
RestartSec=5
|
|
231
|
+
|
|
232
|
+
[Install]
|
|
233
|
+
WantedBy=default.target
|
|
234
|
+
EOF
|
|
235
|
+
|
|
236
|
+
systemctl --user enable metame
|
|
237
|
+
systemctl --user start metame
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
> WSL2 requires systemd enabled first: add `[boot]\nsystemd=true` to `/etc/wsl.conf`, then restart WSL.
|
|
241
|
+
|
|
242
|
+
> **WSL limitation:** `/mac` commands (macOS AppleScript/JXA automation) are not available.
|
|
188
243
|
|
|
189
244
|
**Create your first Agent:**
|
|
190
245
|
|
|
191
|
-
1.
|
|
192
|
-
2.
|
|
193
|
-
3.
|
|
246
|
+
1. In any existing group, say: `Create an agent, directory ~/xxx, responsible for xxx`
|
|
247
|
+
2. Bot replies: ✅ Agent created — **send `/activate` in your new group to bind it**
|
|
248
|
+
3. Create a new group, add the bot, send `/activate` → binding complete
|
|
194
249
|
|
|
195
|
-
> Want more Agents? Repeat:
|
|
250
|
+
> Want more Agents? Repeat: create in any group → new target group → `/activate`. Each group = independent AI workspace.
|
|
196
251
|
|
|
197
252
|
---
|
|
198
253
|
|
|
@@ -209,39 +264,60 @@ npm install -g metame-cli && metame
|
|
|
209
264
|
| **Browser Automation** | Built-in Playwright MCP. Browser control out of the box for every user. |
|
|
210
265
|
| **Provider Relay** | Route through any Anthropic-compatible API. Use GPT-4, DeepSeek, Gemini — zero config file mutation. |
|
|
211
266
|
| **Metacognition** | Detects behavioral patterns (decision style, comfort zones, goal drift) and injects mirror observations. Zero extra API cost. |
|
|
267
|
+
| **Multi-User ACL** | Role-based permission system (admin / member / stranger). Share bots with teammates safely. Dynamic user management via `/user` commands with hot-reload config. |
|
|
268
|
+
| **Team Task** | Multi-agent task board for cross-agent collaboration. Agents can create, assign, and track tasks across workspaces. N-agent session scoping for parallel team workflows. |
|
|
212
269
|
| **Emergency Tools** | `/doctor` diagnostics, `/mac` macOS control helpers, `/sh` raw shell, `/fix` config restore, `/undo` git-based rollback. |
|
|
213
270
|
|
|
214
271
|
## Defining Your Agents
|
|
215
272
|
|
|
216
|
-
|
|
273
|
+
MetaMe's design philosophy: **one folder = one agent.**
|
|
217
274
|
|
|
218
|
-
|
|
275
|
+
Give an agent a directory, drop a `CLAUDE.md` inside describing its role, and you're done. The folder is the agent — it can be a code project, a blog repo, any workspace you already have.
|
|
219
276
|
|
|
220
|
-
|
|
277
|
+
### Option 1: Just say it (recommended)
|
|
278
|
+
|
|
279
|
+
No commands needed. Tell the bot what you want in plain language. **The agent is created without binding to the current group** — send `/activate` in your new target group to complete the binding:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
You: Create an agent, directory ~/projects/assistant, responsible for writing and content
|
|
283
|
+
Bot: ✅ Agent「assistant」created
|
|
284
|
+
Dir: ~/projects/assistant
|
|
285
|
+
📝 CLAUDE.md written
|
|
286
|
+
|
|
287
|
+
Next: send /activate in your new group to bind
|
|
288
|
+
|
|
289
|
+
── In the new group ──
|
|
290
|
+
|
|
291
|
+
You: /activate
|
|
292
|
+
Bot: 🤖 assistant bound
|
|
293
|
+
Dir: ~/projects/assistant
|
|
294
|
+
|
|
295
|
+
You: Change this agent's role to: focused on Python backend development
|
|
296
|
+
Bot: ✅ Role definition updated in CLAUDE.md
|
|
297
|
+
|
|
298
|
+
You: List all agents
|
|
299
|
+
Bot: 📋 Agent list
|
|
300
|
+
🤖 assistant ◀ current
|
|
301
|
+
Dir: ~/projects/assistant
|
|
302
|
+
...
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Supported intents: create, bind (`/agent bind`), unbind, edit role, list — just say it naturally.
|
|
306
|
+
|
|
307
|
+
### Option 2: Commands
|
|
308
|
+
|
|
309
|
+
Use `/agent` commands in any Telegram/Feishu group:
|
|
221
310
|
|
|
222
311
|
| Command | What it does |
|
|
223
312
|
|---------|-------------|
|
|
224
|
-
| `/
|
|
225
|
-
| `/agent bind <name> [dir]` |
|
|
313
|
+
| `/activate` | In a new group, sends this to auto-bind the most recently created pending agent. |
|
|
314
|
+
| `/agent bind <name> [dir]` | Manual bind: register this group as a named agent. Works anytime — no need to recreate if agent already exists. |
|
|
226
315
|
| `/agent list` | Show all configured agents. |
|
|
227
316
|
| `/agent edit` | Update the current agent's role description (rewrites its `CLAUDE.md` section). |
|
|
317
|
+
| `/agent unbind` | Remove this group's agent binding. |
|
|
228
318
|
| `/agent reset` | Remove the current agent's role section. |
|
|
229
319
|
|
|
230
|
-
|
|
231
|
-
```
|
|
232
|
-
You: /agent new
|
|
233
|
-
Bot: Please select a working directory:
|
|
234
|
-
📁 ~/AGI 📁 ~/projects 📁 ~/Desktop
|
|
235
|
-
You: ~/AGI/MyProject/NewDir
|
|
236
|
-
Bot: ✅ 已新建目录:~/AGI/MyProject/NewDir
|
|
237
|
-
What should we name this agent?
|
|
238
|
-
You: 小美
|
|
239
|
-
Bot: Describe 小美's role and responsibilities:
|
|
240
|
-
You: Personal assistant. Manages my calendar, drafts messages, and tracks todos.
|
|
241
|
-
Bot: ✅ Agent「小美」created. CLAUDE.md updated with role definition.
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
You can tap a button to pick an existing directory, or type any path directly in chat. If the path doesn't exist, it's created automatically. All entry points (`/agent new` wizard and `/agent bind`) validate that the directory is real before saving.
|
|
320
|
+
> **Binding protection**: Each group can only be bound to one agent. Existing bindings cannot be overwritten without explicit `force:true`.
|
|
245
321
|
|
|
246
322
|
### From config file (for power users)
|
|
247
323
|
|
|
@@ -295,12 +371,22 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
|
|
|
295
371
|
| `/undo <hash>` | Roll back to a specific git checkpoint |
|
|
296
372
|
| `/list` | Browse & download project files |
|
|
297
373
|
| `/model` | Switch model (sonnet/opus/haiku) |
|
|
298
|
-
| `/
|
|
374
|
+
| `/activate` | Activate and bind the most recently created pending agent in a new group |
|
|
375
|
+
| `/agent bind <name> [dir]` | Manually register group as dedicated agent |
|
|
299
376
|
| `/mac` | macOS control helper: permissions check/open + AppleScript/JXA execution |
|
|
300
377
|
| `/sh <cmd>` | Raw shell — bypasses Claude |
|
|
301
378
|
| `/memory` | Memory stats: fact count, session tags, DB size |
|
|
302
379
|
| `/memory <keyword>` | Search long-term facts by keyword |
|
|
303
380
|
| `/doctor` | Interactive diagnostics |
|
|
381
|
+
| `/user add <open_id>` | Add a user (admin only) |
|
|
382
|
+
| `/user role <open_id> <admin\|member>` | Set user role |
|
|
383
|
+
| `/user list` | List all configured users |
|
|
384
|
+
| `/user remove <open_id>` | Remove a user |
|
|
385
|
+
| `/sessions` | Browse recent sessions with last message preview |
|
|
386
|
+
| `/teamtask create <agent> <goal>` | Create a cross-agent collaboration task |
|
|
387
|
+
| `/teamtask` | List recent TeamTasks (last 10) |
|
|
388
|
+
| `/teamtask <task_id>` | View task detail |
|
|
389
|
+
| `/teamtask resume <task_id>` | Resume a task |
|
|
304
390
|
|
|
305
391
|
## How It Works
|
|
306
392
|
|
|
@@ -335,7 +421,7 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
|
|
|
335
421
|
## Security
|
|
336
422
|
|
|
337
423
|
- All data stays on your machine. No cloud, no telemetry.
|
|
338
|
-
- `allowed_chat_ids` whitelist —
|
|
424
|
+
- `allowed_chat_ids` whitelist — new groups get a smart prompt: if a pending agent activation exists, they're guided to send `/activate`; otherwise they receive setup instructions.
|
|
339
425
|
- `operator_ids` for shared groups — non-operators get read-only mode.
|
|
340
426
|
- `~/.metame/` directory is mode 700.
|
|
341
427
|
- Bot tokens stored locally, never transmitted.
|
|
@@ -346,7 +432,7 @@ All agents share your cognitive profile (`~/.claude_profile.yaml`) — they all
|
|
|
346
432
|
|--------|-------|
|
|
347
433
|
| Daemon memory (idle) | ~100 MB RSS — standard Node.js process baseline |
|
|
348
434
|
| Daemon CPU (idle, between heartbeats) | ~0% — event-loop sleeping |
|
|
349
|
-
| Cognitive profile injection | ~
|
|
435
|
+
| Cognitive profile injection | ~600 tokens/session (0.3% of 200k context) |
|
|
350
436
|
| Dispatch latency (Unix socket) | <100ms |
|
|
351
437
|
| Memory consolidation (per session) | ~1,500–2,000 tokens input + ~50–300 tokens output (Haiku) |
|
|
352
438
|
| Session summary (per session) | ~400–900 tokens input + ≤250 tokens output (Haiku) |
|
package/index.js
CHANGED
|
@@ -30,7 +30,7 @@ if (!fs.existsSync(METAME_DIR)) {
|
|
|
30
30
|
// Auto-deploy bundled scripts to ~/.metame/
|
|
31
31
|
// IMPORTANT: daemon.yaml is USER CONFIG — never overwrite it. Only daemon-default.yaml (template) is synced.
|
|
32
32
|
const scriptsDir = path.join(__dirname, 'scripts');
|
|
33
|
-
const BUNDLED_BASE_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.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', 'qmd-client.js', 'session-summarize.js', 'check-macos-control-capabilities.sh'];
|
|
33
|
+
const BUNDLED_BASE_SCRIPTS = ['signal-capture.js', 'distill.js', 'schema.js', 'pending-traits.js', 'migrate-v2.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-gc.js', 'qmd-client.js', 'session-summarize.js', 'check-macos-control-capabilities.sh', 'usage-classifier.js', 'task-board.js', 'memory-nightly-reflect.js', 'memory-index.js'];
|
|
34
34
|
const DAEMON_MODULE_SCRIPTS = (() => {
|
|
35
35
|
try {
|
|
36
36
|
return fs.readdirSync(scriptsDir).filter((f) => /^daemon-[\w-]+\.js$/.test(f));
|
|
@@ -78,6 +78,44 @@ if (scriptsUpdated) {
|
|
|
78
78
|
console.log('📦 Scripts synced to ~/.metame/ — daemon will auto-restart when idle.');
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// ---------------------------------------------------------
|
|
82
|
+
// Deploy bundled skills to ~/.claude/skills/
|
|
83
|
+
// Only installs if not already present — never overwrites user customizations.
|
|
84
|
+
// ---------------------------------------------------------
|
|
85
|
+
const CLAUDE_SKILLS_DIR = path.join(HOME_DIR, '.claude', 'skills');
|
|
86
|
+
const bundledSkillsDir = path.join(__dirname, 'skills');
|
|
87
|
+
if (fs.existsSync(bundledSkillsDir)) {
|
|
88
|
+
try {
|
|
89
|
+
if (!fs.existsSync(CLAUDE_SKILLS_DIR)) {
|
|
90
|
+
fs.mkdirSync(CLAUDE_SKILLS_DIR, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
const skillsInstalled = [];
|
|
93
|
+
for (const skillName of fs.readdirSync(bundledSkillsDir)) {
|
|
94
|
+
const srcSkill = path.join(bundledSkillsDir, skillName);
|
|
95
|
+
const destSkill = path.join(CLAUDE_SKILLS_DIR, skillName);
|
|
96
|
+
if (!fs.statSync(srcSkill).isDirectory()) continue;
|
|
97
|
+
if (fs.existsSync(destSkill)) continue; // already installed, respect user's version
|
|
98
|
+
// Copy skill directory recursively
|
|
99
|
+
const copyDir = (src, dest) => {
|
|
100
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
101
|
+
for (const entry of fs.readdirSync(src)) {
|
|
102
|
+
const s = path.join(src, entry);
|
|
103
|
+
const d = path.join(dest, entry);
|
|
104
|
+
if (fs.statSync(s).isDirectory()) copyDir(s, d);
|
|
105
|
+
else fs.copyFileSync(s, d);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
copyDir(srcSkill, destSkill);
|
|
109
|
+
skillsInstalled.push(skillName);
|
|
110
|
+
}
|
|
111
|
+
if (skillsInstalled.length > 0) {
|
|
112
|
+
console.log(`🧠 Skills installed: ${skillsInstalled.join(', ')}`);
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Non-fatal
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
81
119
|
// Load daemon config for local launch flags
|
|
82
120
|
let daemonCfg = {};
|
|
83
121
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metame-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.20",
|
|
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": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --test scripts/*.test.js",
|
|
15
15
|
"start": "node index.js",
|
|
16
|
-
"sync:plugin": "cp 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-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'",
|
|
16
|
+
"sync:plugin": "cp 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'",
|
|
17
17
|
"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'",
|
|
18
18
|
"precommit": "npm run sync:plugin && npm run restart:daemon"
|
|
19
19
|
},
|
|
@@ -26,6 +26,10 @@ function createAdminCommandHandler(deps) {
|
|
|
26
26
|
skillEvolution,
|
|
27
27
|
taskBoard,
|
|
28
28
|
taskEnvelope,
|
|
29
|
+
getActiveProcesses,
|
|
30
|
+
getMessageQueue,
|
|
31
|
+
loadState,
|
|
32
|
+
saveState,
|
|
29
33
|
} = deps;
|
|
30
34
|
|
|
31
35
|
function resolveProjectKey(targetName, projects) {
|
|
@@ -102,7 +106,13 @@ function createAdminCommandHandler(deps) {
|
|
|
102
106
|
if (fs.existsSync(BRAIN_FILE)) {
|
|
103
107
|
const doc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|
|
104
108
|
if (doc.identity) msg += `\nProfile: ${doc.identity.nickname || 'unknown'}`;
|
|
105
|
-
|
|
109
|
+
const nowPath = require('path').join(require('os').homedir(), '.metame', 'memory', 'NOW.md');
|
|
110
|
+
try {
|
|
111
|
+
if (fs.existsSync(nowPath)) {
|
|
112
|
+
const nowContent = fs.readFileSync(nowPath, 'utf8').trim().split('\n')[0];
|
|
113
|
+
if (nowContent) msg += `\nNOW: ${nowContent.slice(0, 80)}`;
|
|
114
|
+
}
|
|
115
|
+
} catch { /* ignore */ }
|
|
106
116
|
}
|
|
107
117
|
} catch { /* ignore */ }
|
|
108
118
|
await bot.sendMessage(chatId, msg);
|
|
@@ -639,6 +649,57 @@ function createAdminCommandHandler(deps) {
|
|
|
639
649
|
return { handled: true, config };
|
|
640
650
|
}
|
|
641
651
|
|
|
652
|
+
// /recover — kill all stuck tasks and reset message queues
|
|
653
|
+
if (text === '/recover') {
|
|
654
|
+
const activeProcesses = getActiveProcesses ? getActiveProcesses() : null;
|
|
655
|
+
const messageQueue = getMessageQueue ? getMessageQueue() : null;
|
|
656
|
+
if (!activeProcesses) {
|
|
657
|
+
await bot.sendMessage(chatId, '❌ 无法访问任务状态');
|
|
658
|
+
return { handled: true, config };
|
|
659
|
+
}
|
|
660
|
+
const stuckChatIds = [...activeProcesses.keys()];
|
|
661
|
+
let killed = 0;
|
|
662
|
+
for (const cid of stuckChatIds) {
|
|
663
|
+
const proc = activeProcesses.get(cid);
|
|
664
|
+
if (proc && proc.child) {
|
|
665
|
+
try { process.kill(-proc.child.pid, 'SIGTERM'); } catch { try { proc.child.kill('SIGTERM'); } catch { } }
|
|
666
|
+
killed++;
|
|
667
|
+
}
|
|
668
|
+
activeProcesses.delete(cid);
|
|
669
|
+
if (messageQueue && messageQueue.has(cid)) {
|
|
670
|
+
const q = messageQueue.get(cid);
|
|
671
|
+
if (q && q.timer) clearTimeout(q.timer);
|
|
672
|
+
messageQueue.delete(cid);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// Clear stale sessions (started: false = never completed first message, likely locked)
|
|
676
|
+
try {
|
|
677
|
+
const state = loadState();
|
|
678
|
+
let cleared = 0;
|
|
679
|
+
for (const [cid, sess] of Object.entries(state.sessions || {})) {
|
|
680
|
+
if (sess && !sess.started) {
|
|
681
|
+
delete state.sessions[cid];
|
|
682
|
+
cleared++;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (cleared > 0) saveState(state);
|
|
686
|
+
} catch { /* non-critical */ }
|
|
687
|
+
// SIGKILL stragglers after 3s grace period
|
|
688
|
+
if (killed > 0) {
|
|
689
|
+
setTimeout(() => {
|
|
690
|
+
for (const cid of stuckChatIds) {
|
|
691
|
+
// proc references are stale but child.pid is still valid for cleanup
|
|
692
|
+
try { const proc = activeProcesses.get(cid); if (proc && proc.child) process.kill(-proc.child.pid, 'SIGKILL'); } catch { }
|
|
693
|
+
}
|
|
694
|
+
}, 3000);
|
|
695
|
+
}
|
|
696
|
+
const summary = killed > 0
|
|
697
|
+
? `✅ 已重置 ${killed} 个卡住的任务,可重新发送消息。`
|
|
698
|
+
: '✅ 当前没有卡住的任务。';
|
|
699
|
+
await bot.sendMessage(chatId, summary);
|
|
700
|
+
return { handled: true, config };
|
|
701
|
+
}
|
|
702
|
+
|
|
642
703
|
// /doctor — diagnostics; /fix — restore backup; /reset — reset model to sonnet
|
|
643
704
|
if (text === '/fix') {
|
|
644
705
|
if (restoreConfig()) {
|
|
@@ -696,15 +757,36 @@ function createAdminCommandHandler(deps) {
|
|
|
696
757
|
const hasBak = fs.existsSync(bakFile);
|
|
697
758
|
checks.push(hasBak ? '✅ 有备份' : '⚠️ 无备份');
|
|
698
759
|
|
|
760
|
+
// Check for stuck tasks (only flag tasks running > 10 minutes as suspicious)
|
|
761
|
+
const activeProcesses = getActiveProcesses ? getActiveProcesses() : null;
|
|
762
|
+
let hasStuck = false;
|
|
763
|
+
if (activeProcesses && activeProcesses.size > 0) {
|
|
764
|
+
const now = Date.now();
|
|
765
|
+
const stuckThreshold = 10 * 60 * 1000; // 10 minutes
|
|
766
|
+
const entries = [...activeProcesses.entries()];
|
|
767
|
+
const stuckEntries = entries.filter(([, proc]) => proc && proc.startedAt && (now - proc.startedAt) > stuckThreshold);
|
|
768
|
+
if (stuckEntries.length > 0) {
|
|
769
|
+
const stuckList = stuckEntries.map(([cid, proc]) => `${cid.slice(-8)}(${Math.round((now - proc.startedAt) / 60000)}min)`).join(', ');
|
|
770
|
+
checks.push(`⚠️ ${stuckEntries.length} 个任务疑似卡住 (${stuckList})`);
|
|
771
|
+
hasStuck = true;
|
|
772
|
+
issues++;
|
|
773
|
+
} else {
|
|
774
|
+
checks.push(`✅ ${entries.length} 个任务正常运行中`);
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
checks.push('✅ 无运行中任务');
|
|
778
|
+
}
|
|
779
|
+
|
|
699
780
|
let msg = `🏥 诊断\n${checks.join('\n')}`;
|
|
700
781
|
if (issues > 0) {
|
|
701
782
|
if (bot.sendButtons) {
|
|
702
783
|
const buttons = [];
|
|
703
|
-
if (
|
|
704
|
-
buttons.push([{ text: '
|
|
784
|
+
if (hasStuck) buttons.push([{ text: '🔧 一键重置卡住任务', callback_data: '/recover' }]);
|
|
785
|
+
if (hasBak) buttons.push([{ text: '📦 恢复配置备份', callback_data: '/fix' }]);
|
|
786
|
+
buttons.push([{ text: '🔄 重置模型 opus', callback_data: '/reset' }]);
|
|
705
787
|
await bot.sendButtons(chatId, msg, buttons);
|
|
706
788
|
} else {
|
|
707
|
-
msg += '\n/fix 恢复备份 /reset 重置opus';
|
|
789
|
+
msg += '\n/recover 重置卡住任务 /fix 恢复备份 /reset 重置opus';
|
|
708
790
|
await bot.sendMessage(chatId, msg);
|
|
709
791
|
}
|
|
710
792
|
} else {
|