@yeaft/webchat-agent 1.0.458 → 1.0.460

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.
@@ -4,10 +4,10 @@ import { CONFIG } from './config.js';
4
4
  import { verifyAgent } from './auth.js';
5
5
 
6
6
  import { encodeKey } from './encryption.js';
7
- import { agents, pendingAgentConnections } from './context.js';
7
+ import { agents, pendingAgentConnections, takeAgentSettingsRequestsForAgent, webClients } from './context.js';
8
8
  import { agentInventoryDb, userDb } from './database.js';
9
9
  import {
10
- parseMessage, broadcastAgentList, clearAgentDirCache
10
+ parseMessage, broadcastAgentList, clearAgentDirCache, sendToWebClient
11
11
  } from './ws-utils.js';
12
12
  import { handleAgentConversation } from './handlers/agent-conversation.js';
13
13
  import { handleAgentOutput } from './handlers/agent-output.js';
@@ -30,6 +30,40 @@ function buildAgentMapKey(ownerId, agentName) {
30
30
  return `${prefix}:${agentName}`;
31
31
  }
32
32
 
33
+ function parseSemver(version) {
34
+ const match = String(version || '').trim().match(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
35
+ if (!match) return null;
36
+ return {
37
+ core: match.slice(1, 4).map(Number),
38
+ prerelease: match[4] ? match[4].split('.') : [],
39
+ };
40
+ }
41
+
42
+ export function isNewerAgentVersion(candidate, current) {
43
+ const next = parseSemver(candidate);
44
+ const installed = parseSemver(current);
45
+ if (!next || !installed) return false;
46
+ for (let index = 0; index < 3; index += 1) {
47
+ if (next.core[index] !== installed.core[index]) return next.core[index] > installed.core[index];
48
+ }
49
+ if (next.prerelease.length === 0 || installed.prerelease.length === 0) {
50
+ return next.prerelease.length === 0 && installed.prerelease.length > 0;
51
+ }
52
+ const length = Math.max(next.prerelease.length, installed.prerelease.length);
53
+ for (let index = 0; index < length; index += 1) {
54
+ const left = next.prerelease[index];
55
+ const right = installed.prerelease[index];
56
+ if (left === right) continue;
57
+ if (left === undefined || right === undefined) return right === undefined;
58
+ const leftNumeric = /^\d+$/.test(left);
59
+ const rightNumeric = /^\d+$/.test(right);
60
+ if (leftNumeric && rightNumeric) return Number(left) > Number(right);
61
+ if (leftNumeric !== rightNumeric) return !leftNumeric;
62
+ return left > right;
63
+ }
64
+ return false;
65
+ }
66
+
33
67
  let nextAgentConnectionGeneration = 0;
34
68
  // Keep the latest generation as a tombstone while an older auth may still arrive.
35
69
  const latestAgentConnectionGenerations = new Map();
@@ -273,6 +307,21 @@ function handleAgentDisconnect(agentId, agentName, ws) {
273
307
  clearAgentDirCache(agentId);
274
308
  clearWorkbenchCorrelationsForAgent(agentId);
275
309
  clearBrowserRuntimeForAgent(agentId);
310
+ for (const pending of takeAgentSettingsRequestsForAgent(agentId)) {
311
+ const client = webClients.get(pending.clientId);
312
+ if (!client?.authenticated) continue;
313
+ const responseTypes = {
314
+ restart: 'restart_agent_ack', dream: 'dream_enabled_changed', upgrade: 'upgrade_agent_ack',
315
+ 'plugins:load': 'yeaft_plugins', 'plugins:update': 'yeaft_plugins_updated',
316
+ 'telemetry:load': 'telemetry_settings', 'telemetry:update': 'telemetry_settings_updated',
317
+ };
318
+ void sendToWebClient(client, {
319
+ type: responseTypes[pending.operation] || 'agent_request_error',
320
+ agentId,
321
+ requestId: pending.requestId,
322
+ error: 'Agent disconnected before completing the request.',
323
+ });
324
+ }
276
325
  // Phase 1: 清理同步超时
277
326
  if (agent._syncTimeout) {
278
327
  clearTimeout(agent._syncTimeout);
@@ -305,6 +354,13 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
305
354
  // agent and keep encrypting outbound (back-compat).
306
355
  const encryptOutbound = !effectiveCapabilities.includes('plaintext-ok');
307
356
 
357
+ const latestAgentVersion = process.env.AGENT_LATEST_VERSION || null;
358
+ // This environment value is only an optional push hint. It is not a registry
359
+ // query and must never claim an equal, older, or malformed version is newer.
360
+ const upgradeAvailable = isNewerAgentVersion(latestAgentVersion, agentVersion)
361
+ ? latestAgentVersion
362
+ : null;
363
+
308
364
  agents.set(agentId, {
309
365
  ws,
310
366
  name: agentName,
@@ -327,6 +383,7 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
327
383
  ownerId,
328
384
  ownerUsername,
329
385
  version: agentVersion,
386
+ upgradeAvailable,
330
387
  platform: agentPlatform,
331
388
  encryptOutbound
332
389
  });
@@ -362,9 +419,6 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
362
419
  });
363
420
 
364
421
  // Send registration (with session key only in production mode)
365
- const latestAgentVersion = process.env.AGENT_LATEST_VERSION || null;
366
- const upgradeAvailable = (latestAgentVersion && agentVersion && latestAgentVersion !== agentVersion) ? latestAgentVersion : null;
367
-
368
422
  ws.send(JSON.stringify({
369
423
  type: 'registered',
370
424
  agentId,
@@ -5,7 +5,12 @@ 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 } from './context.js';
8
+ import {
9
+ agents,
10
+ clearAgentSettingsRequestsForClient,
11
+ clearYeaftDebugRequestsForClient,
12
+ webClients,
13
+ } from './context.js';
9
14
  import {
10
15
  applyClientHello,
11
16
  BROWSER_RUNTIME_PROTOCOL,
@@ -178,6 +183,7 @@ export function handleWebConnection(ws, url, req = {}) {
178
183
  }
179
184
  }
180
185
  clearWorkCenterRequestsForClient(client);
186
+ clearAgentSettingsRequestsForClient(clientId);
181
187
  clearYeaftDebugRequestsForClient(clientId);
182
188
  const browserPeers = clearBrowserRuntimeForClient(client);
183
189
  for (const peer of browserPeers) {
@@ -44,20 +44,21 @@ export async function sendToWebClient(client, msg) {
44
44
  // to plaintext when the agent advertised the `plaintext-ok` capability.
45
45
  // Falls back to ciphertext for old agents that haven't.
46
46
  export async function sendToAgent(agent, msg) {
47
- if (agent.ws.readyState !== WebSocket.OPEN) return;
47
+ if (!agent?.ws || agent.ws.readyState !== WebSocket.OPEN) return false;
48
48
 
49
49
  if (CONFIG.skipAuth || agent.encryptOutbound === false) {
50
50
  agent.ws.send(JSON.stringify(msg));
51
- return;
51
+ return true;
52
52
  }
53
53
 
54
54
  if (!agent.sessionKey) {
55
55
  console.error('Cannot send to agent: missing session key in production mode');
56
56
  agent.ws.close(1008, 'Encryption required');
57
- return;
57
+ return false;
58
58
  }
59
59
  const encrypted = await encrypt(msg, agent.sessionKey);
60
60
  agent.ws.send(JSON.stringify(encrypted));
61
+ return true;
61
62
  }
62
63
 
63
64
  // Parse incoming message (decrypt if needed)
@@ -171,6 +172,7 @@ export async function broadcastAgentList() {
171
172
  capabilities: agent.capabilities || ['terminal', 'file_editor', 'background_tasks'],
172
173
  ...(agent.capabilityMetadataProvided === true ? { capabilityMetadataProvided: true } : {}),
173
174
  version: agent.version || null,
175
+ upgradeAvailable: agent.upgradeAvailable || null,
174
176
  dreamEnabled: agent.dreamEnabled !== false,
175
177
  yeaftStatus: agent.yeaftStatus || null,
176
178
  proxyPorts: agent.proxyPorts || [],
@@ -374,9 +376,8 @@ export async function forwardToClients(agentId, conversationId, msg) {
374
376
  // 转发消息给指定 agent
375
377
  export async function forwardToAgent(agentId, msg) {
376
378
  const agent = agents.get(agentId);
377
- if (agent) {
378
- await sendToAgent(agent, msg);
379
- }
379
+ if (!agent) return false;
380
+ return sendToAgent(agent, msg);
380
381
  }
381
382
 
382
383
  // 通过 agent name 查找 agent
@@ -1 +1 @@
1
- {"version":"1.0.458"}
1
+ {"version":"1.0.460"}