metame-cli 1.4.34 → 1.5.1

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.
Files changed (48) hide show
  1. package/README.md +136 -94
  2. package/index.js +312 -57
  3. package/package.json +8 -4
  4. package/scripts/agent-layer.js +320 -0
  5. package/scripts/daemon-admin-commands.js +328 -28
  6. package/scripts/daemon-agent-commands.js +145 -6
  7. package/scripts/daemon-agent-tools.js +163 -7
  8. package/scripts/daemon-bridges.js +110 -20
  9. package/scripts/daemon-checkpoints.js +36 -7
  10. package/scripts/daemon-claude-engine.js +849 -358
  11. package/scripts/daemon-command-router.js +31 -10
  12. package/scripts/daemon-default.yaml +28 -4
  13. package/scripts/daemon-engine-runtime.js +328 -0
  14. package/scripts/daemon-exec-commands.js +15 -7
  15. package/scripts/daemon-notify.js +37 -1
  16. package/scripts/daemon-ops-commands.js +8 -6
  17. package/scripts/daemon-runtime-lifecycle.js +129 -5
  18. package/scripts/daemon-session-commands.js +60 -25
  19. package/scripts/daemon-session-store.js +121 -13
  20. package/scripts/daemon-task-scheduler.js +129 -49
  21. package/scripts/daemon-user-acl.js +35 -9
  22. package/scripts/daemon.js +268 -33
  23. package/scripts/distill.js +327 -18
  24. package/scripts/docs/agent-guide.md +12 -0
  25. package/scripts/docs/maintenance-manual.md +155 -0
  26. package/scripts/docs/pointer-map.md +110 -0
  27. package/scripts/feishu-adapter.js +42 -13
  28. package/scripts/hooks/stop-session-capture.js +243 -0
  29. package/scripts/memory-extract.js +105 -6
  30. package/scripts/memory-nightly-reflect.js +199 -11
  31. package/scripts/memory.js +134 -3
  32. package/scripts/mentor-engine.js +405 -0
  33. package/scripts/platform.js +24 -0
  34. package/scripts/providers.js +182 -22
  35. package/scripts/schema.js +12 -0
  36. package/scripts/session-analytics.js +245 -12
  37. package/scripts/skill-changelog.js +245 -0
  38. package/scripts/skill-evolution.js +288 -5
  39. package/scripts/telegram-adapter.js +12 -8
  40. package/scripts/usage-classifier.js +1 -1
  41. package/scripts/daemon-admin-commands.test.js +0 -333
  42. package/scripts/daemon-task-envelope.test.js +0 -59
  43. package/scripts/daemon-task-scheduler.test.js +0 -106
  44. package/scripts/reliability-core.test.js +0 -280
  45. package/scripts/skill-evolution.test.js +0 -113
  46. package/scripts/task-board.test.js +0 -83
  47. package/scripts/test_daemon.js +0 -1407
  48. package/scripts/utils.test.js +0 -192
@@ -0,0 +1,320 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const yaml = require('./resolve-yaml');
8
+
9
+ const DEFAULT_SOUL_TEMPLATE = (name) => `# Soul
10
+
11
+ ## Identity
12
+ 你是一个稳定、专业、可信赖的智能体。
13
+ 长期身份:${name || 'MetaMe Agent'}。
14
+
15
+ ## Mission
16
+ 围绕当前项目持续协助用户完成目标,优先保证结果可落地。
17
+
18
+ ## Temperament
19
+ 清晰、克制、严谨、面向结果。
20
+
21
+ ## Judgment
22
+ 优先保证正确性、稳定性、一致性。
23
+
24
+ ## Boundaries
25
+ 不编造事实;不跳过验证;发现风险时明确提醒。
26
+ `;
27
+
28
+ const DEFAULT_MEMORY_SNAPSHOT = `# Memory Snapshot
29
+
30
+ 当前尚无足够历史记录。
31
+ 后续将根据会话、事实提取与反思结果自动更新。
32
+ `;
33
+
34
+ function sanitizeSlug(input, fallback = 'agent') {
35
+ const cleaned = String(input || '')
36
+ .trim()
37
+ .toLowerCase()
38
+ .replace(/[^a-z0-9]+/g, '_')
39
+ .replace(/^_+|_+$/g, '');
40
+ return cleaned || fallback;
41
+ }
42
+
43
+ function normalizeEngine(engine) {
44
+ return String(engine || '').trim().toLowerCase() === 'codex' ? 'codex' : 'claude';
45
+ }
46
+
47
+ function getAgentsRoot(homeDir = os.homedir()) {
48
+ return path.join(homeDir, '.metame', 'agents');
49
+ }
50
+
51
+ function createAgentId({ agentName, projectKey, cwd } = {}) {
52
+ if (projectKey) return sanitizeSlug(projectKey, 'agent');
53
+ if (agentName) return sanitizeSlug(agentName, 'agent');
54
+ if (cwd) return sanitizeSlug(path.basename(String(cwd)), 'agent');
55
+ return 'agent';
56
+ }
57
+
58
+ function getAgentPaths(agentId, homeDir = os.homedir()) {
59
+ const root = getAgentsRoot(homeDir);
60
+ const dir = path.join(root, agentId);
61
+ return {
62
+ root,
63
+ dir,
64
+ yaml: path.join(dir, 'agent.yaml'),
65
+ soul: path.join(dir, 'soul.md'),
66
+ memory: path.join(dir, 'memory-snapshot.md'),
67
+ };
68
+ }
69
+
70
+ function ensureDirSync(dirPath) {
71
+ fs.mkdirSync(dirPath, { recursive: true });
72
+ }
73
+
74
+ function writeIfMissing(filePath, content) {
75
+ if (!fs.existsSync(filePath)) {
76
+ fs.writeFileSync(filePath, content, 'utf8');
77
+ }
78
+ }
79
+
80
+ function readYamlSafe(filePath) {
81
+ try {
82
+ if (!fs.existsSync(filePath)) return null;
83
+ return yaml.load(fs.readFileSync(filePath, 'utf8')) || null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ function ensureAgentFiles({
90
+ agentId,
91
+ agentName,
92
+ projectKey,
93
+ engine,
94
+ aliases = [],
95
+ homeDir = os.homedir(),
96
+ }) {
97
+ const paths = getAgentPaths(agentId, homeDir);
98
+ ensureDirSync(paths.dir);
99
+
100
+ const existing = readYamlSafe(paths.yaml) || {};
101
+ const payload = {
102
+ id: agentId,
103
+ name: String(agentName || existing.name || agentId),
104
+ project_key: String(projectKey || existing.project_key || ''),
105
+ engine: normalizeEngine(engine || existing.engine),
106
+ aliases: Array.from(new Set([
107
+ ...((Array.isArray(existing.aliases) ? existing.aliases : []).map(String)),
108
+ ...aliases.map(String).filter(Boolean),
109
+ ])),
110
+ };
111
+
112
+ fs.writeFileSync(paths.yaml, yaml.dump(payload, { lineWidth: -1 }), 'utf8');
113
+ writeIfMissing(paths.soul, DEFAULT_SOUL_TEMPLATE(payload.name));
114
+ writeIfMissing(paths.memory, DEFAULT_MEMORY_SNAPSHOT);
115
+
116
+ return { agentId, paths, metadata: payload };
117
+ }
118
+
119
+ function tryRemoveExisting(filePath) {
120
+ try { fs.rmSync(filePath, { force: true }); } catch { /* ignore */ }
121
+ }
122
+
123
+ /**
124
+ * Create a symlink from linkPath → targetPath with graceful fallbacks for Windows:
125
+ * 1. symlink (relative target, preferred)
126
+ * 2. hardlink (same drive, no privilege needed on most Windows)
127
+ * 3. plain file copy (last resort; note: will not track future changes to target)
128
+ */
129
+ function createLinkOrMirror(targetPath, linkPath) {
130
+ const relativeTarget = path.relative(path.dirname(linkPath), targetPath) || path.basename(targetPath);
131
+ tryRemoveExisting(linkPath);
132
+
133
+ try {
134
+ fs.symlinkSync(relativeTarget, linkPath, 'file');
135
+ return { mode: 'symlink', path: linkPath };
136
+ } catch (symlinkErr) {
137
+ const sameRoot = path.parse(targetPath).root.toLowerCase() === path.parse(linkPath).root.toLowerCase();
138
+ if (sameRoot) {
139
+ try {
140
+ fs.linkSync(targetPath, linkPath);
141
+ return { mode: 'hardlink', path: linkPath };
142
+ } catch { /* ignore */ }
143
+ }
144
+
145
+ // Last resort: plain copy (no mirror comment — keeps content clean for model consumption)
146
+ const content = fs.readFileSync(targetPath, 'utf8');
147
+ fs.writeFileSync(linkPath, content, 'utf8');
148
+ return { mode: 'mirror', path: linkPath, warning: symlinkErr && symlinkErr.message ? symlinkErr.message : null };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Ensure CLAUDE.md in workspaceDir has @SOUL.md at the top.
154
+ * If CLAUDE.md does not exist yet, skip — editAgentRoleDefinition will create it later,
155
+ * and createNewWorkspaceAgent will call this again after that.
156
+ * Returns: 'prepended' | 'already-present' | 'skipped'
157
+ */
158
+ function ensureClaudeMdSoulImport(workspaceDir) {
159
+ try {
160
+ const claudeMdPath = path.join(workspaceDir, 'CLAUDE.md');
161
+ if (!fs.existsSync(claudeMdPath)) return 'skipped';
162
+ const existing = fs.readFileSync(claudeMdPath, 'utf8');
163
+ if (existing.includes('@SOUL.md')) return 'already-present';
164
+ fs.writeFileSync(claudeMdPath, '@SOUL.md' + '\n\n' + existing, 'utf8');
165
+ return 'prepended';
166
+ } catch {
167
+ return 'skipped';
168
+ }
169
+ }
170
+
171
+ function ensureProjectAgentViews(workspaceDir, agentPaths) {
172
+ ensureDirSync(workspaceDir);
173
+ // Inject @SOUL.md import into CLAUDE.md so Claude auto-loads soul on every session.
174
+ ensureClaudeMdSoulImport(workspaceDir);
175
+ return {
176
+ soul: createLinkOrMirror(agentPaths.soul, path.join(workspaceDir, 'SOUL.md')),
177
+ memory: createLinkOrMirror(agentPaths.memory, path.join(workspaceDir, 'MEMORY.md')),
178
+ };
179
+ }
180
+
181
+ function ensureAgentLayer(options) {
182
+ const agentId = options.agentId || createAgentId(options);
183
+ const ensured = ensureAgentFiles({ ...options, agentId });
184
+ const views = options.workspaceDir
185
+ ? ensureProjectAgentViews(options.workspaceDir, ensured.paths)
186
+ : null;
187
+ return { agentId, paths: ensured.paths, metadata: ensured.metadata, views };
188
+ }
189
+
190
+ /**
191
+ * Lazy-migration: repair the agent soul layer for an existing project that predates this system.
192
+ * Safe to call repeatedly — idempotent. Does NOT overwrite existing soul.md or memory-snapshot.md.
193
+ */
194
+ function repairAgentLayer(projectKey, project, homeDir = os.homedir()) {
195
+ if (!project || !project.cwd) return null;
196
+ const agentId = project.agent_id || sanitizeSlug(projectKey, 'agent');
197
+ return ensureAgentLayer({
198
+ agentId,
199
+ projectKey,
200
+ agentName: project.name || projectKey,
201
+ workspaceDir: project.cwd,
202
+ engine: normalizeEngine(project.engine),
203
+ aliases: Array.isArray(project.nicknames) ? project.nicknames.map(String) : [],
204
+ homeDir,
205
+ });
206
+ }
207
+
208
+ function trimFileContent(filePath, maxChars = 2400) {
209
+ try {
210
+ if (!fs.existsSync(filePath)) return '';
211
+ const raw = fs.readFileSync(filePath, 'utf8').trim();
212
+ if (!raw) return '';
213
+ return raw.length > maxChars ? `${raw.slice(0, maxChars)}\n...[truncated]` : raw;
214
+ } catch {
215
+ return '';
216
+ }
217
+ }
218
+
219
+ function resolveAgentPathsForProject(project = {}, homeDir = os.homedir()) {
220
+ const cwd = project && project.cwd ? String(project.cwd) : '';
221
+ if (project && project.agent_id) {
222
+ return getAgentPaths(String(project.agent_id), homeDir);
223
+ }
224
+ if (cwd) {
225
+ // Fallback for old projects without agent_id: read from project directory directly
226
+ return { soul: path.join(cwd, 'SOUL.md'), memory: path.join(cwd, 'MEMORY.md') };
227
+ }
228
+ return null;
229
+ }
230
+
231
+ /**
232
+ * Build agent context hint.
233
+ *
234
+ * Soul is no longer injected via prompt for either engine — loaded via file system instead,
235
+ * which is persistent across session resumes:
236
+ * - Claude: @SOUL.md import prepended to CLAUDE.md (auto-loaded by Claude CLI on every session)
237
+ * - Codex: AGENTS.md = CLAUDE.md + SOUL.md, merged on each new session start
238
+ *
239
+ * Only memory-snapshot still needs prompt injection (neither engine auto-discovers it).
240
+ * The engineName param is kept for API compatibility.
241
+ */
242
+ function buildAgentContextForEngine(project = {}, engineName = 'claude', homeDir = os.homedir()) {
243
+ const paths = resolveAgentPathsForProject(project, homeDir);
244
+ if (!paths) return { soul: '', memory: '', hint: '' };
245
+
246
+ const memory = trimFileContent(paths.memory);
247
+ const hint = memory ? '\n\n[Agent memory snapshot:\n' + memory + ']' : '';
248
+ return { soul: '', memory, hint };
249
+ }
250
+
251
+ /** Backward-compat alias — always uses the claude (full-injection) path. */
252
+ function buildAgentContextForProject(project = {}, homeDir = os.homedir()) {
253
+ return buildAgentContextForEngine(project, 'claude', homeDir);
254
+ }
255
+
256
+ /**
257
+ * Build memory snapshot markdown from recent session summaries and facts.
258
+ * Used to auto-refresh memory-snapshot.md after sessions.
259
+ */
260
+ function buildMemorySnapshotContent(sessions = [], facts = []) {
261
+ const lines = ['# Memory Snapshot', ''];
262
+ if (sessions.length === 0 && facts.length === 0) {
263
+ lines.push('当前尚无足够历史记录。');
264
+ lines.push('后续将根据会话、事实提取与反思结果自动更新。');
265
+ return lines.join('\n');
266
+ }
267
+ if (sessions.length > 0) {
268
+ lines.push('## 近期会话摘要', '');
269
+ for (const s of sessions) {
270
+ const date = s.created_at ? String(s.created_at).slice(0, 10) : '';
271
+ const kw = s.keywords ? `(关键词: ${s.keywords})` : '';
272
+ lines.push(`- [${date}] ${s.summary || '(无摘要)'}${kw}`);
273
+ }
274
+ lines.push('');
275
+ }
276
+ if (facts.length > 0) {
277
+ lines.push('## 关键事实', '');
278
+ for (const f of facts) {
279
+ lines.push(`- [${f.relation || 'fact'}] ${f.value}`);
280
+ }
281
+ lines.push('');
282
+ }
283
+ return lines.join('\n');
284
+ }
285
+
286
+ /**
287
+ * Overwrite memory-snapshot.md for the given agent.
288
+ * Returns true on success, false if the agent directory doesn't exist yet.
289
+ */
290
+ function refreshMemorySnapshot(agentId, content, homeDir = os.homedir()) {
291
+ if (!agentId || !content) return false;
292
+ try {
293
+ const paths = getAgentPaths(agentId, homeDir);
294
+ if (!fs.existsSync(paths.dir)) return false;
295
+ fs.writeFileSync(paths.memory, content, 'utf8');
296
+ return true;
297
+ } catch {
298
+ return false;
299
+ }
300
+ }
301
+
302
+ module.exports = {
303
+ DEFAULT_SOUL_TEMPLATE,
304
+ DEFAULT_MEMORY_SNAPSHOT,
305
+ sanitizeSlug,
306
+ normalizeEngine,
307
+ getAgentsRoot,
308
+ createAgentId,
309
+ getAgentPaths,
310
+ ensureAgentFiles,
311
+ createLinkOrMirror,
312
+ ensureProjectAgentViews,
313
+ ensureClaudeMdSoulImport,
314
+ ensureAgentLayer,
315
+ repairAgentLayer,
316
+ buildAgentContextForEngine,
317
+ buildAgentContextForProject,
318
+ buildMemorySnapshotContent,
319
+ refreshMemorySnapshot,
320
+ };