ec-web 0.0.3 → 0.0.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.
- package/dist/server.js +73 -12
- package/dist/sources/monitor.js +90 -7
- package/dist/sources/msg.js +193 -7
- package/dist/sources/relation-roles.js +2 -24
- package/dist/sources/role-definitions.js +11 -29
- package/dist/sources/session-codex.js +43 -29
- package/dist/sources/session-origin.js +195 -0
- package/dist/sources/session.js +211 -33
- package/dist/static/app.js +421 -152
- package/dist/static/index.html +8 -9
- package/dist/static/style.css +81 -1
- package/dist/static/vendor/echarts-5.6.0.min.js +45 -0
- package/package.json +2 -2
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* 功能:
|
|
5
5
|
* 1. 读取 roles.json 或内置角色配置
|
|
6
6
|
* 2. 提供角色定义的 CRUD API
|
|
7
|
-
* 3. WebSocket
|
|
7
|
+
* 3. WebSocket 提供按需快照
|
|
8
8
|
*
|
|
9
9
|
* 数据流:
|
|
10
10
|
* - 读取:从 evolcore/dist/config/roles.ts 导入 readRolesConfig
|
|
11
11
|
* - 写入:保存到 .evolcore/roles.json
|
|
12
|
-
* -
|
|
12
|
+
* - 刷新:进入页面、切换 Agent 或完成写操作时按需加载
|
|
13
13
|
*/
|
|
14
14
|
import { getModelCatalogSnapshot } from './models.js';
|
|
15
15
|
import { resolveParentDistModule, toFileUrl } from './parent-package.js';
|
|
@@ -116,6 +116,11 @@ async function rolePolicyRevision(aid, auth) {
|
|
|
116
116
|
throw Object.assign(new Error(response.error.message), { menuResponse: response });
|
|
117
117
|
return response.data.policyRevision;
|
|
118
118
|
}
|
|
119
|
+
export function visibleRoleDefinitionOptions(roleOptions) {
|
|
120
|
+
return roleOptions
|
|
121
|
+
.filter(item => item?.kind === 'user' || item?.value === 'admin')
|
|
122
|
+
.sort((left, right) => Number(right.value === 'admin') - Number(left.value === 'admin'));
|
|
123
|
+
}
|
|
119
124
|
async function buildMenuSnapshot(aid, agents, auth) {
|
|
120
125
|
const [roleOptions, defaults] = await Promise.all([
|
|
121
126
|
execRoleMenu(aid, { type: 'menu.options', id: `ecweb-role-options-${Date.now()}`, args: { option: 'roles' } }, auth),
|
|
@@ -125,8 +130,8 @@ async function buildMenuSnapshot(aid, agents, auth) {
|
|
|
125
130
|
throw Object.assign(new Error(roleOptions.error.message), { menuResponse: roleOptions });
|
|
126
131
|
if (defaults.error)
|
|
127
132
|
throw Object.assign(new Error(defaults.error.message), { menuResponse: defaults });
|
|
128
|
-
const
|
|
129
|
-
const details = await Promise.all(
|
|
133
|
+
const visibleRoles = visibleRoleDefinitionOptions(roleOptions.data);
|
|
134
|
+
const details = await Promise.all(visibleRoles.map(item => execRoleMenu(aid, {
|
|
130
135
|
type: 'menu.query', id: `ecweb-role-${item.value}-${Date.now()}`,
|
|
131
136
|
args: { view: 'role', roleId: item.value },
|
|
132
137
|
}, auth)));
|
|
@@ -634,31 +639,8 @@ export const roleDefinitionsSource = {
|
|
|
634
639
|
async snapshot(params = {}) {
|
|
635
640
|
return buildSnapshot(params, params.__menuAuth ?? {});
|
|
636
641
|
},
|
|
637
|
-
subscribe(_params,
|
|
638
|
-
|
|
639
|
-
let stopped = false;
|
|
640
|
-
const tick = async () => {
|
|
641
|
-
if (stopped)
|
|
642
|
-
return;
|
|
643
|
-
try {
|
|
644
|
-
const snap = await buildSnapshot(_params, _params.__menuAuth ?? {});
|
|
645
|
-
const json = JSON.stringify(snap);
|
|
646
|
-
if (json !== lastJson) {
|
|
647
|
-
lastJson = json;
|
|
648
|
-
push(snap);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
catch (err) {
|
|
652
|
-
console.error('[role-definitions] Polling error:', err);
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
// 立即执行一次
|
|
656
|
-
tick();
|
|
657
|
-
const timer = setInterval(tick, 2000); // 2秒轮询
|
|
658
|
-
return () => {
|
|
659
|
-
stopped = true;
|
|
660
|
-
clearInterval(timer);
|
|
661
|
-
};
|
|
642
|
+
subscribe(_params, _push) {
|
|
643
|
+
return () => { };
|
|
662
644
|
}
|
|
663
645
|
};
|
|
664
646
|
async function handleRoleMenuCompatibility(req, res, auth, aid, agents, parsedUrl) {
|
|
@@ -12,7 +12,9 @@ import path from 'path';
|
|
|
12
12
|
import os from 'os';
|
|
13
13
|
import { createRequire } from 'module';
|
|
14
14
|
import { resolvePaths } from '../paths.js';
|
|
15
|
-
import { encodePath
|
|
15
|
+
import { encodePath } from '../fs-utils.js';
|
|
16
|
+
import { getAgentSelfNames } from './msg.js';
|
|
17
|
+
import { buildSessionBindMap, classifyTranscriptSource, isEvolcoreRoutedMessage, isSyntheticCliPrompt, resolveSessionBindSelfName, } from './session-origin.js';
|
|
16
18
|
const requireFromHere = createRequire(import.meta.url);
|
|
17
19
|
let _dlog = null;
|
|
18
20
|
export function setDebugLog(log) { _dlog = log; }
|
|
@@ -107,7 +109,7 @@ function listProjects() {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
// ── 会话列表 ──
|
|
110
|
-
const CACHE_VERSION =
|
|
112
|
+
const CACHE_VERSION = 2;
|
|
111
113
|
const _metaCache = new Map();
|
|
112
114
|
function cacheDir(encoded) {
|
|
113
115
|
const dataDir = resolvePaths().dataDir;
|
|
@@ -170,6 +172,7 @@ function extractMetaFromRollout(file, id) {
|
|
|
170
172
|
}
|
|
171
173
|
catch { }
|
|
172
174
|
let title = '', firstUser = '', gitBranch = '', version = '', userMsgs = 0, totalMsgs = 0;
|
|
175
|
+
let hasRealUserInput = false;
|
|
173
176
|
const lines = raw.split('\n');
|
|
174
177
|
for (const line of lines) {
|
|
175
178
|
if (!line.trim())
|
|
@@ -187,6 +190,8 @@ function extractMetaFromRollout(file, id) {
|
|
|
187
190
|
if (event.type === 'event_msg' && event.payload?.type === 'user_message' && event.payload.message) {
|
|
188
191
|
totalMsgs++;
|
|
189
192
|
userMsgs++;
|
|
193
|
+
if (!isSyntheticCliPrompt(event.payload.message, 'codex'))
|
|
194
|
+
hasRealUserInput = true;
|
|
190
195
|
if (!firstUser) {
|
|
191
196
|
const text = event.payload.message.trim().replace(/\s+/g, ' ');
|
|
192
197
|
firstUser = text.substring(0, 120);
|
|
@@ -216,15 +221,34 @@ function extractMetaFromRollout(file, id) {
|
|
|
216
221
|
totalMsgs,
|
|
217
222
|
sizeKB: Math.round(stat.size / 1024),
|
|
218
223
|
lastActivity: stat.mtimeMs,
|
|
224
|
+
hasRealUserInput,
|
|
225
|
+
backendSource: '',
|
|
226
|
+
threadSource: '',
|
|
227
|
+
evolcoreRouted: false,
|
|
219
228
|
};
|
|
220
229
|
}
|
|
230
|
+
function withThreadSource(meta, row) {
|
|
231
|
+
return {
|
|
232
|
+
...meta,
|
|
233
|
+
backendSource: typeof row.source === 'string' ? row.source : '',
|
|
234
|
+
threadSource: typeof row.thread_source === 'string' ? row.thread_source : '',
|
|
235
|
+
evolcoreRouted: isEvolcoreRoutedMessage(row.first_user_message),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function isCodexCliCandidate(meta) {
|
|
239
|
+
if (meta.threadSource === 'subagent')
|
|
240
|
+
return false;
|
|
241
|
+
if (meta.backendSource !== 'cli' && meta.backendSource !== 'vscode')
|
|
242
|
+
return false;
|
|
243
|
+
return meta.hasRealUserInput && !meta.evolcoreRouted;
|
|
244
|
+
}
|
|
221
245
|
function listTranscripts(encoded, cwd) {
|
|
222
246
|
const db = getDb();
|
|
223
247
|
if (!db)
|
|
224
248
|
return [];
|
|
225
249
|
try {
|
|
226
250
|
const rows = db.prepare(`
|
|
227
|
-
SELECT id, title, first_user_message, updated_at, rollout_path
|
|
251
|
+
SELECT id, title, first_user_message, updated_at, rollout_path, source, thread_source
|
|
228
252
|
FROM threads
|
|
229
253
|
WHERE cwd = ? AND archived = 0
|
|
230
254
|
ORDER BY updated_at DESC
|
|
@@ -248,6 +272,10 @@ function listTranscripts(encoded, cwd) {
|
|
|
248
272
|
totalMsgs: 0,
|
|
249
273
|
sizeKB: 0,
|
|
250
274
|
lastActivity: (row.updated_at || 0) * 1000,
|
|
275
|
+
hasRealUserInput: !!firstUser && !isSyntheticCliPrompt(firstUser, 'codex'),
|
|
276
|
+
backendSource: typeof row.source === 'string' ? row.source : '',
|
|
277
|
+
threadSource: typeof row.thread_source === 'string' ? row.thread_source : '',
|
|
278
|
+
evolcoreRouted: isEvolcoreRoutedMessage(row.first_user_message),
|
|
251
279
|
});
|
|
252
280
|
continue;
|
|
253
281
|
}
|
|
@@ -261,21 +289,21 @@ function listTranscripts(encoded, cwd) {
|
|
|
261
289
|
const mtime = stat.mtimeMs;
|
|
262
290
|
const mem = _metaCache.get(rolloutPath);
|
|
263
291
|
if (mem && mem.mtime === mtime) {
|
|
264
|
-
out.push(mem.meta);
|
|
292
|
+
out.push(withThreadSource(mem.meta, row));
|
|
265
293
|
hitMem++;
|
|
266
294
|
continue;
|
|
267
295
|
}
|
|
268
296
|
const disk = readDiskCache(encoded, id, mtime, stat.size);
|
|
269
297
|
if (disk) {
|
|
270
298
|
_metaCache.set(rolloutPath, { mtime, meta: disk });
|
|
271
|
-
out.push(disk);
|
|
299
|
+
out.push(withThreadSource(disk, row));
|
|
272
300
|
hitDisk++;
|
|
273
301
|
continue;
|
|
274
302
|
}
|
|
275
303
|
const meta = extractMetaFromRollout(rolloutPath, id);
|
|
276
304
|
_metaCache.set(rolloutPath, { mtime, meta });
|
|
277
305
|
writeDiskCache(encoded, id, mtime, stat.size, meta);
|
|
278
|
-
out.push(meta);
|
|
306
|
+
out.push(withThreadSource(meta, row));
|
|
279
307
|
parsed++;
|
|
280
308
|
}
|
|
281
309
|
dlog(`[codex] listTranscripts ${cwd.slice(-20)}: ${rows.length} threads (mem=${hitMem} disk=${hitDisk} parsed=${parsed})`);
|
|
@@ -288,25 +316,7 @@ function listTranscripts(encoded, cwd) {
|
|
|
288
316
|
}
|
|
289
317
|
// ── 绑定关系 ──
|
|
290
318
|
export function buildBindMap() {
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
const p = resolvePaths();
|
|
294
|
-
for (const dir of scanChatDirs(p.sessionsDir)) {
|
|
295
|
-
const active = readJsonFile(path.join(dir.dirPath, 'active.json'));
|
|
296
|
-
if (!active || !active.agentSessionId)
|
|
297
|
-
continue;
|
|
298
|
-
map.set(active.agentSessionId, {
|
|
299
|
-
channelType: active.channelType || dir.channelType,
|
|
300
|
-
channelId: active.channelId || dir.channelId,
|
|
301
|
-
selfAID: active.selfAID || dir.selfAID,
|
|
302
|
-
peerName: (active.metadata && active.metadata.peerName) || null,
|
|
303
|
-
name: active.name,
|
|
304
|
-
updatedAt: active.updatedAt || 0,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
catch { }
|
|
309
|
-
return map;
|
|
319
|
+
return buildSessionBindMap();
|
|
310
320
|
}
|
|
311
321
|
// ── 项目解析 ──
|
|
312
322
|
function resolveProject(params, projects) {
|
|
@@ -611,7 +621,7 @@ function readTranscriptFile(threadId, cwd) {
|
|
|
611
621
|
};
|
|
612
622
|
}
|
|
613
623
|
// ── Snapshot ──
|
|
614
|
-
function buildSnapshot(params) {
|
|
624
|
+
async function buildSnapshot(params) {
|
|
615
625
|
const projects = listProjects();
|
|
616
626
|
const bindMap = buildBindMap();
|
|
617
627
|
const project = resolveProject(params, projects);
|
|
@@ -626,13 +636,15 @@ function buildSnapshot(params) {
|
|
|
626
636
|
};
|
|
627
637
|
}
|
|
628
638
|
const metas = listTranscripts(project.encoded, project.cwd);
|
|
639
|
+
const selfNames = await getAgentSelfNames();
|
|
629
640
|
const transcripts = metas.map(m => {
|
|
630
641
|
const bind = bindMap.get(m.id);
|
|
631
642
|
return {
|
|
632
643
|
...m,
|
|
644
|
+
sourceType: classifyTranscriptSource(bind, isCodexCliCandidate(m)),
|
|
633
645
|
bound: !!bind,
|
|
634
646
|
boundChannel: bind?.channelType ?? null,
|
|
635
|
-
|
|
647
|
+
boundSelf: resolveSessionBindSelfName(bind, selfNames),
|
|
636
648
|
online: bind ? (Date.now() - bind.updatedAt < 5 * 60 * 1000) : false,
|
|
637
649
|
};
|
|
638
650
|
});
|
|
@@ -655,6 +667,7 @@ function buildSnapshot(params) {
|
|
|
655
667
|
}
|
|
656
668
|
const detail = readTranscriptFile(sessionId, project.cwd);
|
|
657
669
|
const bind = bindMap.get(sessionId);
|
|
670
|
+
const transcript = metas.find(meta => meta.id === sessionId);
|
|
658
671
|
const header = {
|
|
659
672
|
sessionId,
|
|
660
673
|
title: detail.title,
|
|
@@ -670,9 +683,10 @@ function buildSnapshot(params) {
|
|
|
670
683
|
outputTokens: detail.outputTokens,
|
|
671
684
|
contextTokens: detail.contextTokens,
|
|
672
685
|
costUsd: detail.costUsd,
|
|
686
|
+
sourceType: classifyTranscriptSource(bind, transcript ? isCodexCliCandidate(transcript) : false),
|
|
673
687
|
bound: !!bind,
|
|
674
688
|
boundChannel: bind?.channelType ?? null,
|
|
675
|
-
|
|
689
|
+
boundSelf: resolveSessionBindSelfName(bind, selfNames),
|
|
676
690
|
online: bind ? (Date.now() - bind.updatedAt < 5 * 60 * 1000) : false,
|
|
677
691
|
};
|
|
678
692
|
return {
|
|
@@ -699,7 +713,7 @@ export function subscribeCodex(params, push) {
|
|
|
699
713
|
clearTimeout(debounce);
|
|
700
714
|
debounce = setTimeout(() => {
|
|
701
715
|
try {
|
|
702
|
-
|
|
716
|
+
void buildSnapshot(params).then(push).catch(() => { });
|
|
703
717
|
}
|
|
704
718
|
catch { }
|
|
705
719
|
}, 150);
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolvePaths } from '../paths.js';
|
|
4
|
+
import { readJsonFile, scanChatDirs } from '../fs-utils.js';
|
|
5
|
+
/** Resolve the local Agent's display name for a session binding. */
|
|
6
|
+
export function resolveSessionBindSelfName(bind, names) {
|
|
7
|
+
if (!bind?.selfAID)
|
|
8
|
+
return null;
|
|
9
|
+
return names.get(bind.selfAID) || bind.selfAID.split('.')[0] || bind.selfAID;
|
|
10
|
+
}
|
|
11
|
+
const bindingFileCache = new Map();
|
|
12
|
+
const SHARED_SYNTHETIC_CLI_PREFIXES = [
|
|
13
|
+
'Use Bash exactly once to run:',
|
|
14
|
+
'Use the Bash tool now to execute',
|
|
15
|
+
'Review this change for security vulnerabilities',
|
|
16
|
+
'You previously flagged these candidate vulnerabilities',
|
|
17
|
+
];
|
|
18
|
+
const CLAUDE_SYNTHETIC_CLI_PREFIXES = ['Base directory for this skill:'];
|
|
19
|
+
const CLAUDE_SYNTHETIC_TAG_RE = /^<\/?(?:local-command|command)-[a-z]+>/i;
|
|
20
|
+
const CLAUDE_SYNTHETIC_SLASH_RE = /^\/(?:resume|clear|compact|model|init|review|cost|config|status|help|agents?|context|memory|export|hooks?|mcp|ide|login|logout|doctor|bug|release-notes|vim|terminal-setup|add-dir|permissions)(?:\s|$)/i;
|
|
21
|
+
const EVOLCORE_ROUTE_PREFIX_RE = /^‹[^›]*·\s*from:[^›]*→[^›]*self:[^›]*›\s*/;
|
|
22
|
+
function normalizePeerType(value) {
|
|
23
|
+
if (typeof value !== 'string' || !value.trim())
|
|
24
|
+
return undefined;
|
|
25
|
+
const key = value.trim().toLowerCase();
|
|
26
|
+
if (key === 'human' || key === 'person' || key === 'user')
|
|
27
|
+
return 'human';
|
|
28
|
+
if (key === 'system' || key === 'trigger' || key === 'scheduler')
|
|
29
|
+
return 'system';
|
|
30
|
+
if (key === 'service' || key === 'daemon' || key === 'tool')
|
|
31
|
+
return 'service';
|
|
32
|
+
if (key === 'unknown')
|
|
33
|
+
return 'unknown';
|
|
34
|
+
return 'agent';
|
|
35
|
+
}
|
|
36
|
+
function classifyBinding(session) {
|
|
37
|
+
const channelType = String(session.channelType || '').toLowerCase();
|
|
38
|
+
const channelId = String(session.channelId || '');
|
|
39
|
+
const threadId = String(session.threadId || '');
|
|
40
|
+
if (channelType === 'daemon') {
|
|
41
|
+
return channelId.startsWith('trigger:') || threadId.startsWith('trigger:') ? 'trigger' : 'unknown';
|
|
42
|
+
}
|
|
43
|
+
if (channelType === 'aun') {
|
|
44
|
+
if (session.chatType === 'group')
|
|
45
|
+
return 'user';
|
|
46
|
+
const peerKind = normalizePeerType(session.metadata?.peerType);
|
|
47
|
+
if (peerKind === 'agent')
|
|
48
|
+
return 'agent';
|
|
49
|
+
if (peerKind === 'human')
|
|
50
|
+
return 'user';
|
|
51
|
+
return 'unknown';
|
|
52
|
+
}
|
|
53
|
+
return channelType ? 'user' : 'unknown';
|
|
54
|
+
}
|
|
55
|
+
function toBinding(session, fallback, currentlyBound = false) {
|
|
56
|
+
if (!session.agentSessionId)
|
|
57
|
+
return null;
|
|
58
|
+
const channelType = session.channelType || fallback.channelType;
|
|
59
|
+
const channelId = session.channelId || fallback.channelId;
|
|
60
|
+
const chatType = session.chatType || 'private';
|
|
61
|
+
return {
|
|
62
|
+
evolSessionId: session.id || null,
|
|
63
|
+
channelType,
|
|
64
|
+
channelId,
|
|
65
|
+
channelKey: session.channel || session.metadata?.channelKey || channelType,
|
|
66
|
+
selfAID: session.selfAID || fallback.selfAID,
|
|
67
|
+
chatType,
|
|
68
|
+
peerId: session.metadata?.peerId || channelId,
|
|
69
|
+
peerName: session.metadata?.peerName || null,
|
|
70
|
+
peerType: session.metadata?.peerType || null,
|
|
71
|
+
...(chatType === 'group' ? { groupId: session.metadata?.groupId || channelId } : {}),
|
|
72
|
+
groupName: session.metadata?.groupName || null,
|
|
73
|
+
displayName: null,
|
|
74
|
+
name: session.name || null,
|
|
75
|
+
threadId: session.threadId || '',
|
|
76
|
+
updatedAt: session.updatedAt || 0,
|
|
77
|
+
currentlyBound,
|
|
78
|
+
sourceType: classifyBinding({ ...session, channelType, channelId, chatType }),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function parseBindingJsonl(file, fallback) {
|
|
82
|
+
let stat;
|
|
83
|
+
try {
|
|
84
|
+
stat = fs.statSync(file);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
bindingFileCache.delete(file);
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const cached = bindingFileCache.get(file);
|
|
91
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size)
|
|
92
|
+
return cached.bindings;
|
|
93
|
+
const byAgentSessionId = new Map();
|
|
94
|
+
let finalAgentSessionId = null;
|
|
95
|
+
try {
|
|
96
|
+
const lines = fs.readFileSync(file, 'utf8').trimEnd().split(/\r?\n/);
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
if (!line.trim())
|
|
99
|
+
continue;
|
|
100
|
+
let session;
|
|
101
|
+
try {
|
|
102
|
+
session = JSON.parse(line);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const agentSessionId = session.agentSessionId;
|
|
108
|
+
finalAgentSessionId = agentSessionId || null;
|
|
109
|
+
if (!agentSessionId)
|
|
110
|
+
continue;
|
|
111
|
+
const binding = toBinding(session, fallback);
|
|
112
|
+
if (!binding)
|
|
113
|
+
continue;
|
|
114
|
+
const existing = byAgentSessionId.get(agentSessionId);
|
|
115
|
+
if (!existing || binding.updatedAt >= existing.updatedAt)
|
|
116
|
+
byAgentSessionId.set(agentSessionId, binding);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
const bindings = Array.from(byAgentSessionId, ([agentSessionId, binding]) => ({
|
|
121
|
+
agentSessionId,
|
|
122
|
+
binding: { ...binding, currentlyBound: agentSessionId === finalAgentSessionId },
|
|
123
|
+
}));
|
|
124
|
+
bindingFileCache.set(file, { mtimeMs: stat.mtimeMs, size: stat.size, bindings });
|
|
125
|
+
return bindings;
|
|
126
|
+
}
|
|
127
|
+
function bindingFiles(dirPath) {
|
|
128
|
+
const files = [];
|
|
129
|
+
for (const dir of [dirPath, path.join(dirPath, '_threads')]) {
|
|
130
|
+
let entries;
|
|
131
|
+
try {
|
|
132
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
if (entry.isFile() && /^meta_.+\.jsonl$/.test(entry.name))
|
|
139
|
+
files.push(path.join(dir, entry.name));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return files;
|
|
143
|
+
}
|
|
144
|
+
export function buildSessionBindMap(sessionsDir = resolvePaths().sessionsDir) {
|
|
145
|
+
const map = new Map();
|
|
146
|
+
const liveFiles = new Set();
|
|
147
|
+
for (const dir of scanChatDirs(sessionsDir)) {
|
|
148
|
+
const fallback = { channelType: dir.channelType, channelId: dir.channelId, selfAID: dir.selfAID };
|
|
149
|
+
const active = readJsonFile(path.join(dir.dirPath, 'active.json'));
|
|
150
|
+
const activeBinding = active ? toBinding(active, fallback, true) : null;
|
|
151
|
+
if (activeBinding && active?.agentSessionId)
|
|
152
|
+
map.set(active.agentSessionId, activeBinding);
|
|
153
|
+
for (const file of bindingFiles(dir.dirPath)) {
|
|
154
|
+
liveFiles.add(file);
|
|
155
|
+
for (const { agentSessionId, binding } of parseBindingJsonl(file, fallback)) {
|
|
156
|
+
const existing = map.get(agentSessionId);
|
|
157
|
+
if (!existing
|
|
158
|
+
|| (binding.currentlyBound && !existing.currentlyBound)
|
|
159
|
+
|| binding.currentlyBound === existing.currentlyBound && binding.updatedAt >= existing.updatedAt) {
|
|
160
|
+
map.set(agentSessionId, binding);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const file of bindingFileCache.keys()) {
|
|
166
|
+
if (path.resolve(file).startsWith(`${path.resolve(sessionsDir)}${path.sep}`) && !liveFiles.has(file)) {
|
|
167
|
+
bindingFileCache.delete(file);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return map;
|
|
171
|
+
}
|
|
172
|
+
export function isSyntheticCliPrompt(text, backend) {
|
|
173
|
+
if (typeof text !== 'string' || !text.trim())
|
|
174
|
+
return false;
|
|
175
|
+
const trimmed = text.trimStart();
|
|
176
|
+
const prefixes = backend === 'claude'
|
|
177
|
+
? SHARED_SYNTHETIC_CLI_PREFIXES.concat(CLAUDE_SYNTHETIC_CLI_PREFIXES)
|
|
178
|
+
: SHARED_SYNTHETIC_CLI_PREFIXES;
|
|
179
|
+
if (prefixes.some(prefix => trimmed.startsWith(prefix)))
|
|
180
|
+
return true;
|
|
181
|
+
return backend === 'claude'
|
|
182
|
+
&& (CLAUDE_SYNTHETIC_TAG_RE.test(trimmed) || CLAUDE_SYNTHETIC_SLASH_RE.test(trimmed));
|
|
183
|
+
}
|
|
184
|
+
export function isEvolcoreRoutedMessage(text) {
|
|
185
|
+
return typeof text === 'string' && EVOLCORE_ROUTE_PREFIX_RE.test(text.trimStart());
|
|
186
|
+
}
|
|
187
|
+
export function classifyTranscriptSource(binding, cliCandidate) {
|
|
188
|
+
if (binding?.currentlyBound)
|
|
189
|
+
return binding.sourceType;
|
|
190
|
+
if (cliCandidate)
|
|
191
|
+
return 'cli';
|
|
192
|
+
if (binding)
|
|
193
|
+
return binding.sourceType;
|
|
194
|
+
return 'unknown';
|
|
195
|
+
}
|