metame-cli 1.5.0 → 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.
- package/README.md +60 -132
- package/index.js +193 -77
- package/package.json +4 -3
- package/scripts/agent-layer.js +320 -0
- package/scripts/daemon-admin-commands.js +80 -25
- package/scripts/daemon-agent-commands.js +81 -0
- package/scripts/daemon-agent-tools.js +145 -10
- package/scripts/daemon-checkpoints.js +36 -7
- package/scripts/daemon-claude-engine.js +201 -169
- package/scripts/daemon-command-router.js +7 -2
- package/scripts/daemon-engine-runtime.js +74 -21
- package/scripts/daemon-exec-commands.js +5 -3
- package/scripts/daemon-ops-commands.js +8 -6
- package/scripts/daemon-runtime-lifecycle.js +127 -4
- package/scripts/daemon-session-commands.js +23 -36
- package/scripts/daemon-session-store.js +120 -13
- package/scripts/daemon-task-scheduler.js +61 -11
- package/scripts/daemon-user-acl.js +10 -1
- package/scripts/daemon.js +192 -21
- package/scripts/distill.js +4 -0
- package/scripts/docs/maintenance-manual.md +39 -3
- package/scripts/docs/pointer-map.md +23 -1
- package/scripts/feishu-adapter.js +36 -12
- package/scripts/memory-extract.js +5 -1
- package/scripts/memory-nightly-reflect.js +3 -0
- package/scripts/mentor-engine.js +4 -4
- package/scripts/platform.js +22 -0
- package/scripts/providers.js +14 -2
- package/scripts/telegram-adapter.js +12 -8
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const {
|
|
4
|
+
createAgentId,
|
|
5
|
+
ensureClaudeMdSoulImport,
|
|
6
|
+
ensureAgentLayer,
|
|
7
|
+
repairAgentLayer,
|
|
8
|
+
refreshMemorySnapshot,
|
|
9
|
+
buildMemorySnapshotContent,
|
|
10
|
+
normalizeEngine: normalizeLayerEngine,
|
|
11
|
+
} = require('./agent-layer');
|
|
12
|
+
|
|
3
13
|
function createAgentTools(deps) {
|
|
4
14
|
const {
|
|
5
15
|
fs,
|
|
@@ -31,8 +41,26 @@ function createAgentTools(deps) {
|
|
|
31
41
|
return (String(agentName || '').replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase() || String(chatId));
|
|
32
42
|
}
|
|
33
43
|
|
|
34
|
-
function
|
|
35
|
-
|
|
44
|
+
function ensureAgentMetadata({ cfg, projectKey, project, safeName, resolvedDir, engine }) {
|
|
45
|
+
const agentId = String(project && project.agent_id ? project.agent_id : createAgentId({
|
|
46
|
+
projectKey,
|
|
47
|
+
agentName: safeName,
|
|
48
|
+
cwd: resolvedDir,
|
|
49
|
+
}));
|
|
50
|
+
const ensured = ensureAgentLayer({
|
|
51
|
+
agentId,
|
|
52
|
+
projectKey,
|
|
53
|
+
agentName: safeName,
|
|
54
|
+
workspaceDir: resolvedDir,
|
|
55
|
+
engine: normalizeLayerEngine(engine || (project && project.engine)),
|
|
56
|
+
aliases: [safeName],
|
|
57
|
+
homeDir: HOME,
|
|
58
|
+
});
|
|
59
|
+
cfg.projects[projectKey] = {
|
|
60
|
+
...cfg.projects[projectKey],
|
|
61
|
+
agent_id: ensured.agentId,
|
|
62
|
+
};
|
|
63
|
+
return ensured;
|
|
36
64
|
}
|
|
37
65
|
|
|
38
66
|
function ensureAdapterConfig(cfg, adapterKey) {
|
|
@@ -53,7 +81,7 @@ function createAgentTools(deps) {
|
|
|
53
81
|
|
|
54
82
|
const projectKey = toProjectKey(safeName, chatId);
|
|
55
83
|
let resolvedDir = resolveWorkspaceDir(workspaceDir);
|
|
56
|
-
const normalizedEngine = engine ?
|
|
84
|
+
const normalizedEngine = engine ? normalizeLayerEngine(engine) : null;
|
|
57
85
|
|
|
58
86
|
if (!resolvedDir) {
|
|
59
87
|
const existing = cfg.projects[projectKey];
|
|
@@ -89,6 +117,7 @@ function createAgentTools(deps) {
|
|
|
89
117
|
name: safeName,
|
|
90
118
|
cwd: resolvedDir,
|
|
91
119
|
nicknames: [safeName],
|
|
120
|
+
agent_id: createAgentId({ projectKey, agentName: safeName, cwd: resolvedDir }),
|
|
92
121
|
...(normalizedEngine === 'codex' ? { engine: 'codex' } : {}),
|
|
93
122
|
};
|
|
94
123
|
} else {
|
|
@@ -101,10 +130,20 @@ function createAgentTools(deps) {
|
|
|
101
130
|
name: safeName,
|
|
102
131
|
cwd: resolvedDir,
|
|
103
132
|
nicknames,
|
|
133
|
+
agent_id: cfg.projects[projectKey].agent_id || createAgentId({ projectKey, agentName: safeName, cwd: resolvedDir }),
|
|
104
134
|
...(normalizedEngine === 'codex' ? { engine: 'codex' } : {}),
|
|
105
135
|
};
|
|
106
136
|
}
|
|
107
137
|
|
|
138
|
+
const agentLayer = ensureAgentMetadata({
|
|
139
|
+
cfg,
|
|
140
|
+
projectKey,
|
|
141
|
+
project: cfg.projects[projectKey],
|
|
142
|
+
safeName,
|
|
143
|
+
resolvedDir,
|
|
144
|
+
engine: normalizedEngine,
|
|
145
|
+
});
|
|
146
|
+
|
|
108
147
|
writeConfigSafe(cfg);
|
|
109
148
|
backupConfig();
|
|
110
149
|
|
|
@@ -117,6 +156,7 @@ function createAgentTools(deps) {
|
|
|
117
156
|
cwd: resolvedDir,
|
|
118
157
|
isNewProject: !existed,
|
|
119
158
|
project: cfg.projects[projectKey],
|
|
159
|
+
agent: agentLayer,
|
|
120
160
|
},
|
|
121
161
|
};
|
|
122
162
|
} catch (e) {
|
|
@@ -138,6 +178,7 @@ function createAgentTools(deps) {
|
|
|
138
178
|
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
139
179
|
if (!fs.existsSync(claudeMdPath)) {
|
|
140
180
|
fs.writeFileSync(claudeMdPath, `## Agent 角色\n\n${safeDelta}\n`, 'utf8');
|
|
181
|
+
try { ensureClaudeMdSoulImport(cwd); } catch { /* non-critical */ }
|
|
141
182
|
return { ok: true, data: { created: true, merged: false, path: claudeMdPath } };
|
|
142
183
|
}
|
|
143
184
|
|
|
@@ -180,6 +221,7 @@ ${safeDelta}
|
|
|
180
221
|
cleanOutput = cleanOutput.replace(/^```[a-zA-Z]*\n/, '').replace(/\n```$/, '');
|
|
181
222
|
}
|
|
182
223
|
fs.writeFileSync(claudeMdPath, cleanOutput, 'utf8');
|
|
224
|
+
try { ensureClaudeMdSoulImport(cwd); } catch { /* non-critical */ }
|
|
183
225
|
return { ok: true, data: { created: false, merged: true, path: claudeMdPath } };
|
|
184
226
|
} catch (e) {
|
|
185
227
|
return { ok: false, error: e.message };
|
|
@@ -188,7 +230,7 @@ ${safeDelta}
|
|
|
188
230
|
|
|
189
231
|
async function createNewWorkspaceAgent(agentName, workspaceDir, roleDescription, chatId, { skipChatBinding = false, engine = null } = {}) {
|
|
190
232
|
let bindData;
|
|
191
|
-
const normalizedEngine = engine ?
|
|
233
|
+
const normalizedEngine = engine ? normalizeLayerEngine(engine) : null;
|
|
192
234
|
|
|
193
235
|
if (skipChatBinding) {
|
|
194
236
|
// Create the project entry without touching chat_agent_map
|
|
@@ -208,21 +250,33 @@ ${safeDelta}
|
|
|
208
250
|
name: safeName,
|
|
209
251
|
cwd: resolvedDir,
|
|
210
252
|
nicknames: [safeName],
|
|
253
|
+
agent_id: createAgentId({ projectKey, agentName: safeName, cwd: resolvedDir }),
|
|
211
254
|
...(normalizedEngine === 'codex' ? { engine: 'codex' } : {}),
|
|
212
255
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
256
|
+
} else {
|
|
257
|
+
cfg.projects[projectKey] = {
|
|
258
|
+
...cfg.projects[projectKey],
|
|
259
|
+
...(normalizedEngine === 'codex' ? { engine: 'codex' } : {}),
|
|
260
|
+
agent_id: cfg.projects[projectKey].agent_id || createAgentId({ projectKey, agentName: safeName, cwd: resolvedDir }),
|
|
261
|
+
};
|
|
219
262
|
}
|
|
263
|
+
const agentLayer = ensureAgentMetadata({
|
|
264
|
+
cfg,
|
|
265
|
+
projectKey,
|
|
266
|
+
project: cfg.projects[projectKey],
|
|
267
|
+
safeName,
|
|
268
|
+
resolvedDir,
|
|
269
|
+
engine: normalizedEngine,
|
|
270
|
+
});
|
|
271
|
+
writeConfigSafe(cfg);
|
|
272
|
+
backupConfig();
|
|
220
273
|
bindData = {
|
|
221
274
|
projectKey,
|
|
222
275
|
cwd: resolvedDir,
|
|
223
276
|
isNewProject: !existed,
|
|
224
277
|
chatId: null, // not bound to any chat
|
|
225
278
|
project: cfg.projects[projectKey],
|
|
279
|
+
agent: agentLayer,
|
|
226
280
|
};
|
|
227
281
|
} else {
|
|
228
282
|
const bindResult = await bindAgentToChat(chatId, agentName, workspaceDir, { engine: normalizedEngine });
|
|
@@ -244,6 +298,10 @@ ${safeDelta}
|
|
|
244
298
|
};
|
|
245
299
|
}
|
|
246
300
|
|
|
301
|
+
// editAgentRoleDefinition may have just created CLAUDE.md for the first time.
|
|
302
|
+
// Ensure @SOUL.md import is present so Claude auto-loads soul on every future session.
|
|
303
|
+
try { ensureClaudeMdSoulImport(bindData.cwd); } catch { /* non-critical */ }
|
|
304
|
+
|
|
247
305
|
return {
|
|
248
306
|
ok: true,
|
|
249
307
|
data: { ...bindData, role: roleResult.data },
|
|
@@ -302,12 +360,89 @@ ${safeDelta}
|
|
|
302
360
|
}
|
|
303
361
|
}
|
|
304
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Lazy-migration repair: given a workspace directory, ensure the agent soul layer
|
|
365
|
+
* (~/.metame/agents/<id>/, SOUL.md, MEMORY.md) exists and is wired up.
|
|
366
|
+
* Persists agent_id back to daemon.yaml if it was missing.
|
|
367
|
+
* Safe to call repeatedly — idempotent.
|
|
368
|
+
*/
|
|
369
|
+
async function repairAgentSoul(workspaceDir) {
|
|
370
|
+
try {
|
|
371
|
+
const cwd = resolveWorkspaceDir(workspaceDir);
|
|
372
|
+
if (!cwd) return { ok: false, error: 'workspaceDir is required' };
|
|
373
|
+
if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) {
|
|
374
|
+
return { ok: false, error: `workspaceDir not found: ${cwd}` };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const cfg = loadConfig();
|
|
378
|
+
let projectKey = null;
|
|
379
|
+
let project = null;
|
|
380
|
+
for (const [key, p] of Object.entries(cfg.projects || {})) {
|
|
381
|
+
if (!p || !p.cwd) continue;
|
|
382
|
+
const pCwd = normalizeCwd ? normalizeCwd(p.cwd) : p.cwd;
|
|
383
|
+
const r1 = path.resolve(pCwd);
|
|
384
|
+
const r2 = path.resolve(cwd);
|
|
385
|
+
const isMatch = process.platform === 'win32'
|
|
386
|
+
? r1.toLowerCase() === r2.toLowerCase()
|
|
387
|
+
: r1 === r2;
|
|
388
|
+
if (isMatch) {
|
|
389
|
+
projectKey = key;
|
|
390
|
+
project = p;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!projectKey) {
|
|
395
|
+
return { ok: false, error: `No registered agent found for: ${cwd}. Run /agent bind first.` };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const ensured = repairAgentLayer(projectKey, project, HOME);
|
|
399
|
+
if (!ensured) return { ok: false, error: 'repairAgentLayer returned null' };
|
|
400
|
+
|
|
401
|
+
// Persist agent_id back to config if it was missing
|
|
402
|
+
if (!project.agent_id) {
|
|
403
|
+
cfg.projects[projectKey] = { ...cfg.projects[projectKey], agent_id: ensured.agentId };
|
|
404
|
+
writeConfigSafe(cfg);
|
|
405
|
+
backupConfig();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
ok: true,
|
|
410
|
+
data: {
|
|
411
|
+
projectKey,
|
|
412
|
+
agentId: ensured.agentId,
|
|
413
|
+
views: ensured.views
|
|
414
|
+
? Object.fromEntries(Object.entries(ensured.views).map(([k, v]) => [k, v.mode]))
|
|
415
|
+
: null,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
} catch (e) {
|
|
419
|
+
return { ok: false, error: e.message };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Refresh memory-snapshot.md from fresh session+fact data.
|
|
425
|
+
* Called by the engine after first-message of a session; also callable directly.
|
|
426
|
+
*/
|
|
427
|
+
async function updateMemorySnapshot(agentId, sessions = [], facts = []) {
|
|
428
|
+
try {
|
|
429
|
+
if (!agentId) return { ok: false, error: 'agentId is required' };
|
|
430
|
+
const content = buildMemorySnapshotContent(sessions, facts);
|
|
431
|
+
const ok = refreshMemorySnapshot(agentId, content, HOME);
|
|
432
|
+
return { ok, error: ok ? null : 'agent directory not found or not yet created' };
|
|
433
|
+
} catch (e) {
|
|
434
|
+
return { ok: false, error: e.message };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
305
438
|
return {
|
|
306
439
|
bindAgentToChat,
|
|
307
440
|
createNewWorkspaceAgent,
|
|
308
441
|
editAgentRoleDefinition,
|
|
309
442
|
listAllAgents,
|
|
310
443
|
unbindCurrentAgent,
|
|
444
|
+
repairAgentSoul,
|
|
445
|
+
updateMemorySnapshot,
|
|
311
446
|
};
|
|
312
447
|
}
|
|
313
448
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
function createCheckpointUtils(deps) {
|
|
4
|
-
const { execSync, path, log } = deps;
|
|
4
|
+
const { execSync, execFile, path, log } = deps;
|
|
5
|
+
const { promisify } = require('util');
|
|
6
|
+
const execFileAsync = execFile ? promisify(execFile) : null;
|
|
5
7
|
|
|
6
8
|
const CHECKPOINT_PREFIX = '[metame-checkpoint]';
|
|
7
9
|
const MAX_CHECKPOINTS = 20;
|
|
@@ -35,19 +37,22 @@ function createCheckpointUtils(deps) {
|
|
|
35
37
|
return message.replace(CHECKPOINT_PREFIX, '').trim();
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// On Windows, git.exe is a console app — windowsHide:true prevents flash
|
|
41
|
+
const WIN_HIDE = process.platform === 'win32' ? { windowsHide: true } : {};
|
|
42
|
+
|
|
38
43
|
function gitCheckpoint(cwd, label) {
|
|
39
44
|
try {
|
|
40
|
-
execSync('git rev-parse --is-inside-work-tree', { cwd, stdio: 'ignore' });
|
|
41
|
-
execSync('git add -A', { cwd, stdio: 'ignore', timeout: 5000 });
|
|
42
|
-
const status = execSync('git status --porcelain', { cwd, encoding: 'utf8', timeout: 5000 }).trim();
|
|
45
|
+
execSync('git rev-parse --is-inside-work-tree', { cwd, stdio: 'ignore', ...WIN_HIDE });
|
|
46
|
+
execSync('git add -A', { cwd, stdio: 'ignore', timeout: 5000, ...WIN_HIDE });
|
|
47
|
+
const status = execSync('git status --porcelain', { cwd, encoding: 'utf8', timeout: 5000, ...WIN_HIDE }).trim();
|
|
43
48
|
if (!status) return null;
|
|
44
49
|
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
45
50
|
const safeLabel = label
|
|
46
51
|
? ' Before: ' + label.replace(/["\n\r]/g, ' ').slice(0, 60).trim()
|
|
47
52
|
: '';
|
|
48
53
|
const msg = `${CHECKPOINT_PREFIX}${safeLabel} (${ts})`;
|
|
49
|
-
execSync(`git commit -m "${msg}" --no-verify`, { cwd, stdio: 'ignore', timeout: 10000 });
|
|
50
|
-
const hash = execSync('git rev-parse HEAD', { cwd, encoding: 'utf8', timeout: 3000 }).trim();
|
|
54
|
+
execSync(`git commit -m "${msg}" --no-verify`, { cwd, stdio: 'ignore', timeout: 10000, ...WIN_HIDE });
|
|
55
|
+
const hash = execSync('git rev-parse HEAD', { cwd, encoding: 'utf8', timeout: 3000, ...WIN_HIDE }).trim();
|
|
51
56
|
log('INFO', `Git checkpoint: ${hash.slice(0, 8)} in ${path.basename(cwd)}${safeLabel}`);
|
|
52
57
|
return hash;
|
|
53
58
|
} catch {
|
|
@@ -55,11 +60,34 @@ function createCheckpointUtils(deps) {
|
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
// Async version: runs git commands without blocking the event loop.
|
|
64
|
+
// Call fire-and-forget before spawning Claude; completes well before Claude's first file write.
|
|
65
|
+
async function gitCheckpointAsync(cwd, label) {
|
|
66
|
+
if (!execFileAsync) return gitCheckpoint(cwd, label); // fallback
|
|
67
|
+
try {
|
|
68
|
+
await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], { cwd, timeout: 3000, ...WIN_HIDE });
|
|
69
|
+
await execFileAsync('git', ['add', '-A'], { cwd, timeout: 5000, ...WIN_HIDE });
|
|
70
|
+
const { stdout: status } = await execFileAsync('git', ['status', '--porcelain'], { cwd, encoding: 'utf8', timeout: 5000, ...WIN_HIDE });
|
|
71
|
+
if (!status.trim()) return null;
|
|
72
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
73
|
+
const safeLabel = label
|
|
74
|
+
? ' Before: ' + label.replace(/["\n\r]/g, ' ').slice(0, 60).trim()
|
|
75
|
+
: '';
|
|
76
|
+
const msg = `${CHECKPOINT_PREFIX}${safeLabel} (${ts})`;
|
|
77
|
+
await execFileAsync('git', ['commit', '-m', msg, '--no-verify'], { cwd, timeout: 10000, ...WIN_HIDE });
|
|
78
|
+
const { stdout: hash } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd, encoding: 'utf8', timeout: 3000, ...WIN_HIDE });
|
|
79
|
+
log('INFO', `Git checkpoint: ${hash.trim().slice(0, 8)} in ${path.basename(cwd)}${safeLabel}`);
|
|
80
|
+
return hash.trim();
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
58
86
|
function listCheckpoints(cwd, limit = 20) {
|
|
59
87
|
try {
|
|
60
88
|
const raw = execSync(
|
|
61
89
|
`git log --fixed-strings --oneline --all --grep="${CHECKPOINT_PREFIX}" -n ${limit} --format="%H %s"`,
|
|
62
|
-
{ cwd, encoding: 'utf8', timeout: 5000 }
|
|
90
|
+
{ cwd, encoding: 'utf8', timeout: 5000, ...WIN_HIDE }
|
|
63
91
|
).trim();
|
|
64
92
|
if (!raw) return [];
|
|
65
93
|
return raw.split('\n').map(line => {
|
|
@@ -81,6 +109,7 @@ function createCheckpointUtils(deps) {
|
|
|
81
109
|
cpExtractTimestamp,
|
|
82
110
|
cpDisplayLabel,
|
|
83
111
|
gitCheckpoint,
|
|
112
|
+
gitCheckpointAsync,
|
|
84
113
|
listCheckpoints,
|
|
85
114
|
cleanupCheckpoints,
|
|
86
115
|
};
|