evolclaw 3.0.0 → 3.1.0
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 +1 -1
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +7 -9
- package/dist/agents/codex-runner.js +2 -0
- package/dist/agents/gemini-runner.js +9 -9
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/aun/aid/identity.js +28 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +4 -1
- package/dist/channels/aun.js +353 -125
- package/dist/channels/dingtalk.js +2 -1
- package/dist/channels/feishu.js +118 -5
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wechat.js +3 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +279 -19
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +589 -0
- package/dist/config-store.js +37 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +46 -22
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +50 -44
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +7 -2
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-processor.js +150 -99
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/session/session-manager.js +98 -64
- package/dist/core/trigger/scheduler.js +1 -1
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +137 -16
- package/dist/net-check.js +640 -0
- package/dist/paths.js +31 -40
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +10 -0
- package/dist/utils/cross-platform.js +17 -8
- package/dist/utils/error-utils.js +10 -2
- package/dist/utils/instance-registry.js +6 -5
- package/dist/utils/log-writer.js +2 -1
- package/dist/utils/logger.js +10 -0
- package/dist/utils/npm-ops.js +35 -3
- package/dist/utils/process-introspect.js +16 -38
- package/dist/watch-msg.js +26 -11
- package/evolclaw-install-aun.md +14 -2
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +43 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +7 -5
- package/dist/agents/templates.js +0 -122
- package/dist/data/prompts.md +0 -137
- package/kits/aun/meta.md +0 -25
- package/kits/aun/role.md +0 -25
- package/kits/templates/group.md +0 -20
- package/kits/templates/private.md +0 -9
- package/kits/templates/system-fragments/personal-context.md +0 -3
- package/kits/templates/system-fragments/self-intro.md +0 -5
- package/kits/templates/system-fragments/speaker-intro.md +0 -5
- package/kits/templates/system-fragments/venue-intro.md +0 -5
- /package/kits/{channels → docs/channels}/aun.md +0 -0
- /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
- /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolvePaths, getPackageRoot } from '../paths.js';
|
|
4
|
+
import { decodeDirSegment, readAllJsonlLines } from '../core/session/session-fs-store.js';
|
|
5
|
+
// ==================== ANSI ====================
|
|
6
|
+
const isTTY = !!process.stdout.isTTY;
|
|
7
|
+
const RST = isTTY ? '\x1b[0m' : '';
|
|
8
|
+
const DIM = isTTY ? '\x1b[2m' : '';
|
|
9
|
+
const BOLD = isTTY ? '\x1b[1m' : '';
|
|
10
|
+
const CYAN = isTTY ? '\x1b[36m' : '';
|
|
11
|
+
const GREEN = isTTY ? '\x1b[32m' : '';
|
|
12
|
+
const BLUE = isTTY ? '\x1b[34m' : '';
|
|
13
|
+
const ORANGE = isTTY ? '\x1b[38;5;208m' : '';
|
|
14
|
+
const MAGENTA = isTTY ? '\x1b[35m' : '';
|
|
15
|
+
const BG_SEL = isTTY ? '\x1b[48;5;236m' : ''; // dark gray background for selected row
|
|
16
|
+
// ==================== Helpers ====================
|
|
17
|
+
function visualWidth(s) {
|
|
18
|
+
const stripped = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
19
|
+
let w = 0;
|
|
20
|
+
for (const ch of stripped) {
|
|
21
|
+
const code = ch.charCodeAt(0);
|
|
22
|
+
w += (code >= 0x4e00 && code <= 0x9fff) || (code >= 0x3000 && code <= 0x30ff) ||
|
|
23
|
+
(code >= 0xff00 && code <= 0xffef) ? 2 : 1;
|
|
24
|
+
}
|
|
25
|
+
return w;
|
|
26
|
+
}
|
|
27
|
+
function padRight(s, width) {
|
|
28
|
+
const pad = Math.max(0, width - visualWidth(s));
|
|
29
|
+
return s + ' '.repeat(pad);
|
|
30
|
+
}
|
|
31
|
+
function truncate(s, maxWidth) {
|
|
32
|
+
let w = 0;
|
|
33
|
+
let i = 0;
|
|
34
|
+
for (const ch of s) {
|
|
35
|
+
const code = ch.charCodeAt(0);
|
|
36
|
+
const cw = (code >= 0x4e00 && code <= 0x9fff) || (code >= 0x3000 && code <= 0x30ff) ||
|
|
37
|
+
(code >= 0xff00 && code <= 0xffef) ? 2 : 1;
|
|
38
|
+
if (w + cw > maxWidth - 1)
|
|
39
|
+
return s.slice(0, i) + '…';
|
|
40
|
+
w += cw;
|
|
41
|
+
i += ch.length;
|
|
42
|
+
}
|
|
43
|
+
return s;
|
|
44
|
+
}
|
|
45
|
+
function wrapText(s, lineWidth, maxLines) {
|
|
46
|
+
const result = [];
|
|
47
|
+
let remaining = s;
|
|
48
|
+
while (remaining.length > 0 && result.length < maxLines) {
|
|
49
|
+
let w = 0;
|
|
50
|
+
let cutIdx = 0;
|
|
51
|
+
for (const ch of remaining) {
|
|
52
|
+
const code = ch.charCodeAt(0);
|
|
53
|
+
const cw = (code >= 0x4e00 && code <= 0x9fff) || (code >= 0x3000 && code <= 0x30ff) ||
|
|
54
|
+
(code >= 0xff00 && code <= 0xffef) ? 2 : 1;
|
|
55
|
+
if (w + cw > lineWidth)
|
|
56
|
+
break;
|
|
57
|
+
w += cw;
|
|
58
|
+
cutIdx += ch.length;
|
|
59
|
+
}
|
|
60
|
+
if (cutIdx === 0)
|
|
61
|
+
break;
|
|
62
|
+
const isLast = result.length === maxLines - 1;
|
|
63
|
+
if (isLast && cutIdx < remaining.length) {
|
|
64
|
+
result.push(truncate(remaining, lineWidth));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
result.push(remaining.slice(0, cutIdx));
|
|
68
|
+
}
|
|
69
|
+
remaining = remaining.slice(cutIdx);
|
|
70
|
+
}
|
|
71
|
+
if (result.length === 0)
|
|
72
|
+
result.push('');
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function formatTimeAgo(ms) {
|
|
76
|
+
const sec = Math.floor(ms / 1000);
|
|
77
|
+
if (sec < 60)
|
|
78
|
+
return `${sec}s`;
|
|
79
|
+
const min = Math.floor(sec / 60);
|
|
80
|
+
if (min < 60)
|
|
81
|
+
return `${min}m`;
|
|
82
|
+
const hour = Math.floor(min / 60);
|
|
83
|
+
if (hour < 24)
|
|
84
|
+
return `${hour}h`;
|
|
85
|
+
return `${Math.floor(hour / 24)}d`;
|
|
86
|
+
}
|
|
87
|
+
function formatNumber(n) {
|
|
88
|
+
return n.toLocaleString('en-US');
|
|
89
|
+
}
|
|
90
|
+
function formatTime(ts) {
|
|
91
|
+
const d = new Date(ts);
|
|
92
|
+
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
93
|
+
}
|
|
94
|
+
function formatDateTime(ts) {
|
|
95
|
+
const d = new Date(ts);
|
|
96
|
+
const mo = String(d.getMonth() + 1).padStart(2, '0');
|
|
97
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
98
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
99
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
100
|
+
return `${mo}-${dd} ${hh}:${mm}`;
|
|
101
|
+
}
|
|
102
|
+
function shortAid(aid) {
|
|
103
|
+
return aid.split('.')[0];
|
|
104
|
+
}
|
|
105
|
+
// ==================== Data Layer ====================
|
|
106
|
+
function getSessionsAunDir() {
|
|
107
|
+
const p = resolvePaths();
|
|
108
|
+
return path.join(p.sessionsDir, 'aun');
|
|
109
|
+
}
|
|
110
|
+
function listLocalAids(aunDir) {
|
|
111
|
+
try {
|
|
112
|
+
return fs.readdirSync(aunDir, { withFileTypes: true })
|
|
113
|
+
.filter(e => e.isDirectory())
|
|
114
|
+
.map(e => decodeDirSegment(e.name));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function listPeers(aunDir, localAid) {
|
|
121
|
+
const aidDir = path.join(aunDir, encodeSegment(localAid));
|
|
122
|
+
try {
|
|
123
|
+
return fs.readdirSync(aidDir, { withFileTypes: true })
|
|
124
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('_'))
|
|
125
|
+
.map(e => decodeDirSegment(e.name));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function readMessages(aunDir, localAid, peerId) {
|
|
132
|
+
const msgPath = path.join(aunDir, encodeSegment(localAid), encodeSegment(peerId), 'messages.jsonl');
|
|
133
|
+
return readAllJsonlLines(msgPath);
|
|
134
|
+
}
|
|
135
|
+
function readPeerName(aunDir, localAid, peerId) {
|
|
136
|
+
const activePath = path.join(aunDir, encodeSegment(localAid), encodeSegment(peerId), 'active.json');
|
|
137
|
+
try {
|
|
138
|
+
const data = JSON.parse(fs.readFileSync(activePath, 'utf-8'));
|
|
139
|
+
return data?.metadata?.peerName || null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function encodeSegment(s) {
|
|
146
|
+
return s.replace(/[/%\\:*?"<>|]/g, ch => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'));
|
|
147
|
+
}
|
|
148
|
+
function loadAidInfo(aunDir, aid) {
|
|
149
|
+
const peers = listPeers(aunDir, aid);
|
|
150
|
+
let totalIn = 0, totalOut = 0;
|
|
151
|
+
for (const peer of peers) {
|
|
152
|
+
const msgs = readMessages(aunDir, aid, peer);
|
|
153
|
+
for (const m of msgs) {
|
|
154
|
+
if (m.dir === 'in')
|
|
155
|
+
totalIn++;
|
|
156
|
+
else
|
|
157
|
+
totalOut++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { aid, totalIn, totalOut, peerCount: peers.length };
|
|
161
|
+
}
|
|
162
|
+
function loadPeerInfos(aunDir, localAid) {
|
|
163
|
+
const peers = listPeers(aunDir, localAid);
|
|
164
|
+
const infos = [];
|
|
165
|
+
for (const peerId of peers) {
|
|
166
|
+
const msgs = readMessages(aunDir, localAid, peerId);
|
|
167
|
+
let inbound = 0, outbound = 0, lastAt = 0;
|
|
168
|
+
for (const m of msgs) {
|
|
169
|
+
if (m.dir === 'in')
|
|
170
|
+
inbound++;
|
|
171
|
+
else
|
|
172
|
+
outbound++;
|
|
173
|
+
if (m.ts > lastAt)
|
|
174
|
+
lastAt = m.ts;
|
|
175
|
+
}
|
|
176
|
+
const peerName = readPeerName(aunDir, localAid, peerId);
|
|
177
|
+
infos.push({ peerId, peerName, inbound, outbound, lastAt });
|
|
178
|
+
}
|
|
179
|
+
infos.sort((a, b) => b.lastAt - a.lastAt);
|
|
180
|
+
return infos;
|
|
181
|
+
}
|
|
182
|
+
function loadAllMessages(aunDir, localAid) {
|
|
183
|
+
const peers = listPeers(aunDir, localAid);
|
|
184
|
+
const all = [];
|
|
185
|
+
for (const peer of peers) {
|
|
186
|
+
all.push(...readMessages(aunDir, localAid, peer));
|
|
187
|
+
}
|
|
188
|
+
all.sort((a, b) => a.ts - b.ts);
|
|
189
|
+
if (all.length > 1000)
|
|
190
|
+
return all.slice(-1000);
|
|
191
|
+
return all;
|
|
192
|
+
}
|
|
193
|
+
// ==================== Rendering ====================
|
|
194
|
+
function renderScrollbar(totalLines, visibleLines, offset, height) {
|
|
195
|
+
if (totalLines <= visibleLines)
|
|
196
|
+
return Array(height).fill(' ');
|
|
197
|
+
const thumbSize = Math.max(1, Math.floor(height * visibleLines / totalLines));
|
|
198
|
+
const maxOffset = totalLines - visibleLines;
|
|
199
|
+
const thumbPos = Math.floor((maxOffset - offset) / maxOffset * (height - thumbSize));
|
|
200
|
+
const bar = [];
|
|
201
|
+
for (let i = 0; i < height; i++) {
|
|
202
|
+
bar.push(i >= thumbPos && i < thumbPos + thumbSize ? `${DIM}█${RST}` : `${DIM}░${RST}`);
|
|
203
|
+
}
|
|
204
|
+
return bar;
|
|
205
|
+
}
|
|
206
|
+
function renderScopePanel(state, width, height) {
|
|
207
|
+
const lines = [];
|
|
208
|
+
const title = `${DIM}─ Scope ─${RST}`;
|
|
209
|
+
lines.push(padRight(title, width));
|
|
210
|
+
const isActive = state.activePanel === 'scope';
|
|
211
|
+
for (let i = 0; i < state.localAids.length && lines.length < height; i++) {
|
|
212
|
+
const a = state.localAids[i];
|
|
213
|
+
const sel = isActive && i === state.scopeIndex;
|
|
214
|
+
const chosen = state.selectedLocalAid === a.aid;
|
|
215
|
+
const bg = sel ? BG_SEL : '';
|
|
216
|
+
const marker = sel ? `${bg}${CYAN}${BOLD}▸ ` : (chosen ? `${CYAN} ` : ' ');
|
|
217
|
+
const name = truncate(shortAid(a.aid), width - 4);
|
|
218
|
+
lines.push(padRight(`${marker}${name}${RST}`, width));
|
|
219
|
+
const statsBg = sel ? BG_SEL : '';
|
|
220
|
+
const stats = `${statsBg} ${DIM}↓${a.totalIn} ↑${a.totalOut} peers:${a.peerCount}${RST}`;
|
|
221
|
+
lines.push(padRight(stats, width));
|
|
222
|
+
if (lines.length < height)
|
|
223
|
+
lines.push(padRight('', width));
|
|
224
|
+
}
|
|
225
|
+
while (lines.length < height)
|
|
226
|
+
lines.push(padRight('', width));
|
|
227
|
+
return lines.slice(0, height);
|
|
228
|
+
}
|
|
229
|
+
function renderStatsPanel(state, width, height) {
|
|
230
|
+
const lines = [];
|
|
231
|
+
const title = `${DIM}─ Stats ─${RST}`;
|
|
232
|
+
lines.push(padRight(title, width));
|
|
233
|
+
if (!state.selectedLocalAid) {
|
|
234
|
+
lines.push(padRight(`${DIM} select an AID${RST}`, width));
|
|
235
|
+
while (lines.length < height)
|
|
236
|
+
lines.push(padRight('', width));
|
|
237
|
+
return lines.slice(0, height);
|
|
238
|
+
}
|
|
239
|
+
const isActive = state.activePanel === 'stats';
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
// "All" item at index 0
|
|
242
|
+
const allSel = isActive && state.statsIndex === 0;
|
|
243
|
+
const allBg = allSel ? BG_SEL : '';
|
|
244
|
+
const allMarker = allSel ? `${allBg}${CYAN}${BOLD}▸ ` : ' ';
|
|
245
|
+
lines.push(padRight(`${allMarker}All (${state.peers.length} peers)${RST}`, width));
|
|
246
|
+
if (lines.length < height)
|
|
247
|
+
lines.push(padRight('', width));
|
|
248
|
+
for (let i = 0; i < state.peers.length && lines.length < height; i++) {
|
|
249
|
+
const p = state.peers[i];
|
|
250
|
+
const sel = isActive && state.statsIndex === i + 1;
|
|
251
|
+
const bg = sel ? BG_SEL : '';
|
|
252
|
+
const marker = sel ? `${bg}${CYAN}${BOLD}▸ ` : ' ';
|
|
253
|
+
const displayName = p.peerName || shortAid(p.peerId);
|
|
254
|
+
const name = truncate(displayName, width - 4);
|
|
255
|
+
lines.push(padRight(`${marker}${name}${RST}`, width));
|
|
256
|
+
const detailBg = sel ? BG_SEL : '';
|
|
257
|
+
const ago = p.lastAt ? formatTimeAgo(now - p.lastAt) : '-';
|
|
258
|
+
const detail = `${detailBg} ${DIM}↓${p.inbound} ↑${p.outbound} ${ago}${RST}`;
|
|
259
|
+
lines.push(padRight(detail, width));
|
|
260
|
+
if (lines.length < height)
|
|
261
|
+
lines.push(padRight('', width));
|
|
262
|
+
}
|
|
263
|
+
while (lines.length < height)
|
|
264
|
+
lines.push(padRight('', width));
|
|
265
|
+
return lines.slice(0, height);
|
|
266
|
+
}
|
|
267
|
+
// ==================== Messages Panel ====================
|
|
268
|
+
function renderMessagesPanel(state, width, height) {
|
|
269
|
+
const lines = [];
|
|
270
|
+
const title = `${DIM}─ Messages ─${RST}`;
|
|
271
|
+
lines.push(padRight(title, width));
|
|
272
|
+
const contentHeight = height - 1;
|
|
273
|
+
const msgs = state.messages;
|
|
274
|
+
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
|
+
const msgWidth = width - 3;
|
|
280
|
+
const contentLineWidth = msgWidth - 2;
|
|
281
|
+
const maxContentLines = 3;
|
|
282
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
283
|
+
const m = msgs[i];
|
|
284
|
+
const time = formatDateTime(m.ts);
|
|
285
|
+
const dir = m.dir === 'in' ? `${GREEN}↓${RST}` : `${BLUE}↑${RST}`;
|
|
286
|
+
const isGroup = m.chatType === 'group';
|
|
287
|
+
const chatTag = isGroup ? `${MAGENTA}[群聊]${RST} ` : '';
|
|
288
|
+
const byteLen = Buffer.byteLength(m.content, 'utf-8');
|
|
289
|
+
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 headerLine = padRight(header, msgWidth);
|
|
294
|
+
const sbIdx = lines.length - 1;
|
|
295
|
+
lines.push(`${headerLine} ${scrollbar[sbIdx] || ' '}`);
|
|
296
|
+
if (lines.length - 1 < contentHeight) {
|
|
297
|
+
const rawContent = m.content.replace(/\n/g, ' ');
|
|
298
|
+
const wrappedLines = wrapText(rawContent, contentLineWidth, maxContentLines);
|
|
299
|
+
for (const wl of wrappedLines) {
|
|
300
|
+
if (lines.length - 1 >= contentHeight)
|
|
301
|
+
break;
|
|
302
|
+
const contentLine = padRight(` ${wl}`, msgWidth);
|
|
303
|
+
const sbIdx2 = lines.length - 1;
|
|
304
|
+
lines.push(`${contentLine} ${scrollbar[sbIdx2] || ' '}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
while (lines.length < height) {
|
|
309
|
+
const sbIdx = lines.length - 1;
|
|
310
|
+
lines.push(padRight('', msgWidth) + ` ${scrollbar[sbIdx] || ' '}`);
|
|
311
|
+
}
|
|
312
|
+
return lines.slice(0, height);
|
|
313
|
+
}
|
|
314
|
+
// ==================== Main Render ====================
|
|
315
|
+
function renderFrame(state) {
|
|
316
|
+
const cols = process.stdout.columns || 120;
|
|
317
|
+
const rows = (process.stdout.rows || 40) - 3;
|
|
318
|
+
const leftW = Math.max(20, Math.floor(cols * 0.20));
|
|
319
|
+
const midW = Math.max(24, Math.floor(cols * 0.22));
|
|
320
|
+
const rightW = Math.max(40, cols - leftW - midW - 4);
|
|
321
|
+
const bodyHeight = rows - 2;
|
|
322
|
+
const leftLines = renderScopePanel(state, leftW, bodyHeight);
|
|
323
|
+
const midLines = renderStatsPanel(state, midW, bodyHeight);
|
|
324
|
+
const msgLines = renderMessagesPanel(state, rightW, bodyHeight);
|
|
325
|
+
const sep = `${DIM}│${RST}`;
|
|
326
|
+
let buf = '\x1b[H';
|
|
327
|
+
const topBorder = `${DIM}┌${'─'.repeat(leftW)}┬${'─'.repeat(midW)}┬${'─'.repeat(rightW + 1)}┐${RST}`;
|
|
328
|
+
buf += `\x1b[2K${topBorder}\n`;
|
|
329
|
+
for (let i = 0; i < bodyHeight; i++) {
|
|
330
|
+
const l = leftLines[i] || padRight('', leftW);
|
|
331
|
+
const m = midLines[i] || padRight('', midW);
|
|
332
|
+
const r = msgLines[i] || padRight('', rightW);
|
|
333
|
+
buf += `\x1b[2K${sep}${l}${sep}${m}${sep}${r}${sep}\n`;
|
|
334
|
+
}
|
|
335
|
+
const bottomBorder = `${DIM}├${'─'.repeat(leftW)}┴${'─'.repeat(midW)}┴${'─'.repeat(rightW + 1)}┤${RST}`;
|
|
336
|
+
buf += `\x1b[2K${bottomBorder}\n`;
|
|
337
|
+
const pkgRoot = getPackageRoot();
|
|
338
|
+
const helpLine = `${DIM}│ Tab: panel ↑↓: nav Enter: select Backspace: back ESC: exit ${pkgRoot}${RST}`;
|
|
339
|
+
buf += `\x1b[2K${helpLine}\n`;
|
|
340
|
+
const closeBorder = `${DIM}└${'─'.repeat(cols - 2)}┘${RST}`;
|
|
341
|
+
buf += `\x1b[2K${closeBorder}\n`;
|
|
342
|
+
return buf;
|
|
343
|
+
}
|
|
344
|
+
// ==================== Main ====================
|
|
345
|
+
export async function cmdWatchMsg() {
|
|
346
|
+
const aunDir = getSessionsAunDir();
|
|
347
|
+
if (!fs.existsSync(aunDir)) {
|
|
348
|
+
console.log('No session data found.');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
let watcher = null;
|
|
352
|
+
const state = {
|
|
353
|
+
activePanel: 'scope',
|
|
354
|
+
localAids: [],
|
|
355
|
+
scopeIndex: 0,
|
|
356
|
+
selectedLocalAid: null,
|
|
357
|
+
peers: [],
|
|
358
|
+
statsIndex: 0,
|
|
359
|
+
selectedPeer: null,
|
|
360
|
+
messages: [],
|
|
361
|
+
messageScrollOffset: 0,
|
|
362
|
+
dirty: true,
|
|
363
|
+
};
|
|
364
|
+
function loadScope() {
|
|
365
|
+
const aids = listLocalAids(aunDir);
|
|
366
|
+
state.localAids = aids.map(aid => loadAidInfo(aunDir, aid));
|
|
367
|
+
state.localAids.sort((a, b) => (b.totalIn + b.totalOut) - (a.totalIn + a.totalOut));
|
|
368
|
+
}
|
|
369
|
+
function selectAid(aid) {
|
|
370
|
+
state.selectedLocalAid = aid;
|
|
371
|
+
state.peers = loadPeerInfos(aunDir, aid);
|
|
372
|
+
state.statsIndex = 0;
|
|
373
|
+
state.selectedPeer = null;
|
|
374
|
+
state.messages = loadAllMessages(aunDir, aid);
|
|
375
|
+
state.messageScrollOffset = 0;
|
|
376
|
+
startWatching(aid);
|
|
377
|
+
}
|
|
378
|
+
function selectPeer(peerId) {
|
|
379
|
+
state.selectedPeer = peerId;
|
|
380
|
+
if (!state.selectedLocalAid)
|
|
381
|
+
return;
|
|
382
|
+
if (peerId) {
|
|
383
|
+
state.messages = readMessages(aunDir, state.selectedLocalAid, peerId);
|
|
384
|
+
if (state.messages.length > 1000)
|
|
385
|
+
state.messages = state.messages.slice(-1000);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
state.messages = loadAllMessages(aunDir, state.selectedLocalAid);
|
|
389
|
+
}
|
|
390
|
+
state.messageScrollOffset = 0;
|
|
391
|
+
}
|
|
392
|
+
function startWatching(aid) {
|
|
393
|
+
if (watcher) {
|
|
394
|
+
watcher.close();
|
|
395
|
+
watcher = null;
|
|
396
|
+
}
|
|
397
|
+
const aidDir = path.join(aunDir, encodeSegment(aid));
|
|
398
|
+
try {
|
|
399
|
+
watcher = fs.watch(aidDir, { recursive: true }, (_, filename) => {
|
|
400
|
+
if (filename && filename.endsWith('messages.jsonl')) {
|
|
401
|
+
refreshData();
|
|
402
|
+
render();
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
catch { /* directory may not exist */ }
|
|
407
|
+
}
|
|
408
|
+
function refreshData() {
|
|
409
|
+
if (!state.selectedLocalAid)
|
|
410
|
+
return;
|
|
411
|
+
state.peers = loadPeerInfos(aunDir, state.selectedLocalAid);
|
|
412
|
+
if (state.selectedPeer) {
|
|
413
|
+
state.messages = readMessages(aunDir, state.selectedLocalAid, state.selectedPeer);
|
|
414
|
+
if (state.messages.length > 1000)
|
|
415
|
+
state.messages = state.messages.slice(-1000);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
state.messages = loadAllMessages(aunDir, state.selectedLocalAid);
|
|
419
|
+
}
|
|
420
|
+
// Also refresh scope stats for the selected AID
|
|
421
|
+
const idx = state.localAids.findIndex(a => a.aid === state.selectedLocalAid);
|
|
422
|
+
if (idx >= 0) {
|
|
423
|
+
state.localAids[idx] = loadAidInfo(aunDir, state.selectedLocalAid);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function render() {
|
|
427
|
+
process.stdout.write(renderFrame(state));
|
|
428
|
+
}
|
|
429
|
+
function cleanup() {
|
|
430
|
+
if (watcher) {
|
|
431
|
+
watcher.close();
|
|
432
|
+
watcher = null;
|
|
433
|
+
}
|
|
434
|
+
if (process.stdin.isTTY)
|
|
435
|
+
try {
|
|
436
|
+
process.stdin.setRawMode(false);
|
|
437
|
+
}
|
|
438
|
+
catch { }
|
|
439
|
+
process.stdin.pause();
|
|
440
|
+
process.stdout.write('\x1b[?25h\x1b[2J\x1b[H');
|
|
441
|
+
}
|
|
442
|
+
function handleKey(data) {
|
|
443
|
+
// ESC
|
|
444
|
+
if (data[0] === 0x1b && data.length === 1) {
|
|
445
|
+
cleanup();
|
|
446
|
+
process.exit(0);
|
|
447
|
+
}
|
|
448
|
+
// Ctrl+C
|
|
449
|
+
if (data[0] === 0x03) {
|
|
450
|
+
cleanup();
|
|
451
|
+
process.exit(0);
|
|
452
|
+
}
|
|
453
|
+
// Arrow keys
|
|
454
|
+
if (data[0] === 0x1b && data[1] === 0x5b) {
|
|
455
|
+
const code = data[2];
|
|
456
|
+
if (code === 0x41)
|
|
457
|
+
handleUp();
|
|
458
|
+
else if (code === 0x42)
|
|
459
|
+
handleDown();
|
|
460
|
+
else if (code === 0x43)
|
|
461
|
+
handleRight();
|
|
462
|
+
else if (code === 0x44)
|
|
463
|
+
handleLeft();
|
|
464
|
+
else if (code === 0x35)
|
|
465
|
+
handlePageUp(); // Page Up: \x1b[5~
|
|
466
|
+
else if (code === 0x36)
|
|
467
|
+
handlePageDown(); // Page Down: \x1b[6~
|
|
468
|
+
render();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
// Tab
|
|
472
|
+
if (data[0] === 0x09) {
|
|
473
|
+
handleRight();
|
|
474
|
+
render();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
// Shift+Tab (some terminals: \x1b[Z)
|
|
478
|
+
if (data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x5a) {
|
|
479
|
+
handleLeft();
|
|
480
|
+
render();
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
// Enter
|
|
484
|
+
if (data[0] === 0x0d) {
|
|
485
|
+
handleEnter();
|
|
486
|
+
render();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// Backspace
|
|
490
|
+
if (data[0] === 0x7f || data[0] === 0x08) {
|
|
491
|
+
handleBackspace();
|
|
492
|
+
render();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function handleUp() {
|
|
497
|
+
if (state.activePanel === 'scope') {
|
|
498
|
+
state.scopeIndex = Math.max(0, state.scopeIndex - 1);
|
|
499
|
+
}
|
|
500
|
+
else if (state.activePanel === 'stats') {
|
|
501
|
+
state.statsIndex = Math.max(0, state.statsIndex - 1);
|
|
502
|
+
}
|
|
503
|
+
else if (state.activePanel === 'messages') {
|
|
504
|
+
state.messageScrollOffset = Math.min(Math.max(0, state.messages.length - 5), state.messageScrollOffset + 3);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function handleDown() {
|
|
508
|
+
if (state.activePanel === 'scope') {
|
|
509
|
+
state.scopeIndex = Math.min(state.localAids.length - 1, state.scopeIndex + 1);
|
|
510
|
+
}
|
|
511
|
+
else if (state.activePanel === 'stats') {
|
|
512
|
+
state.statsIndex = Math.min(state.peers.length, state.statsIndex + 1);
|
|
513
|
+
}
|
|
514
|
+
else if (state.activePanel === 'messages') {
|
|
515
|
+
state.messageScrollOffset = Math.max(0, state.messageScrollOffset - 3);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function handleLeft() {
|
|
519
|
+
if (state.activePanel === 'messages')
|
|
520
|
+
state.activePanel = 'stats';
|
|
521
|
+
else if (state.activePanel === 'stats')
|
|
522
|
+
state.activePanel = 'scope';
|
|
523
|
+
}
|
|
524
|
+
function handleRight() {
|
|
525
|
+
if (state.activePanel === 'scope')
|
|
526
|
+
state.activePanel = 'stats';
|
|
527
|
+
else if (state.activePanel === 'stats')
|
|
528
|
+
state.activePanel = 'messages';
|
|
529
|
+
}
|
|
530
|
+
function handlePageUp() {
|
|
531
|
+
if (state.activePanel === 'messages') {
|
|
532
|
+
const pageSize = (process.stdout.rows || 40) - 6;
|
|
533
|
+
state.messageScrollOffset = Math.min(Math.max(0, state.messages.length - 5), state.messageScrollOffset + pageSize);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function handlePageDown() {
|
|
537
|
+
if (state.activePanel === 'messages') {
|
|
538
|
+
const pageSize = (process.stdout.rows || 40) - 6;
|
|
539
|
+
state.messageScrollOffset = Math.max(0, state.messageScrollOffset - pageSize);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function handleEnter() {
|
|
543
|
+
if (state.activePanel === 'scope' && state.localAids.length > 0) {
|
|
544
|
+
const aid = state.localAids[state.scopeIndex];
|
|
545
|
+
selectAid(aid.aid);
|
|
546
|
+
state.activePanel = 'stats';
|
|
547
|
+
}
|
|
548
|
+
else if (state.activePanel === 'stats') {
|
|
549
|
+
if (state.statsIndex === 0) {
|
|
550
|
+
selectPeer(null);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
const peer = state.peers[state.statsIndex - 1];
|
|
554
|
+
if (peer)
|
|
555
|
+
selectPeer(peer.peerId);
|
|
556
|
+
}
|
|
557
|
+
state.activePanel = 'messages';
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function handleBackspace() {
|
|
561
|
+
if (state.activePanel === 'messages') {
|
|
562
|
+
state.activePanel = 'stats';
|
|
563
|
+
state.messageScrollOffset = 0;
|
|
564
|
+
}
|
|
565
|
+
else if (state.activePanel === 'stats') {
|
|
566
|
+
state.activePanel = 'scope';
|
|
567
|
+
state.selectedLocalAid = null;
|
|
568
|
+
state.peers = [];
|
|
569
|
+
state.messages = [];
|
|
570
|
+
if (watcher) {
|
|
571
|
+
watcher.close();
|
|
572
|
+
watcher = null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// ── Init ──
|
|
577
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
578
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
579
|
+
loadScope();
|
|
580
|
+
process.stdout.write('\x1b[?25l\x1b[2J\x1b[H');
|
|
581
|
+
render();
|
|
582
|
+
if (process.stdin.isTTY) {
|
|
583
|
+
process.stdin.setRawMode(true);
|
|
584
|
+
process.stdin.resume();
|
|
585
|
+
process.stdin.on('data', handleKey);
|
|
586
|
+
}
|
|
587
|
+
// Keep process alive
|
|
588
|
+
await new Promise(() => { });
|
|
589
|
+
}
|
package/dist/config-store.js
CHANGED
|
@@ -77,6 +77,15 @@ export function loadDefaults() {
|
|
|
77
77
|
export function saveDefaults(value) {
|
|
78
78
|
atomicWriteJson(resolvePaths().defaultsConfig, value);
|
|
79
79
|
}
|
|
80
|
+
export function loadProcessConfig() {
|
|
81
|
+
const raw = atomicReadJson(resolvePaths().processConfig);
|
|
82
|
+
if (raw === null)
|
|
83
|
+
return {};
|
|
84
|
+
return expandEnvRefs(raw);
|
|
85
|
+
}
|
|
86
|
+
export function saveProcessConfig(value) {
|
|
87
|
+
atomicWriteJson(resolvePaths().processConfig, value);
|
|
88
|
+
}
|
|
80
89
|
// ── 自动迁移 ───────────────────────────────────────────────────────────
|
|
81
90
|
/**
|
|
82
91
|
* 启动时自动迁移:如果 agents/defaults.json 不存在但 data/evolclaw.json 存在,
|
|
@@ -455,21 +464,44 @@ export function ensureAgentDirSkeleton(aid) {
|
|
|
455
464
|
const root = agentDir(aid);
|
|
456
465
|
const subs = [
|
|
457
466
|
'personal',
|
|
458
|
-
'
|
|
459
|
-
'
|
|
460
|
-
'
|
|
461
|
-
'
|
|
462
|
-
'
|
|
467
|
+
'personal/memory',
|
|
468
|
+
'personal/skills',
|
|
469
|
+
'relations/contacts',
|
|
470
|
+
'relations/_observed',
|
|
471
|
+
'relations/_observed/_index',
|
|
472
|
+
'relations/_index',
|
|
473
|
+
'relations/_trash',
|
|
463
474
|
'venues',
|
|
464
475
|
'venues/_index',
|
|
465
476
|
'venues/_trash',
|
|
466
477
|
'sessions',
|
|
467
478
|
'data/cache',
|
|
479
|
+
'index',
|
|
468
480
|
];
|
|
469
481
|
for (const sub of subs) {
|
|
470
482
|
fs.mkdirSync(path.join(root, sub), { recursive: true });
|
|
471
483
|
}
|
|
472
484
|
}
|
|
485
|
+
// ── identities → relations 迁移 ──────────────────────────────────────
|
|
486
|
+
/**
|
|
487
|
+
* 启动时自动迁移:如果 agents/<aid>/identities/ 存在但 relations/ 不存在,
|
|
488
|
+
* 将 identities/ 重命名为 relations/。幂等。
|
|
489
|
+
*/
|
|
490
|
+
export function migrateIdentitiesIfNeeded() {
|
|
491
|
+
const agentsDir = resolvePaths().agentsDir;
|
|
492
|
+
if (!fs.existsSync(agentsDir))
|
|
493
|
+
return;
|
|
494
|
+
for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
495
|
+
if (!entry.isDirectory())
|
|
496
|
+
continue;
|
|
497
|
+
const oldDir = path.join(agentsDir, entry.name, 'identities');
|
|
498
|
+
const newDir = path.join(agentsDir, entry.name, 'relations');
|
|
499
|
+
if (fs.existsSync(oldDir) && !fs.existsSync(newDir)) {
|
|
500
|
+
fs.renameSync(oldDir, newDir);
|
|
501
|
+
logger.info(`[migrate] Renamed agents/${entry.name}/identities/ → relations/`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
473
505
|
// ── Project Migration ────────────────────────────────────────────────────────
|
|
474
506
|
import os from 'os';
|
|
475
507
|
/** 将绝对路径编码为 Claude Code 的目录名格式(/ \ . 替换为 -) */
|