@yeaft/webchat-agent 1.0.387 → 1.0.389
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/index.js +3 -1
- package/local-runtime/server/context.js +78 -0
- package/local-runtime/server/handlers/agent-output.js +36 -25
- package/local-runtime/server/handlers/client-conversation.js +47 -1
- package/local-runtime/server/handlers/client-misc.js +9 -3
- package/local-runtime/server/ws-agent.js +7 -1
- package/local-runtime/server/ws-client.js +2 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +95 -104
- 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
- package/yeaft/debug-trace.js +168 -1
- package/yeaft/web-bridge.js +7 -0
package/connection/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export function connect(WebSocketImpl = WebSocket) {
|
|
|
25
25
|
name: ctx.CONFIG.agentName,
|
|
26
26
|
instanceId,
|
|
27
27
|
workDir: ctx.CONFIG.workDir,
|
|
28
|
+
platform: process.platform,
|
|
28
29
|
capabilities: ctx.agentCapabilities.join(',')
|
|
29
30
|
});
|
|
30
31
|
|
|
@@ -72,7 +73,8 @@ export function connect(WebSocketImpl = WebSocket) {
|
|
|
72
73
|
tempId: msg.tempId,
|
|
73
74
|
secret: ctx.CONFIG.agentSecret,
|
|
74
75
|
capabilities: ctx.agentCapabilities,
|
|
75
|
-
version: ctx.agentVersion
|
|
76
|
+
version: ctx.agentVersion,
|
|
77
|
+
platform: process.platform
|
|
76
78
|
}));
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
@@ -37,6 +37,84 @@ export const userFileTabs = new Map();
|
|
|
37
37
|
// fileId → { buffer, mimeType, filename, createdAt, token }
|
|
38
38
|
export const previewFiles = new Map();
|
|
39
39
|
|
|
40
|
+
// Debug trace replies may come from an older Agent that does not echo the
|
|
41
|
+
// private browser client id. Keep the correlation on the Server, where the
|
|
42
|
+
// original Agent/Session ownership check happened, and consume it exactly once.
|
|
43
|
+
const YEAFT_DEBUG_REQUEST_TTL_MS = 30_000;
|
|
44
|
+
const YEAFT_DEBUG_REQUEST_MAX_PENDING = 2048;
|
|
45
|
+
export const pendingYeaftDebugRequests = new Map();
|
|
46
|
+
|
|
47
|
+
function yeaftDebugRequestKey(agentId, requestId) {
|
|
48
|
+
return `${String(agentId || '')}\u0000${String(requestId || '')}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pruneYeaftDebugRequests(now = Date.now()) {
|
|
52
|
+
for (const [key, pending] of pendingYeaftDebugRequests) {
|
|
53
|
+
if (!pending || pending.expiresAt <= now || !webClients.has(pending.clientId)) {
|
|
54
|
+
pendingYeaftDebugRequests.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register one owner-checked browser request before forwarding it to an Agent.
|
|
61
|
+
* @param {{agentId:string, requestId:string, sessionId:string, clientId:string, userId:string}} request
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
export function registerYeaftDebugRequest({ agentId, requestId, sessionId, clientId, userId }) {
|
|
65
|
+
if (!agentId || !requestId || !sessionId || !clientId || !userId) return false;
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
pruneYeaftDebugRequests(now);
|
|
68
|
+
const key = yeaftDebugRequestKey(agentId, requestId);
|
|
69
|
+
if (pendingYeaftDebugRequests.has(key)) return false;
|
|
70
|
+
if (pendingYeaftDebugRequests.size >= YEAFT_DEBUG_REQUEST_MAX_PENDING) {
|
|
71
|
+
const oldestKey = pendingYeaftDebugRequests.keys().next().value;
|
|
72
|
+
if (oldestKey != null) pendingYeaftDebugRequests.delete(oldestKey);
|
|
73
|
+
}
|
|
74
|
+
pendingYeaftDebugRequests.set(key, {
|
|
75
|
+
agentId,
|
|
76
|
+
requestId,
|
|
77
|
+
sessionId,
|
|
78
|
+
clientId,
|
|
79
|
+
userId,
|
|
80
|
+
expiresAt: now + YEAFT_DEBUG_REQUEST_TTL_MS,
|
|
81
|
+
});
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Consume a matching, unexpired correlation exactly once.
|
|
87
|
+
* @param {{agentId:string, requestId:string, sessionId?:string|null}} response
|
|
88
|
+
* @returns {{agentId:string, requestId:string, sessionId:string, clientId:string, userId:string, expiresAt:number}|null}
|
|
89
|
+
*/
|
|
90
|
+
export function consumeYeaftDebugRequest({ agentId, requestId, sessionId }) {
|
|
91
|
+
if (!agentId || !requestId) return null;
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
pruneYeaftDebugRequests(now);
|
|
94
|
+
const key = yeaftDebugRequestKey(agentId, requestId);
|
|
95
|
+
const pending = pendingYeaftDebugRequests.get(key);
|
|
96
|
+
if (!pending) return null;
|
|
97
|
+
if (sessionId && pending.sessionId !== sessionId) return null;
|
|
98
|
+
pendingYeaftDebugRequests.delete(key);
|
|
99
|
+
return pending;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Remove one correlation after forwarding fails. */
|
|
103
|
+
export function deleteYeaftDebugRequest({ agentId, requestId, clientId = null }) {
|
|
104
|
+
const key = yeaftDebugRequestKey(agentId, requestId);
|
|
105
|
+
const pending = pendingYeaftDebugRequests.get(key);
|
|
106
|
+
if (!pending || (clientId && pending.clientId !== clientId)) return false;
|
|
107
|
+
return pendingYeaftDebugRequests.delete(key);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Remove all correlations owned by a disconnected browser client. */
|
|
111
|
+
export function clearYeaftDebugRequestsForClient(clientId) {
|
|
112
|
+
if (!clientId) return;
|
|
113
|
+
for (const [key, pending] of pendingYeaftDebugRequests) {
|
|
114
|
+
if (pending?.clientId === clientId) pendingYeaftDebugRequests.delete(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
40
118
|
// Admin dashboard usage stats.
|
|
41
119
|
// userId → { requests, bytesSent, bytesReceived, messages, sessions }
|
|
42
120
|
// `messages` is user turn count. bytesSent/bytesReceived are message traffic
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto';
|
|
|
2
2
|
import { messageDb, sessionUiMetadataDb, yeaftProjectDb, yeaftSessionDb } from '../database.js';
|
|
3
3
|
import { transaction } from '../db/connection.js';
|
|
4
4
|
import { broadcastAgentList, broadcastSessionCatalog, forwardToClients, sendToAgent, sendToWebClient } from '../ws-utils.js';
|
|
5
|
-
import { webClients, previewFiles } from '../context.js';
|
|
5
|
+
import { consumeYeaftDebugRequest, webClients, previewFiles } from '../context.js';
|
|
6
6
|
import { CONFIG } from '../config.js';
|
|
7
7
|
import { yeaftAssetStore } from '../yeaft-asset-store.js';
|
|
8
8
|
import { recordPerfTraceEvent } from '../perf-trace.js';
|
|
@@ -987,32 +987,43 @@ export async function handleAgentOutput(agentId, agent, msg) {
|
|
|
987
987
|
break;
|
|
988
988
|
}
|
|
989
989
|
|
|
990
|
-
case 'yeaft_debug_history':
|
|
991
|
-
//
|
|
992
|
-
//
|
|
993
|
-
//
|
|
994
|
-
//
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
})
|
|
1013
|
-
|
|
990
|
+
case 'yeaft_debug_history': {
|
|
991
|
+
// Debug history is request/Turn scoped and may contain raw provider or
|
|
992
|
+
// tool payloads. The Server registered this Agent + requestId after the
|
|
993
|
+
// compound Session ownership check, so rolling upgrades remain safe even
|
|
994
|
+
// when an older Agent does not echo the private browser client id.
|
|
995
|
+
const pending = consumeYeaftDebugRequest({
|
|
996
|
+
agentId,
|
|
997
|
+
requestId: msg.requestId,
|
|
998
|
+
sessionId: msg.sessionId,
|
|
999
|
+
});
|
|
1000
|
+
const targetClient = pending ? webClients.get(pending.clientId) : null;
|
|
1001
|
+
const ownerMatches = CONFIG.skipAuth || (
|
|
1002
|
+
pending?.userId === agent.ownerId
|
|
1003
|
+
&& targetClient?.userId === pending.userId
|
|
1004
|
+
);
|
|
1005
|
+
if (targetClient?.authenticated && ownerMatches) {
|
|
1006
|
+
await sendToWebClient(targetClient, {
|
|
1007
|
+
type: 'yeaft_debug_history',
|
|
1008
|
+
agentId,
|
|
1009
|
+
loops: Array.isArray(msg.loops) ? msg.loops : [],
|
|
1010
|
+
turns: Array.isArray(msg.turns) ? msg.turns : [],
|
|
1011
|
+
dreamEvents: Array.isArray(msg.dreamEvents) ? msg.dreamEvents : [],
|
|
1012
|
+
...(msg.projection && typeof msg.projection === 'object' ? { projection: msg.projection } : {}),
|
|
1013
|
+
...(msg.sessionId != null ? { sessionId: msg.sessionId } : {}),
|
|
1014
|
+
...(msg.threadId != null ? { threadId: msg.threadId } : {}),
|
|
1015
|
+
...(msg.requestId != null ? { requestId: msg.requestId } : {}),
|
|
1016
|
+
...(msg.requestKind != null ? { requestKind: msg.requestKind } : {}),
|
|
1017
|
+
...(msg.search != null ? { search: msg.search } : {}),
|
|
1018
|
+
...(msg.hasMore != null ? { hasMore: !!msg.hasMore } : {}),
|
|
1019
|
+
...(msg.limit != null ? { limit: msg.limit } : {}),
|
|
1020
|
+
...(msg.indexOnly != null ? { indexOnly: !!msg.indexOnly } : {}),
|
|
1021
|
+
...(msg.detailTurnId != null ? { detailTurnId: msg.detailTurnId } : {}),
|
|
1022
|
+
...(msg.error != null ? { error: msg.error } : {}),
|
|
1023
|
+
});
|
|
1014
1024
|
}
|
|
1015
1025
|
break;
|
|
1026
|
+
}
|
|
1016
1027
|
|
|
1017
1028
|
default:
|
|
1018
1029
|
return false; // Not handled
|
|
@@ -8,7 +8,14 @@ import {
|
|
|
8
8
|
yeaftSessionDb,
|
|
9
9
|
sessionUiMetadataDb,
|
|
10
10
|
} from '../database.js';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
agents,
|
|
13
|
+
deleteYeaftDebugRequest,
|
|
14
|
+
pendingFiles,
|
|
15
|
+
registerYeaftDebugRequest,
|
|
16
|
+
trackUserTurn,
|
|
17
|
+
webClients,
|
|
18
|
+
} from '../context.js';
|
|
12
19
|
import {
|
|
13
20
|
sendToWebClient, forwardToAgent,
|
|
14
21
|
broadcastAgentList, broadcastSessionCatalog, buildSessionCatalog, buildHiddenSessionCatalog,
|
|
@@ -1288,6 +1295,45 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
|
|
|
1288
1295
|
break;
|
|
1289
1296
|
}
|
|
1290
1297
|
|
|
1298
|
+
case 'yeaft_fetch_debug_history': {
|
|
1299
|
+
// Debug traces can contain raw prompts, provider payloads, and tool
|
|
1300
|
+
// output. Treat this as a precise Session-owned request instead of the
|
|
1301
|
+
// generic Yeaft relay: require the compound Agent + Session identity and
|
|
1302
|
+
// correlate the response back to the requesting browser tab.
|
|
1303
|
+
const debugAgentId = msg.agentId;
|
|
1304
|
+
const debugSessionId = typeof msg.sessionId === 'string' ? msg.sessionId : '';
|
|
1305
|
+
const debugRequestId = typeof msg.requestId === 'string' ? msg.requestId : '';
|
|
1306
|
+
if (!debugAgentId || !debugSessionId || !debugRequestId) return;
|
|
1307
|
+
if (!await checkAgentAccess(debugAgentId)) return;
|
|
1308
|
+
if (!CONFIG.skipAuth && !yeaftSessionDb.getForAgent(client.userId, debugAgentId, debugSessionId)) return;
|
|
1309
|
+
const registered = registerYeaftDebugRequest({
|
|
1310
|
+
agentId: debugAgentId,
|
|
1311
|
+
requestId: debugRequestId,
|
|
1312
|
+
sessionId: debugSessionId,
|
|
1313
|
+
clientId,
|
|
1314
|
+
userId: client.userId,
|
|
1315
|
+
});
|
|
1316
|
+
if (!registered) return;
|
|
1317
|
+
try {
|
|
1318
|
+
await forwardToAgent(debugAgentId, {
|
|
1319
|
+
type: 'yeaft_fetch_debug_history',
|
|
1320
|
+
sessionId: debugSessionId,
|
|
1321
|
+
requestId: debugRequestId,
|
|
1322
|
+
requestKind: msg.requestKind === 'detail' ? 'detail' : 'list',
|
|
1323
|
+
limit: typeof msg.limit === 'number' ? msg.limit : 10,
|
|
1324
|
+
dreamLimit: typeof msg.dreamLimit === 'number' ? msg.dreamLimit : 5,
|
|
1325
|
+
indexOnly: msg.indexOnly === true,
|
|
1326
|
+
detailTurnId: typeof msg.detailTurnId === 'string' ? msg.detailTurnId : null,
|
|
1327
|
+
search: typeof msg.search === 'string' ? msg.search.slice(0, 500) : '',
|
|
1328
|
+
_requestClientId: clientId,
|
|
1329
|
+
});
|
|
1330
|
+
} catch (err) {
|
|
1331
|
+
deleteYeaftDebugRequest({ agentId: debugAgentId, requestId: debugRequestId, clientId });
|
|
1332
|
+
throw err;
|
|
1333
|
+
}
|
|
1334
|
+
break;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1291
1337
|
case 'yeaft_load_history_outline': {
|
|
1292
1338
|
const outlineAgentId = msg.agentId;
|
|
1293
1339
|
const outlineSessionId = typeof msg.sessionId === 'string' ? msg.sessionId : '';
|
|
@@ -8,8 +8,14 @@ import {
|
|
|
8
8
|
// builds without this capability may still inherit the installed package cwd.
|
|
9
9
|
export const SAFE_REMOTE_UPGRADE_CAPABILITY = 'remote_upgrade_safe';
|
|
10
10
|
|
|
11
|
-
export function requiresManualUpgradeBridge(capabilities) {
|
|
12
|
-
|
|
11
|
+
export function requiresManualUpgradeBridge(capabilities, platform = null) {
|
|
12
|
+
if (Array.isArray(capabilities) && capabilities.includes(SAFE_REMOTE_UPGRADE_CAPABILITY)) return false;
|
|
13
|
+
const normalizedPlatform = typeof platform === 'string' ? platform.trim().toLowerCase() : '';
|
|
14
|
+
if (normalizedPlatform) return normalizedPlatform === 'win32';
|
|
15
|
+
// v1.0.373 predates explicit platform metadata but advertises this Linux-only
|
|
16
|
+
// capability, so it is safe to distinguish from the affected Windows build.
|
|
17
|
+
if (Array.isArray(capabilities) && capabilities.includes('work_item_attachments')) return false;
|
|
18
|
+
return true;
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
/**
|
|
@@ -36,7 +42,7 @@ export async function handleClientMisc(clientId, client, msg, checkAgentAccess)
|
|
|
36
42
|
if (!upgradeAgentId) break;
|
|
37
43
|
if (!await checkAgentAccess(upgradeAgentId)) break;
|
|
38
44
|
const upgradeAgent = agents.get(upgradeAgentId);
|
|
39
|
-
if (requiresManualUpgradeBridge(upgradeAgent?.capabilities)) {
|
|
45
|
+
if (requiresManualUpgradeBridge(upgradeAgent?.capabilities, upgradeAgent?.platform)) {
|
|
40
46
|
await sendToWebClient(client, {
|
|
41
47
|
type: 'upgrade_agent_ack',
|
|
42
48
|
agentId: upgradeAgentId,
|
|
@@ -54,6 +54,7 @@ export function handleAgentConnection(ws, url) {
|
|
|
54
54
|
const agentName = url.searchParams.get('name') || `Agent-${clientAgentId.slice(0, 8)}`;
|
|
55
55
|
const instanceId = url.searchParams.get('instanceId') || clientAgentId;
|
|
56
56
|
const workDir = url.searchParams.get('workDir') || '';
|
|
57
|
+
const urlPlatform = url.searchParams.get('platform') || null;
|
|
57
58
|
|
|
58
59
|
// Both authenticated and SKIP_AUTH connections use the existing auth frame
|
|
59
60
|
// for registration metadata. Released Agents already send their version there;
|
|
@@ -118,6 +119,9 @@ export function handleAgentConnection(ws, url) {
|
|
|
118
119
|
|
|
119
120
|
const capabilities = Array.isArray(msg.capabilities) ? msg.capabilities : urlCapabilities;
|
|
120
121
|
const agentVersion = msg.version || null;
|
|
122
|
+
const agentPlatform = typeof msg.platform === 'string' && msg.platform
|
|
123
|
+
? msg.platform
|
|
124
|
+
: urlPlatform;
|
|
121
125
|
// Local no-auth mode still has one durable browser owner. This makes
|
|
122
126
|
// the server-side Session catalog persistent without changing generic
|
|
123
127
|
// development-server behavior, which remains ownerless.
|
|
@@ -149,6 +153,7 @@ export function handleAgentConnection(ws, url) {
|
|
|
149
153
|
ownerUsername,
|
|
150
154
|
agentVersion,
|
|
151
155
|
registeredInstanceId,
|
|
156
|
+
agentPlatform,
|
|
152
157
|
);
|
|
153
158
|
pruneAgentConnectionGenerations();
|
|
154
159
|
}
|
|
@@ -233,7 +238,7 @@ function handleAgentDisconnect(agentId, agentName, ws) {
|
|
|
233
238
|
broadcastAgentList();
|
|
234
239
|
}
|
|
235
240
|
|
|
236
|
-
function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey, capabilities = [], ownerId = null, ownerUsername = null, agentVersion = null, instanceId = null) {
|
|
241
|
+
function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey, capabilities = [], ownerId = null, ownerUsername = null, agentVersion = null, instanceId = null, agentPlatform = null) {
|
|
237
242
|
// 如果是重连,保留 conversations;否则(server 重启)创建空 Map
|
|
238
243
|
const existingAgent = agents.get(agentId);
|
|
239
244
|
const conversations = existingAgent?.conversations || new Map();
|
|
@@ -270,6 +275,7 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
|
|
|
270
275
|
ownerId,
|
|
271
276
|
ownerUsername,
|
|
272
277
|
version: agentVersion,
|
|
278
|
+
platform: agentPlatform,
|
|
273
279
|
encryptOutbound
|
|
274
280
|
});
|
|
275
281
|
|
|
@@ -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, webClients, isHeartbeatMessageType, trackRequest } from './context.js';
|
|
8
|
+
import { agents, clearYeaftDebugRequestsForClient, webClients, isHeartbeatMessageType, trackRequest } from './context.js';
|
|
9
9
|
import {
|
|
10
10
|
parseMessage, sendToWebClient, sendToAgent,
|
|
11
11
|
broadcastAgentList, resolveAgentAccessError
|
|
@@ -163,6 +163,7 @@ export function handleWebConnection(ws, url, req = {}) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
clearWorkCenterRequestsForClient(client);
|
|
166
|
+
clearYeaftDebugRequestsForClient(clientId);
|
|
166
167
|
webClients.delete(clientId);
|
|
167
168
|
console.log(`Web client disconnected: ${clientId}`);
|
|
168
169
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.389"}
|