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.
@@ -1,9 +1,11 @@
1
1
  /**
2
- * 会话数据源 — 多 baseagent 支持(Claude / Codex)。
2
+ * 会话数据源 — 多 baseagent 支持(Claude / Codex / EC Agent)。
3
3
  *
4
- * 根据 params.baseagent 参数路由到对应的实现:
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
- const baseagent = params.baseagent || 'claude';
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
- return buildSnapshot(params);
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.baseagent || 'claude';
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;
@@ -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
- // baseagents:以 defaults.json 的 active/model/effort 为主,
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',
@@ -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;