@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.
- package/connection/message-router.js +6 -6
- package/connection/upgrade.js +20 -16
- package/index.js +1 -1
- package/local-runtime/server/context.js +101 -3
- package/local-runtime/server/handlers/agent-sync.js +37 -36
- package/local-runtime/server/handlers/client-conversation.js +10 -3
- package/local-runtime/server/handlers/client-misc.js +62 -19
- package/local-runtime/server/ws-agent.js +59 -5
- package/local-runtime/server/ws-client.js +7 -1
- package/local-runtime/server/ws-utils.js +7 -6
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +228 -116
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
-
|
|
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.
|
|
1
|
+
{"version":"1.0.460"}
|