metame-cli 1.5.21 → 1.5.22

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.
@@ -0,0 +1,282 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ bindAgentToChat,
5
+ createWorkspaceAgent,
6
+ editAgentRole,
7
+ listAgents,
8
+ unbindAgent,
9
+ } = require('./daemon-agent-workflow');
10
+ const {
11
+ classifyAgentIntent,
12
+ detectCloneIntent,
13
+ detectTeamIntent,
14
+ extractPathFromText,
15
+ } = require('./agent-intent-shared');
16
+
17
+ function extractAgentName(input) {
18
+ const text = String(input || '').trim();
19
+ const byNameField = text.match(/(?:名字|名称|叫做?|名为|named?)\s*(?:为)?\s*[“"'「]?([^\s,。;;!!??"“”'‘’`]+)[”"'」]?/i);
20
+ if (byNameField) return byNameField[1].trim();
21
+ const byBind = text.match(/(?:bind|绑定)\s*(?:到|为|成)?\s*[“"'「]?([a-zA-Z0-9_\-\u4e00-\u9fa5]+)[”"'」]?/i);
22
+ if (byBind) return byBind[1].trim();
23
+ return '';
24
+ }
25
+
26
+ function deriveAgentName(input, workspaceDir) {
27
+ const explicit = extractAgentName(input);
28
+ if (explicit) return explicit;
29
+ if (workspaceDir) {
30
+ const normalized = String(workspaceDir).replace(/[\\/]+$/, '');
31
+ const basename = normalized.split(/[/\\]/).filter(Boolean).pop();
32
+ if (basename) return basename;
33
+ }
34
+ return 'workspace-agent';
35
+ }
36
+
37
+ function extractQuotedContent(input) {
38
+ const m = String(input || '').match(/[“"'「](.+?)[”"'」]/);
39
+ return m ? m[1].trim() : '';
40
+ }
41
+
42
+ function deriveRoleDelta(input) {
43
+ const text = String(input || '').trim();
44
+ const quoted = extractQuotedContent(text);
45
+ if (quoted) return quoted;
46
+ const byVerb = text.match(/(?:改成|改为|变成|设为|更新为)\s*[::]?\s*(.+)$/);
47
+ if (byVerb) return byVerb[1].trim();
48
+ return text;
49
+ }
50
+
51
+ function deriveCreateRoleDelta(input) {
52
+ const text = String(input || '').trim();
53
+ const quoted = extractQuotedContent(text);
54
+ if (quoted) return quoted;
55
+ const byRoleField = text.match(/(?:角色|职责|人设)\s*(?:是|为|:|:)?\s*(.+)$/i);
56
+ if (byRoleField) return byRoleField[1].trim();
57
+ return '';
58
+ }
59
+
60
+ function inferAgentEngineFromText(input) {
61
+ const text = String(input || '').trim().toLowerCase();
62
+ if (!text) return null;
63
+ if (/\bcodex\b/.test(text) || /柯德|科德/.test(text)) return 'codex';
64
+ return null;
65
+ }
66
+
67
+
68
+ function projectNameFromResult(data, fallbackName) {
69
+ if (data && data.project && data.project.name) return data.project.name;
70
+ if (data && data.projectKey) return data.projectKey;
71
+ return fallbackName || 'workspace-agent';
72
+ }
73
+
74
+ function createAgentIntentHandler(deps) {
75
+ const {
76
+ agentTools,
77
+ handleAgentCommand,
78
+ attachOrCreateSession,
79
+ normalizeCwd,
80
+ getDefaultEngine,
81
+ loadConfig,
82
+ getBoundProjectForChat,
83
+ log,
84
+ pendingActivations,
85
+ hasFreshPendingFlow,
86
+ HOME,
87
+ writeConfigSafe,
88
+ backupConfig,
89
+ } = deps;
90
+
91
+ return async function handleAgentIntent(bot, chatId, text, config) {
92
+ if (!agentTools || !text || text.startsWith('/')) return false;
93
+ const key = String(chatId);
94
+ if (hasFreshPendingFlow(key) || hasFreshPendingFlow(key + ':edit')) return false;
95
+
96
+ const input = String(text || '').trim();
97
+ if (!input) return false;
98
+
99
+ const intent = classifyAgentIntent(input);
100
+ if (!intent) return false;
101
+
102
+ if (intent.action === 'wizard_clone') {
103
+ if (typeof log === 'function') log('INFO', `[CloneIntent] "${input.slice(0, 80)}" → /agent new clone`);
104
+ await handleAgentCommand({ bot, chatId, text: '/agent new clone', config });
105
+ return true;
106
+ }
107
+
108
+ if (intent.action === 'wizard_team') {
109
+ if (typeof log === 'function') log('INFO', `[TeamIntent] "${input.slice(0, 80)}" → /agent new team`);
110
+ await handleAgentCommand({ bot, chatId, text: '/agent new team', config });
111
+ return true;
112
+ }
113
+
114
+ if (intent.action === 'activate') {
115
+ if (typeof log === 'function') log('INFO', `[AgentIntent] "${input.slice(0, 80)}" → /activate`);
116
+ await handleAgentCommand({ bot, chatId, text: '/activate', config });
117
+ return true;
118
+ }
119
+
120
+ if (intent.action === 'reset') {
121
+ if (typeof log === 'function') log('INFO', `[AgentIntent] "${input.slice(0, 80)}" → /agent reset`);
122
+ await handleAgentCommand({ bot, chatId, text: '/agent reset', config });
123
+ return true;
124
+ }
125
+
126
+ if (intent.action === 'soul') {
127
+ const soulCommand = /repair/i.test(input) || /修复/.test(input) ? '/agent soul repair' : '/agent soul';
128
+ if (typeof log === 'function') log('INFO', `[AgentIntent] "${input.slice(0, 80)}" → ${soulCommand}`);
129
+ await handleAgentCommand({ bot, chatId, text: soulCommand, config });
130
+ return true;
131
+ }
132
+
133
+ if (intent.action === 'list') {
134
+ const res = await listAgents({ agentTools, chatId, loadConfig });
135
+ if (!res.ok) {
136
+ await bot.sendMessage(chatId, `❌ 查询 Agent 失败: ${res.error}`);
137
+ return true;
138
+ }
139
+ const agents = res.data.agents || [];
140
+ if (agents.length === 0) {
141
+ await bot.sendMessage(chatId, '暂无已配置的 Agent。你可以直接说“给这个群创建一个 Agent,目录是 ~/xxx”。');
142
+ return true;
143
+ }
144
+ const lines = ['📋 当前 Agent 列表', ''];
145
+ for (const a of agents) {
146
+ const marker = a.key === res.data.boundKey ? ' ◀ 当前' : '';
147
+ lines.push(`${a.icon || '🤖'} ${a.name}${marker}`);
148
+ lines.push(`目录: ${a.cwd}`);
149
+ lines.push(`Key: ${a.key}`);
150
+ lines.push('');
151
+ }
152
+ await bot.sendMessage(chatId, lines.join('\n').trimEnd());
153
+ return true;
154
+ }
155
+
156
+ if (intent.action === 'unbind') {
157
+ const res = await unbindAgent({ agentTools, chatId, loadConfig, writeConfigSafe, backupConfig });
158
+ if (!res.ok) {
159
+ await bot.sendMessage(chatId, `❌ 解绑失败: ${res.error}`);
160
+ return true;
161
+ }
162
+ if (res.data.unbound) {
163
+ await bot.sendMessage(chatId, `✅ 已解绑当前群(原 Agent: ${res.data.previousProjectKey})`);
164
+ } else {
165
+ await bot.sendMessage(chatId, '当前群没有绑定 Agent,无需解绑。');
166
+ }
167
+ return true;
168
+ }
169
+
170
+ if (intent.action === 'edit_role') {
171
+ const freshCfg = loadConfig();
172
+ const bound = getBoundProjectForChat(chatId, freshCfg);
173
+ if (!bound.project || !bound.project.cwd) {
174
+ await bot.sendMessage(chatId, '❌ 当前群未绑定 Agent。先说“给这个群绑定一个 Agent,目录是 ~/xxx”。');
175
+ return true;
176
+ }
177
+ if (agentTools && typeof agentTools.repairAgentSoul === 'function') {
178
+ await agentTools.repairAgentSoul(bound.project.cwd).catch(() => {});
179
+ }
180
+ const roleDelta = deriveRoleDelta(input);
181
+ const res = await editAgentRole({ agentTools, workspaceDir: bound.project.cwd, deltaText: roleDelta });
182
+ if (!res.ok) {
183
+ await bot.sendMessage(chatId, `❌ 更新角色失败: ${res.error}`);
184
+ return true;
185
+ }
186
+ await bot.sendMessage(chatId, res.data.created ? '✅ 已创建 CLAUDE.md 并写入角色定义' : '✅ 角色定义已更新到 CLAUDE.md');
187
+ return true;
188
+ }
189
+
190
+ if (intent.action === 'create') {
191
+ if (!intent.workspaceDir) {
192
+ await bot.sendMessage(chatId, [
193
+ '我可以帮你创建 Agent,还差一个工作目录。',
194
+ '例如:`给这个群创建一个 Agent,目录是 ~/projects/foo`',
195
+ 'Windows 也可以直接发:`C:\\\\work\\\\foo`',
196
+ '也可以直接回我一个路径(`~/`、`/`、`./`、`../`、`C:\\\\` 开头都行)。',
197
+ ].join('\n'));
198
+ return true;
199
+ }
200
+ const agentName = deriveAgentName(input, intent.workspaceDir);
201
+ const roleDelta = deriveCreateRoleDelta(input);
202
+ const inferredEngine = inferAgentEngineFromText(input);
203
+ const res = await createWorkspaceAgent({
204
+ agentTools,
205
+ chatId,
206
+ agentName,
207
+ workspaceDir: intent.workspaceDir,
208
+ roleDescription: roleDelta,
209
+ pendingActivations,
210
+ skipChatBinding: true,
211
+ engine: inferredEngine,
212
+ attachOrCreateSession,
213
+ normalizeCwd,
214
+ getDefaultEngine,
215
+ });
216
+ if (!res.ok) {
217
+ await bot.sendMessage(chatId, `❌ 创建 Agent 失败: ${res.error}`);
218
+ return true;
219
+ }
220
+ const data = res.data || {};
221
+ const projName = projectNameFromResult(data, agentName);
222
+ const engineTip = data.project && data.project.engine ? `\n引擎: ${data.project.engine}` : '';
223
+ await bot.sendMessage(chatId,
224
+ `✅ Agent「${projName}」已创建\n目录: ${data.cwd || '(未知)'}${engineTip}\n\n` +
225
+ `**下一步**: 在新群里发送 \`/activate\` 完成绑定(30分钟内有效)`
226
+ );
227
+ return true;
228
+ }
229
+
230
+ if (intent.action === 'bind') {
231
+ const agentName = deriveAgentName(input, intent.workspaceDir);
232
+ const inferredEngine = inferAgentEngineFromText(input);
233
+ const bindTools = agentTools && typeof agentTools.bindAgentToChat === 'function'
234
+ ? {
235
+ ...agentTools,
236
+ bindAgentToChat: (targetChatId, targetAgentName, targetWorkspaceDir) => agentTools.bindAgentToChat(
237
+ targetChatId,
238
+ targetAgentName,
239
+ targetWorkspaceDir,
240
+ { engine: inferredEngine }
241
+ ),
242
+ }
243
+ : agentTools;
244
+ const res = await bindAgentToChat({
245
+ agentTools: bindTools,
246
+ bot,
247
+ chatId,
248
+ agentName,
249
+ agentCwd: intent.workspaceDir || null,
250
+ HOME,
251
+ attachOrCreateSession,
252
+ normalizeCwd,
253
+ getDefaultEngine,
254
+ announce: false,
255
+ });
256
+ if (!res.ok) {
257
+ await bot.sendMessage(chatId, `❌ 绑定失败: ${res.error}`);
258
+ return true;
259
+ }
260
+ const data = res.data || {};
261
+ const projName = projectNameFromResult(data, agentName);
262
+ await bot.sendMessage(chatId, `✅ 已绑定 Agent\n名称: ${projName}\n目录: ${data.cwd || '(未知)'}`);
263
+ return true;
264
+ }
265
+
266
+ return false;
267
+ };
268
+ }
269
+
270
+ module.exports = {
271
+ createAgentIntentHandler,
272
+ _private: {
273
+ classifyAgentIntent,
274
+ detectCloneIntent,
275
+ detectTeamIntent,
276
+ deriveAgentName,
277
+ deriveCreateRoleDelta,
278
+ deriveRoleDelta,
279
+ extractPathFromText,
280
+ inferAgentEngineFromText,
281
+ },
282
+ };
@@ -0,0 +1,243 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { createLinkOrMirror } = require('./agent-layer');
6
+
7
+ function resolveCloneParentCwd({ isClone, loadConfig, chatId, normalizeCwd }) {
8
+ if (!isClone) return null;
9
+ const cfg = loadConfig();
10
+ const agentMap = {
11
+ ...(cfg && cfg.telegram ? cfg.telegram.chat_agent_map : {}),
12
+ ...(cfg && cfg.feishu ? cfg.feishu.chat_agent_map : {}),
13
+ };
14
+ const boundKey = agentMap[String(chatId)];
15
+ const boundProj = boundKey && cfg.projects && cfg.projects[boundKey];
16
+ return boundProj && boundProj.cwd ? normalizeCwd(boundProj.cwd) : null;
17
+ }
18
+
19
+ function stampFlow(flow) {
20
+ return { ...flow, __ts: Date.now() };
21
+ }
22
+
23
+ function inheritParentAgentContext({ fs, path, childCwd, parentCwd }) {
24
+ const childClaudeMd = path.join(childCwd, 'CLAUDE.md');
25
+ const childSoulMd = path.join(childCwd, 'SOUL.md');
26
+ const parentClaudeMd = parentCwd ? path.join(parentCwd, 'CLAUDE.md') : '';
27
+ const parentSoulMd = parentCwd ? path.join(parentCwd, 'SOUL.md') : '';
28
+
29
+ const result = {
30
+ inheritedClaude: false,
31
+ inheritedSoul: false,
32
+ created: false,
33
+ };
34
+
35
+ if (parentClaudeMd && fs.existsSync(parentClaudeMd)) {
36
+ createLinkOrMirror(parentClaudeMd, childClaudeMd);
37
+ result.inheritedClaude = true;
38
+ result.created = true;
39
+ }
40
+ if (parentSoulMd && fs.existsSync(parentSoulMd)) {
41
+ createLinkOrMirror(parentSoulMd, childSoulMd);
42
+ result.inheritedSoul = true;
43
+ result.created = true;
44
+ }
45
+ return result;
46
+ }
47
+
48
+ async function startNewAgentWizard({
49
+ bot,
50
+ chatId,
51
+ secondArg,
52
+ pendingTeamFlows,
53
+ pendingAgentFlows,
54
+ loadConfig,
55
+ normalizeCwd,
56
+ sendBrowse,
57
+ HOME,
58
+ }) {
59
+ if (secondArg === 'team') {
60
+ if (!pendingTeamFlows) {
61
+ await bot.sendMessage(chatId, '❌ 团队向导暂不可用');
62
+ return true;
63
+ }
64
+ pendingTeamFlows.set(String(chatId), stampFlow({ step: 'name' }));
65
+ await bot.sendMessage(chatId, `🏗️ **团队创建向导**
66
+
67
+ 请输入团队名称(如:短剧团队、销售团队):
68
+
69
+ 输入 /cancel 可取消`);
70
+ return true;
71
+ }
72
+
73
+ const isClone = secondArg === 'clone';
74
+ const parentCwd = resolveCloneParentCwd({
75
+ isClone,
76
+ loadConfig,
77
+ chatId,
78
+ normalizeCwd,
79
+ });
80
+ pendingAgentFlows.set(String(chatId), stampFlow({ step: 'dir', isClone, parentCwd }));
81
+ const hint = isClone ? `(${parentCwd ? '分身模式:将继承父 Agent 上下文' : '⚠️ 当前群未绑定 Agent'})` : '';
82
+ await sendBrowse(bot, chatId, 'agent-new', HOME, `${isClone ? '步骤1/2' : '步骤1/3'}:选择 Agent 的工作目录${hint}`);
83
+ return true;
84
+ }
85
+
86
+ async function completeAgentCreation({
87
+ bot,
88
+ chatId,
89
+ flow,
90
+ description,
91
+ createWorkspaceAgent,
92
+ doBindAgent,
93
+ mergeAgentRole,
94
+ }) {
95
+ const { dir, name, isClone, parentCwd } = flow;
96
+ await bot.sendMessage(chatId, `⏳ 正在配置 Agent「${name}」,稍等...`);
97
+ try {
98
+ let createResult;
99
+ if (typeof createWorkspaceAgent === 'function') {
100
+ createResult = await createWorkspaceAgent({
101
+ chatId,
102
+ agentName: name,
103
+ workspaceDir: dir,
104
+ roleDescription: isClone ? '' : description,
105
+ });
106
+ } else {
107
+ await doBindAgent(bot, chatId, name, dir);
108
+ createResult = { ok: true, data: {} };
109
+ }
110
+ if (!createResult || createResult.ok === false) {
111
+ await bot.sendMessage(chatId, `❌ 创建 Agent 失败: ${(createResult && createResult.error) || 'unknown error'}`);
112
+ return true;
113
+ }
114
+ const mergeResult = isClone
115
+ ? inheritParentAgentContext({ fs, path, childCwd: dir, parentCwd })
116
+ : (createResult.data && createResult.data.role
117
+ ? createResult.data.role
118
+ : await mergeAgentRole(dir, description, isClone, parentCwd));
119
+ if (mergeResult && mergeResult.error) {
120
+ await bot.sendMessage(chatId, `⚠️ CLAUDE.md 合并失败: ${mergeResult.error},其他配置已保存`);
121
+ } else if (isClone) {
122
+ await bot.sendMessage(
123
+ chatId,
124
+ `🧬 已继承父 Agent 上下文${mergeResult && mergeResult.inheritedSoul ? '(含 Soul)' : ''}\n✅ Agent「${name}」创建完成`
125
+ );
126
+ } else if (mergeResult && mergeResult.created) {
127
+ await bot.sendMessage(chatId, `📝 已创建 CLAUDE.md\n✅ Agent「${name}」创建完成`);
128
+ } else {
129
+ await bot.sendMessage(chatId, `📝 已更新 CLAUDE.md\n✅ Agent「${name}」创建完成`);
130
+ }
131
+ } catch (e) {
132
+ await bot.sendMessage(chatId, `❌ 创建 Agent 失败: ${e.message}`);
133
+ }
134
+ return true;
135
+ }
136
+
137
+ function readAgentRolePreview({ fs, path, cwd }) {
138
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
139
+ let currentContent = '(CLAUDE.md 不存在)';
140
+ if (fs.existsSync(claudeMdPath)) {
141
+ currentContent = fs.readFileSync(claudeMdPath, 'utf8');
142
+ if (currentContent.length > 500) currentContent = currentContent.slice(0, 500) + '\n...(已截断)';
143
+ }
144
+ return currentContent;
145
+ }
146
+
147
+ function resetAgentRoleSection({ fs, path, cwd }) {
148
+ const claudeMdPath = path.join(cwd, 'CLAUDE.md');
149
+ if (!fs.existsSync(claudeMdPath)) {
150
+ return { ok: true, status: 'missing' };
151
+ }
152
+
153
+ const before = fs.readFileSync(claudeMdPath, 'utf8');
154
+ const after = before
155
+ .replace(/(?:^|\n)##\s+[^\n]*(?:角色|职责|人设)[^\n]*\n[\s\S]*?(?=\n## |$)/g, '')
156
+ .replace(/(?:^|\n)## Agent 角色\n[\s\S]*?(?=\n## |$)/g, '')
157
+ .trimStart();
158
+ if (after === before.trimStart()) {
159
+ return { ok: true, status: 'unchanged' };
160
+ }
161
+
162
+ fs.writeFileSync(claudeMdPath, after, 'utf8');
163
+ return { ok: true, status: 'reset' };
164
+ }
165
+
166
+ async function handleSoulCommand({
167
+ bot,
168
+ chatId,
169
+ soulAction,
170
+ soulText,
171
+ cwd,
172
+ fs,
173
+ path,
174
+ agentTools,
175
+ }) {
176
+ const soulPath = path.join(cwd, 'SOUL.md');
177
+
178
+ if (soulAction === 'repair') {
179
+ if (!agentTools || typeof agentTools.repairAgentSoul !== 'function') {
180
+ await bot.sendMessage(chatId, '❌ agentTools 不可用');
181
+ return true;
182
+ }
183
+ const res = await agentTools.repairAgentSoul(cwd);
184
+ if (!res.ok) {
185
+ await bot.sendMessage(chatId, '❌ Soul 修复失败: ' + res.error);
186
+ return true;
187
+ }
188
+ const viewModes = res.data.views
189
+ ? Object.entries(res.data.views).map(([k, v]) => k + ':' + v).join(', ')
190
+ : '—';
191
+ await bot.sendMessage(chatId, [
192
+ '✅ Agent Soul 层已就绪',
193
+ 'agent_id: ' + res.data.agentId,
194
+ '链接方式: ' + viewModes,
195
+ '',
196
+ '文件位置:',
197
+ ' SOUL.md → ~/.metame/agents/' + res.data.agentId + '/soul.md',
198
+ ' MEMORY.md → ~/.metame/agents/' + res.data.agentId + '/memory-snapshot.md',
199
+ ].join('\n'));
200
+ return true;
201
+ }
202
+
203
+ if (soulAction === 'edit') {
204
+ if (!soulText) {
205
+ await bot.sendMessage(chatId, '用法: /agent soul edit <新内容>\n当前内容: /agent soul');
206
+ return true;
207
+ }
208
+ try {
209
+ fs.writeFileSync(soulPath, soulText, 'utf8');
210
+ await bot.sendMessage(chatId, '✅ SOUL.md 已更新');
211
+ } catch (e) {
212
+ await bot.sendMessage(chatId, '❌ 写入失败: ' + e.message);
213
+ }
214
+ return true;
215
+ }
216
+
217
+ if (!fs.existsSync(soulPath)) {
218
+ await bot.sendMessage(chatId, [
219
+ '⚠️ SOUL.md 不存在',
220
+ '',
221
+ '老项目或刚绑定的 Agent 可能尚未建立 Soul 层。',
222
+ '运行 /agent soul repair 自动生成。',
223
+ ].join('\n'));
224
+ return true;
225
+ }
226
+
227
+ try {
228
+ const soulContent = fs.readFileSync(soulPath, 'utf8').trim().slice(0, 2000);
229
+ await bot.sendMessage(chatId, '📋 当前 Soul:\n\n' + soulContent);
230
+ } catch (e) {
231
+ await bot.sendMessage(chatId, '❌ 读取 SOUL.md 失败: ' + e.message);
232
+ }
233
+ return true;
234
+ }
235
+
236
+ module.exports = {
237
+ resolveCloneParentCwd,
238
+ startNewAgentWizard,
239
+ completeAgentCreation,
240
+ readAgentRolePreview,
241
+ resetAgentRoleSection,
242
+ handleSoulCommand,
243
+ };