metame-cli 1.5.4 → 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 +6 -1
- package/index.js +277 -55
- package/package.json +2 -2
- package/scripts/agent-layer.js +4 -2
- package/scripts/bin/dispatch_to +17 -5
- package/scripts/daemon-admin-commands.js +264 -62
- package/scripts/daemon-agent-commands.js +188 -66
- package/scripts/daemon-bridges.js +447 -48
- package/scripts/daemon-claude-engine.js +650 -103
- package/scripts/daemon-command-router.js +134 -27
- package/scripts/daemon-command-session-route.js +118 -0
- package/scripts/daemon-default.yaml +2 -0
- package/scripts/daemon-engine-runtime.js +96 -20
- package/scripts/daemon-exec-commands.js +106 -50
- package/scripts/daemon-file-browser.js +63 -7
- package/scripts/daemon-notify.js +18 -4
- package/scripts/daemon-ops-commands.js +16 -2
- package/scripts/daemon-remote-dispatch.js +34 -2
- 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 +610 -181
- package/scripts/docs/hook-config.md +7 -4
- package/scripts/docs/maintenance-manual.md +8 -1
- package/scripts/feishu-adapter.js +7 -15
- package/scripts/hooks/doc-router.js +29 -0
- package/scripts/hooks/intent-doc-router.js +54 -0
- package/scripts/hooks/intent-engine.js +9 -40
- package/scripts/intent-registry.js +59 -0
- package/scripts/memory-extract.js +59 -0
- 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 +150 -11
- package/scripts/hooks/intent-agent-manage.js +0 -50
- package/scripts/hooks/intent-hook-config.js +0 -28
|
@@ -13,6 +13,7 @@ function createAgentCommandHandler(deps) {
|
|
|
13
13
|
sendBrowse,
|
|
14
14
|
sendDirPicker,
|
|
15
15
|
getSession,
|
|
16
|
+
getSessionForEngine,
|
|
16
17
|
listRecentSessions,
|
|
17
18
|
buildSessionCardElements,
|
|
18
19
|
sessionLabel,
|
|
@@ -40,6 +41,103 @@ function createAgentCommandHandler(deps) {
|
|
|
40
41
|
return n === 'codex' ? 'codex' : getDefaultEngine();
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function inferStoredEngine(rawSession) {
|
|
45
|
+
if (!rawSession || typeof rawSession !== 'object') return getDefaultEngine();
|
|
46
|
+
if (rawSession.engine) return normalizeEngineName(rawSession.engine);
|
|
47
|
+
const slots = rawSession.engines && typeof rawSession.engines === 'object' ? rawSession.engines : null;
|
|
48
|
+
if (!slots) return getDefaultEngine();
|
|
49
|
+
const started = Object.entries(slots).find(([, slot]) => slot && slot.started);
|
|
50
|
+
if (started) return normalizeEngineName(started[0]);
|
|
51
|
+
const available = Object.keys(slots);
|
|
52
|
+
return available.length === 1 ? normalizeEngineName(available[0]) : getDefaultEngine();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildBoundSessionChatId(projectKey) {
|
|
56
|
+
const key = String(projectKey || '').trim();
|
|
57
|
+
return key ? `_bound_${key}` : '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getSessionRoute(chatId) {
|
|
61
|
+
const cfg = loadConfig();
|
|
62
|
+
const state = loadState();
|
|
63
|
+
const chatKey = String(chatId);
|
|
64
|
+
const agentMap = { ...(cfg.telegram ? cfg.telegram.chat_agent_map : {}), ...(cfg.feishu ? cfg.feishu.chat_agent_map : {}) };
|
|
65
|
+
const boundKey = agentMap[chatKey] || null;
|
|
66
|
+
const boundProj = boundKey && cfg.projects ? cfg.projects[boundKey] : null;
|
|
67
|
+
const stickyKey = state && state.team_sticky ? state.team_sticky[chatKey] : null;
|
|
68
|
+
const stickyMember = stickyKey && boundProj && Array.isArray(boundProj.team)
|
|
69
|
+
? boundProj.team.find((m) => m && m.key === stickyKey)
|
|
70
|
+
: null;
|
|
71
|
+
|
|
72
|
+
if (stickyMember) {
|
|
73
|
+
return {
|
|
74
|
+
sessionChatId: `_agent_${stickyMember.key}`,
|
|
75
|
+
cwd: stickyMember.cwd ? normalizeCwd(stickyMember.cwd) : (boundProj && boundProj.cwd ? normalizeCwd(boundProj.cwd) : null),
|
|
76
|
+
engine: normalizeEngineName(stickyMember.engine || (boundProj && boundProj.engine)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (boundProj) {
|
|
81
|
+
return {
|
|
82
|
+
sessionChatId: buildBoundSessionChatId(boundKey),
|
|
83
|
+
cwd: boundProj.cwd ? normalizeCwd(boundProj.cwd) : null,
|
|
84
|
+
engine: normalizeEngineName(boundProj.engine),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const rawSession = getSession(chatId);
|
|
89
|
+
return {
|
|
90
|
+
sessionChatId: String(chatId),
|
|
91
|
+
cwd: rawSession && rawSession.cwd ? normalizeCwd(rawSession.cwd) : null,
|
|
92
|
+
engine: inferStoredEngine(rawSession),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getCurrentEngine(chatId) {
|
|
97
|
+
return getSessionRoute(chatId).engine;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getLogicalSessionForRoute(route) {
|
|
101
|
+
if (!route || !route.sessionChatId) return null;
|
|
102
|
+
if (typeof getSessionForEngine === 'function') {
|
|
103
|
+
const engineSession = getSessionForEngine(route.sessionChatId, route.engine);
|
|
104
|
+
if (engineSession && engineSession.id) return engineSession;
|
|
105
|
+
}
|
|
106
|
+
const raw = getSession(route.sessionChatId);
|
|
107
|
+
if (!raw) return null;
|
|
108
|
+
const slot = raw.engines && raw.engines[route.engine];
|
|
109
|
+
if (slot && slot.id) return { cwd: raw.cwd, engine: route.engine, ...slot };
|
|
110
|
+
if (raw.id) return { cwd: raw.cwd, engine: route.engine, id: raw.id, started: !!raw.started };
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildResumeChoices({ recentSessions, currentLogical, curCwd, currentEngine, isLogicalRoute }) {
|
|
115
|
+
const items = [];
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
if (
|
|
118
|
+
isLogicalRoute
|
|
119
|
+
&& currentLogical
|
|
120
|
+
&& currentLogical.id
|
|
121
|
+
&& currentLogical.started
|
|
122
|
+
) {
|
|
123
|
+
items.push({
|
|
124
|
+
sessionId: currentLogical.id,
|
|
125
|
+
projectPath: currentLogical.cwd || curCwd || HOME,
|
|
126
|
+
engine: currentEngine,
|
|
127
|
+
customTitle: '当前会话',
|
|
128
|
+
summary: '优先续接当前智能体会话',
|
|
129
|
+
});
|
|
130
|
+
seen.add(String(currentLogical.id));
|
|
131
|
+
}
|
|
132
|
+
for (const session of recentSessions || []) {
|
|
133
|
+
const key = String(session && session.sessionId || '');
|
|
134
|
+
if (!key || seen.has(key)) continue;
|
|
135
|
+
items.push(session);
|
|
136
|
+
seen.add(key);
|
|
137
|
+
}
|
|
138
|
+
return items;
|
|
139
|
+
}
|
|
140
|
+
|
|
43
141
|
function inferEngineByCwd(cfg, cwd) {
|
|
44
142
|
if (!cfg || !cfg.projects || !cwd) return null;
|
|
45
143
|
const targetCwd = normalizeCwd(cwd);
|
|
@@ -52,18 +150,20 @@ function createAgentCommandHandler(deps) {
|
|
|
52
150
|
return null;
|
|
53
151
|
}
|
|
54
152
|
|
|
153
|
+
async function autoCreateSessionOnEmptyResume(bot, chatId, cwd, engine) {
|
|
154
|
+
const resolvedCwd = cwd ? normalizeCwd(cwd) : null;
|
|
155
|
+
if (!resolvedCwd || !fs.existsSync(resolvedCwd) || typeof attachOrCreateSession !== 'function') {
|
|
156
|
+
await bot.sendMessage(chatId, `No sessions found${resolvedCwd ? ' in ' + path.basename(resolvedCwd) : ''}. Try /new first.`);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
attachOrCreateSession(getSessionRoute(chatId).sessionChatId, resolvedCwd, '', engine || getDefaultEngine());
|
|
160
|
+
await bot.sendMessage(chatId, `📁 ${path.basename(resolvedCwd)}\n✅ 已自动创建新会话`);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
55
164
|
// Pending activations have no TTL — they persist until consumed.
|
|
56
165
|
// The creating chatId is stored to prevent self-activation.
|
|
57
166
|
|
|
58
|
-
function storePendingActivation(agentKey, agentName, cwd, createdByChatId) {
|
|
59
|
-
if (!pendingActivations) return;
|
|
60
|
-
pendingActivations.set(agentKey, {
|
|
61
|
-
agentKey, agentName, cwd,
|
|
62
|
-
createdByChatId: String(createdByChatId),
|
|
63
|
-
createdAt: Date.now(),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
167
|
// Returns the latest pending activation, excluding the creating chat
|
|
68
168
|
function getLatestActivationForChat(chatId) {
|
|
69
169
|
if (!pendingActivations || pendingActivations.size === 0) return null;
|
|
@@ -188,25 +288,6 @@ function createAgentCommandHandler(deps) {
|
|
|
188
288
|
return { ok: true, data: legacy };
|
|
189
289
|
}
|
|
190
290
|
|
|
191
|
-
async function createAgentViaUnifiedApi(chatId, name, dir, roleDesc, opts = {}) {
|
|
192
|
-
// Default: skip binding the creating chat — let the target group activate via /activate
|
|
193
|
-
const { skipChatBinding = true, engine = null } = opts;
|
|
194
|
-
if (agentTools && typeof agentTools.createNewWorkspaceAgent === 'function') {
|
|
195
|
-
const res = await agentTools.createNewWorkspaceAgent(name, dir, roleDesc, chatId, { skipChatBinding, engine });
|
|
196
|
-
if (res.ok && skipChatBinding && res.data && res.data.projectKey) {
|
|
197
|
-
storePendingActivation(res.data.projectKey, name, res.data.cwd, chatId);
|
|
198
|
-
}
|
|
199
|
-
return res;
|
|
200
|
-
}
|
|
201
|
-
const bound = await doBindAgent({ sendMessage: async () => {} }, chatId, name, dir);
|
|
202
|
-
if (!bound || bound.ok === false) {
|
|
203
|
-
return { ok: false, error: (bound && bound.error) || 'bind failed' };
|
|
204
|
-
}
|
|
205
|
-
const merged = await mergeAgentRole(dir, roleDesc);
|
|
206
|
-
if (merged.error) return { ok: false, error: merged.error };
|
|
207
|
-
return { ok: true, data: { cwd: dir, project: { name }, role: merged } };
|
|
208
|
-
}
|
|
209
|
-
|
|
210
291
|
async function listAgentsViaUnifiedApi(chatId) {
|
|
211
292
|
if (agentTools && typeof agentTools.listAllAgents === 'function') {
|
|
212
293
|
return agentTools.listAllAgents(chatId);
|
|
@@ -271,43 +352,66 @@ function createAgentCommandHandler(deps) {
|
|
|
271
352
|
const arg = text.slice(7).trim();
|
|
272
353
|
|
|
273
354
|
// Get current workdir to scope session list — prefer bound project cwd over session cwd
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
const
|
|
355
|
+
const route = getSessionRoute(chatId);
|
|
356
|
+
const isLogicalRoute = route.sessionChatId !== String(chatId);
|
|
357
|
+
const currentLogical = getLogicalSessionForRoute(route);
|
|
358
|
+
const curSession = getSession(route.sessionChatId) || getSession(chatId);
|
|
359
|
+
const curCwd = route.cwd || (curSession ? curSession.cwd : null);
|
|
360
|
+
const currentEngine = getCurrentEngine(chatId);
|
|
361
|
+
const recentSessions = listRecentSessions(5, curCwd, currentEngine);
|
|
362
|
+
const resumeChoices = buildResumeChoices({
|
|
363
|
+
recentSessions,
|
|
364
|
+
currentLogical,
|
|
365
|
+
curCwd,
|
|
366
|
+
currentEngine,
|
|
367
|
+
isLogicalRoute,
|
|
368
|
+
});
|
|
282
369
|
|
|
283
370
|
if (!arg) {
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
return true;
|
|
371
|
+
if (resumeChoices.length === 0) {
|
|
372
|
+
return autoCreateSessionOnEmptyResume(bot, chatId, curCwd, currentEngine);
|
|
287
373
|
}
|
|
288
374
|
const headerTitle = curCwd ? `📋 Sessions in ${path.basename(curCwd)}` : '📋 Recent Sessions';
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
375
|
+
try {
|
|
376
|
+
if (bot.sendRawCard) {
|
|
377
|
+
await bot.sendRawCard(chatId, headerTitle, buildSessionCardElements(resumeChoices));
|
|
378
|
+
} else {
|
|
379
|
+
throw new Error('raw-card-unavailable');
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
try {
|
|
383
|
+
if (bot.sendButtons) {
|
|
384
|
+
const buttons = resumeChoices.map(s => {
|
|
385
|
+
return [{ text: sessionLabel(s), callback_data: `/resume ${s.sessionId}` }];
|
|
386
|
+
});
|
|
387
|
+
await bot.sendButtons(chatId, headerTitle, buttons);
|
|
388
|
+
} else {
|
|
389
|
+
throw new Error('buttons-unavailable');
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
const _tags2 = loadSessionTags();
|
|
393
|
+
let msg = `${headerTitle}\n`;
|
|
394
|
+
msg += '\n';
|
|
395
|
+
resumeChoices.forEach((s, i) => {
|
|
396
|
+
msg += sessionRichLabel(s, i + 1, _tags2) + '\n';
|
|
397
|
+
});
|
|
398
|
+
await bot.sendMessage(chatId, msg);
|
|
399
|
+
}
|
|
303
400
|
}
|
|
304
401
|
return true;
|
|
305
402
|
}
|
|
306
403
|
|
|
307
|
-
// Argument given -> match
|
|
308
|
-
|
|
404
|
+
// Argument given -> match current resume choices first (includes synthetic
|
|
405
|
+
// "当前会话" entry for logical routes), then fall back to global history.
|
|
406
|
+
const allSessions = listRecentSessions(50, null, currentEngine);
|
|
309
407
|
const argLower = arg.toLowerCase();
|
|
310
|
-
let fullMatch =
|
|
408
|
+
let fullMatch = resumeChoices.find(s => s.customTitle && s.customTitle.toLowerCase() === argLower);
|
|
409
|
+
if (!fullMatch) {
|
|
410
|
+
fullMatch = allSessions.find(s => s.customTitle && s.customTitle.toLowerCase() === argLower);
|
|
411
|
+
}
|
|
412
|
+
if (!fullMatch) {
|
|
413
|
+
fullMatch = resumeChoices.find(s => s.customTitle && s.customTitle.toLowerCase().includes(argLower));
|
|
414
|
+
}
|
|
311
415
|
if (!fullMatch) {
|
|
312
416
|
fullMatch = allSessions.find(s => s.customTitle && s.customTitle.toLowerCase().includes(argLower));
|
|
313
417
|
}
|
|
@@ -315,35 +419,53 @@ function createAgentCommandHandler(deps) {
|
|
|
315
419
|
fullMatch = recentSessions.find(s => s.sessionId.startsWith(arg))
|
|
316
420
|
|| allSessions.find(s => s.sessionId.startsWith(arg));
|
|
317
421
|
}
|
|
422
|
+
if (!fullMatch) {
|
|
423
|
+
fullMatch = resumeChoices.find(s => s.sessionId.startsWith(arg));
|
|
424
|
+
}
|
|
318
425
|
if (!fullMatch) {
|
|
319
426
|
// keep historical behavior:
|
|
320
427
|
// "/resume 看到的session信息太少了" should be treated as normal text
|
|
321
428
|
return null;
|
|
322
429
|
}
|
|
323
430
|
const sessionId = fullMatch.sessionId;
|
|
324
|
-
const cwd = fullMatch.projectPath || (
|
|
431
|
+
const cwd = fullMatch.projectPath || (curSession && curSession.cwd) || HOME;
|
|
325
432
|
|
|
326
433
|
const state2 = loadState();
|
|
327
434
|
const cfgForEngine = loadConfig();
|
|
328
|
-
const
|
|
329
|
-
// For bound chats, write session to virtual chatId (_agent_{key}) so askClaude picks it up
|
|
330
|
-
const resumeChatAgentMap = { ...(cfgForEngine.telegram ? cfgForEngine.telegram.chat_agent_map : {}), ...(cfgForEngine.feishu ? cfgForEngine.feishu.chat_agent_map : {}) };
|
|
331
|
-
const resumeBoundKey = resumeChatAgentMap[String(chatId)];
|
|
332
|
-
const sessionKey = resumeBoundKey ? `_agent_${resumeBoundKey}` : String(chatId);
|
|
435
|
+
const sessionKey = route.sessionChatId;
|
|
333
436
|
const existing = state2.sessions[sessionKey] || {};
|
|
437
|
+
const existingEngine = normalizeEngineName(
|
|
438
|
+
existing.engine
|
|
439
|
+
|| (existing.engines && Object.entries(existing.engines).find(([, slot]) => slot && slot.started)?.[0])
|
|
440
|
+
);
|
|
441
|
+
const engineByTargetCwd = normalizeEngineName(fullMatch.engine)
|
|
442
|
+
|| inferEngineByCwd(cfgForEngine, cwd)
|
|
443
|
+
|| existingEngine;
|
|
444
|
+
const selectedLogicalCurrent = isLogicalRoute
|
|
445
|
+
&& currentLogical
|
|
446
|
+
&& currentLogical.id
|
|
447
|
+
&& sessionId === currentLogical.id;
|
|
448
|
+
const targetSessionId = sessionId;
|
|
449
|
+
const targetCwd = cwd;
|
|
334
450
|
const existingEngines = existing.engines || {};
|
|
335
451
|
state2.sessions[sessionKey] = {
|
|
336
452
|
...existing,
|
|
337
|
-
cwd,
|
|
338
|
-
|
|
453
|
+
cwd: targetCwd,
|
|
454
|
+
id: targetSessionId,
|
|
455
|
+
started: true,
|
|
456
|
+
engine: engineByTargetCwd,
|
|
457
|
+
engines: { ...existingEngines, [engineByTargetCwd]: { id: targetSessionId, started: true } },
|
|
339
458
|
};
|
|
340
459
|
saveState(state2);
|
|
341
460
|
const name = fullMatch.customTitle;
|
|
342
|
-
const label = name || (fullMatch.summary || fullMatch.firstPrompt || '').slice(0, 40) ||
|
|
461
|
+
const label = name || (fullMatch.summary || fullMatch.firstPrompt || '').slice(0, 40) || targetSessionId.slice(0, 8);
|
|
343
462
|
|
|
344
463
|
// 读取最近对话片段,帮助确认是否切换到正确的 session
|
|
345
|
-
const recentCtx = getSessionRecentContext ? getSessionRecentContext(
|
|
464
|
+
const recentCtx = getSessionRecentContext ? getSessionRecentContext(targetSessionId) : null;
|
|
346
465
|
let msg = `✅ 已切换: **${label}**\n📁 ${path.basename(cwd)}`;
|
|
466
|
+
if (selectedLogicalCurrent) {
|
|
467
|
+
msg += '\n\n已恢复当前智能体会话。';
|
|
468
|
+
}
|
|
347
469
|
if (recentCtx) {
|
|
348
470
|
if (recentCtx.lastUser) {
|
|
349
471
|
const snippet = recentCtx.lastUser.replace(/\n/g, ' ').slice(0, 80);
|