ec-web 0.0.20 → 0.0.22

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.
@@ -10,9 +10,10 @@
10
10
  */
11
11
  import { resolvePaths } from '../paths.js';
12
12
  import { ipcQuery } from '../ipc-client.js';
13
- async function getSnapshot() {
13
+ import { watchMenuRequestAuth } from './types.js';
14
+ async function getSnapshot(params = {}) {
14
15
  const sock = resolvePaths().socket;
15
- const resp = await ipcQuery(sock, { type: 'menu.exec', payload: { type: 'menu.query', name: 'gateway', id: 'ecw-gateway-snap' }, auth: { source: 'ecweb', localDirect: true } }, 5000);
16
+ const resp = await ipcQuery(sock, { type: 'menu.exec', payload: { type: 'menu.query', name: 'gateway', id: 'ecw-gateway-snap' }, auth: watchMenuRequestAuth(params) }, 5000);
16
17
  if (!resp) {
17
18
  return { gateways: [], scopes: [], types: [], error: 'evolcore 未运行或 socket 不可达' };
18
19
  }
@@ -34,8 +35,8 @@ async function getSnapshot() {
34
35
  }
35
36
  export const gatewaySource = {
36
37
  kind: 'gateway',
37
- async snapshot() {
38
- return getSnapshot();
38
+ async snapshot(params = {}) {
39
+ return getSnapshot(params);
39
40
  },
40
41
  subscribe(_params, _push) {
41
42
  // 网关配置变更不频繁,无文件监听;前端通过操作后主动 re-subscribe 刷新。
@@ -14,6 +14,7 @@
14
14
  import fs from 'node:fs';
15
15
  import { resolvePaths } from '../paths.js';
16
16
  import { ipcQuery } from '../ipc-client.js';
17
+ import crypto from 'node:crypto';
17
18
  function processGroupKey(process) {
18
19
  return `${process.kind}\u0000${process.name}`;
19
20
  }
@@ -192,9 +193,9 @@ function sampleNetworkRate(ts) {
192
193
  previousNetworkSample = { ...totals, ts };
193
194
  return latestNetworkRate;
194
195
  }
195
- async function fetchSnapshot() {
196
+ async function fetchSnapshot(leaseId) {
196
197
  const p = resolvePaths();
197
- const resp = await ipcQuery(p.socket, { type: 'monitor-snapshot' }, 3000);
198
+ const resp = await ipcQuery(p.socket, { type: 'monitor-snapshot', ...(leaseId ? { leaseId } : {}) }, 3000);
198
199
  // resp === null → daemon 离线或 socket 不可达;resp.ok === false → 命令不受支持(旧 daemon)
199
200
  if (!resp?.ok) {
200
201
  return { daemonRunning: resp !== null, snapshot: null, history: { fine: [], mid: [], coarse: [] } };
@@ -237,8 +238,24 @@ async function fetchSnapshot() {
237
238
  }
238
239
  export const monitorSource = {
239
240
  kind: 'monitor',
240
- async snapshot() {
241
- return fetchSnapshot();
241
+ async activate(params) {
242
+ const leaseId = crypto.randomUUID();
243
+ const p = resolvePaths();
244
+ const response = await ipcQuery(p.socket, { type: 'monitor-subscribe', leaseId }, 3000);
245
+ if (!response?.ok)
246
+ throw new Error('daemon rejected monitor subscription');
247
+ params.__monitorLeaseId = leaseId;
248
+ let released = false;
249
+ return () => {
250
+ if (released)
251
+ return;
252
+ released = true;
253
+ delete params.__monitorLeaseId;
254
+ void ipcQuery(p.socket, { type: 'monitor-unsubscribe', leaseId }, 3000);
255
+ };
256
+ },
257
+ async snapshot(params) {
258
+ return fetchSnapshot(params?.__monitorLeaseId);
242
259
  },
243
260
  subscribe(_params, push) {
244
261
  let lastJson = '';
@@ -247,7 +264,7 @@ export const monitorSource = {
247
264
  if (stopped)
248
265
  return;
249
266
  try {
250
- const snap = await fetchSnapshot();
267
+ const snap = await fetchSnapshot(_params.__monitorLeaseId);
251
268
  const json = JSON.stringify(snap);
252
269
  if (json !== lastJson) {
253
270
  lastJson = json;
@@ -13,8 +13,6 @@ import { decodeDirSegment, isTransientProtocolMessage, readAllJsonlLines, readJs
13
13
  let contextModulesPromise = null;
14
14
  const sessionAdapterPromises = new Map();
15
15
  let peerDisplayResolverPromise = null;
16
- const aunGroupNameCache = new Map();
17
- const aunGroupNameRequests = new Map();
18
16
  let agentDisplayNamesCache = null;
19
17
  const messageFileCache = new Map();
20
18
  function getContextModules() {
@@ -67,94 +65,10 @@ export function resolveAunGroupName(names, groupId, fallback) {
67
65
  }
68
66
  return fallback;
69
67
  }
70
- async function fetchAunGroupNames(aid) {
71
- const names = new Map();
72
- let cursor = null;
73
- let page = 0;
74
- do {
75
- page += 1;
76
- const response = await ipcQuery(resolvePaths().socket, {
77
- type: 'menu.exec',
78
- payload: {
79
- type: 'menu.query',
80
- name: 'connect',
81
- cmd: '/connect',
82
- agent: aid,
83
- id: `ecweb-msg-groups-${Date.now()}-${page}`,
84
- args: { self: aid, view: 'groups', limit: 200, ...(cursor ? { cursor } : {}) },
85
- },
86
- auth: { source: 'ecweb', localDirect: true },
87
- }, 10_000);
88
- const data = response?.ok && !response.response?.error ? response.response?.data : null;
89
- if (!data || !Array.isArray(data.items))
90
- break;
91
- for (const item of data.items) {
92
- const groupId = String(item?.groupId || '').trim();
93
- const displayName = String(item?.displayName || '').trim();
94
- if (!groupId || !displayName)
95
- continue;
96
- for (const variant of aunGroupIdVariants(groupId))
97
- names.set(variant, displayName);
98
- }
99
- cursor = typeof data.nextCursor === 'string' && data.nextCursor ? data.nextCursor : null;
100
- } while (cursor && page < 20);
101
- return names;
102
- }
103
- function getAunGroupNames(aid) {
104
- const cached = aunGroupNameCache.get(aid);
105
- if (cached && cached.expiresAt > Date.now())
106
- return Promise.resolve(cached.names);
107
- const pending = aunGroupNameRequests.get(aid);
108
- if (pending)
109
- return pending;
110
- const request = fetchAunGroupNames(aid)
111
- .then(names => {
112
- aunGroupNameCache.set(aid, {
113
- names,
114
- expiresAt: Date.now() + (names.size ? 5 * 60_000 : 15_000),
115
- });
116
- return names;
117
- })
118
- .catch(() => new Map())
119
- .finally(() => aunGroupNameRequests.delete(aid));
120
- aunGroupNameRequests.set(aid, request);
121
- return request;
122
- }
123
- async function hydrateAunGroupNames(chats, waitForRefresh = false) {
124
- const aids = [...new Set(chats
125
- .filter(chat => chat.channelType === 'aun' && chat.chatType === 'group')
126
- .map(chat => chat.selfAID))];
127
- if (!aids.length)
128
- return;
129
- const directories = new Map();
130
- if (waitForRefresh) {
131
- for (const [aid, names] of await Promise.all(aids.map(async (aid) => [aid, await getAunGroupNames(aid)]))) {
132
- directories.set(aid, names);
133
- }
134
- }
135
- else {
136
- for (const aid of aids) {
137
- const cached = aunGroupNameCache.get(aid);
138
- if (cached)
139
- directories.set(aid, cached.names);
140
- if (!cached || cached.expiresAt <= Date.now())
141
- void getAunGroupNames(aid);
142
- }
143
- }
144
- for (const chat of chats) {
145
- if (chat.channelType !== 'aun' || chat.chatType !== 'group')
146
- continue;
147
- const groupName = resolveAunGroupName(directories.get(chat.selfAID) || new Map(), chat.groupId, chat.groupName);
148
- if (!groupName)
149
- continue;
150
- chat.groupName = groupName;
151
- chat.peerName = groupName;
152
- }
153
- }
154
68
  export function resolveSessionBindDisplayName(bind, resolver, aunGroupNames = new Map()) {
155
69
  const isGroup = bind.chatType === 'group' || !!bind.groupId;
156
70
  if (bind.channelType === 'aun' && isGroup) {
157
- return resolveAunGroupName(aunGroupNames, bind.groupId || bind.channelId, bind.groupName || null);
71
+ return bind.groupName || bind.groupId || bind.channelId || null;
158
72
  }
159
73
  if (isGroup)
160
74
  return bind.groupName || null;
@@ -166,18 +80,14 @@ export function resolveSessionBindDisplayName(bind, resolver, aunGroupNames = ne
166
80
  }, bind.peerId || bind.channelId, bind.peerName);
167
81
  }
168
82
  /** 为会话页绑定标签补齐联系人名或 AUN 群名,不改变原始 channelId。 */
169
- export async function hydrateSessionBindDisplayNames(binds) {
83
+ export async function hydrateSessionBindDisplayNames(binds, params = {}) {
84
+ void params;
170
85
  const items = [...binds];
171
86
  if (!items.length)
172
87
  return;
173
88
  const resolver = await getPeerDisplayResolver();
174
- const groupAids = [...new Set(items
175
- .filter(bind => bind.channelType === 'aun' && (bind.chatType === 'group' || !!bind.groupId))
176
- .map(bind => bind.selfAID)
177
- .filter(Boolean))];
178
- const groupDirectories = new Map(await Promise.all(groupAids.map(async (aid) => [aid, await getAunGroupNames(aid)])));
179
89
  for (const bind of items) {
180
- bind.displayName = resolveSessionBindDisplayName(bind, resolver, groupDirectories.get(bind.selfAID) || new Map());
90
+ bind.displayName = resolveSessionBindDisplayName(bind, resolver, new Map());
181
91
  }
182
92
  }
183
93
  function getSessionAdapter(baseagent) {
@@ -943,8 +853,6 @@ async function buildSnapshot(params) {
943
853
  : requestedScope;
944
854
  const peer = params.peer || null;
945
855
  const inScope = selectedChats(chats, scope);
946
- if (scope)
947
- await hydrateAunGroupNames(inScope);
948
856
  const peers = scope ? mergePeerInfo(inScope) : [];
949
857
  const requestedLimit = Number(params.limit);
950
858
  const limit = Number.isFinite(requestedLimit) ? Math.min(Math.max(Math.trunc(requestedLimit), 20), 200) : 100;