evolclaw 3.1.0 → 3.1.1
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/dist/agents/claude-runner.js +40 -3
- package/dist/aun/msg/p2p.js +38 -0
- package/dist/channels/aun.js +80 -27
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -2
- package/dist/channels/qqbot.js +1 -0
- package/dist/channels/wechat.js +1 -0
- package/dist/channels/wecom.js +1 -0
- package/dist/cli/index.js +147 -29
- package/dist/cli/init.js +3 -4
- package/dist/cli/watch-msg.js +107 -30
- package/dist/config-store.js +45 -0
- package/dist/core/command-handler.js +86 -82
- package/dist/core/message/im-renderer.js +43 -4
- package/dist/core/message/message-bridge.js +4 -0
- package/dist/core/message/message-log.js +6 -1
- package/dist/core/message/message-processor.js +50 -34
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-manager.js +7 -3
- package/dist/core/trigger/manager.js +16 -0
- package/dist/core/trigger/parser.js +110 -0
- package/dist/core/trigger/scheduler.js +6 -0
- package/dist/index.js +49 -3
- package/dist/utils/error-utils.js +17 -13
- package/dist/utils/stats.js +216 -2
- package/kits/docs/evolclaw/MSG_PRIVATE.md +53 -6
- package/kits/rules/06-channel.md +30 -0
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -231,11 +231,53 @@ function reportOrphans(orphans) {
|
|
|
231
231
|
console.log(' 这些进程不属于当前 HOME 的实例登记簿,自动清理不会处理它们。');
|
|
232
232
|
console.log(' 使用 evolclaw restart --clear 一并清掉,或手动 kill。');
|
|
233
233
|
}
|
|
234
|
-
|
|
235
|
-
const
|
|
234
|
+
function formatLocalTime(ms) {
|
|
235
|
+
const d = new Date(ms);
|
|
236
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
|
|
237
|
+
}
|
|
238
|
+
function printStartupInfo() {
|
|
236
239
|
const pkgRoot = getPackageRoot();
|
|
237
240
|
const isNpmInstall = pkgRoot.includes('node_modules');
|
|
238
|
-
|
|
241
|
+
const cliRunsSource = !import.meta.url.includes('/dist/');
|
|
242
|
+
const daemonEntry = path.join(pkgRoot, 'dist', 'index.js');
|
|
243
|
+
const daemonRunsDist = fs.existsSync(daemonEntry);
|
|
244
|
+
const scanDir = path.join(pkgRoot, daemonRunsDist ? 'dist' : 'src');
|
|
245
|
+
let latestMtime = 0;
|
|
246
|
+
const scanRecursive = (dir) => {
|
|
247
|
+
try {
|
|
248
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
249
|
+
if (entry.name === 'node_modules')
|
|
250
|
+
continue;
|
|
251
|
+
const full = path.join(dir, entry.name);
|
|
252
|
+
if (entry.isDirectory()) {
|
|
253
|
+
scanRecursive(full);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
|
|
257
|
+
const mt = fs.statSync(full).mtimeMs;
|
|
258
|
+
if (mt > latestMtime)
|
|
259
|
+
latestMtime = mt;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch { }
|
|
264
|
+
};
|
|
265
|
+
scanRecursive(scanDir);
|
|
266
|
+
let version = '?';
|
|
267
|
+
try {
|
|
268
|
+
version = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf-8')).version;
|
|
269
|
+
}
|
|
270
|
+
catch { }
|
|
271
|
+
console.log(` EvolClaw v${version}`);
|
|
272
|
+
console.log(` 包路径: ${pkgRoot}`);
|
|
273
|
+
console.log(` 安装类型: ${isNpmInstall ? 'npm全局安装' : '开发仓(link)'}`);
|
|
274
|
+
console.log(` CLI执行: ${cliRunsSource ? '源码(tsx)' : '编译产物(dist)'}`);
|
|
275
|
+
console.log(` Daemon执行: ${daemonRunsDist ? '编译产物(dist)' : '未知'}`);
|
|
276
|
+
console.log(` 代码时间: ${latestMtime ? formatLocalTime(latestMtime) : '?'}`);
|
|
277
|
+
}
|
|
278
|
+
async function cmdStart() {
|
|
279
|
+
const cmdStartedAt = Date.now();
|
|
280
|
+
printStartupInfo();
|
|
239
281
|
const p = resolvePaths();
|
|
240
282
|
ensureDataDirs();
|
|
241
283
|
// 旧配置自动迁移(evolclaw.json → 新结构)
|
|
@@ -263,7 +305,7 @@ async function cmdStart() {
|
|
|
263
305
|
if (aliveMains.length > 0) {
|
|
264
306
|
const first = aliveMains[0];
|
|
265
307
|
console.log(`❌ EvolClaw is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
|
|
266
|
-
console.log(` 启动于: ${first.record.startedAtIso}`);
|
|
308
|
+
console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
|
|
267
309
|
console.log(` 启动方式: ${first.record.launchedBy}`);
|
|
268
310
|
// 报告 AID 状态
|
|
269
311
|
if (status.aidLastActivity.size > 0) {
|
|
@@ -444,9 +486,7 @@ async function cmdStop() {
|
|
|
444
486
|
}
|
|
445
487
|
async function cmdRestart(opts = {}) {
|
|
446
488
|
const cmdStartedAt = Date.now();
|
|
447
|
-
|
|
448
|
-
const isNpmInstall = pkgRoot.includes('node_modules');
|
|
449
|
-
console.log(`⏱ ${new Date().toLocaleString()} [${isNpmInstall ? 'pkg' : 'dev'}] ${pkgRoot}`);
|
|
489
|
+
printStartupInfo();
|
|
450
490
|
console.log('🔄 Restarting EvolClaw...');
|
|
451
491
|
// 版本检查与自动升级
|
|
452
492
|
console.log('📦 Checking for updates...');
|
|
@@ -1523,7 +1563,7 @@ function cmdWatch() {
|
|
|
1523
1563
|
}
|
|
1524
1564
|
const m = aliveMainEntries[0].record;
|
|
1525
1565
|
const uptime = formatTimeAgo(Date.now() - m.startedAt);
|
|
1526
|
-
console.log(`📦 Instance: PID ${m.pid} | 启动于 ${m.startedAtIso} (${uptime}) | via ${m.launchedBy}`);
|
|
1566
|
+
console.log(`📦 Instance: PID ${m.pid} | 启动于 ${new Date(m.startedAtIso).toLocaleString()} (${uptime}) | via ${m.launchedBy}`);
|
|
1527
1567
|
if (instStatus.aidLastActivity.size > 0) {
|
|
1528
1568
|
const now = Date.now();
|
|
1529
1569
|
const aidLines = [];
|
|
@@ -1773,6 +1813,11 @@ async function cmdWatchAid() {
|
|
|
1773
1813
|
const COL_LRECV = 10;
|
|
1774
1814
|
const COL_LSENT = 10;
|
|
1775
1815
|
const COL_PEERS = 5;
|
|
1816
|
+
// 表头跟随系统语言
|
|
1817
|
+
const isChinese = (process.env.LANG || process.env.LC_ALL || process.env.LANGUAGE || Intl.DateTimeFormat().resolvedOptions().locale || '').toLowerCase().includes('zh');
|
|
1818
|
+
const HEADERS = isChinese
|
|
1819
|
+
? { aid: 'AID', status: '状态', uptime: '运行', state: '工作', reconn: '重连', recv: '收', sent: '发', sys: '系统', bin: '入流量', bout: '出流量', lrecv: '最后收', lsent: '最后发', peers: '对端' }
|
|
1820
|
+
: { aid: 'AID', status: 'STATUS', uptime: 'UPTIME', state: 'STATE', reconn: 'RECONN', recv: 'RECV', sent: 'SENT', sys: 'SYS R/S', bin: 'BYTES IN', bout: 'BYTES OUT', lrecv: 'LAST RECV', lsent: 'LAST SENT', peers: 'PEERS' };
|
|
1776
1821
|
function formatDuration(ms) {
|
|
1777
1822
|
const sec = Math.floor(ms / 1000);
|
|
1778
1823
|
if (sec < 60)
|
|
@@ -1790,19 +1835,19 @@ async function cmdWatchAid() {
|
|
|
1790
1835
|
}
|
|
1791
1836
|
function renderHeader() {
|
|
1792
1837
|
return ' ' +
|
|
1793
|
-
padRight(
|
|
1794
|
-
padRight(
|
|
1795
|
-
padRight(
|
|
1796
|
-
padRight(
|
|
1797
|
-
padRight(
|
|
1798
|
-
padRight(
|
|
1799
|
-
padRight(
|
|
1800
|
-
padRight(
|
|
1801
|
-
padRight(
|
|
1802
|
-
padRight(
|
|
1803
|
-
padRight(
|
|
1804
|
-
padRight(
|
|
1805
|
-
padRight(
|
|
1838
|
+
padRight(HEADERS.aid, COL_AID) +
|
|
1839
|
+
padRight(HEADERS.status, COL_STATUS) +
|
|
1840
|
+
padRight(HEADERS.uptime, COL_UPTIME) +
|
|
1841
|
+
padRight(HEADERS.state, COL_STATE) +
|
|
1842
|
+
padRight(HEADERS.reconn, COL_RECONN) +
|
|
1843
|
+
padRight(HEADERS.recv, COL_RECV) +
|
|
1844
|
+
padRight(HEADERS.sent, COL_SENT) +
|
|
1845
|
+
padRight(HEADERS.sys, COL_SYS) +
|
|
1846
|
+
padRight(HEADERS.bin, COL_BIN) +
|
|
1847
|
+
padRight(HEADERS.bout, COL_BOUT) +
|
|
1848
|
+
padRight(HEADERS.lrecv, COL_LRECV) +
|
|
1849
|
+
padRight(HEADERS.lsent, COL_LSENT) +
|
|
1850
|
+
padRight(HEADERS.peers, COL_PEERS);
|
|
1806
1851
|
}
|
|
1807
1852
|
function renderRow(aid, stats, projectPath) {
|
|
1808
1853
|
const aidLabel = aid.aid.length > COL_AID - 2 ? aid.aid.slice(0, COL_AID - 4) + '..' : aid.aid;
|
|
@@ -1841,6 +1886,13 @@ async function cmdWatchAid() {
|
|
|
1841
1886
|
const nameReset = refreshedAids.has(aid.aid) ? '' : RST;
|
|
1842
1887
|
const BLUE = useColor ? '\x1b[34m' : '';
|
|
1843
1888
|
const ORANGE = useColor ? '\x1b[38;5;208m' : '';
|
|
1889
|
+
const MAGENTA = useColor ? '\x1b[35m' : '';
|
|
1890
|
+
// 标记生成:[明文/密文|自主/响应](紫色=工具渲染标记)
|
|
1891
|
+
const mkTags = (encrypt, chatmode) => {
|
|
1892
|
+
const enc = encrypt ? '密文' : '明文';
|
|
1893
|
+
const mode = chatmode === 'proactive' ? '自主' : '响应';
|
|
1894
|
+
return `${MAGENTA}[${enc}|${mode}]${RST}`;
|
|
1895
|
+
};
|
|
1844
1896
|
let msgPreview = '';
|
|
1845
1897
|
if (stats?.lastReceivedAt || stats?.lastSentAt) {
|
|
1846
1898
|
const recvTs = stats.lastReceivedAt ?? 0;
|
|
@@ -1851,13 +1903,53 @@ async function cmdWatchAid() {
|
|
|
1851
1903
|
}
|
|
1852
1904
|
else if (stats.lastSentText) {
|
|
1853
1905
|
const toShort = stats.lastSentTo ? stats.lastSentTo.split('.')[0] : '';
|
|
1854
|
-
|
|
1906
|
+
const tags = mkTags(stats.lastSentEncrypt, stats.lastSentChatmode);
|
|
1907
|
+
// task 进行中时也显示计数(processing > 0 说明还在跑)
|
|
1908
|
+
const isWorking = (stats.processing ?? 0) > 0;
|
|
1909
|
+
const taskEnd = stats?.lastTaskEnd;
|
|
1910
|
+
const counts = isWorking && taskEnd
|
|
1911
|
+
? `${MAGENTA}[大模型${taskEnd.numTurns}|调用${taskEnd.toolUseCount}|thought${taskEnd.thoughtPutCount}|msg${taskEnd.replyCount}]${RST}`
|
|
1912
|
+
: '';
|
|
1913
|
+
msgPreview = `${BLUE}↑${tags}${counts} ${toShort ? `${ORANGE}${toShort}${RST}${BLUE}: ` : ''}${stats.lastSentText.replace(/\n/g, ' ').slice(0, 60)}${RST}`;
|
|
1855
1914
|
}
|
|
1856
1915
|
else if (stats.lastReceivedText) {
|
|
1857
1916
|
const fromShort = stats.lastReceivedFrom ? stats.lastReceivedFrom.split('.')[0] : '';
|
|
1858
1917
|
msgPreview = `${GREEN}↓ ${fromShort ? `${ORANGE}${fromShort}${RST}${GREEN}: ` : ''}${stats.lastReceivedText.replace(/\n/g, ' ').slice(0, 60)}${RST}`;
|
|
1859
1918
|
}
|
|
1860
1919
|
}
|
|
1920
|
+
// 任务结束状态覆盖:仅当 taskEnd 比最后收发都新时才覆盖
|
|
1921
|
+
const taskEnd = stats?.lastTaskEnd;
|
|
1922
|
+
if (taskEnd && taskEnd.ts >= (stats?.lastSentAt ?? 0) && taskEnd.ts >= (stats?.lastReceivedAt ?? 0)) {
|
|
1923
|
+
const tags = mkTags(taskEnd.encrypt, taskEnd.chatmode);
|
|
1924
|
+
// 计数标记: [大模型N|调用N|thoughtN(streamN)|msgN]
|
|
1925
|
+
const thoughtLabel = taskEnd.thoughtPutCount > 0
|
|
1926
|
+
? `thought${taskEnd.numTurns}(stream${taskEnd.thoughtPutCount})`
|
|
1927
|
+
: `thought${taskEnd.numTurns}`;
|
|
1928
|
+
const counts = `${MAGENTA}[大模型${taskEnd.numTurns}|调用${taskEnd.toolUseCount}|${thoughtLabel}|msg${taskEnd.replyCount}]${RST}`;
|
|
1929
|
+
if (taskEnd.status === 'error') {
|
|
1930
|
+
msgPreview = `${RED}${tags}${counts} 错误: ${taskEnd.errorType ?? '未知错误'}${RST}`;
|
|
1931
|
+
}
|
|
1932
|
+
else if (taskEnd.sentDuringTask) {
|
|
1933
|
+
// 有 message.send:蓝色加粗 + 内容
|
|
1934
|
+
const toShort = stats?.lastSentTo ? stats.lastSentTo.split('.')[0] : '';
|
|
1935
|
+
const textPreview = stats?.lastSentText ? stats.lastSentText.replace(/\n/g, ' ').slice(0, 60) : '';
|
|
1936
|
+
msgPreview = `${BOLD}${BLUE}↑${tags}${counts} ${toShort ? `${ORANGE}${toShort}${RST}${BOLD}${BLUE}: ` : ''}${textPreview}${RST}`;
|
|
1937
|
+
}
|
|
1938
|
+
else if (taskEnd.thoughtDuringTask) {
|
|
1939
|
+
// 只有 thought:普通蓝色 + thought 内容
|
|
1940
|
+
const textPreview = taskEnd.lastThoughtText
|
|
1941
|
+
? taskEnd.lastThoughtText.replace(/\n/g, ' ').slice(0, 60)
|
|
1942
|
+
: (taskEnd.finalText ? taskEnd.finalText.replace(/\n/g, ' ').slice(0, 60) : '');
|
|
1943
|
+
msgPreview = `${BLUE}↑${tags}${counts} ${textPreview}${RST}`;
|
|
1944
|
+
}
|
|
1945
|
+
else {
|
|
1946
|
+
// 既没 send 也没 thought
|
|
1947
|
+
const textPreview = taskEnd.finalText
|
|
1948
|
+
? taskEnd.finalText.replace(/\n/g, ' ').slice(0, 60)
|
|
1949
|
+
: '(无输出)';
|
|
1950
|
+
msgPreview = `${ORANGE}${tags}${counts} ${textPreview}${RST}`;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1861
1953
|
const subLine1 = ` ${nameColor}${namePart}${nameReset}${msgPreview ? ' ' + msgPreview : ''}`;
|
|
1862
1954
|
const dirLabel = projectPath || '—';
|
|
1863
1955
|
const subLine2 = `${DIM} ${dirLabel}${RST}`;
|
|
@@ -2698,7 +2790,7 @@ async function cmdCtl(args) {
|
|
|
2698
2790
|
async function cmdAgent(args) {
|
|
2699
2791
|
const sub = args[0];
|
|
2700
2792
|
const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
|
|
2701
|
-
if (sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
|
|
2793
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
|
|
2702
2794
|
console.log(`用法: evolclaw agent <command>
|
|
2703
2795
|
|
|
2704
2796
|
Commands:
|
|
@@ -2750,17 +2842,43 @@ Options:
|
|
|
2750
2842
|
console.log('No agents configured.');
|
|
2751
2843
|
return;
|
|
2752
2844
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2845
|
+
// 表头跟随系统语言
|
|
2846
|
+
const isChinese = (process.env.LANG || process.env.LC_ALL || process.env.LANGUAGE || Intl.DateTimeFormat().resolvedOptions().locale || '').toLowerCase().includes('zh');
|
|
2847
|
+
const headers = isChinese
|
|
2848
|
+
? { name: '名称', status: '状态', channels: '渠道', project: '项目', baseagent: '基座', lastActive: '最后活跃' }
|
|
2849
|
+
: { name: 'NAME', status: 'STATUS', channels: 'CHANNELS', project: 'PROJECT', baseagent: 'BASEAGENT', lastActive: 'LAST ACTIVE' };
|
|
2850
|
+
// 计算各列实际需要的宽度
|
|
2851
|
+
let maxNameLen = headers.name.length;
|
|
2852
|
+
let maxStatusLen = headers.status.length;
|
|
2853
|
+
let maxChannelsLen = headers.channels.length;
|
|
2854
|
+
let maxProjectLen = headers.project.length;
|
|
2855
|
+
let maxBaseagentLen = headers.baseagent.length;
|
|
2856
|
+
for (const info of result.agents) {
|
|
2857
|
+
maxNameLen = Math.max(maxNameLen, info.name.length);
|
|
2858
|
+
maxStatusLen = Math.max(maxStatusLen, (info.status || 'stopped').length);
|
|
2859
|
+
const channelsStr = info.channels?.length > 0 ? info.channels.join(', ') : '—';
|
|
2860
|
+
maxChannelsLen = Math.max(maxChannelsLen, channelsStr.length);
|
|
2861
|
+
const projectStr = info.projectPath ? path.basename(info.projectPath) : '—';
|
|
2862
|
+
maxProjectLen = Math.max(maxProjectLen, projectStr.length);
|
|
2863
|
+
maxBaseagentLen = Math.max(maxBaseagentLen, (info.baseagent || '—').length);
|
|
2864
|
+
}
|
|
2865
|
+
// 加 2 作为列间距
|
|
2866
|
+
const colName = maxNameLen + 2;
|
|
2867
|
+
const colStatus = maxStatusLen + 2;
|
|
2868
|
+
const colChannels = maxChannelsLen + 1;
|
|
2869
|
+
const colProject = maxProjectLen + 2;
|
|
2870
|
+
const colBaseagent = maxBaseagentLen + 2;
|
|
2871
|
+
console.log(headers.name.padEnd(colName) + headers.status.padEnd(colStatus) + headers.channels.padEnd(colChannels) +
|
|
2872
|
+
headers.project.padEnd(colProject) + headers.baseagent.padEnd(colBaseagent) + headers.lastActive);
|
|
2755
2873
|
for (const info of result.agents) {
|
|
2756
2874
|
const name = info.name;
|
|
2757
2875
|
const status = info.status || 'stopped';
|
|
2758
|
-
const channels = info.channels?.length > 0 ? info.channels.join(', ')
|
|
2876
|
+
const channels = info.channels?.length > 0 ? info.channels.join(', ') : '—';
|
|
2759
2877
|
const project = info.projectPath ? path.basename(info.projectPath) : '—';
|
|
2760
2878
|
const baseagent = info.baseagent || '—';
|
|
2761
2879
|
const lastActive = info.lastActivity ? formatTimeAgo(Date.now() - info.lastActivity) : '—';
|
|
2762
|
-
console.log(name.padEnd(
|
|
2763
|
-
project.padEnd(
|
|
2880
|
+
console.log(name.padEnd(colName) + status.padEnd(colStatus) + channels.padEnd(colChannels) +
|
|
2881
|
+
project.padEnd(colProject) + baseagent.padEnd(colBaseagent) + lastActive);
|
|
2764
2882
|
}
|
|
2765
2883
|
return;
|
|
2766
2884
|
}
|
|
@@ -3161,7 +3279,7 @@ async function cmdAid(args) {
|
|
|
3161
3279
|
const sub = args[0] || 'list';
|
|
3162
3280
|
const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
|
|
3163
3281
|
const aunPath = resolveAunPath(args);
|
|
3164
|
-
if (sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
|
|
3282
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
|
|
3165
3283
|
console.log(`用法: evolclaw aid <command>
|
|
3166
3284
|
|
|
3167
3285
|
Commands:
|
package/dist/cli/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
2
|
import readline from 'readline';
|
|
4
3
|
import { resolvePaths, ensureDataDirs } from '../paths.js';
|
|
5
4
|
import { commandExists } from '../utils/cross-platform.js';
|
|
6
5
|
import { scanInstances } from '../utils/instance-registry.js';
|
|
6
|
+
import { saveDefaultsSafe } from '../config-store.js';
|
|
7
7
|
// ==================== Helpers ====================
|
|
8
8
|
function ask(rl, question) {
|
|
9
9
|
return new Promise(resolve => rl.question(question, resolve));
|
|
@@ -28,9 +28,8 @@ function buildDefaults(chosen) {
|
|
|
28
28
|
baseagents: { [chosen]: env ? { apiKey: `$ENV:${env}` } : {} },
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
function writeDefaults(
|
|
32
|
-
|
|
33
|
-
fs.writeFileSync(defaultsPath, JSON.stringify(buildDefaults(chosen), null, 2) + '\n');
|
|
31
|
+
function writeDefaults(_defaultsPath, chosen) {
|
|
32
|
+
saveDefaultsSafe(buildDefaults(chosen));
|
|
34
33
|
}
|
|
35
34
|
// ==================== Main ====================
|
|
36
35
|
export async function cmdInit(options) {
|
package/dist/cli/watch-msg.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { resolvePaths
|
|
3
|
+
import { resolvePaths } from '../paths.js';
|
|
4
4
|
import { decodeDirSegment, readAllJsonlLines } from '../core/session/session-fs-store.js';
|
|
5
5
|
// ==================== ANSI ====================
|
|
6
6
|
const isTTY = !!process.stdout.isTTY;
|
|
@@ -84,6 +84,34 @@ function formatTimeAgo(ms) {
|
|
|
84
84
|
return `${hour}h`;
|
|
85
85
|
return `${Math.floor(hour / 24)}d`;
|
|
86
86
|
}
|
|
87
|
+
function getCodeTime(pkgRoot) {
|
|
88
|
+
let latestMtime = 0;
|
|
89
|
+
const scanDir = fs.existsSync(path.join(pkgRoot, 'dist')) ? path.join(pkgRoot, 'dist') : path.join(pkgRoot, 'src');
|
|
90
|
+
const scanRecursive = (dir) => {
|
|
91
|
+
try {
|
|
92
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
93
|
+
if (entry.name === 'node_modules')
|
|
94
|
+
continue;
|
|
95
|
+
const full = path.join(dir, entry.name);
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
scanRecursive(full);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
|
|
101
|
+
const mt = fs.statSync(full).mtimeMs;
|
|
102
|
+
if (mt > latestMtime)
|
|
103
|
+
latestMtime = mt;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
};
|
|
109
|
+
scanRecursive(scanDir);
|
|
110
|
+
if (!latestMtime)
|
|
111
|
+
return '?';
|
|
112
|
+
const d = new Date(latestMtime);
|
|
113
|
+
return `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
114
|
+
}
|
|
87
115
|
function formatNumber(n) {
|
|
88
116
|
return n.toLocaleString('en-US');
|
|
89
117
|
}
|
|
@@ -267,43 +295,71 @@ function renderStatsPanel(state, width, height) {
|
|
|
267
295
|
// ==================== Messages Panel ====================
|
|
268
296
|
function renderMessagesPanel(state, width, height) {
|
|
269
297
|
const lines = [];
|
|
270
|
-
const
|
|
298
|
+
const lastTs = state.messages.length > 0 ? state.messages[state.messages.length - 1].ts : 0;
|
|
299
|
+
const lastTimeStr = lastTs ? formatDateTime(lastTs) : '--';
|
|
300
|
+
const title = `${DIM}─ Messages (${state.messages.length}, last: ${lastTimeStr}) ─${RST}`;
|
|
271
301
|
lines.push(padRight(title, width));
|
|
272
302
|
const contentHeight = height - 1;
|
|
273
303
|
const msgs = state.messages;
|
|
274
304
|
const totalMsgs = msgs.length;
|
|
275
|
-
const visibleCount = contentHeight;
|
|
276
|
-
const startIdx = Math.max(0, totalMsgs - visibleCount - state.messageScrollOffset);
|
|
277
|
-
const endIdx = Math.min(totalMsgs, startIdx + visibleCount);
|
|
278
|
-
const scrollbar = renderScrollbar(totalMsgs, visibleCount, state.messageScrollOffset, contentHeight);
|
|
279
305
|
const msgWidth = width - 3;
|
|
280
306
|
const contentLineWidth = msgWidth - 2;
|
|
281
307
|
const maxContentLines = 3;
|
|
282
|
-
|
|
283
|
-
|
|
308
|
+
// 先构造每条消息的渲染行,从最末尾的可见消息往回收集,直到填满 contentHeight
|
|
309
|
+
function renderOneMsg(m) {
|
|
284
310
|
const time = formatDateTime(m.ts);
|
|
285
311
|
const dir = m.dir === 'in' ? `${GREEN}↓${RST}` : `${BLUE}↑${RST}`;
|
|
286
312
|
const isGroup = m.chatType === 'group';
|
|
287
|
-
const chatTag = isGroup ? `${MAGENTA}[群聊]${RST}
|
|
313
|
+
const chatTag = isGroup ? `${MAGENTA}[群聊]${RST}` : '';
|
|
314
|
+
const encLabel = m.encrypt ? '密文' : '明文';
|
|
315
|
+
const modeLabel = m.chatmode === 'proactive' ? '自主' : '响应';
|
|
316
|
+
const metaTags = (m.encrypt != null || m.chatmode) ? `${MAGENTA}[${encLabel}|${modeLabel}]${RST}` : '';
|
|
317
|
+
let typeTag = '';
|
|
318
|
+
if (m.dir === 'out') {
|
|
319
|
+
const source = m.source === 'cli' ? 'cli' : 'daemon';
|
|
320
|
+
const method = m.msgType === 'thought' ? 'thought' : 'send';
|
|
321
|
+
typeTag = `${DIM}[${source}|${method}]${RST}`;
|
|
322
|
+
}
|
|
288
323
|
const byteLen = Buffer.byteLength(m.content, 'utf-8');
|
|
289
324
|
const lenTag = `${DIM}${formatNumber(byteLen)}B${RST}`;
|
|
290
|
-
const fromDisplay = isGroup && m.groupId && m.dir === 'in' ? m.groupId : m.from;
|
|
291
|
-
const toDisplay = isGroup && m.groupId && m.dir === 'out' ? m.groupId : m.to;
|
|
292
|
-
const header = `${DIM}${time}${RST} ${dir}${chatTag} ${ORANGE}${fromDisplay}${RST}${DIM}→${RST}${GREEN}${toDisplay}${RST} ${lenTag}`;
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
325
|
+
const fromDisplay = isGroup && m.groupId && m.dir === 'in' ? m.groupId : m.from.split('.')[0];
|
|
326
|
+
const toDisplay = isGroup && m.groupId && m.dir === 'out' ? m.groupId : m.to.split('.')[0];
|
|
327
|
+
const header = `${DIM}${time}${RST} ${dir}${chatTag}${metaTags}${typeTag} ${ORANGE}${fromDisplay}${RST}${DIM}→${RST}${GREEN}${toDisplay}${RST} ${lenTag}`;
|
|
328
|
+
const out = [padRight(header, msgWidth)];
|
|
329
|
+
const rawContent = m.content.replace(/\n/g, ' ');
|
|
330
|
+
const wrappedLines = wrapText(rawContent, contentLineWidth, maxContentLines);
|
|
331
|
+
for (const wl of wrappedLines) {
|
|
332
|
+
out.push(padRight(` ${wl}`, msgWidth));
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
// 从 endIdx-1 开始倒序,往回累积,直到行数填满 contentHeight
|
|
337
|
+
const endIdx = Math.max(0, totalMsgs - state.messageScrollOffset);
|
|
338
|
+
const collected = []; // 每条消息的行数组
|
|
339
|
+
let totalLines = 0;
|
|
340
|
+
let firstShownIdx = endIdx; // 首条可见消息的下标
|
|
341
|
+
for (let i = endIdx - 1; i >= 0; i--) {
|
|
342
|
+
const rendered = renderOneMsg(msgs[i]);
|
|
343
|
+
if (totalLines + rendered.length > contentHeight && collected.length > 0)
|
|
344
|
+
break;
|
|
345
|
+
collected.unshift(rendered);
|
|
346
|
+
totalLines += rendered.length;
|
|
347
|
+
firstShownIdx = i;
|
|
348
|
+
if (totalLines >= contentHeight)
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
const visibleMsgCount = endIdx - firstShownIdx;
|
|
352
|
+
const scrollbar = renderScrollbar(totalMsgs, visibleMsgCount, state.messageScrollOffset, contentHeight);
|
|
353
|
+
// 正序输出(旧→新)
|
|
354
|
+
for (const rendered of collected) {
|
|
355
|
+
for (const line of rendered) {
|
|
356
|
+
if (lines.length - 1 >= contentHeight)
|
|
357
|
+
break;
|
|
358
|
+
const sbIdx = lines.length - 1;
|
|
359
|
+
lines.push(`${line} ${scrollbar[sbIdx] || ' '}`);
|
|
306
360
|
}
|
|
361
|
+
if (lines.length - 1 >= contentHeight)
|
|
362
|
+
break;
|
|
307
363
|
}
|
|
308
364
|
while (lines.length < height) {
|
|
309
365
|
const sbIdx = lines.length - 1;
|
|
@@ -314,11 +370,11 @@ function renderMessagesPanel(state, width, height) {
|
|
|
314
370
|
// ==================== Main Render ====================
|
|
315
371
|
function renderFrame(state) {
|
|
316
372
|
const cols = process.stdout.columns || 120;
|
|
317
|
-
const rows = (process.stdout.rows || 40)
|
|
373
|
+
const rows = (process.stdout.rows || 40);
|
|
374
|
+
const bodyHeight = rows - 4;
|
|
318
375
|
const leftW = Math.max(20, Math.floor(cols * 0.20));
|
|
319
376
|
const midW = Math.max(24, Math.floor(cols * 0.22));
|
|
320
377
|
const rightW = Math.max(40, cols - leftW - midW - 4);
|
|
321
|
-
const bodyHeight = rows - 2;
|
|
322
378
|
const leftLines = renderScopePanel(state, leftW, bodyHeight);
|
|
323
379
|
const midLines = renderStatsPanel(state, midW, bodyHeight);
|
|
324
380
|
const msgLines = renderMessagesPanel(state, rightW, bodyHeight);
|
|
@@ -334,11 +390,11 @@ function renderFrame(state) {
|
|
|
334
390
|
}
|
|
335
391
|
const bottomBorder = `${DIM}├${'─'.repeat(leftW)}┴${'─'.repeat(midW)}┴${'─'.repeat(rightW + 1)}┤${RST}`;
|
|
336
392
|
buf += `\x1b[2K${bottomBorder}\n`;
|
|
337
|
-
const
|
|
338
|
-
const helpLine = `${DIM}│
|
|
393
|
+
const helpText = `Tab: panel ↑↓: nav Enter: select Backspace: back ESC: exit`;
|
|
394
|
+
const helpLine = `${DIM}│ ${helpText.slice(0, cols - 4)} ${RST}`;
|
|
339
395
|
buf += `\x1b[2K${helpLine}\n`;
|
|
340
396
|
const closeBorder = `${DIM}└${'─'.repeat(cols - 2)}┘${RST}`;
|
|
341
|
-
buf += `\x1b[2K${closeBorder}
|
|
397
|
+
buf += `\x1b[2K${closeBorder}`;
|
|
342
398
|
return buf;
|
|
343
399
|
}
|
|
344
400
|
// ==================== Main ====================
|
|
@@ -409,6 +465,7 @@ export async function cmdWatchMsg() {
|
|
|
409
465
|
if (!state.selectedLocalAid)
|
|
410
466
|
return;
|
|
411
467
|
state.peers = loadPeerInfos(aunDir, state.selectedLocalAid);
|
|
468
|
+
const prevCount = state.messages.length;
|
|
412
469
|
if (state.selectedPeer) {
|
|
413
470
|
state.messages = readMessages(aunDir, state.selectedLocalAid, state.selectedPeer);
|
|
414
471
|
if (state.messages.length > 1000)
|
|
@@ -417,6 +474,10 @@ export async function cmdWatchMsg() {
|
|
|
417
474
|
else {
|
|
418
475
|
state.messages = loadAllMessages(aunDir, state.selectedLocalAid);
|
|
419
476
|
}
|
|
477
|
+
// 有新消息时自动滚到底部
|
|
478
|
+
if (state.messages.length > prevCount) {
|
|
479
|
+
state.messageScrollOffset = 0;
|
|
480
|
+
}
|
|
420
481
|
// Also refresh scope stats for the selected AID
|
|
421
482
|
const idx = state.localAids.findIndex(a => a.aid === state.selectedLocalAid);
|
|
422
483
|
if (idx >= 0) {
|
|
@@ -431,6 +492,7 @@ export async function cmdWatchMsg() {
|
|
|
431
492
|
watcher.close();
|
|
432
493
|
watcher = null;
|
|
433
494
|
}
|
|
495
|
+
clearInterval(pollTimer);
|
|
434
496
|
if (process.stdin.isTTY)
|
|
435
497
|
try {
|
|
436
498
|
process.stdin.setRawMode(false);
|
|
@@ -579,6 +641,21 @@ export async function cmdWatchMsg() {
|
|
|
579
641
|
loadScope();
|
|
580
642
|
process.stdout.write('\x1b[?25l\x1b[2J\x1b[H');
|
|
581
643
|
render();
|
|
644
|
+
// 定时轮询:5 秒检查一次,有变化才刷新
|
|
645
|
+
let lastMsgCount = state.messages.length;
|
|
646
|
+
let lastMsgTs = state.messages.length > 0 ? state.messages[state.messages.length - 1].ts : 0;
|
|
647
|
+
const pollTimer = setInterval(() => {
|
|
648
|
+
if (!state.selectedLocalAid)
|
|
649
|
+
return;
|
|
650
|
+
refreshData();
|
|
651
|
+
const newCount = state.messages.length;
|
|
652
|
+
const newTs = newCount > 0 ? state.messages[newCount - 1].ts : 0;
|
|
653
|
+
if (newCount !== lastMsgCount || newTs !== lastMsgTs) {
|
|
654
|
+
lastMsgCount = newCount;
|
|
655
|
+
lastMsgTs = newTs;
|
|
656
|
+
render();
|
|
657
|
+
}
|
|
658
|
+
}, 5000);
|
|
582
659
|
if (process.stdin.isTTY) {
|
|
583
660
|
process.stdin.setRawMode(true);
|
|
584
661
|
process.stdin.resume();
|
package/dist/config-store.js
CHANGED
|
@@ -75,8 +75,53 @@ export function loadDefaults() {
|
|
|
75
75
|
return expandEnvRefs(raw);
|
|
76
76
|
}
|
|
77
77
|
export function saveDefaults(value) {
|
|
78
|
+
backupDefaults(resolvePaths().defaultsConfig);
|
|
78
79
|
atomicWriteJson(resolvePaths().defaultsConfig, value);
|
|
79
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* 备份 defaults.json 为 defaults_YYYYMMDDhhmmss.json。文件不存在时为 no-op。
|
|
83
|
+
* 同秒重复调用会被覆盖(同一秒内的内容相同,可接受)。
|
|
84
|
+
*/
|
|
85
|
+
function backupDefaults(filePath) {
|
|
86
|
+
if (!fs.existsSync(filePath))
|
|
87
|
+
return;
|
|
88
|
+
const now = new Date();
|
|
89
|
+
const ts = now.getFullYear().toString()
|
|
90
|
+
+ String(now.getMonth() + 1).padStart(2, '0')
|
|
91
|
+
+ String(now.getDate()).padStart(2, '0')
|
|
92
|
+
+ String(now.getHours()).padStart(2, '0')
|
|
93
|
+
+ String(now.getMinutes()).padStart(2, '0')
|
|
94
|
+
+ String(now.getSeconds()).padStart(2, '0');
|
|
95
|
+
const backupPath = path.join(path.dirname(filePath), `defaults_${ts}.json`);
|
|
96
|
+
try {
|
|
97
|
+
fs.copyFileSync(filePath, backupPath);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
logger.warn(`[config] backup failed: ${backupPath}: ${e}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 安全写入 defaults.json:备份现有文件 → 深合并 patch → 原子写入。
|
|
105
|
+
*
|
|
106
|
+
* 与 saveDefaults() 不同,本函数保留现有字段,仅覆盖 patch 中显式指定的字段。
|
|
107
|
+
* 适用场景:evolclaw init 仅修改 active_baseagent/baseagents 时,不应丢失 chatmode/projects 等其它字段。
|
|
108
|
+
*/
|
|
109
|
+
export function saveDefaultsSafe(patch) {
|
|
110
|
+
const p = resolvePaths().defaultsConfig;
|
|
111
|
+
let existing = null;
|
|
112
|
+
try {
|
|
113
|
+
existing = atomicReadJson(p);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
logger.warn(`[config] existing defaults.json unparsable, will be backed up and replaced: ${e}`);
|
|
117
|
+
}
|
|
118
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
119
|
+
backupDefaults(p);
|
|
120
|
+
const merged = existing
|
|
121
|
+
? deepMergeObject(existing, patch)
|
|
122
|
+
: { $schema_version: CONFIG_SCHEMA_VERSION, ...patch };
|
|
123
|
+
atomicWriteJson(p, merged);
|
|
124
|
+
}
|
|
80
125
|
export function loadProcessConfig() {
|
|
81
126
|
const raw = atomicReadJson(resolvePaths().processConfig);
|
|
82
127
|
if (raw === null)
|