metame-cli 1.5.3 → 1.5.5
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 -18
- package/index.js +352 -79
- package/package.json +2 -2
- package/scripts/agent-layer.js +4 -2
- package/scripts/bin/dispatch_to +178 -90
- package/scripts/daemon-admin-commands.js +353 -105
- package/scripts/daemon-agent-commands.js +434 -66
- package/scripts/daemon-bridges.js +477 -68
- package/scripts/daemon-claude-engine.js +1267 -674
- package/scripts/daemon-command-router.js +205 -27
- package/scripts/daemon-command-session-route.js +118 -0
- package/scripts/daemon-default.yaml +7 -0
- package/scripts/daemon-engine-runtime.js +96 -20
- package/scripts/daemon-exec-commands.js +108 -49
- package/scripts/daemon-file-browser.js +64 -7
- package/scripts/daemon-notify.js +18 -4
- package/scripts/daemon-ops-commands.js +16 -2
- package/scripts/daemon-remote-dispatch.js +55 -1
- package/scripts/daemon-runtime-lifecycle.js +87 -0
- package/scripts/daemon-session-commands.js +102 -45
- package/scripts/daemon-session-store.js +497 -66
- package/scripts/daemon-siri-bridge.js +234 -0
- package/scripts/daemon-siri-imessage.js +209 -0
- package/scripts/daemon-task-scheduler.js +10 -2
- package/scripts/daemon.js +697 -179
- package/scripts/daemon.yaml +7 -0
- package/scripts/docs/agent-guide.md +36 -3
- package/scripts/docs/hook-config.md +134 -0
- package/scripts/docs/maintenance-manual.md +162 -5
- package/scripts/docs/pointer-map.md +60 -5
- package/scripts/feishu-adapter.js +7 -15
- package/scripts/hooks/doc-router.js +29 -0
- package/scripts/hooks/hook-utils.js +61 -0
- package/scripts/hooks/intent-doc-router.js +54 -0
- package/scripts/hooks/intent-engine.js +72 -0
- package/scripts/hooks/intent-file-transfer.js +51 -0
- package/scripts/hooks/intent-memory-recall.js +35 -0
- package/scripts/hooks/intent-ops-assist.js +54 -0
- package/scripts/hooks/intent-task-create.js +35 -0
- package/scripts/hooks/intent-team-dispatch.js +106 -0
- package/scripts/hooks/team-context.js +143 -0
- package/scripts/intent-registry.js +59 -0
- package/scripts/memory-extract.js +59 -0
- package/scripts/memory-nightly-reflect.js +109 -43
- package/scripts/memory.js +55 -17
- package/scripts/mentor-engine.js +6 -0
- package/scripts/schema.js +1 -0
- package/scripts/self-reflect.js +110 -12
- package/scripts/session-analytics.js +160 -0
- package/scripts/signal-capture.js +1 -1
- package/scripts/team-dispatch.js +315 -0
|
@@ -14,7 +14,6 @@ function createSessionCommandHandler(deps) {
|
|
|
14
14
|
sendBrowse,
|
|
15
15
|
sendDirPicker,
|
|
16
16
|
createSession,
|
|
17
|
-
getSessionForEngine,
|
|
18
17
|
getCachedFile,
|
|
19
18
|
getSession,
|
|
20
19
|
listRecentSessions,
|
|
@@ -26,7 +25,6 @@ function createSessionCommandHandler(deps) {
|
|
|
26
25
|
loadSessionTags,
|
|
27
26
|
sessionRichLabel,
|
|
28
27
|
buildSessionCardElements,
|
|
29
|
-
sessionLabel,
|
|
30
28
|
getDefaultEngine = () => 'claude',
|
|
31
29
|
} = deps;
|
|
32
30
|
|
|
@@ -35,23 +33,71 @@ function createSessionCommandHandler(deps) {
|
|
|
35
33
|
return n === 'codex' ? 'codex' : getDefaultEngine();
|
|
36
34
|
}
|
|
37
35
|
|
|
36
|
+
function inferStoredEngine(rawSession) {
|
|
37
|
+
if (!rawSession || typeof rawSession !== 'object') return getDefaultEngine();
|
|
38
|
+
if (rawSession.engine) return normalizeEngineName(rawSession.engine);
|
|
39
|
+
const slots = rawSession.engines && typeof rawSession.engines === 'object' ? rawSession.engines : null;
|
|
40
|
+
if (!slots) return getDefaultEngine();
|
|
41
|
+
const started = Object.entries(slots).find(([, slot]) => slot && slot.started);
|
|
42
|
+
if (started) return normalizeEngineName(started[0]);
|
|
43
|
+
const available = Object.keys(slots);
|
|
44
|
+
return available.length === 1 ? normalizeEngineName(available[0]) : getDefaultEngine();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildBoundSessionChatId(projectKey) {
|
|
48
|
+
const key = String(projectKey || '').trim();
|
|
49
|
+
return key ? `_bound_${key}` : '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getSessionRoute(chatId) {
|
|
53
|
+
const cfg = loadConfig();
|
|
54
|
+
const state = loadState();
|
|
55
|
+
const chatKey = String(chatId);
|
|
56
|
+
const agentMap = { ...(cfg.telegram ? cfg.telegram.chat_agent_map : {}), ...(cfg.feishu ? cfg.feishu.chat_agent_map : {}) };
|
|
57
|
+
const boundKey = agentMap[chatKey] || null;
|
|
58
|
+
const boundProj = boundKey && cfg.projects ? cfg.projects[boundKey] : null;
|
|
59
|
+
const stickyKey = state && state.team_sticky ? state.team_sticky[chatKey] : null;
|
|
60
|
+
const stickyMember = stickyKey && boundProj && Array.isArray(boundProj.team)
|
|
61
|
+
? boundProj.team.find((m) => m && m.key === stickyKey)
|
|
62
|
+
: null;
|
|
63
|
+
|
|
64
|
+
if (stickyMember) {
|
|
65
|
+
return {
|
|
66
|
+
sessionChatId: `_agent_${stickyMember.key}`,
|
|
67
|
+
cwd: stickyMember.cwd ? normalizeCwd(stickyMember.cwd) : (boundProj && boundProj.cwd ? normalizeCwd(boundProj.cwd) : null),
|
|
68
|
+
engine: normalizeEngineName(stickyMember.engine || (boundProj && boundProj.engine)),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (boundProj) {
|
|
73
|
+
return {
|
|
74
|
+
sessionChatId: buildBoundSessionChatId(boundKey),
|
|
75
|
+
cwd: boundProj.cwd ? normalizeCwd(boundProj.cwd) : null,
|
|
76
|
+
engine: normalizeEngineName(boundProj.engine),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rawSession = getSession(chatId);
|
|
81
|
+
return {
|
|
82
|
+
sessionChatId: String(chatId),
|
|
83
|
+
cwd: rawSession && rawSession.cwd ? normalizeCwd(rawSession.cwd) : null,
|
|
84
|
+
engine: inferStoredEngine(rawSession),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getCurrentEngine(chatId) {
|
|
89
|
+
return getSessionRoute(chatId).engine;
|
|
90
|
+
}
|
|
91
|
+
|
|
38
92
|
function getBoundCwd(chatId) {
|
|
39
93
|
try {
|
|
40
|
-
|
|
41
|
-
const chatAgentMap = { ...(cfg.telegram ? cfg.telegram.chat_agent_map : {}), ...(cfg.feishu ? cfg.feishu.chat_agent_map : {}) };
|
|
42
|
-
const mappedKey = chatAgentMap[String(chatId)];
|
|
43
|
-
const proj = mappedKey && cfg.projects ? cfg.projects[mappedKey] : null;
|
|
44
|
-
return (proj && proj.cwd) ? normalizeCwd(proj.cwd) : null;
|
|
94
|
+
return getSessionRoute(chatId).cwd;
|
|
45
95
|
} catch { return null; }
|
|
46
96
|
}
|
|
47
97
|
|
|
48
98
|
// Write per-engine session slot, preserving cwd and other engine slots.
|
|
49
99
|
function attachEngineSession(state, chatId, engine, sessionId, cwd) {
|
|
50
|
-
|
|
51
|
-
const cfg = loadConfig();
|
|
52
|
-
const agentMap = { ...(cfg.telegram ? cfg.telegram.chat_agent_map : {}), ...(cfg.feishu ? cfg.feishu.chat_agent_map : {}) };
|
|
53
|
-
const boundKey = agentMap[String(chatId)];
|
|
54
|
-
const effectiveId = boundKey ? `_agent_${boundKey}` : String(chatId);
|
|
100
|
+
const effectiveId = getSessionRoute(chatId).sessionChatId;
|
|
55
101
|
const existing = state.sessions[effectiveId] || {};
|
|
56
102
|
const existingEngines = existing.engines || {};
|
|
57
103
|
state.sessions[effectiveId] = {
|
|
@@ -71,6 +117,18 @@ function createSessionCommandHandler(deps) {
|
|
|
71
117
|
return null;
|
|
72
118
|
}
|
|
73
119
|
|
|
120
|
+
async function autoCreateSessionWhenEmpty(bot, chatId, cwd, engine) {
|
|
121
|
+
const resolvedCwd = cwd ? normalizeCwd(cwd) : null;
|
|
122
|
+
if (!resolvedCwd || !fs.existsSync(resolvedCwd)) {
|
|
123
|
+
await bot.sendMessage(chatId, `No sessions found${resolvedCwd ? ' in ' + path.basename(resolvedCwd) : ''}. Try /new first.`);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const route = getSessionRoute(chatId);
|
|
127
|
+
const session = createSession(route.sessionChatId, resolvedCwd, '', engine || getDefaultEngine());
|
|
128
|
+
await bot.sendMessage(chatId, `📁 ${path.basename(resolvedCwd)}\n✅ 已自动创建新会话\nWorkdir: ${session.cwd}`);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
74
132
|
async function handleSessionCommand(ctx) {
|
|
75
133
|
const { bot, chatId, text } = ctx;
|
|
76
134
|
|
|
@@ -98,13 +156,9 @@ function createSessionCommandHandler(deps) {
|
|
|
98
156
|
const arg = text.slice(4).trim();
|
|
99
157
|
if (!arg) {
|
|
100
158
|
// In a dedicated agent group, use the agent's bound cwd directly
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const boundProj = boundKey && newCfg.projects && newCfg.projects[boundKey];
|
|
105
|
-
if (boundProj && boundProj.cwd) {
|
|
106
|
-
const boundCwd = normalizeCwd(boundProj.cwd);
|
|
107
|
-
const session = createSession(chatId, boundCwd, '', normalizeEngineName(boundProj.engine));
|
|
159
|
+
const route = getSessionRoute(chatId);
|
|
160
|
+
if (route.cwd) {
|
|
161
|
+
const session = createSession(route.sessionChatId, route.cwd, '', route.engine);
|
|
108
162
|
await bot.sendMessage(chatId, `✅ 新会话已创建\nWorkdir: ${session.cwd}`);
|
|
109
163
|
return true;
|
|
110
164
|
}
|
|
@@ -136,7 +190,7 @@ function createSessionCommandHandler(deps) {
|
|
|
136
190
|
const mappedProjForEngine = mappedKeyForEngine && cfgForEngine.projects ? cfgForEngine.projects[mappedKeyForEngine] : null;
|
|
137
191
|
const currentEngine = getDefaultEngine();
|
|
138
192
|
const sessionEngine = normalizeEngineName((mappedProjForEngine && mappedProjForEngine.engine) || currentEngine);
|
|
139
|
-
const session = createSession(chatId, dirPath, sessionName || '', sessionEngine);
|
|
193
|
+
const session = createSession(getSessionRoute(chatId).sessionChatId, dirPath, sessionName || '', sessionEngine);
|
|
140
194
|
const label = sessionName ? `[${sessionName}]` : '';
|
|
141
195
|
await bot.sendMessage(chatId, `New session ${label}\nWorkdir: ${session.cwd}`);
|
|
142
196
|
return true;
|
|
@@ -174,17 +228,19 @@ function createSessionCommandHandler(deps) {
|
|
|
174
228
|
|
|
175
229
|
// /last — smart resume: prefer current cwd, then most recent globally
|
|
176
230
|
if (text === '/last') {
|
|
177
|
-
const
|
|
231
|
+
const currentEngine = getCurrentEngine(chatId);
|
|
232
|
+
const route = getSessionRoute(chatId);
|
|
233
|
+
const curSession = getSession(route.sessionChatId) || getSession(chatId);
|
|
178
234
|
const curCwd = curSession ? curSession.cwd : null;
|
|
179
235
|
|
|
180
236
|
// Strategy: try current cwd first, then fall back to global
|
|
181
237
|
let s = null;
|
|
182
238
|
if (curCwd) {
|
|
183
|
-
const cwdSessions = listRecentSessions(1, curCwd);
|
|
239
|
+
const cwdSessions = listRecentSessions(1, curCwd, currentEngine);
|
|
184
240
|
if (cwdSessions.length > 0) s = cwdSessions[0];
|
|
185
241
|
}
|
|
186
242
|
if (!s) {
|
|
187
|
-
const globalSessions = listRecentSessions(1);
|
|
243
|
+
const globalSessions = listRecentSessions(1, null, currentEngine);
|
|
188
244
|
if (globalSessions.length > 0) s = globalSessions[0];
|
|
189
245
|
}
|
|
190
246
|
|
|
@@ -192,7 +248,7 @@ function createSessionCommandHandler(deps) {
|
|
|
192
248
|
// Last resort: use __continue__ to resume whatever Claude thinks is last
|
|
193
249
|
const state2 = loadState();
|
|
194
250
|
const cfgForEngine = loadConfig();
|
|
195
|
-
const engineByCwd = inferEngineByCwd(cfgForEngine, curCwd || HOME) ||
|
|
251
|
+
const engineByCwd = inferEngineByCwd(cfgForEngine, curCwd || HOME) || currentEngine;
|
|
196
252
|
attachEngineSession(state2, chatId, engineByCwd, '__continue__', curCwd || HOME);
|
|
197
253
|
saveState(state2);
|
|
198
254
|
await bot.sendMessage(chatId, `⚡ Resuming last session in ${path.basename(curCwd || HOME)}`);
|
|
@@ -201,7 +257,7 @@ function createSessionCommandHandler(deps) {
|
|
|
201
257
|
|
|
202
258
|
const state2 = loadState();
|
|
203
259
|
const cfgForEngine = loadConfig();
|
|
204
|
-
const engineByCwd = inferEngineByCwd(cfgForEngine, s.projectPath || HOME) || getDefaultEngine();
|
|
260
|
+
const engineByCwd = normalizeEngineName(s.engine) || inferEngineByCwd(cfgForEngine, s.projectPath || HOME) || getDefaultEngine();
|
|
205
261
|
attachEngineSession(state2, chatId, engineByCwd, s.sessionId, s.projectPath || HOME);
|
|
206
262
|
saveState(state2);
|
|
207
263
|
// Display: name/summary + id on separate lines
|
|
@@ -267,13 +323,14 @@ function createSessionCommandHandler(deps) {
|
|
|
267
323
|
|
|
268
324
|
// /sessions — compact list, tap to see details, then tap to switch
|
|
269
325
|
if (text === '/sessions') {
|
|
270
|
-
const
|
|
271
|
-
const codexLimitTip = '⚠️ 当前为 Codex 会话:`/sessions` 列表暂仅展示 Claude 本地会话,Codex 会话暂不可见。';
|
|
272
|
-
const allSessions = listRecentSessions(15, getBoundCwd(chatId));
|
|
326
|
+
const allSessions = listRecentSessions(15, getBoundCwd(chatId), getCurrentEngine(chatId));
|
|
273
327
|
if (allSessions.length === 0) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
328
|
+
return autoCreateSessionWhenEmpty(
|
|
329
|
+
bot,
|
|
330
|
+
chatId,
|
|
331
|
+
getBoundCwd(chatId) || (getSession(chatId) && getSession(chatId).cwd),
|
|
332
|
+
getCurrentEngine(chatId)
|
|
333
|
+
);
|
|
277
334
|
}
|
|
278
335
|
if (bot.sendButtons) {
|
|
279
336
|
await bot.sendRawCard(chatId, '📋 Recent Sessions', buildSessionCardElements(allSessions));
|
|
@@ -283,19 +340,15 @@ function createSessionCommandHandler(deps) {
|
|
|
283
340
|
allSessions.forEach((s, i) => {
|
|
284
341
|
msg += sessionRichLabel(s, i + 1, _tags1) + '\n';
|
|
285
342
|
});
|
|
286
|
-
if (currentEngine === 'codex') msg += `\n${codexLimitTip}\n`;
|
|
287
343
|
await bot.sendMessage(chatId, msg);
|
|
288
344
|
}
|
|
289
|
-
if (bot.sendButtons && currentEngine === 'codex') {
|
|
290
|
-
await bot.sendMessage(chatId, codexLimitTip);
|
|
291
|
-
}
|
|
292
345
|
return true;
|
|
293
346
|
}
|
|
294
347
|
|
|
295
348
|
// /sess <id> — show session detail card with switch button
|
|
296
349
|
if (text.startsWith('/sess ')) {
|
|
297
350
|
const sid = text.slice(6).trim();
|
|
298
|
-
const allSessions = listRecentSessions(50);
|
|
351
|
+
const allSessions = listRecentSessions(50, null, getCurrentEngine(chatId));
|
|
299
352
|
const s = allSessions.find(x => x.sessionId === sid || x.sessionId.startsWith(sid));
|
|
300
353
|
if (!s) {
|
|
301
354
|
await bot.sendMessage(chatId, `Session not found: ${sid.slice(0, 8)}`);
|
|
@@ -373,9 +426,10 @@ function createSessionCommandHandler(deps) {
|
|
|
373
426
|
}
|
|
374
427
|
// /cd last — sync to computer: switch to most recent session AND its directory
|
|
375
428
|
if (newCwd === 'last') {
|
|
376
|
-
const
|
|
429
|
+
const route = getSessionRoute(chatId);
|
|
430
|
+
const currentSession = getSession(route.sessionChatId) || getSession(chatId);
|
|
377
431
|
const excludeId = currentSession?.id;
|
|
378
|
-
const recent = listRecentSessions(10);
|
|
432
|
+
const recent = listRecentSessions(10, null, getCurrentEngine(chatId));
|
|
379
433
|
const filtered = excludeId ? recent.filter(s => s.sessionId !== excludeId) : recent;
|
|
380
434
|
|
|
381
435
|
// For bound chats, prefer sessions from the same project to avoid
|
|
@@ -416,7 +470,7 @@ function createSessionCommandHandler(deps) {
|
|
|
416
470
|
}
|
|
417
471
|
const state2 = loadState();
|
|
418
472
|
// Try to find existing session in this directory
|
|
419
|
-
const recentInDir = listRecentSessions(1, newCwd);
|
|
473
|
+
const recentInDir = listRecentSessions(1, newCwd, getCurrentEngine(chatId));
|
|
420
474
|
if (recentInDir.length > 0 && recentInDir[0].sessionId) {
|
|
421
475
|
// Attach to existing session in this directory
|
|
422
476
|
const target = recentInDir[0];
|
|
@@ -426,14 +480,14 @@ function createSessionCommandHandler(deps) {
|
|
|
426
480
|
saveState(state2);
|
|
427
481
|
const label = target.customTitle || target.summary?.slice(0, 30) || target.sessionId.slice(0, 8);
|
|
428
482
|
await bot.sendMessage(chatId, `📁 ${path.basename(newCwd)}\n🔄 Attached: ${label}`);
|
|
429
|
-
} else if (!state2.sessions[chatId]) {
|
|
483
|
+
} else if (!state2.sessions[getSessionRoute(chatId).sessionChatId]) {
|
|
430
484
|
const cfgForEngine = loadConfig();
|
|
431
485
|
const engineByCwd = inferEngineByCwd(cfgForEngine, newCwd);
|
|
432
486
|
const currentEngine = getDefaultEngine();
|
|
433
|
-
createSession(chatId, newCwd, '', engineByCwd || currentEngine);
|
|
487
|
+
createSession(getSessionRoute(chatId).sessionChatId, newCwd, '', engineByCwd || currentEngine);
|
|
434
488
|
await bot.sendMessage(chatId, `📁 ${path.basename(newCwd)} (new session)`);
|
|
435
489
|
} else {
|
|
436
|
-
state2.sessions[chatId].cwd = newCwd;
|
|
490
|
+
state2.sessions[getSessionRoute(chatId).sessionChatId].cwd = newCwd;
|
|
437
491
|
saveState(state2);
|
|
438
492
|
await bot.sendMessage(chatId, `📁 ${path.basename(newCwd)}`);
|
|
439
493
|
}
|
|
@@ -443,7 +497,8 @@ function createSessionCommandHandler(deps) {
|
|
|
443
497
|
|
|
444
498
|
// /list [subdir|glob|fullpath] — list files (zero token, daemon-only)
|
|
445
499
|
if (text === '/list' || text.startsWith('/list ')) {
|
|
446
|
-
const
|
|
500
|
+
const route = getSessionRoute(chatId);
|
|
501
|
+
const session = getSession(route.sessionChatId) || getSession(chatId);
|
|
447
502
|
const cwd = session?.cwd || HOME;
|
|
448
503
|
const arg = text.slice(5).trim();
|
|
449
504
|
// If arg is an absolute or ~ path, list that directly
|
|
@@ -466,7 +521,8 @@ function createSessionCommandHandler(deps) {
|
|
|
466
521
|
await bot.sendMessage(chatId, 'Usage: /name <session name>');
|
|
467
522
|
return true;
|
|
468
523
|
}
|
|
469
|
-
const
|
|
524
|
+
const route = getSessionRoute(chatId);
|
|
525
|
+
const session = getSession(route.sessionChatId) || getSession(chatId);
|
|
470
526
|
if (!session) {
|
|
471
527
|
await bot.sendMessage(chatId, 'No active session. Start one first.');
|
|
472
528
|
return true;
|
|
@@ -482,7 +538,8 @@ function createSessionCommandHandler(deps) {
|
|
|
482
538
|
}
|
|
483
539
|
|
|
484
540
|
if (text === '/session') {
|
|
485
|
-
const
|
|
541
|
+
const route = getSessionRoute(chatId);
|
|
542
|
+
const session = getSession(route.sessionChatId) || getSession(chatId);
|
|
486
543
|
if (!session) {
|
|
487
544
|
await bot.sendMessage(chatId, 'No active session. Send any message to start one.');
|
|
488
545
|
} else {
|