ec-web 0.0.25 → 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;
@@ -46,41 +46,41 @@ export function queryStatsForDashboard() {
46
46
  const h24ago = Date.now() - 24 * 60 * 60 * 1000;
47
47
  try {
48
48
  // Today summary with cost
49
- const todayRow = db.prepare(`
50
- SELECT
51
- COALESCE(SUM(input_tokens),0) AS input_tokens,
52
- COALESCE(SUM(output_tokens),0) AS output_tokens,
53
- COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
54
- COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
55
- COUNT(*) AS call_count,
56
- COALESCE(SUM(cost_gateway_usd),0) AS cost_usd,
57
- COALESCE(SUM(cost_gateway_cny),0) AS cost_cny
58
- FROM usage_events WHERE ts >= ?
49
+ const todayRow = db.prepare(`
50
+ SELECT
51
+ COALESCE(SUM(input_tokens),0) AS input_tokens,
52
+ COALESCE(SUM(output_tokens),0) AS output_tokens,
53
+ COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
54
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
55
+ COUNT(*) AS call_count,
56
+ COALESCE(SUM(cost_gateway_usd),0) AS cost_usd,
57
+ COALESCE(SUM(cost_gateway_cny),0) AS cost_cny
58
+ FROM usage_events WHERE ts >= ?
59
59
  `).get(todayStart);
60
60
  const totalIn = (todayRow.input_tokens ?? 0) + (todayRow.cache_read_tokens ?? 0);
61
61
  const hitRate = totalIn > 0 ? (todayRow.cache_read_tokens ?? 0) / totalIn : 0;
62
62
  // Hourly (last 24h)
63
- const hourly = db.prepare(`
64
- SELECT
65
- strftime('%Y-%m-%d %H:00', ts/1000, 'unixepoch', 'localtime') AS hour,
66
- COALESCE(SUM(input_tokens),0) AS input_tokens,
67
- COALESCE(SUM(output_tokens),0) AS output_tokens,
68
- COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
69
- COUNT(*) AS call_count
70
- FROM usage_events WHERE ts >= ?
71
- GROUP BY hour ORDER BY hour
63
+ const hourly = db.prepare(`
64
+ SELECT
65
+ strftime('%Y-%m-%d %H:00', ts/1000, 'unixepoch', 'localtime') AS hour,
66
+ COALESCE(SUM(input_tokens),0) AS input_tokens,
67
+ COALESCE(SUM(output_tokens),0) AS output_tokens,
68
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
69
+ COUNT(*) AS call_count
70
+ FROM usage_events WHERE ts >= ?
71
+ GROUP BY hour ORDER BY hour
72
72
  `).all(h24ago);
73
73
  // Top models (today)
74
- const top_models = db.prepare(`
75
- SELECT model, SUM(input_tokens+output_tokens) AS total_tokens, COUNT(*) AS call_count
76
- FROM usage_events WHERE ts >= ?
77
- GROUP BY model ORDER BY total_tokens DESC LIMIT 10
74
+ const top_models = db.prepare(`
75
+ SELECT model, SUM(input_tokens+output_tokens) AS total_tokens, COUNT(*) AS call_count
76
+ FROM usage_events WHERE ts >= ?
77
+ GROUP BY model ORDER BY total_tokens DESC LIMIT 10
78
78
  `).all(todayStart);
79
79
  // Top peers (today)
80
- const top_peers = db.prepare(`
81
- SELECT peer_key, SUM(input_tokens+output_tokens) AS total_tokens, COUNT(*) AS call_count
82
- FROM usage_events WHERE ts >= ?
83
- GROUP BY peer_key ORDER BY total_tokens DESC LIMIT 5
80
+ const top_peers = db.prepare(`
81
+ SELECT peer_key, SUM(input_tokens+output_tokens) AS total_tokens, COUNT(*) AS call_count
82
+ FROM usage_events WHERE ts >= ?
83
+ GROUP BY peer_key ORDER BY total_tokens DESC LIMIT 5
84
84
  `).all(todayStart);
85
85
  return {
86
86
  today: { ...todayRow, cache_hit_rate: hitRate },
@@ -124,16 +124,16 @@ export function queryStatsExplorer(params) {
124
124
  }
125
125
  const where = conds.length ? 'WHERE ' + conds.join(' AND ') : '';
126
126
  try {
127
- return db.prepare(`
128
- SELECT
129
- strftime('${strfmt}', ts/1000, 'unixepoch', 'localtime') AS period,
130
- COALESCE(SUM(input_tokens),0) AS input_tokens,
131
- COALESCE(SUM(output_tokens),0) AS output_tokens,
132
- COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
133
- COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
134
- COUNT(*) AS call_count
135
- FROM usage_events ${where}
136
- GROUP BY period ORDER BY period
127
+ return db.prepare(`
128
+ SELECT
129
+ strftime('${strfmt}', ts/1000, 'unixepoch', 'localtime') AS period,
130
+ COALESCE(SUM(input_tokens),0) AS input_tokens,
131
+ COALESCE(SUM(output_tokens),0) AS output_tokens,
132
+ COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
133
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
134
+ COUNT(*) AS call_count
135
+ FROM usage_events ${where}
136
+ GROUP BY period ORDER BY period
137
137
  `).all(...p);
138
138
  }
139
139
  finally {
@@ -162,14 +162,14 @@ export function queryStatsByPeer(params) {
162
162
  const where = conds.length ? 'WHERE ' + conds.join(' AND ') : '';
163
163
  const limit = params.limit ?? 50;
164
164
  try {
165
- const rows = db.prepare(`
166
- SELECT peer_key, peer_type,
167
- COALESCE(SUM(input_tokens),0) AS input_tokens,
168
- COALESCE(SUM(output_tokens),0) AS output_tokens,
169
- COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
170
- COUNT(*) AS call_count
171
- FROM usage_events ${where}
172
- GROUP BY peer_key ORDER BY (COALESCE(SUM(input_tokens),0) + COALESCE(SUM(output_tokens),0)) DESC LIMIT ${limit}
165
+ const rows = db.prepare(`
166
+ SELECT peer_key, peer_type,
167
+ COALESCE(SUM(input_tokens),0) AS input_tokens,
168
+ COALESCE(SUM(output_tokens),0) AS output_tokens,
169
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
170
+ COUNT(*) AS call_count
171
+ FROM usage_events ${where}
172
+ GROUP BY peer_key ORDER BY (COALESCE(SUM(input_tokens),0) + COALESCE(SUM(output_tokens),0)) DESC LIMIT ${limit}
173
173
  `).all(...p);
174
174
  // 为每个peer添加名称和详细信息
175
175
  return rows.map((row) => {
@@ -212,34 +212,34 @@ export function queryStatsOverview(params) {
212
212
  }
213
213
  const where = conds.length ? 'WHERE ' + conds.join(' AND ') : '';
214
214
  // Token and cost aggregation with filters
215
- const allRow = db.prepare(`
216
- SELECT
217
- COALESCE(SUM(input_tokens),0) AS input_tokens,
218
- COALESCE(SUM(output_tokens),0) AS output_tokens,
219
- COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
220
- COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
221
- COUNT(*) AS call_count,
222
- COALESCE(SUM(cost_official_usd),0) AS cost_official_usd,
223
- COALESCE(SUM(cost_official_cny),0) AS cost_official_cny,
224
- COALESCE(SUM(cost_gateway_usd),0) AS cost_usd,
225
- COALESCE(SUM(cost_gateway_cny),0) AS cost_cny
226
- FROM usage_events ${where}
215
+ const allRow = db.prepare(`
216
+ SELECT
217
+ COALESCE(SUM(input_tokens),0) AS input_tokens,
218
+ COALESCE(SUM(output_tokens),0) AS output_tokens,
219
+ COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
220
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
221
+ COUNT(*) AS call_count,
222
+ COALESCE(SUM(cost_official_usd),0) AS cost_official_usd,
223
+ COALESCE(SUM(cost_official_cny),0) AS cost_official_cny,
224
+ COALESCE(SUM(cost_gateway_usd),0) AS cost_usd,
225
+ COALESCE(SUM(cost_gateway_cny),0) AS cost_cny
226
+ FROM usage_events ${where}
227
227
  `).get(...p);
228
228
  // By agent aggregation with filters
229
- const byAgentRows = db.prepare(`
230
- SELECT agent_aid,
231
- COALESCE(SUM(input_tokens),0) AS input_tokens,
232
- COALESCE(SUM(output_tokens),0) AS output_tokens,
233
- COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
234
- COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
235
- COUNT(*) AS call_count,
236
- COALESCE(SUM(cost_official_usd),0) AS cost_official_usd,
237
- COALESCE(SUM(cost_official_cny),0) AS cost_official_cny,
238
- COALESCE(SUM(cost_gateway_usd),0) AS cost_usd,
239
- COALESCE(SUM(cost_gateway_cny),0) AS cost_cny
240
- FROM usage_events ${where}
241
- GROUP BY agent_aid
242
- ORDER BY (COALESCE(SUM(input_tokens),0) + COALESCE(SUM(output_tokens),0)) DESC
229
+ const byAgentRows = db.prepare(`
230
+ SELECT agent_aid,
231
+ COALESCE(SUM(input_tokens),0) AS input_tokens,
232
+ COALESCE(SUM(output_tokens),0) AS output_tokens,
233
+ COALESCE(SUM(cache_creation_tokens),0) AS cache_creation_tokens,
234
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
235
+ COUNT(*) AS call_count,
236
+ COALESCE(SUM(cost_official_usd),0) AS cost_official_usd,
237
+ COALESCE(SUM(cost_official_cny),0) AS cost_official_cny,
238
+ COALESCE(SUM(cost_gateway_usd),0) AS cost_usd,
239
+ COALESCE(SUM(cost_gateway_cny),0) AS cost_cny
240
+ FROM usage_events ${where}
241
+ GROUP BY agent_aid
242
+ ORDER BY (COALESCE(SUM(input_tokens),0) + COALESCE(SUM(output_tokens),0)) DESC
243
243
  `).all(...p);
244
244
  // 为每个agent添加名称
245
245
  const byAgentWithNames = byAgentRows.map((row) => ({
@@ -303,14 +303,14 @@ export function queryStatsByAgent(params) {
303
303
  const where = conds.length ? 'WHERE ' + conds.join(' AND ') : '';
304
304
  const limit = params.limit ?? 50;
305
305
  try {
306
- const rows = db.prepare(`
307
- SELECT agent_aid,
308
- COALESCE(SUM(input_tokens),0) AS input_tokens,
309
- COALESCE(SUM(output_tokens),0) AS output_tokens,
310
- COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
311
- COUNT(*) AS call_count
312
- FROM usage_events ${where}
313
- GROUP BY agent_aid ORDER BY (COALESCE(SUM(input_tokens),0) + COALESCE(SUM(output_tokens),0)) DESC LIMIT ${limit}
306
+ const rows = db.prepare(`
307
+ SELECT agent_aid,
308
+ COALESCE(SUM(input_tokens),0) AS input_tokens,
309
+ COALESCE(SUM(output_tokens),0) AS output_tokens,
310
+ COALESCE(SUM(cache_read_tokens),0) AS cache_read_tokens,
311
+ COUNT(*) AS call_count
312
+ FROM usage_events ${where}
313
+ GROUP BY agent_aid ORDER BY (COALESCE(SUM(input_tokens),0) + COALESCE(SUM(output_tokens),0)) DESC LIMIT ${limit}
314
314
  `).all(...p);
315
315
  // 为每个agent添加名称
316
316
  return rows.map((row) => ({
@@ -353,15 +353,15 @@ export function queryUsageDetail(params) {
353
353
  const countRow = db.prepare(`SELECT COUNT(*) as total FROM usage_events ${where}`).get(...p);
354
354
  const total = countRow?.total || 0;
355
355
  // 获取数据
356
- const data = db.prepare(`
357
- SELECT ts, agent_aid, peer_key, model,
358
- input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
359
- COALESCE(cost_official_usd, 0) AS cost_official_usd,
360
- COALESCE(cost_official_cny, 0) AS cost_official_cny,
361
- COALESCE(cost_gateway_usd, 0) AS cost_gateway_usd,
362
- COALESCE(cost_gateway_cny, 0) AS cost_gateway_cny
363
- FROM usage_events ${where}
364
- ORDER BY ts DESC LIMIT ${limit} OFFSET ${offset}
356
+ const data = db.prepare(`
357
+ SELECT ts, agent_aid, peer_key, model,
358
+ input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
359
+ COALESCE(cost_official_usd, 0) AS cost_official_usd,
360
+ COALESCE(cost_official_cny, 0) AS cost_official_cny,
361
+ COALESCE(cost_gateway_usd, 0) AS cost_gateway_usd,
362
+ COALESCE(cost_gateway_cny, 0) AS cost_gateway_cny
363
+ FROM usage_events ${where}
364
+ ORDER BY ts DESC LIMIT ${limit} OFFSET ${offset}
365
365
  `).all(...p);
366
366
  // 为每条记录添加agent_name (需要从文件系统读取agent.md)
367
367
  const dataWithNames = data.map((row) => ({
@@ -391,10 +391,10 @@ export function queryUsedModels(params) {
391
391
  }
392
392
  const where = conds.length ? 'WHERE ' + conds.join(' AND ') : '';
393
393
  try {
394
- const rows = db.prepare(`
395
- SELECT DISTINCT model
396
- FROM usage_events ${where}
397
- ORDER BY model
394
+ const rows = db.prepare(`
395
+ SELECT DISTINCT model
396
+ FROM usage_events ${where}
397
+ ORDER BY model
398
398
  `).all(...p);
399
399
  return rows.map((row) => row.model).filter(Boolean);
400
400
  }
@@ -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;
@@ -13,6 +13,7 @@ export function watchMenuAuth(params) {
13
13
  return {
14
14
  localDirect: auth?.localDirect === true,
15
15
  actorAid: typeof auth?.actorAid === 'string' ? auth.actorAid : null,
16
+ daemonOwner: auth?.daemonOwner === true,
16
17
  // This marker is injected by the ECWeb server, never read from browser
17
18
  // request parameters. Authenticated ECWeb has daemon-owner authority.
18
19
  canApprovePersistentFullAccess: auth?.canApprovePersistentFullAccess === true,