metame-cli 1.5.22 → 1.5.23

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/index.js CHANGED
@@ -540,11 +540,28 @@ if (syntaxErrors.length > 0) {
540
540
  const changed = syncDirFiles(group.srcDir, destDir, { fileList: group.fileList });
541
541
  return updated || changed;
542
542
  }, false);
543
- if (scriptsUpdated) {
543
+ if (scriptsUpdated) {
544
544
  console.log(`${icon("pkg")} Scripts synced to ~/.metame/.`);
545
545
  }
546
546
  }
547
547
 
548
+ try {
549
+ const runtimeEnvFile = path.join(METAME_DIR, 'runtime-env.json');
550
+ const runtimeNodeModules = path.join(__dirname, 'node_modules');
551
+ const runtimeEnvPayload = {
552
+ metameRoot: __dirname,
553
+ nodeModules: runtimeNodeModules,
554
+ generatedAt: new Date().toISOString(),
555
+ };
556
+ const nextContent = JSON.stringify(runtimeEnvPayload, null, 2) + '\n';
557
+ const prevContent = fs.existsSync(runtimeEnvFile) ? fs.readFileSync(runtimeEnvFile, 'utf8') : '';
558
+ if (prevContent !== nextContent) {
559
+ fs.writeFileSync(runtimeEnvFile, nextContent, 'utf8');
560
+ }
561
+ } catch (e) {
562
+ console.log(`${icon("warn")} Runtime env sync skipped: ${e.message}`);
563
+ }
564
+
548
565
  // Docs: lazy-load references for CLAUDE.md pointer instructions
549
566
  syncDirFiles(path.join(__dirname, 'scripts', 'docs'), path.join(METAME_DIR, 'docs'));
550
567
  // Bin: CLI tools (dispatch_to etc.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metame-cli",
3
- "version": "1.5.22",
3
+ "version": "1.5.23",
4
4
  "description": "The Cognitive Profile Layer for Claude Code. Knows how you think, not just what you said.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -39,6 +39,7 @@ function createAgentCommandHandler(deps) {
39
39
  loadSessionTags,
40
40
  sessionRichLabel,
41
41
  getSessionRecentContext,
42
+ getSessionRecentDialogue,
42
43
  pendingBinds,
43
44
  pendingAgentFlows,
44
45
  pendingTeamFlows,
@@ -421,11 +422,19 @@ function createAgentCommandHandler(deps) {
421
422
 
422
423
  // 读取最近对话片段,帮助确认是否切换到正确的 session
423
424
  const recentCtx = getSessionRecentContext ? getSessionRecentContext(targetSessionId) : null;
425
+ const recentDialogue = getSessionRecentDialogue ? getSessionRecentDialogue(targetSessionId, 4) : null;
424
426
  let msg = `✅ 已切换: **${label}**\n📁 ${path.basename(cwd)}`;
425
427
  if (selectedLogicalCurrent) {
426
428
  msg += '\n\n已恢复当前智能体会话。';
427
429
  }
428
- if (recentCtx) {
430
+ if (Array.isArray(recentDialogue) && recentDialogue.length > 0) {
431
+ msg += '\n\n最近对话:';
432
+ for (const item of recentDialogue) {
433
+ const marker = item.role === 'assistant' ? '🤖' : '👤';
434
+ const snippet = String(item.text || '').replace(/\n/g, ' ').slice(0, 120);
435
+ if (snippet) msg += `\n${marker} ${snippet}`;
436
+ }
437
+ } else if (recentCtx) {
429
438
  if (recentCtx.lastUser) {
430
439
  const snippet = recentCtx.lastUser.replace(/\n/g, ' ').slice(0, 80);
431
440
  msg += `\n\n💬 上次你说: _${snippet}${recentCtx.lastUser.length > 80 ? '…' : ''}_`;
@@ -30,6 +30,7 @@ function createSessionCommandHandler(deps) {
30
30
  sessionRichLabel,
31
31
  buildSessionCardElements,
32
32
  getSessionRecentContext,
33
+ getSessionRecentDialogue,
33
34
  getDefaultEngine = () => 'claude',
34
35
  } = deps;
35
36
 
@@ -430,11 +431,24 @@ function createSessionCommandHandler(deps) {
430
431
  const recentCtx = typeof getSessionRecentContext === 'function'
431
432
  ? getSessionRecentContext(target.sessionId)
432
433
  : null;
434
+ const recentDialogue = typeof getSessionRecentDialogue === 'function'
435
+ ? getSessionRecentDialogue(target.sessionId, 4)
436
+ : null;
433
437
  const title = target.customTitle || target.summary || target.sessionId.slice(0, 8);
434
438
  const lines = [`▶️ Resumed: ${title}`];
435
439
  if (attached.cwd) lines.push(`📁 ${path.basename(attached.cwd)}`);
436
- if (recentCtx && recentCtx.lastUser) lines.push(`👤 ${String(recentCtx.lastUser).replace(/\n/g, ' ').slice(0, 80)}`);
437
- if (recentCtx && recentCtx.lastAssistant) lines.push(`🤖 ${String(recentCtx.lastAssistant).replace(/\n/g, ' ').slice(0, 80)}`);
440
+ if (Array.isArray(recentDialogue) && recentDialogue.length > 0) {
441
+ lines.push('');
442
+ lines.push('最近对话:');
443
+ recentDialogue.forEach((item) => {
444
+ const marker = item.role === 'assistant' ? '🤖' : '👤';
445
+ const text = String(item.text || '').replace(/\n/g, ' ').slice(0, 120);
446
+ if (text) lines.push(`${marker} ${text}`);
447
+ });
448
+ } else {
449
+ if (recentCtx && recentCtx.lastUser) lines.push(`👤 ${String(recentCtx.lastUser).replace(/\n/g, ' ').slice(0, 80)}`);
450
+ if (recentCtx && recentCtx.lastAssistant) lines.push(`🤖 ${String(recentCtx.lastAssistant).replace(/\n/g, ' ').slice(0, 80)}`);
451
+ }
438
452
  await bot.sendMessage(chatId, lines.join('\n'));
439
453
  return true;
440
454
  }
@@ -352,6 +352,29 @@ function createSessionStore(deps) {
352
352
  return '';
353
353
  }
354
354
 
355
+ function extractRecentClaudeDialogueFromLines(lines, maxMessages = 4) {
356
+ const collected = [];
357
+ for (const line of lines) {
358
+ if (!line) continue;
359
+ try {
360
+ const d = JSON.parse(line);
361
+ if (d.type === 'user' && d.message && d.userType !== 'internal') {
362
+ const raw = _extractMessageText(d);
363
+ if (raw.length > 2) {
364
+ collected.push({ role: 'user', text: raw.slice(0, 160) });
365
+ }
366
+ } else if (d.type === 'assistant' && d.message) {
367
+ const raw = _extractMessageText(d);
368
+ if (raw.length > 2) {
369
+ collected.push({ role: 'assistant', text: raw.slice(0, 160) });
370
+ }
371
+ }
372
+ } catch { /* skip */ }
373
+ if (collected.length >= maxMessages) break;
374
+ }
375
+ return collected.reverse();
376
+ }
377
+
355
378
  function scanClaudeSessions() {
356
379
  try {
357
380
  if (!fs.existsSync(CLAUDE_PROJECTS_DIR)) return [];
@@ -638,6 +661,59 @@ function createSessionStore(deps) {
638
661
  }
639
662
  }
640
663
 
664
+ function parseCodexSessionRecentDialogue(sessionFile, maxMessages = 4) {
665
+ try {
666
+ if (!sessionFile || !fs.existsSync(sessionFile)) return [];
667
+ const lines = fs.readFileSync(sessionFile, 'utf8').split('\n').filter(Boolean);
668
+ const items = [];
669
+ let pendingAssistant = '';
670
+
671
+ function pushDialogueItem(role, text) {
672
+ const clean = String(text || '').trim();
673
+ if (!clean) return;
674
+ const clipped = clean.slice(0, 160);
675
+ const prev = items[items.length - 1];
676
+ if (prev && prev.role === role) {
677
+ prev.text = clipped;
678
+ return;
679
+ }
680
+ items.push({ role, text: clipped });
681
+ }
682
+
683
+ for (const line of lines) {
684
+ let entry;
685
+ try {
686
+ entry = JSON.parse(line);
687
+ } catch {
688
+ continue;
689
+ }
690
+ if (entry.type === 'response_item' && entry.payload && entry.payload.type === 'message') {
691
+ const role = String(entry.payload.role || '').toLowerCase();
692
+ if (role !== 'user' && role !== 'assistant') continue;
693
+ const text = stripCodexInjectedHints(extractCodexMessageText(entry.payload.content || entry.payload));
694
+ if (!text) continue;
695
+ if (role === 'user') {
696
+ if (pendingAssistant) {
697
+ pushDialogueItem('assistant', pendingAssistant);
698
+ pendingAssistant = '';
699
+ }
700
+ pushDialogueItem('user', text);
701
+ } else {
702
+ pendingAssistant = '';
703
+ pushDialogueItem('assistant', text);
704
+ }
705
+ } else if (entry.type === 'event_msg' && entry.payload && entry.payload.type === 'agent_message') {
706
+ const text = stripCodexInjectedHints(extractCodexMessageText(entry.payload.message));
707
+ if (text) pendingAssistant = text;
708
+ }
709
+ }
710
+ if (pendingAssistant) pushDialogueItem('assistant', pendingAssistant);
711
+ return items.slice(-maxMessages);
712
+ } catch {
713
+ return [];
714
+ }
715
+ }
716
+
641
717
  function enrichCodexSession(session) {
642
718
  if (!session || session._enriched) return session;
643
719
  try {
@@ -1173,6 +1249,32 @@ function createSessionStore(deps) {
1173
1249
  } catch { return null; }
1174
1250
  }
1175
1251
 
1252
+ function getSessionRecentDialogue(sessionId, maxMessages = 4) {
1253
+ try {
1254
+ const limit = Math.max(1, Math.min(Number(maxMessages) || 4, 8));
1255
+ const sessionFile = findSessionFile(sessionId);
1256
+ if (sessionFile) {
1257
+ const stat = fs.statSync(sessionFile);
1258
+ const tailSize = Math.min(262144, stat.size);
1259
+ const buf = Buffer.alloc(tailSize);
1260
+ const fd = fs.openSync(sessionFile, 'r');
1261
+ try {
1262
+ fs.readSync(fd, buf, 0, tailSize, stat.size - tailSize);
1263
+ } finally {
1264
+ fs.closeSync(fd);
1265
+ }
1266
+ const lines = buf.toString('utf8').split('\n').reverse();
1267
+ const dialogue = extractRecentClaudeDialogueFromLines(lines, limit);
1268
+ return dialogue.length ? dialogue : null;
1269
+ }
1270
+ const codexFile = findCodexSessionFile(sessionId);
1271
+ const dialogue = parseCodexSessionRecentDialogue(codexFile, limit);
1272
+ return dialogue.length ? dialogue : null;
1273
+ } catch {
1274
+ return null;
1275
+ }
1276
+ }
1277
+
1176
1278
  function markSessionStarted(chatId, engine) {
1177
1279
  const state = loadState();
1178
1280
  const s = state.sessions[chatId];
@@ -1368,6 +1470,7 @@ function createSessionStore(deps) {
1368
1470
  writeSessionName,
1369
1471
  markSessionStarted,
1370
1472
  getSessionRecentContext,
1473
+ getSessionRecentDialogue,
1371
1474
  isEngineSessionValid,
1372
1475
  getCodexSessionSandboxProfile,
1373
1476
  getCodexSessionPermissionMode,
@@ -1378,6 +1481,7 @@ function createSessionStore(deps) {
1378
1481
  stripCodexInjectedHints,
1379
1482
  looksLikeInternalCodexPrompt,
1380
1483
  parseCodexSessionPreview,
1484
+ parseCodexSessionRecentDialogue,
1381
1485
  buildPendingStateSessions,
1382
1486
  },
1383
1487
  };
package/scripts/daemon.js CHANGED
@@ -36,9 +36,11 @@ const fs = require('fs');
36
36
  const path = require('path');
37
37
  const os = require('os');
38
38
  const { execSync, execFileSync, execFile, spawn } = require('child_process');
39
+ const { bootstrapRuntimeModulePaths } = require('./runtime-bootstrap');
39
40
 
40
41
  const HOME = os.homedir();
41
42
  const METAME_DIR = path.join(HOME, '.metame');
43
+ bootstrapRuntimeModulePaths(METAME_DIR);
42
44
  const CONFIG_FILE = path.join(METAME_DIR, 'daemon.yaml');
43
45
  const STATE_FILE = path.join(METAME_DIR, 'daemon_state.json');
44
46
  const PID_FILE = path.join(METAME_DIR, 'daemon.pid');
@@ -1765,6 +1767,7 @@ const {
1765
1767
  sessionLabel,
1766
1768
  sessionRichLabel,
1767
1769
  getSessionRecentContext,
1770
+ getSessionRecentDialogue,
1768
1771
  buildSessionCardElements,
1769
1772
  getSession,
1770
1773
  getSessionForEngine,
@@ -2014,6 +2017,8 @@ const { handleSessionCommand } = createSessionCommandHandler({
2014
2017
  loadSessionTags,
2015
2018
  sessionRichLabel,
2016
2019
  buildSessionCardElements,
2020
+ getSessionRecentContext,
2021
+ getSessionRecentDialogue,
2017
2022
  sessionLabel,
2018
2023
  getDefaultEngine,
2019
2024
  });
@@ -2051,6 +2056,7 @@ const { spawnClaudeAsync, askClaude } = createClaudeEngine({
2051
2056
  findSessionFile,
2052
2057
  listRecentSessions,
2053
2058
  getSessionRecentContext,
2059
+ getSessionRecentDialogue,
2054
2060
  isEngineSessionValid,
2055
2061
  getCodexSessionSandboxProfile,
2056
2062
  getCodexSessionPermissionMode,
@@ -2122,6 +2128,7 @@ const { handleAgentCommand } = createAgentCommandHandler({
2122
2128
  loadSessionTags,
2123
2129
  sessionRichLabel,
2124
2130
  getSessionRecentContext,
2131
+ getSessionRecentDialogue,
2125
2132
  pendingBinds,
2126
2133
  pendingAgentFlows,
2127
2134
  pendingTeamFlows,
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('path');
4
+ const { bootstrapRuntimeModulePaths } = require('./runtime-bootstrap');
5
+
6
+ bootstrapRuntimeModulePaths(__dirname);
4
7
 
5
8
  let yaml;
6
9
  try {
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const Module = require('module');
7
+
8
+ function readRuntimeEnvFile(baseDir) {
9
+ const runtimeFile = path.join(baseDir, 'runtime-env.json');
10
+ try {
11
+ if (!fs.existsSync(runtimeFile)) return null;
12
+ return JSON.parse(fs.readFileSync(runtimeFile, 'utf8'));
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ function collectNodeModuleCandidates(baseDir) {
19
+ const candidates = [];
20
+ const runtimeEnv = readRuntimeEnvFile(baseDir);
21
+ const metameRoot = String(process.env.METAME_ROOT || runtimeEnv?.metameRoot || '').trim();
22
+ const runtimeNodeModules = String(runtimeEnv?.nodeModules || '').trim();
23
+
24
+ if (runtimeNodeModules) candidates.push(runtimeNodeModules);
25
+ if (metameRoot) candidates.push(path.join(metameRoot, 'node_modules'));
26
+
27
+ candidates.push(path.join(baseDir, 'node_modules'));
28
+ candidates.push(path.resolve(baseDir, '..', 'node_modules'));
29
+
30
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
31
+ if (home) candidates.push(path.join(home, '.metame', 'node_modules'));
32
+
33
+ return [...new Set(candidates.filter(Boolean))];
34
+ }
35
+
36
+ function bootstrapRuntimeModulePaths(baseDir = __dirname) {
37
+ const runtimeEnv = readRuntimeEnvFile(baseDir);
38
+ if (!process.env.METAME_ROOT && runtimeEnv?.metameRoot) {
39
+ process.env.METAME_ROOT = runtimeEnv.metameRoot;
40
+ }
41
+
42
+ const existingNodePath = String(process.env.NODE_PATH || '')
43
+ .split(path.delimiter)
44
+ .map(p => p.trim())
45
+ .filter(Boolean);
46
+
47
+ let updated = false;
48
+ for (const candidate of collectNodeModuleCandidates(baseDir)) {
49
+ try {
50
+ if (!fs.existsSync(candidate) || !fs.statSync(candidate).isDirectory()) continue;
51
+ } catch {
52
+ continue;
53
+ }
54
+ if (!existingNodePath.includes(candidate)) {
55
+ existingNodePath.unshift(candidate);
56
+ updated = true;
57
+ }
58
+ if (!Module.globalPaths.includes(candidate)) {
59
+ Module.globalPaths.unshift(candidate);
60
+ updated = true;
61
+ }
62
+ }
63
+
64
+ if (updated) {
65
+ process.env.NODE_PATH = existingNodePath.join(path.delimiter);
66
+ if (typeof Module._initPaths === 'function') Module._initPaths();
67
+ }
68
+
69
+ return {
70
+ metameRoot: process.env.METAME_ROOT || '',
71
+ nodePath: process.env.NODE_PATH || '',
72
+ };
73
+ }
74
+
75
+ module.exports = {
76
+ bootstrapRuntimeModulePaths,
77
+ };