@yeaft/webchat-agent 1.0.445 → 1.0.447

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.
@@ -5,7 +5,7 @@ import { verifyAgent } from './auth.js';
5
5
 
6
6
  import { encodeKey } from './encryption.js';
7
7
  import { agents, pendingAgentConnections } from './context.js';
8
- import { userDb } from './database.js';
8
+ import { agentInventoryDb, userDb } from './database.js';
9
9
  import {
10
10
  parseMessage, broadcastAgentList, clearAgentDirCache
11
11
  } from './ws-utils.js';
@@ -52,6 +52,35 @@ function pruneAgentConnectionGenerations() {
52
52
  }
53
53
  }
54
54
 
55
+ function persistAgentInventory(agentId, agent) {
56
+ if (!agentInventoryDb?.upsert || !agent?.name) return;
57
+ try {
58
+ agentInventoryDb.upsert({
59
+ ...agent,
60
+ id: agentId,
61
+ lastSeenAt: agent.lastSeenAt,
62
+ lastConnectedAt: agent.lastConnectedAt,
63
+ });
64
+ } catch (error) {
65
+ // The inventory is an admin read model. Never reject a live Agent because
66
+ // this best-effort durability write failed.
67
+ console.error(`[AgentInventory] Failed to persist ${agentId}:`, error.message);
68
+ }
69
+ }
70
+
71
+ function persistAgentHeartbeat(agentId, agent, now = Date.now()) {
72
+ if (!agentInventoryDb?.touch || !agent) return;
73
+ const lastPersistedAt = Number(agent.inventoryLastPersistedAt) || 0;
74
+ if (lastPersistedAt && now - lastPersistedAt < 10_000) return;
75
+ agent.inventoryLastPersistedAt = now;
76
+ try {
77
+ const touched = agentInventoryDb.touch(agentId, now);
78
+ if (touched === false) persistAgentInventory(agentId, agent);
79
+ } catch (error) {
80
+ console.error(`[AgentInventory] Failed to persist heartbeat for ${agentId}:`, error.message);
81
+ }
82
+ }
83
+
55
84
  export function handleAgentConnection(ws, url) {
56
85
  const clientAgentId = url.searchParams.get('id') || randomUUID();
57
86
  const agentName = url.searchParams.get('name') || `Agent-${clientAgentId.slice(0, 8)}`;
@@ -183,7 +212,9 @@ export function handleAgentConnection(ws, url) {
183
212
  ws.close(1008, 'Account disabled');
184
213
  return;
185
214
  }
186
- markAgentHeartbeatSeen(agent);
215
+ const now = Date.now();
216
+ markAgentHeartbeatSeen(agent, now);
217
+ persistAgentHeartbeat(resolvedAgentId, agent, now);
187
218
  const msg = await parseMessage(data, agent.sessionKey);
188
219
  if (msg) {
189
220
  console.log(`[Agent] Received message from ${resolvedAgentId}: ${msg.type}`);
@@ -235,6 +266,9 @@ export function handleAgentConnection(ws, url) {
235
266
  function handleAgentDisconnect(agentId, agentName, ws) {
236
267
  const agent = agents.get(agentId);
237
268
  if (!agent || agent.ws !== ws) return;
269
+ // Capture the last in-memory heartbeat before removing the live socket. The
270
+ // durable row remains an offline historical record; it never grants access.
271
+ persistAgentInventory(agentId, agent);
238
272
  // Phase 4: 清理目录缓存
239
273
  clearAgentDirCache(agentId);
240
274
  clearWorkbenchCorrelationsForAgent(agentId);
@@ -280,6 +314,7 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
280
314
  sessionKey,
281
315
  isAlive: true,
282
316
  lastSeenAt: Date.now(),
317
+ lastConnectedAt: Date.now(),
283
318
  capabilities: effectiveCapabilities,
284
319
  // The fallback list supports old UI features but cannot prove that an
285
320
  // Agent made an explicit capability claim.
@@ -305,6 +340,7 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
305
340
  }
306
341
  }, 30000);
307
342
  agents.get(agentId)._syncTimeout = syncTimeout;
343
+ persistAgentInventory(agentId, agents.get(agentId));
308
344
 
309
345
  if (existingAgent?.ws && existingAgent.ws !== ws) {
310
346
  clearBrowserRuntimeForAgent(agentId);
@@ -315,7 +351,9 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
315
351
  ws.on('pong', () => {
316
352
  const agent = agents.get(agentId);
317
353
  if (agent?.ws === ws) {
318
- markAgentHeartbeatSeen(agent);
354
+ const now = Date.now();
355
+ markAgentHeartbeatSeen(agent, now);
356
+ persistAgentHeartbeat(agentId, agent, now);
319
357
  if (agent.pingSentAt) {
320
358
  agent.latency = Date.now() - agent.pingSentAt;
321
359
  agent.pingSentAt = null;
@@ -5,7 +5,7 @@ import { generateSkipAuthSession } from './auth.js';
5
5
  import { authenticateRequest } from './auth/request-auth.js';
6
6
  import { encodeKey } from './encryption.js';
7
7
  import { userDb } from './database.js';
8
- import { agents, clearYeaftDebugRequestsForClient, webClients, isHeartbeatMessageType, trackRequest } from './context.js';
8
+ import { agents, clearYeaftDebugRequestsForClient, webClients } from './context.js';
9
9
  import {
10
10
  applyClientHello,
11
11
  BROWSER_RUNTIME_PROTOCOL,
@@ -136,12 +136,6 @@ export function handleWebConnection(ws, url, req = {}) {
136
136
  const client = webClients.get(clientId);
137
137
  const msg = await parseMessage(data, client?.sessionKey);
138
138
  if (!msg) return;
139
- // Stats tracking: exclude heartbeat/control frames. User-turn bytes are
140
- // accounted at the send handlers, not here, so pings and dashboard polling
141
- // don't inflate traffic.
142
- if (!isHeartbeatMessageType(msg.type)) {
143
- trackRequest(client?.userId, data.length || 0, msg.type);
144
- }
145
139
  if (msg.perfTraceId) {
146
140
  recordPerfTraceEvent({
147
141
  traceId: msg.perfTraceId,
@@ -1 +1 @@
1
- {"version":"1.0.445"}
1
+ {"version":"1.0.447"}