ec-web 0.0.26 → 0.0.27
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/index.js +1 -1
- package/dist/paths.js +4 -0
- package/dist/server.js +35 -20
- package/dist/sources/msg.js +21 -4
- package/dist/sources/role-definitions.js +53 -29
- package/dist/sources/session-codex.js +23 -5
- package/dist/sources/session-ecagent.js +665 -0
- package/dist/sources/session-origin.js +16 -0
- package/dist/sources/session.js +69 -165
- package/dist/sources/system.js +1 -33
- package/dist/sources/triggers.js +1 -1
- package/dist/static/app.js +336 -96
- package/dist/static/basic-settings.js +3 -3
- package/dist/static/index.html +1 -1
- package/dist/static/style.css +37 -0
- package/package.json +1 -1
package/dist/sources/session.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 会话数据源 — 多 baseagent 支持(Claude / Codex)。
|
|
2
|
+
* 会话数据源 — 多 baseagent 支持(Claude / Codex / EC Agent)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 读取器按 base agent 标识路由,标识来自 params.baseagent,缺省时取
|
|
5
|
+
* defaults.json 的 active_baseagent(见 resolveSessionBaseagent):
|
|
5
6
|
* - claude: ~/.claude/projects/<encodedProject>/*.jsonl
|
|
6
7
|
* - codex: ~/.codex/state_*.sqlite + ~/.codex/sessions/YYYY/MM/DD/*.jsonl
|
|
8
|
+
* - ecagent: $EVOLCORE_HOME/agents/<aid>/sessions/--<encodedProject>--/*.jsonl
|
|
7
9
|
*
|
|
8
10
|
* snapshot:
|
|
9
11
|
* - 无 sessionId: 项目列表 + 选中项目的全部 transcript 概要
|
|
@@ -15,13 +17,18 @@ import path from 'path';
|
|
|
15
17
|
import { resolvePaths, ccProjectsDir } from '../paths.js';
|
|
16
18
|
import { encodePath } from '../fs-utils.js';
|
|
17
19
|
import { snapshotCodex, subscribeCodex, setDebugLog as setCodexDebugLog } from './session-codex.js';
|
|
20
|
+
import { snapshotEcagent, subscribeEcagent, setDebugLog as setEcagentDebugLog } from './session-ecagent.js';
|
|
21
|
+
import { resolveParentDistModule, toFileUrl } from './parent-package.js';
|
|
22
|
+
// 保持原有公开面:runner 子任务聚合器从这里导出(实现见 session-origin.ts)。
|
|
23
|
+
export { readRunnerTaskTurns };
|
|
18
24
|
import { getAgentSelfNames } from './msg.js';
|
|
19
25
|
import { parseTranscriptResultKey, previewLargeToolResults, windowTranscriptTurns } from './transcript-window.js';
|
|
20
|
-
import { buildSessionBindMap, classifyTranscriptSource, isSyntheticCliPrompt, resolveSessionBindSelfName, } from './session-origin.js';
|
|
26
|
+
import { buildSessionBindMap, readRunnerTaskTurns, classifyTranscriptSource, isSyntheticCliPrompt, resolveSessionBindSelfName, } from './session-origin.js';
|
|
21
27
|
let _dlog = null;
|
|
22
28
|
export function setDebugLog(log) {
|
|
23
29
|
_dlog = log;
|
|
24
30
|
setCodexDebugLog(log); // 同步传递给 Codex 模块
|
|
31
|
+
setEcagentDebugLog(log); // 同步传递给 EC Agent 模块
|
|
25
32
|
}
|
|
26
33
|
function dlog(line) { if (_dlog)
|
|
27
34
|
try {
|
|
@@ -328,161 +335,8 @@ function resolveProject(params, projects) {
|
|
|
328
335
|
export function buildBindMap() {
|
|
329
336
|
return buildSessionBindMap();
|
|
330
337
|
}
|
|
331
|
-
// ── EvolCore runner
|
|
332
|
-
const RUNNER_TASK_EVENT_TYPES = new Set([
|
|
333
|
-
'runner:task-started',
|
|
334
|
-
'runner:task-progress',
|
|
335
|
-
'runner:task-notification',
|
|
336
|
-
]);
|
|
338
|
+
// ── EvolCore runner 子任务事件(见 session-origin.ts,与 Codex / EC Agent 会话源共用)──
|
|
337
339
|
const EVENT_LOG_RE = /^events(?:-.+)?\.log$/;
|
|
338
|
-
const _runnerLogCache = new Map();
|
|
339
|
-
function parseRunnerTaskLines(raw) {
|
|
340
|
-
const events = [];
|
|
341
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
342
|
-
if (!line.includes('"type":"runner:task-'))
|
|
343
|
-
continue;
|
|
344
|
-
try {
|
|
345
|
-
const event = JSON.parse(line);
|
|
346
|
-
if (RUNNER_TASK_EVENT_TYPES.has(event.type) && event.sessionId)
|
|
347
|
-
events.push(event);
|
|
348
|
-
}
|
|
349
|
-
catch { }
|
|
350
|
-
}
|
|
351
|
-
return events;
|
|
352
|
-
}
|
|
353
|
-
function readRunnerLogFile(file) {
|
|
354
|
-
let stat;
|
|
355
|
-
try {
|
|
356
|
-
stat = fs.statSync(file);
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
_runnerLogCache.delete(file);
|
|
360
|
-
return [];
|
|
361
|
-
}
|
|
362
|
-
let cached = _runnerLogCache.get(file);
|
|
363
|
-
if (!cached || stat.size < cached.offset)
|
|
364
|
-
cached = { offset: 0, events: [] };
|
|
365
|
-
if (stat.size === cached.offset) {
|
|
366
|
-
_runnerLogCache.set(file, cached);
|
|
367
|
-
return cached.events;
|
|
368
|
-
}
|
|
369
|
-
const bytes = stat.size - cached.offset;
|
|
370
|
-
const buffer = Buffer.allocUnsafe(bytes);
|
|
371
|
-
let fd = null;
|
|
372
|
-
try {
|
|
373
|
-
fd = fs.openSync(file, 'r');
|
|
374
|
-
fs.readSync(fd, buffer, 0, bytes, cached.offset);
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
return cached.events;
|
|
378
|
-
}
|
|
379
|
-
finally {
|
|
380
|
-
if (fd != null)
|
|
381
|
-
try {
|
|
382
|
-
fs.closeSync(fd);
|
|
383
|
-
}
|
|
384
|
-
catch { }
|
|
385
|
-
}
|
|
386
|
-
// 只推进到最后一个完整 JSONL 行,避免 watcher 在写入中途触发时丢事件。
|
|
387
|
-
const lastBreak = buffer.lastIndexOf(0x0a);
|
|
388
|
-
if (lastBreak < 0) {
|
|
389
|
-
_runnerLogCache.set(file, cached);
|
|
390
|
-
return cached.events;
|
|
391
|
-
}
|
|
392
|
-
const parsed = parseRunnerTaskLines(buffer.subarray(0, lastBreak + 1).toString('utf8'));
|
|
393
|
-
cached = {
|
|
394
|
-
offset: cached.offset + lastBreak + 1,
|
|
395
|
-
events: cached.events.concat(parsed),
|
|
396
|
-
};
|
|
397
|
-
_runnerLogCache.set(file, cached);
|
|
398
|
-
return cached.events;
|
|
399
|
-
}
|
|
400
|
-
function runnerEventTimestamp(event) {
|
|
401
|
-
const timestamp = Number(event.timestamp);
|
|
402
|
-
if (Number.isFinite(timestamp) && timestamp > 0)
|
|
403
|
-
return timestamp;
|
|
404
|
-
const parsed = event.ts ? Date.parse(event.ts) : 0;
|
|
405
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
406
|
-
}
|
|
407
|
-
export function readRunnerTaskTurns(logsDir, evolSessionId) {
|
|
408
|
-
let files;
|
|
409
|
-
try {
|
|
410
|
-
files = fs.readdirSync(logsDir)
|
|
411
|
-
.filter(name => EVENT_LOG_RE.test(name))
|
|
412
|
-
.sort()
|
|
413
|
-
.map(name => path.join(logsDir, name));
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
return [];
|
|
417
|
-
}
|
|
418
|
-
const validFiles = new Set(files);
|
|
419
|
-
for (const cachedFile of _runnerLogCache.keys()) {
|
|
420
|
-
if (path.dirname(cachedFile) === logsDir && !validFiles.has(cachedFile))
|
|
421
|
-
_runnerLogCache.delete(cachedFile);
|
|
422
|
-
}
|
|
423
|
-
const events = files
|
|
424
|
-
.flatMap(readRunnerLogFile)
|
|
425
|
-
.filter(event => event.sessionId === evolSessionId)
|
|
426
|
-
.sort((a, b) => runnerEventTimestamp(a) - runnerEventTimestamp(b));
|
|
427
|
-
const tasks = new Map();
|
|
428
|
-
for (const event of events) {
|
|
429
|
-
const taskId = event.taskId || event.toolUseId;
|
|
430
|
-
if (!taskId)
|
|
431
|
-
continue;
|
|
432
|
-
const at = runnerEventTimestamp(event);
|
|
433
|
-
let task = tasks.get(taskId);
|
|
434
|
-
if (!task) {
|
|
435
|
-
task = {
|
|
436
|
-
taskId,
|
|
437
|
-
taskKind: event.taskKind || 'agent',
|
|
438
|
-
toolUseId: event.toolUseId || null,
|
|
439
|
-
description: event.description || taskId,
|
|
440
|
-
subagentType: event.subagentType || null,
|
|
441
|
-
status: 'running',
|
|
442
|
-
terminalReason: '',
|
|
443
|
-
progress: '',
|
|
444
|
-
lastToolName: '',
|
|
445
|
-
totalTokens: 0,
|
|
446
|
-
toolUses: 0,
|
|
447
|
-
durationMs: 0,
|
|
448
|
-
startedAt: at,
|
|
449
|
-
updatedAt: at,
|
|
450
|
-
};
|
|
451
|
-
tasks.set(taskId, task);
|
|
452
|
-
}
|
|
453
|
-
if (event.type === 'runner:task-started') {
|
|
454
|
-
task.startedAt = task.startedAt ? Math.min(task.startedAt, at) : at;
|
|
455
|
-
task.status = 'running';
|
|
456
|
-
}
|
|
457
|
-
else if (event.type === 'runner:task-progress') {
|
|
458
|
-
task.progress = event.summary || event.description || task.progress;
|
|
459
|
-
}
|
|
460
|
-
else if (event.type === 'runner:task-notification') {
|
|
461
|
-
task.status = event.status || 'completed';
|
|
462
|
-
task.terminalReason = event.terminalReason || task.terminalReason;
|
|
463
|
-
}
|
|
464
|
-
task.updatedAt = Math.max(task.updatedAt || 0, at);
|
|
465
|
-
task.taskKind = event.taskKind || task.taskKind;
|
|
466
|
-
task.toolUseId = event.toolUseId || task.toolUseId;
|
|
467
|
-
task.description = event.type === 'runner:task-progress'
|
|
468
|
-
? task.description
|
|
469
|
-
: (event.description || task.description);
|
|
470
|
-
task.subagentType = event.subagentType || task.subagentType;
|
|
471
|
-
task.lastToolName = event.lastToolName || task.lastToolName;
|
|
472
|
-
task.totalTokens = event.usage?.totalTokens ?? event.totalTokens ?? task.totalTokens;
|
|
473
|
-
task.toolUses = event.usage?.toolUses ?? event.toolUses ?? task.toolUses;
|
|
474
|
-
task.durationMs = event.usage?.durationMs ?? event.durationMs ?? task.durationMs;
|
|
475
|
-
}
|
|
476
|
-
return Array.from(tasks.values())
|
|
477
|
-
.sort((a, b) => a.startedAt - b.startedAt)
|
|
478
|
-
.map(task => ({
|
|
479
|
-
role: 'system',
|
|
480
|
-
ts: task.startedAt,
|
|
481
|
-
uuid: `runner-task:${evolSessionId}:${task.taskId}`,
|
|
482
|
-
category: 'subtask',
|
|
483
|
-
blocks: [{ kind: 'subtask', ...task }],
|
|
484
|
-
}));
|
|
485
|
-
}
|
|
486
340
|
// ── 完整 transcript 解析(省略 tool 参数/chat send 检测等细节,保留费用计算)──
|
|
487
341
|
const PRICING = {
|
|
488
342
|
'claude-opus-4-8': [5, 6.25, 0.5, 25], 'claude-opus-4': [5, 6.25, 0.5, 25],
|
|
@@ -615,7 +469,7 @@ function readTranscriptFile(file, readStats) {
|
|
|
615
469
|
cacheTranscriptDetail(file, stat.mtimeMs, stat.size, detail);
|
|
616
470
|
return detail;
|
|
617
471
|
}
|
|
618
|
-
export function readClaudeSessionResult(params) {
|
|
472
|
+
export async function readClaudeSessionResult(params) {
|
|
619
473
|
const projects = listProjects();
|
|
620
474
|
const project = resolveProject(params, projects);
|
|
621
475
|
const sessionId = typeof params.sessionId === 'string' ? params.sessionId : '';
|
|
@@ -627,7 +481,7 @@ export function readClaudeSessionResult(params) {
|
|
|
627
481
|
return null;
|
|
628
482
|
const detail = readTranscriptFile(file);
|
|
629
483
|
const bind = buildBindMap().get(sessionId);
|
|
630
|
-
const taskTurns = bind?.evolSessionId ? readRunnerTaskTurns(resolvePaths().logs, bind.evolSessionId) : [];
|
|
484
|
+
const taskTurns = bind?.evolSessionId ? await readRunnerTaskTurns(resolvePaths().logs, bind.evolSessionId) : [];
|
|
631
485
|
const turns = detail.turns.concat(taskTurns).sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
|
632
486
|
const block = turns[key[0]]?.blocks?.[key[1]];
|
|
633
487
|
return block?.kind === 'tool_result' && typeof block.text === 'string' ? block.text : null;
|
|
@@ -679,7 +533,7 @@ async function buildSnapshot(params) {
|
|
|
679
533
|
const detail = readTranscriptFile(path.join(ccProjectsDir(), project.encoded, `${sessionId}.jsonl`), metrics);
|
|
680
534
|
metrics.detailMs = Date.now() - detailStartedAt;
|
|
681
535
|
const bind = bindMap.get(sessionId);
|
|
682
|
-
const taskTurns = bind?.evolSessionId ? readRunnerTaskTurns(resolvePaths().logs, bind.evolSessionId) : [];
|
|
536
|
+
const taskTurns = bind?.evolSessionId ? await readRunnerTaskTurns(resolvePaths().logs, bind.evolSessionId) : [];
|
|
683
537
|
const turns = detail.turns.concat(taskTurns).sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
|
684
538
|
const page = windowTranscriptTurns(turns, params.turnLimit, params.turnBefore);
|
|
685
539
|
const counts = { ...detail.counts, subtask: taskTurns.length };
|
|
@@ -688,19 +542,69 @@ async function buildSnapshot(params) {
|
|
|
688
542
|
metrics.totalMs = Date.now() - startedAt;
|
|
689
543
|
return { baseagent: 'claude', projects: projList, project: project.encoded, transcripts, turns: previewLargeToolResults(page.turns, page.turnPage.start), turnPage: page.turnPage, sessionId, header, metrics };
|
|
690
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* 会话读取器按 base agent 标识路由;标识来自显式参数,缺省时取
|
|
547
|
+
* defaults.json 的 active_baseagent。这里不再硬编码 claude 回落:没有配置
|
|
548
|
+
* 默认后端时返回空快照,由前端提示,而不是悄悄展示另一个后端的转录。
|
|
549
|
+
*/
|
|
550
|
+
export function resolveSessionBaseagent(params) {
|
|
551
|
+
const explicit = typeof params.baseagent === 'string' ? params.baseagent.trim() : '';
|
|
552
|
+
if (explicit)
|
|
553
|
+
return explicit;
|
|
554
|
+
return configuredActiveBaseagent;
|
|
555
|
+
}
|
|
556
|
+
let configuredActiveBaseagent = null;
|
|
557
|
+
/** 刷新 defaults.json 里的 active_baseagent(server 启动与配置变更时调用)。 */
|
|
558
|
+
export async function refreshConfiguredBaseagent() {
|
|
559
|
+
try {
|
|
560
|
+
const core = await import(toFileUrl(resolveParentDistModule('agents', 'baseagent-availability.js')));
|
|
561
|
+
const snapshot = await core.getBaseagentSnapshot({ probe: false });
|
|
562
|
+
configuredActiveBaseagent = snapshot.active;
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
configuredActiveBaseagent = null;
|
|
566
|
+
}
|
|
567
|
+
return configuredActiveBaseagent;
|
|
568
|
+
}
|
|
569
|
+
export function getConfiguredBaseagent() {
|
|
570
|
+
return configuredActiveBaseagent;
|
|
571
|
+
}
|
|
572
|
+
const EMPTY_SNAPSHOT = (baseagent) => ({
|
|
573
|
+
baseagent,
|
|
574
|
+
projects: [],
|
|
575
|
+
project: null,
|
|
576
|
+
transcripts: [],
|
|
577
|
+
turns: [],
|
|
578
|
+
sessionId: null,
|
|
579
|
+
noConfiguredBaseagent: baseagent == null,
|
|
580
|
+
});
|
|
691
581
|
export const sessionSource = {
|
|
692
582
|
kind: 'session',
|
|
693
583
|
async snapshot(params = {}) {
|
|
694
|
-
|
|
584
|
+
if (!params.baseagent)
|
|
585
|
+
await refreshConfiguredBaseagent();
|
|
586
|
+
const baseagent = resolveSessionBaseagent(params);
|
|
587
|
+
if (!baseagent)
|
|
588
|
+
return EMPTY_SNAPSHOT(null);
|
|
695
589
|
if (baseagent === 'codex') {
|
|
696
|
-
return snapshotCodex(params);
|
|
590
|
+
return snapshotCodex({ ...params, baseagent });
|
|
697
591
|
}
|
|
698
|
-
|
|
592
|
+
if (baseagent === 'ecagent') {
|
|
593
|
+
return snapshotEcagent({ ...params, baseagent });
|
|
594
|
+
}
|
|
595
|
+
return buildSnapshot({ ...params, baseagent });
|
|
699
596
|
},
|
|
700
597
|
subscribe(params, push) {
|
|
701
|
-
const baseagent = params
|
|
598
|
+
const baseagent = resolveSessionBaseagent(params);
|
|
599
|
+
if (!baseagent) {
|
|
600
|
+
push(EMPTY_SNAPSHOT(null));
|
|
601
|
+
return () => { };
|
|
602
|
+
}
|
|
702
603
|
if (baseagent === 'codex') {
|
|
703
|
-
return subscribeCodex(params, push);
|
|
604
|
+
return subscribeCodex({ ...params, baseagent }, push);
|
|
605
|
+
}
|
|
606
|
+
if (baseagent === 'ecagent') {
|
|
607
|
+
return subscribeEcagent({ ...params, baseagent }, push);
|
|
704
608
|
}
|
|
705
609
|
const p = resolvePaths();
|
|
706
610
|
let projectWatcher = null, sessionWatcher = null, logWatcher = null, debounce = null;
|
package/dist/sources/system.js
CHANGED
|
@@ -7,34 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* subscribe: 30s 轮询 + JSON diff,仅变化时 push。
|
|
9
9
|
*/
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
10
|
import { resolvePaths } from '../paths.js';
|
|
13
11
|
import { ipcQuery } from '../ipc-client.js';
|
|
14
12
|
import { watchMenuRequestAuth } from './types.js';
|
|
15
|
-
function readDefaultBaseagents() {
|
|
16
|
-
try {
|
|
17
|
-
const p = resolvePaths();
|
|
18
|
-
const defaultsPath = path.join(p.root, 'agents', 'defaults.json');
|
|
19
|
-
const raw = JSON.parse(fs.readFileSync(defaultsPath, 'utf-8'));
|
|
20
|
-
const baseagents = raw?.baseagents;
|
|
21
|
-
if (!baseagents || typeof baseagents !== 'object')
|
|
22
|
-
return [];
|
|
23
|
-
const active = typeof raw.active_baseagent === 'string' ? raw.active_baseagent : null;
|
|
24
|
-
return Object.entries(baseagents).map(([name, cfg]) => {
|
|
25
|
-
const c = cfg && typeof cfg === 'object' ? cfg : {};
|
|
26
|
-
return {
|
|
27
|
-
name,
|
|
28
|
-
active: name === active,
|
|
29
|
-
model: c.model ?? null,
|
|
30
|
-
effort: c.effort ?? c.reasoning ?? null,
|
|
31
|
-
};
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
13
|
async function menuExec(payload, params) {
|
|
39
14
|
const p = resolvePaths();
|
|
40
15
|
const r = await ipcQuery(p.socket, { type: 'menu.exec', payload, auth: watchMenuRequestAuth(params) }, 5000);
|
|
@@ -48,14 +23,7 @@ async function buildSnapshot(params = {}) {
|
|
|
48
23
|
const daemonRunning = listResp !== null;
|
|
49
24
|
// sysResp 形如 { type:'menu.response', id, name, data | error }
|
|
50
25
|
const system = sysResp?.data ?? null;
|
|
51
|
-
|
|
52
|
-
// 再从后端返回的 baseagents([{name,version}])按 name 补上 CLI 版本号。
|
|
53
|
-
let baseagents = readDefaultBaseagents();
|
|
54
|
-
if (system && Array.isArray(system.baseagents)) {
|
|
55
|
-
const verByName = new Map(system.baseagents.map((b) => [b.name, b.version ?? null]));
|
|
56
|
-
baseagents = baseagents.map((b) => ({ ...b, version: verByName.get(b.name) ?? null }));
|
|
57
|
-
}
|
|
58
|
-
return { daemonRunning, system: system ? { ...system, baseagents } : system, upgrade: null, check: null };
|
|
26
|
+
return { daemonRunning, system, upgrade: null, check: null };
|
|
59
27
|
}
|
|
60
28
|
export const systemSource = {
|
|
61
29
|
kind: 'system',
|
package/dist/sources/triggers.js
CHANGED
|
@@ -42,7 +42,7 @@ function readDaemonOwnerAid(p) {
|
|
|
42
42
|
function readFullAccessEnabled(p) {
|
|
43
43
|
try {
|
|
44
44
|
const raw = JSON.parse(fs.readFileSync(p.daemonConfig, 'utf8'));
|
|
45
|
-
return raw?.fullaccess?.enabled === true;
|
|
45
|
+
return raw?.fullaccess?.enabled === undefined || raw.fullaccess.enabled === true;
|
|
46
46
|
}
|
|
47
47
|
catch {
|
|
48
48
|
return false;
|