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.
Files changed (40) hide show
  1. package/README.md +6 -1
  2. package/index.js +277 -55
  3. package/package.json +2 -2
  4. package/scripts/agent-layer.js +4 -2
  5. package/scripts/bin/dispatch_to +17 -5
  6. package/scripts/daemon-admin-commands.js +264 -62
  7. package/scripts/daemon-agent-commands.js +188 -66
  8. package/scripts/daemon-bridges.js +447 -48
  9. package/scripts/daemon-claude-engine.js +650 -103
  10. package/scripts/daemon-command-router.js +134 -27
  11. package/scripts/daemon-command-session-route.js +118 -0
  12. package/scripts/daemon-default.yaml +2 -0
  13. package/scripts/daemon-engine-runtime.js +96 -20
  14. package/scripts/daemon-exec-commands.js +106 -50
  15. package/scripts/daemon-file-browser.js +63 -7
  16. package/scripts/daemon-notify.js +18 -4
  17. package/scripts/daemon-ops-commands.js +16 -2
  18. package/scripts/daemon-remote-dispatch.js +34 -2
  19. package/scripts/daemon-session-commands.js +102 -45
  20. package/scripts/daemon-session-store.js +497 -66
  21. package/scripts/daemon-siri-bridge.js +234 -0
  22. package/scripts/daemon-siri-imessage.js +209 -0
  23. package/scripts/daemon-task-scheduler.js +10 -2
  24. package/scripts/daemon.js +610 -181
  25. package/scripts/docs/hook-config.md +7 -4
  26. package/scripts/docs/maintenance-manual.md +8 -1
  27. package/scripts/feishu-adapter.js +7 -15
  28. package/scripts/hooks/doc-router.js +29 -0
  29. package/scripts/hooks/intent-doc-router.js +54 -0
  30. package/scripts/hooks/intent-engine.js +9 -40
  31. package/scripts/intent-registry.js +59 -0
  32. package/scripts/memory-extract.js +59 -0
  33. package/scripts/mentor-engine.js +6 -0
  34. package/scripts/schema.js +1 -0
  35. package/scripts/self-reflect.js +110 -12
  36. package/scripts/session-analytics.js +160 -0
  37. package/scripts/signal-capture.js +1 -1
  38. package/scripts/team-dispatch.js +150 -11
  39. package/scripts/hooks/intent-agent-manage.js +0 -50
  40. package/scripts/hooks/intent-hook-config.js +0 -28
@@ -895,6 +895,161 @@ function detectSignificantSession(skeleton) {
895
895
  return { significant: reasons.length > 0, reasons };
896
896
  }
897
897
 
898
+ // ─────────────────────────────────────────────────────────────────────────────
899
+ // Codex session adapter
900
+ // Reads ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl (first line only, ~1KB)
901
+ // and ~/.codex/history.jsonl (user messages). Reuses the same state DB with
902
+ // a 'codex_facts' key to avoid collisions with Claude session IDs.
903
+ // ─────────────────────────────────────────────────────────────────────────────
904
+
905
+ const CODEX_SESSIONS_ROOT = path.join(HOME, '.codex', 'sessions');
906
+ const CODEX_HISTORY_FILE = path.join(HOME, '.codex', 'history.jsonl');
907
+ // Matches: rollout-YYYY-MM-DDTHH-MM-SS-<uuid>.jsonl (colons replaced with dashes)
908
+ const CODEX_ROLLOUT_PATTERN = /^rollout-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-(.+)\.jsonl$/;
909
+
910
+ /**
911
+ * Load ~/.codex/history.jsonl into a Map<session_id, [{ts, text}]>.
912
+ * Pass sessionIds to load only the sessions you need — avoids reading the
913
+ * whole file (which grows unbounded) when only a few sessions are relevant.
914
+ *
915
+ * @param {string[]|null} sessionIds - allowlist; null/empty loads everything
916
+ */
917
+ function loadCodexHistory(sessionIds = null) {
918
+ const map = new Map();
919
+ const allow = sessionIds && sessionIds.length > 0 ? new Set(sessionIds) : null;
920
+ try {
921
+ if (!fs.existsSync(CODEX_HISTORY_FILE)) return map;
922
+ const lines = fs.readFileSync(CODEX_HISTORY_FILE, 'utf8').split('\n');
923
+ for (const line of lines) {
924
+ if (!line.trim()) continue;
925
+ let entry;
926
+ try { entry = JSON.parse(line); } catch { continue; }
927
+ if (!entry.session_id || !entry.text) continue;
928
+ if (allow && !allow.has(entry.session_id)) continue;
929
+ if (!map.has(entry.session_id)) map.set(entry.session_id, []);
930
+ map.get(entry.session_id).push({ ts: entry.ts, text: entry.text });
931
+ }
932
+ } catch { /* non-fatal */ }
933
+ return map;
934
+ }
935
+
936
+ /**
937
+ * Find all Codex rollout files not yet processed by memory-extract.
938
+ * Filename pattern: rollout-YYYY-MM-DDTHH-MM-SS-<uuid>.jsonl
939
+ */
940
+ function findAllUnextractedCodexSessions(limit = 30) {
941
+ if (!fs.existsSync(CODEX_SESSIONS_ROOT)) return [];
942
+ const results = [];
943
+ try {
944
+ const years = fs.readdirSync(CODEX_SESSIONS_ROOT).filter(d => /^\d{4}$/.test(d));
945
+ for (const year of years) {
946
+ const yearDir = path.join(CODEX_SESSIONS_ROOT, year);
947
+ const months = fs.readdirSync(yearDir).filter(d => /^\d{2}$/.test(d));
948
+ for (const month of months) {
949
+ const monthDir = path.join(yearDir, month);
950
+ const days = fs.readdirSync(monthDir).filter(d => /^\d{2}$/.test(d));
951
+ for (const day of days) {
952
+ const dayDir = path.join(monthDir, day);
953
+ let files;
954
+ try { files = fs.readdirSync(dayDir); } catch { continue; }
955
+ for (const file of files) {
956
+ if (!file.startsWith('rollout-') || !file.endsWith('.jsonl')) continue;
957
+ // Extract UUID from: rollout-YYYY-MM-DDTHH-MM-SS-<uuid>.jsonl
958
+ const m = file.match(CODEX_ROLLOUT_PATTERN);
959
+ if (!m) continue;
960
+ const sessionId = m[1];
961
+ if (isProcessed('codex_facts', sessionId)) continue;
962
+ const fullPath = path.join(dayDir, file);
963
+ let fstat;
964
+ try { fstat = fs.statSync(fullPath); } catch { continue; }
965
+ if (fstat.size < MIN_FILE_SIZE) continue;
966
+ results.push({ path: fullPath, session_id: sessionId, mtime: fstat.mtimeMs });
967
+ }
968
+ }
969
+ }
970
+ }
971
+ } catch { return []; }
972
+ results.sort((a, b) => b.mtime - a.mtime);
973
+ return results.slice(0, limit);
974
+ }
975
+
976
+ /**
977
+ * Build { skeleton, evidence } for a Codex session.
978
+ * Reads only the first 2KB of the rollout file (session_meta line) — never
979
+ * loads the full transcript. Enriches with user messages from historyMap.
980
+ *
981
+ * @param {string} rolloutPath - absolute path to rollout-*.jsonl
982
+ * @param {Map} historyMap - returned by loadCodexHistory()
983
+ */
984
+ function buildCodexInput(rolloutPath, historyMap) {
985
+ let sessionMeta = null;
986
+ let fileSessionId = null;
987
+ try {
988
+ const m = path.basename(rolloutPath).match(CODEX_ROLLOUT_PATTERN);
989
+ if (m) fileSessionId = m[1];
990
+
991
+ // Read only first 2KB to get session_meta without loading the full transcript
992
+ let fd;
993
+ try {
994
+ fd = fs.openSync(rolloutPath, 'r');
995
+ const buf = Buffer.alloc(2048);
996
+ const bytesRead = fs.readSync(fd, buf, 0, 2048, 0);
997
+ const firstLine = buf.slice(0, bytesRead).toString('utf8').split('\n')[0];
998
+ const parsed = JSON.parse(firstLine);
999
+ if (parsed.type === 'session_meta') sessionMeta = parsed.payload;
1000
+ } finally {
1001
+ if (fd !== undefined) try { fs.closeSync(fd); } catch { /* ignore */ }
1002
+ }
1003
+ } catch { /* non-fatal */ }
1004
+
1005
+ const sessionId = (sessionMeta && sessionMeta.id) || fileSessionId;
1006
+ const cwd = (sessionMeta && sessionMeta.cwd) || null;
1007
+ const { project, project_id: projectId } = deriveProjectInfo(cwd || '');
1008
+
1009
+ // User messages from history index (sorted chronologically)
1010
+ const userMsgs = (sessionId && historyMap.get(sessionId)) || [];
1011
+ userMsgs.sort((a, b) => a.ts - b.ts);
1012
+
1013
+ const firstTs = userMsgs.length > 0 ? new Date(userMsgs[0].ts * 1000).toISOString() : null;
1014
+ const lastTs = userMsgs.length > 1 ? new Date(userMsgs[userMsgs.length - 1].ts * 1000).toISOString() : firstTs;
1015
+ const durationMin = userMsgs.length > 1
1016
+ ? Math.round((userMsgs[userMsgs.length - 1].ts - userMsgs[0].ts) / 6) / 10
1017
+ : 0;
1018
+
1019
+ const skeleton = {
1020
+ session_id: sessionId || path.basename(rolloutPath, '.jsonl'),
1021
+ user_snippets: userMsgs.map(m => m.text.slice(0, 200)),
1022
+ tool_counts: {},
1023
+ total_tool_calls: 0,
1024
+ message_count: userMsgs.length,
1025
+ duration_min: durationMin,
1026
+ project: project || 'unknown',
1027
+ project_id: projectId || null,
1028
+ project_path: cwd,
1029
+ branch: null,
1030
+ engine: 'codex',
1031
+ model_provider: sessionMeta && sessionMeta.model_provider,
1032
+ first_ts: firstTs,
1033
+ last_ts: lastTs,
1034
+ };
1035
+
1036
+ const evidence = {
1037
+ user_messages: userMsgs.map(m => m.text).filter(Boolean).slice(0, 15),
1038
+ tool_traces: [],
1039
+ key_results: [],
1040
+ file_anchors: cwd ? [cwd] : [],
1041
+ };
1042
+
1043
+ return { skeleton, evidence };
1044
+ }
1045
+
1046
+ /**
1047
+ * Mark a Codex session as facts-extracted.
1048
+ */
1049
+ function markCodexFactsExtracted(sessionId) {
1050
+ markProcessed('codex_facts', sessionId);
1051
+ }
1052
+
898
1053
  module.exports = {
899
1054
  findLatestUnanalyzedSession,
900
1055
  findSessionById,
@@ -908,6 +1063,11 @@ module.exports = {
908
1063
  detectSignificantSession,
909
1064
  markAnalyzed,
910
1065
  markFactsExtracted,
1066
+ // Codex adapter
1067
+ loadCodexHistory,
1068
+ findAllUnextractedCodexSessions,
1069
+ buildCodexInput,
1070
+ markCodexFactsExtracted,
911
1071
  };
912
1072
 
913
1073
  // Direct execution for testing
@@ -26,7 +26,7 @@ const ABSOLUTE_MAX_CAPTURE_CHARS = 6000;
26
26
 
27
27
  // Strong directive signals → high confidence (direct write to T3)
28
28
  // Allow up to 6 chars between key words (e.g. "以后代码一律" = "以后" + "代码" + "一律")
29
- const STRONG_SIGNAL_ZH = /以后.{0,6}(都|一律|每次|全部|统一)|永远.{0,4}(不要|别|不能|要)|千万.{0,4}(别|不要)|记住|一定.{0,4}(要|得)|一律|统一用/;
29
+ const STRONG_SIGNAL_ZH = /以后.{0,6}(都|总是|一律|每次|全部|统一)|永远.{0,4}(不要|别|不能|要)|千万.{0,4}(别|不要)|记住|一定.{0,4}(要|得)|一律|统一用/;
30
30
  const STRONG_SIGNAL_EN = /(from now on|always|never|don't ever|remember to|every time)/i;
31
31
 
32
32
  // Implicit preference signals → normal confidence (needs accumulation)
@@ -6,7 +6,8 @@
6
6
  * Single source of truth for:
7
7
  * - Project/team member resolution by name or nickname
8
8
  * - Team roster hint generation (injected into member sessions)
9
- * - Prompt enrichment with shared context (inbox / now.md / _latest.md)
9
+ * - Prompt enrichment with scoped context (private now / shared now / inbox / _latest.md)
10
+ * - Dispatch context file writes for target-only and team-shared tasks
10
11
  *
11
12
  * Used by: dispatch_to binary, daemon-admin-commands, daemon-bridges, daemon.js
12
13
  */
@@ -112,13 +113,132 @@ function buildTeamRosterHint(parentKey, memberKey, projects) {
112
113
  ].join('\n');
113
114
  }
114
115
 
116
+ function resolveDispatchActor(sourceKey, projects) {
117
+ const rawKey = String(sourceKey || '').trim();
118
+ const userSources = new Set(['', 'unknown', 'claude_session', '_claude_session', 'user']);
119
+ if (userSources.has(rawKey)) return { key: 'user', name: '用户', icon: '👤', isUser: true };
120
+ const proj = projects && projects[rawKey];
121
+ if (proj) return { key: rawKey, name: proj.name || rawKey, icon: proj.icon || '🤖', isUser: false };
122
+ return { key: rawKey || 'unknown', name: rawKey || 'unknown', icon: '🤖', isUser: false };
123
+ }
124
+
125
+ function buildPrivateNowContent({ actor, target, title, prompt, timeStr, dispatchId, taskId, scopeId, chain }) {
126
+ const lines = [
127
+ '# 当前任务',
128
+ `**最后更新**: ${timeStr} **更新者**: ${actor.icon} ${actor.name}`,
129
+ '',
130
+ '## 当前派发',
131
+ `- **目标**: ${target.icon} ${target.name} (${target.key})`,
132
+ `- **任务**: ${title || prompt.slice(0, 120) || '(empty)'}`,
133
+ dispatchId ? `- **编号**: ${dispatchId}` : '',
134
+ taskId ? `- **TeamTask**: ${taskId}` : '',
135
+ scopeId && scopeId !== taskId ? `- **Scope**: ${scopeId}` : '',
136
+ '',
137
+ '## 任务链',
138
+ chain && chain.length > 0 ? chain.join(' → ') : `${actor.key} → ${target.key}`,
139
+ ].filter(Boolean);
140
+ return `${lines.join('\n')}\n`;
141
+ }
142
+
143
+ function buildSharedNowContent({ actor, target, title, prompt, timeStr, dispatchId, taskId, scopeId, chain }) {
144
+ const lines = [
145
+ '# 共享当前状态',
146
+ `**最后更新**: ${timeStr} **更新者**: ${actor.icon} ${actor.name}`,
147
+ '',
148
+ '## 当前任务',
149
+ `- **派发给**: ${target.icon} ${target.name} (${target.key})`,
150
+ `- **任务**: ${title || prompt.slice(0, 120) || '(empty)'}`,
151
+ dispatchId ? `- **编号**: ${dispatchId}` : '',
152
+ taskId ? `- **TeamTask**: ${taskId}` : '',
153
+ scopeId && scopeId !== taskId ? `- **Scope**: ${scopeId}` : '',
154
+ `- **时间**: ${timeStr}`,
155
+ '',
156
+ '## 任务链',
157
+ chain && chain.length > 0 ? chain.join(' → ') : `${actor.key} → ${target.key}`,
158
+ ].filter(Boolean);
159
+ return `${lines.join('\n')}\n`;
160
+ }
161
+
162
+ function updateDispatchContextFiles({ fs: fsMod = fs, path: pathMod = path, baseDir = METAME_DIR, fullMsg, targetProject, config, envelope, logger = null }) {
163
+ if (!fullMsg || !targetProject) return { targetNowPath: null, sharedNowPath: null, tasksFilePath: null };
164
+
165
+ const logWarn = (msg) => {
166
+ if (typeof logger === 'function') logger(msg);
167
+ };
168
+ const nowDir = pathMod.join(baseDir, 'memory', 'now');
169
+ const sharedDir = pathMod.join(baseDir, 'memory', 'shared');
170
+ const targetNowPath = pathMod.join(nowDir, `${targetProject}.md`);
171
+ const sharedNowPath = pathMod.join(nowDir, 'shared.md');
172
+ const tasksFilePath = pathMod.join(sharedDir, 'tasks.md');
173
+ fsMod.mkdirSync(nowDir, { recursive: true });
174
+
175
+ const projects = (config && config.projects) || {};
176
+ const actor = resolveDispatchActor((fullMsg && fullMsg.source_sender_key) || (fullMsg && fullMsg.from), projects);
177
+ const targetProj = projects[targetProject] || {};
178
+ const target = { key: targetProject, name: targetProj.name || targetProject, icon: targetProj.icon || '🤖' };
179
+ const prompt = String(fullMsg && fullMsg.payload && fullMsg.payload.prompt || '').trim();
180
+ const title = String(fullMsg && fullMsg.payload && fullMsg.payload.title || '').trim();
181
+ const now = new Date();
182
+ const timeStr = now.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
183
+ const dateStr = now.toISOString().slice(0, 10);
184
+ const taskId = String(envelope && envelope.task_id || '').trim();
185
+ const scopeId = String(envelope && envelope.scope_id || '').trim();
186
+ const isSharedTeamTask = !!(envelope && envelope.task_kind === 'team');
187
+
188
+ fsMod.writeFileSync(targetNowPath, buildPrivateNowContent({
189
+ actor, target, title, prompt, timeStr,
190
+ dispatchId: fullMsg.id, taskId, scopeId, chain: fullMsg.chain,
191
+ }), 'utf8');
192
+
193
+ if (!isSharedTeamTask) return { targetNowPath, sharedNowPath: null, tasksFilePath: null };
194
+
195
+ fsMod.writeFileSync(sharedNowPath, buildSharedNowContent({
196
+ actor, target, title, prompt, timeStr,
197
+ dispatchId: fullMsg.id, taskId, scopeId, chain: fullMsg.chain,
198
+ }), 'utf8');
199
+
200
+ try {
201
+ if (!fsMod.existsSync(sharedDir)) fsMod.mkdirSync(sharedDir, { recursive: true });
202
+ const taskLine = `- [${dateStr}] ${actor.icon} ${actor.name} → ${target.icon} ${target.name}: ${title || prompt.slice(0, 40)}`;
203
+ let tasksContent = fsMod.existsSync(tasksFilePath)
204
+ ? fsMod.readFileSync(tasksFilePath, 'utf8')
205
+ : '# 任务看板\n\n## 🔄 进行中\n\n## ✅ 已完成\n\n## 📅 待开始\n';
206
+ if (!tasksContent.includes(taskLine)) {
207
+ const lines = tasksContent.split('\n');
208
+ const nextLines = [];
209
+ let inserted = false;
210
+ let inProgress = false;
211
+ for (const line of lines) {
212
+ nextLines.push(line);
213
+ if (line.includes('## 🔄 进行中')) {
214
+ inProgress = true;
215
+ continue;
216
+ }
217
+ if (inProgress && line.startsWith('## ')) {
218
+ nextLines.splice(nextLines.length - 1, 0, taskLine);
219
+ inserted = true;
220
+ inProgress = false;
221
+ }
222
+ }
223
+ if (!inserted) nextLines.push(taskLine);
224
+ tasksContent = nextLines.join('\n');
225
+ fsMod.writeFileSync(tasksFilePath, tasksContent, 'utf8');
226
+ }
227
+ } catch (e) {
228
+ logWarn(`Failed to update shared task board: ${e.message}`);
229
+ }
230
+
231
+ return { targetNowPath, sharedNowPath, tasksFilePath };
232
+ }
233
+
115
234
  // ─────────────────────────────────────────────────────────────────────────────
116
235
  // Prompt enrichment (shared context injection)
117
236
  // ─────────────────────────────────────────────────────────────────────────────
118
237
 
119
238
  /**
120
239
  * Enrich a dispatch prompt with shared context read at send time:
121
- * 1. now/shared.md global progress whiteboard
240
+ * 1. now/<target>.md target private progress handoff
241
+ * 2. now/shared.md — global team progress whiteboard (only when includeShared=true)
122
242
  * 2. agents/<target>_latest.md — target's last output
123
243
  * 3. inbox/<target>/ — unread messages (archived to read/ after reading)
124
244
  *
@@ -129,20 +249,32 @@ function buildTeamRosterHint(parentKey, memberKey, projects) {
129
249
  * @param {string} [metameDir] - override METAME_DIR (for testing)
130
250
  * @returns {string}
131
251
  */
132
- function buildEnrichedPrompt(target, rawPrompt, metameDir) {
252
+ function buildEnrichedPrompt(target, rawPrompt, metameDir, opts = {}) {
133
253
  const base = metameDir || METAME_DIR;
254
+ const includeShared = !!(opts && opts.includeShared);
134
255
  let ctx = '';
135
256
 
136
- // 1. Shared progress whiteboard
257
+ // 1. Target private now file
137
258
  try {
138
- const nowFile = path.join(base, 'memory', 'now', 'shared.md');
139
- if (fs.existsSync(nowFile)) {
140
- const content = fs.readFileSync(nowFile, 'utf8').trim();
141
- if (content) ctx += `[共享进度 now.md]\n${content}\n\n`;
259
+ const targetNowFile = path.join(base, 'memory', 'now', `${target}.md`);
260
+ if (fs.existsSync(targetNowFile)) {
261
+ const content = fs.readFileSync(targetNowFile, 'utf8').trim();
262
+ if (content) ctx += `[当前进度 now/${target}.md]\n${content}\n\n`;
142
263
  }
143
264
  } catch { /* non-critical */ }
144
265
 
145
- // 2. Target's last output
266
+ // 2. Shared progress whiteboard for real team tasks only
267
+ if (includeShared) {
268
+ try {
269
+ const nowFile = path.join(base, 'memory', 'now', 'shared.md');
270
+ if (fs.existsSync(nowFile)) {
271
+ const content = fs.readFileSync(nowFile, 'utf8').trim();
272
+ if (content) ctx += `[共享进度 now/shared.md]\n${content}\n\n`;
273
+ }
274
+ } catch { /* non-critical */ }
275
+ }
276
+
277
+ // 3. Target's last output
146
278
  try {
147
279
  const latestFile = path.join(base, 'memory', 'agents', `${target}_latest.md`);
148
280
  if (fs.existsSync(latestFile)) {
@@ -151,7 +283,7 @@ function buildEnrichedPrompt(target, rawPrompt, metameDir) {
151
283
  }
152
284
  } catch { /* non-critical */ }
153
285
 
154
- // 3. Inbox unread messages (archive after reading)
286
+ // 4. Inbox unread messages (archive after reading)
155
287
  try {
156
288
  const inboxDir = path.join(base, 'memory', 'inbox', target);
157
289
  const readDir = path.join(inboxDir, 'read');
@@ -173,4 +305,11 @@ function buildEnrichedPrompt(target, rawPrompt, metameDir) {
173
305
  return ctx ? `${ctx}---\n${rawPrompt}` : rawPrompt;
174
306
  }
175
307
 
176
- module.exports = { resolveProjectKey, findTeamMember, buildTeamRosterHint, buildEnrichedPrompt };
308
+ module.exports = {
309
+ resolveProjectKey,
310
+ findTeamMember,
311
+ buildTeamRosterHint,
312
+ buildEnrichedPrompt,
313
+ resolveDispatchActor,
314
+ updateDispatchContextFiles,
315
+ };
@@ -1,50 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Agent Management Intent Module
5
- *
6
- * Detects when the user asks about creating, managing, or binding agents,
7
- * or asks about code structure / upgrade progress.
8
- *
9
- * @param {string} prompt
10
- * @returns {string|null}
11
- */
12
-
13
- const AGENT_MANAGE_PATTERNS = [
14
- // Creating / binding / managing agents
15
- /(?:创建|新建|添加|注册|绑定|配置|管理).{0,8}(?:agent|机器人|bot|智能体)/i,
16
- /(?:agent|bot|智能体).{0,8}(?:创建|新建|添加|注册|绑定|配置|管理)/i,
17
- // English
18
- /\b(?:create|add|register|bind|manage|setup|configure)\s+(?:an?\s+)?agent\b/i,
19
- ];
20
-
21
- const CODE_STRUCTURE_PATTERNS = [
22
- // Code structure / upgrade / script entry questions
23
- /(?:代码结构|脚本入口|升级进度|模块关系|文件结构)/,
24
- /(?:pointer.?map|架构图|入口文件)/i,
25
- ];
26
-
27
- module.exports = function detectAgentManage(prompt) {
28
- const isManage = AGENT_MANAGE_PATTERNS.some(re => re.test(prompt));
29
- const isStructure = CODE_STRUCTURE_PATTERNS.some(re => re.test(prompt));
30
-
31
- if (!isManage && !isStructure) return null;
32
-
33
- const hints = [];
34
-
35
- if (isManage) {
36
- hints.push(
37
- '[Agent 管理提示]',
38
- '- 创建/管理/绑定 Agent → 先 `cat ~/.metame/docs/agent-guide.md` 获取完整流程',
39
- );
40
- }
41
-
42
- if (isStructure) {
43
- hints.push(
44
- '[代码结构提示]',
45
- '- 代码结构/脚本入口/升级进度 → 先 `cat ~/.metame/docs/pointer-map.md` 获取索引',
46
- );
47
- }
48
-
49
- return hints.join('\n');
50
- };
@@ -1,28 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Hook Config Intent Module
5
- *
6
- * Detects when the user asks about hook/intent engine configuration.
7
- *
8
- * @param {string} prompt
9
- * @returns {string|null}
10
- */
11
-
12
- const HOOK_PATTERNS = [
13
- // Hook / intent configuration
14
- /(?:hook|intent|意图).{0,10}(?:配置|设置|开关|新增|添加|修改|怎么配|怎么设置|怎么改)/i,
15
- /(?:配置|设置|开关|新增|添加|修改).{0,10}(?:hook|intent|意图)/i,
16
- // Specific intent engine topics
17
- /intent.?engine/i,
18
- /意图引擎|意图模块/,
19
- ];
20
-
21
- module.exports = function detectHookConfig(prompt) {
22
- if (!HOOK_PATTERNS.some(re => re.test(prompt))) return null;
23
-
24
- return [
25
- '[Intent Engine 配置提示]',
26
- '- Hook/Intent 配置操作 → 先 `cat ~/.metame/docs/hook-config.md` 获取完整手册',
27
- ].join('\n');
28
- };