@yeaft/webchat-agent 1.0.416 → 1.0.418
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/browser-runtime/browser-install.js +39 -0
- package/browser-runtime/chromium.js +192 -0
- package/browser-runtime/cli.js +1 -1
- package/browser-runtime/extension/manifest.json +3 -0
- package/browser-runtime/extension/offscreen.js +201 -25
- package/browser-runtime/extension/popup.js +4 -1
- package/browser-runtime/extension/service-worker.js +47 -7
- package/browser-runtime/extension.js +1 -1
- package/browser-runtime/index.js +3 -0
- package/browser-runtime/local-bridge.js +194 -0
- package/browser-runtime/messages.js +123 -0
- package/browser-runtime/service.js +769 -30
- package/connection/index.js +2 -0
- package/connection/message-router.js +2 -0
- package/index.js +13 -0
- package/local-runtime/server/.env.example +10 -0
- package/local-runtime/server/browser-runtime-routes.js +284 -0
- package/local-runtime/server/client-protocol.js +8 -0
- package/local-runtime/server/config.js +28 -0
- package/local-runtime/server/handlers/agent-browser.js +393 -0
- package/local-runtime/server/handlers/agent-sync.js +14 -0
- package/local-runtime/server/handlers/client-browser.js +337 -0
- package/local-runtime/server/ws-agent.js +9 -1
- package/local-runtime/server/ws-client.js +41 -2
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +220 -98
- 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
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { CONFIG } from '../config.js';
|
|
3
|
+
import { agents } from '../context.js';
|
|
4
|
+
import { sendToAgent, sendToWebClient } from '../ws-utils.js';
|
|
5
|
+
import {
|
|
6
|
+
browserPeerMatchesClient,
|
|
7
|
+
browserServerIdentity,
|
|
8
|
+
deleteBrowserPeer,
|
|
9
|
+
getBrowserPeer,
|
|
10
|
+
getBrowserRoute,
|
|
11
|
+
mintBrowserIceServers,
|
|
12
|
+
registerBrowserCreateRequest,
|
|
13
|
+
registerBrowserRequest,
|
|
14
|
+
reserveBrowserPeer,
|
|
15
|
+
} from '../browser-runtime-routes.js';
|
|
16
|
+
|
|
17
|
+
const CLIENT_BROWSER_TYPES = new Set([
|
|
18
|
+
'browser_runtime_status',
|
|
19
|
+
'browser_runtime_install',
|
|
20
|
+
'browser_runtime_enable',
|
|
21
|
+
'browser_session_create',
|
|
22
|
+
'browser_session_get',
|
|
23
|
+
'browser_session_list',
|
|
24
|
+
'browser_session_close',
|
|
25
|
+
'browser_peer_attach',
|
|
26
|
+
'browser_peer_answer',
|
|
27
|
+
'browser_peer_ice_candidate',
|
|
28
|
+
'browser_peer_detach',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function clean(value, max = 512) {
|
|
32
|
+
return typeof value === 'string' ? value.trim().slice(0, max) : '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function digest(value) {
|
|
36
|
+
return createHash('sha256').update(JSON.stringify(value || null)).digest('hex');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function fail(client, msg, code, safeError = code) {
|
|
40
|
+
const type = String(msg.type || '');
|
|
41
|
+
await sendToWebClient(client, {
|
|
42
|
+
type: type.startsWith('browser_peer_') ? 'browser_peer_error'
|
|
43
|
+
: type.startsWith('browser_runtime_') ? 'browser_runtime_error'
|
|
44
|
+
: 'browser_session_error',
|
|
45
|
+
requestId: clean(msg.requestId) || null,
|
|
46
|
+
browserSessionId: clean(msg.browserSessionId) || null,
|
|
47
|
+
peerId: clean(msg.peerId) || null,
|
|
48
|
+
connectionGeneration: Number(msg.connectionGeneration) || null,
|
|
49
|
+
code,
|
|
50
|
+
safeError,
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function agentSupportsBrowserSetup(agent) {
|
|
56
|
+
return new Set(agent?.capabilities || []).has('browser_runtime_setup');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function agentSupportsBrowser(agent) {
|
|
60
|
+
const capabilities = new Set(agent?.capabilities || []);
|
|
61
|
+
return capabilities.has('browser_runtime')
|
|
62
|
+
&& capabilities.has('browser_webrtc')
|
|
63
|
+
&& (capabilities.has('browser_capture_tab') || capabilities.has('browser_capture_cdp'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function safeInitialUrl(value) {
|
|
67
|
+
const raw = clean(value, 4096) || 'about:blank';
|
|
68
|
+
if (raw === 'about:blank') return raw;
|
|
69
|
+
try {
|
|
70
|
+
const url = new URL(raw);
|
|
71
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return null;
|
|
72
|
+
return url.href;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function canonicalCreate(msg) {
|
|
79
|
+
const initialUrl = safeInitialUrl(msg.options?.initialUrl);
|
|
80
|
+
if (!initialUrl) return null;
|
|
81
|
+
return {
|
|
82
|
+
sourceRef: msg.sourceRef && typeof msg.sourceRef === 'object' ? {
|
|
83
|
+
kind: clean(msg.sourceRef.kind, 32),
|
|
84
|
+
sessionId: clean(msg.sourceRef.sessionId),
|
|
85
|
+
conversationId: clean(msg.sourceRef.conversationId),
|
|
86
|
+
workItemId: clean(msg.sourceRef.workItemId),
|
|
87
|
+
} : null,
|
|
88
|
+
options: {
|
|
89
|
+
initialUrl,
|
|
90
|
+
viewport: {
|
|
91
|
+
width: Math.min(1920, Math.max(320, Math.floor(Number(msg.options?.viewport?.width) || 1280))),
|
|
92
|
+
height: Math.min(1080, Math.max(240, Math.floor(Number(msg.options?.viewport?.height) || 720))),
|
|
93
|
+
deviceScaleFactor: Math.min(2, Math.max(1, Number(msg.options?.viewport?.deviceScaleFactor) || 1)),
|
|
94
|
+
},
|
|
95
|
+
locale: clean(msg.options?.locale, 32) || 'en-US',
|
|
96
|
+
capturePreference: ['auto', 'tab'].includes(msg.options?.capturePreference)
|
|
97
|
+
? msg.options.capturePreference : 'auto',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ownerRoute(client, msg) {
|
|
103
|
+
const route = getBrowserRoute(msg.agentId, msg.browserSessionId);
|
|
104
|
+
return route && route.ownerUserId === client.userId ? route : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Owner-checked Web → Server → Agent Browser lifecycle and signaling relay. */
|
|
108
|
+
export async function handleClientBrowser(client, msg, checkAgentAccess) {
|
|
109
|
+
if (!CLIENT_BROWSER_TYPES.has(msg?.type)) return false;
|
|
110
|
+
if (!CONFIG.browserRuntime.enabled) return fail(client, msg, 'browser_runtime_disabled');
|
|
111
|
+
const setupRequest = String(msg.type || '').startsWith('browser_runtime_');
|
|
112
|
+
if (setupRequest) {
|
|
113
|
+
if (client.browserRuntimeSetupProtocol !== 1) {
|
|
114
|
+
return fail(client, msg, 'browser_setup_protocol_required');
|
|
115
|
+
}
|
|
116
|
+
} else if (client.browserRuntimeProtocol !== 1) {
|
|
117
|
+
return fail(client, msg, 'browser_protocol_required');
|
|
118
|
+
}
|
|
119
|
+
const agentId = clean(msg.agentId);
|
|
120
|
+
if (!await checkAgentAccess(agentId)) return true;
|
|
121
|
+
const agent = agents.get(agentId);
|
|
122
|
+
if (setupRequest ? !agentSupportsBrowserSetup(agent) : !agentSupportsBrowser(agent)) {
|
|
123
|
+
return fail(client, msg, 'browser_runtime_unavailable');
|
|
124
|
+
}
|
|
125
|
+
const requestId = clean(msg.requestId);
|
|
126
|
+
const requestRequired = msg.type !== 'browser_peer_answer'
|
|
127
|
+
&& msg.type !== 'browser_peer_ice_candidate';
|
|
128
|
+
if (requestRequired && !requestId) return fail(client, msg, 'browser_request_id_required');
|
|
129
|
+
const identity = browserServerIdentity(client);
|
|
130
|
+
|
|
131
|
+
if (setupRequest) {
|
|
132
|
+
const canonical = msg.type === 'browser_runtime_install' ? {
|
|
133
|
+
confirmedBuildId: clean(msg.confirmedBuildId, 128),
|
|
134
|
+
confirmedDownloadBytes: Number(msg.confirmedDownloadBytes) || 0,
|
|
135
|
+
} : {};
|
|
136
|
+
const registration = registerBrowserCreateRequest({
|
|
137
|
+
agentId,
|
|
138
|
+
client,
|
|
139
|
+
requestId,
|
|
140
|
+
digest: digest({ type: msg.type, ...canonical }),
|
|
141
|
+
kind: msg.type,
|
|
142
|
+
});
|
|
143
|
+
if (registration.conflict) return fail(client, msg, 'browser_request_conflict');
|
|
144
|
+
if (registration.capacity) return fail(client, msg, 'browser_request_capacity');
|
|
145
|
+
if (registration.duplicate) {
|
|
146
|
+
if (registration.request.response) await sendToWebClient(client, registration.request.response);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
await sendToAgent(agent, {
|
|
150
|
+
type: msg.type,
|
|
151
|
+
agentId,
|
|
152
|
+
requestId: registration.request.serverRequestId,
|
|
153
|
+
...canonical,
|
|
154
|
+
serverIdentity: identity,
|
|
155
|
+
});
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (msg.type === 'browser_session_create') {
|
|
160
|
+
const canonical = canonicalCreate(msg);
|
|
161
|
+
if (!canonical) return fail(client, msg, 'browser_url_invalid');
|
|
162
|
+
const registration = registerBrowserCreateRequest({
|
|
163
|
+
agentId,
|
|
164
|
+
client,
|
|
165
|
+
requestId,
|
|
166
|
+
digest: digest(canonical),
|
|
167
|
+
});
|
|
168
|
+
if (registration.conflict) return fail(client, msg, 'browser_request_conflict');
|
|
169
|
+
if (registration.capacity) return fail(client, msg, 'browser_request_capacity');
|
|
170
|
+
if (registration.duplicate) {
|
|
171
|
+
if (registration.request.response) {
|
|
172
|
+
await sendToWebClient(client, registration.request.response);
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
await sendToAgent(agent, {
|
|
177
|
+
type: msg.type,
|
|
178
|
+
agentId,
|
|
179
|
+
requestId: registration.request.serverRequestId,
|
|
180
|
+
...canonical,
|
|
181
|
+
serverIdentity: identity,
|
|
182
|
+
});
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (msg.type === 'browser_session_list') {
|
|
187
|
+
const request = registerBrowserRequest({ agentId, client, requestId, kind: msg.type });
|
|
188
|
+
if (!request) return fail(client, msg, 'browser_request_conflict');
|
|
189
|
+
await sendToAgent(agent, {
|
|
190
|
+
type: msg.type,
|
|
191
|
+
agentId,
|
|
192
|
+
requestId: request.serverRequestId,
|
|
193
|
+
serverIdentity: identity,
|
|
194
|
+
});
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (msg.type === 'browser_peer_attach') {
|
|
199
|
+
if (!ownerRoute(client, msg)) return fail(client, msg, 'browser_session_not_found');
|
|
200
|
+
const connectionGeneration = Number(msg.connectionGeneration);
|
|
201
|
+
const reserved = reserveBrowserPeer({
|
|
202
|
+
agentId,
|
|
203
|
+
browserSessionId: clean(msg.browserSessionId),
|
|
204
|
+
client,
|
|
205
|
+
requestId,
|
|
206
|
+
connectionGeneration,
|
|
207
|
+
role: 'viewer',
|
|
208
|
+
});
|
|
209
|
+
if (reserved.error) return fail(client, msg, reserved.error);
|
|
210
|
+
const peer = reserved.peer;
|
|
211
|
+
if (reserved.duplicate) {
|
|
212
|
+
if (peer.state === 'prepared' || peer.state === 'offered' || peer.state === 'connected') {
|
|
213
|
+
await sendToWebClient(client, {
|
|
214
|
+
type: 'browser_peer_prepared',
|
|
215
|
+
agentId,
|
|
216
|
+
browserSessionId: peer.browserSessionId,
|
|
217
|
+
peerId: peer.peerId,
|
|
218
|
+
requestId: peer.requestId,
|
|
219
|
+
connectionGeneration: peer.connectionGeneration,
|
|
220
|
+
iceTransportPolicy: peer.iceTransportPolicy,
|
|
221
|
+
iceServers: peer.webIceServers || [],
|
|
222
|
+
role: peer.role,
|
|
223
|
+
});
|
|
224
|
+
if (peer.pendingOffer) await sendToWebClient(client, peer.pendingOffer);
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
const commonScope = {
|
|
229
|
+
ownerUserId: client.userId,
|
|
230
|
+
agentId,
|
|
231
|
+
browserSessionId: peer.browserSessionId,
|
|
232
|
+
peerId: peer.peerId,
|
|
233
|
+
connectionGeneration: peer.connectionGeneration,
|
|
234
|
+
};
|
|
235
|
+
try {
|
|
236
|
+
await sendToAgent(agent, {
|
|
237
|
+
type: 'browser_peer_prepare',
|
|
238
|
+
agentId,
|
|
239
|
+
browserSessionId: peer.browserSessionId,
|
|
240
|
+
peerId: peer.peerId,
|
|
241
|
+
requestId,
|
|
242
|
+
connectionGeneration: peer.connectionGeneration,
|
|
243
|
+
serverIdentity: identity,
|
|
244
|
+
routeExpiresAt: peer.expiresAt,
|
|
245
|
+
iceTransportPolicy: CONFIG.browserRuntime.iceTransportPolicy,
|
|
246
|
+
agentIceServers: mintBrowserIceServers({ ...commonScope, endpointRole: 'agent' }),
|
|
247
|
+
});
|
|
248
|
+
peer.webIceServers = mintBrowserIceServers({ ...commonScope, endpointRole: 'web' });
|
|
249
|
+
peer.state = 'preparing';
|
|
250
|
+
} catch (error) {
|
|
251
|
+
deleteBrowserPeer(peer.peerId);
|
|
252
|
+
return fail(client, msg, 'browser_peer_prepare_failed', String(error?.message || error).slice(0, 500));
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (msg.type === 'browser_session_get' || msg.type === 'browser_session_close') {
|
|
258
|
+
const route = ownerRoute(client, msg);
|
|
259
|
+
if (!route) return fail(client, msg, 'browser_session_not_found');
|
|
260
|
+
const request = registerBrowserRequest({
|
|
261
|
+
agentId,
|
|
262
|
+
client,
|
|
263
|
+
requestId,
|
|
264
|
+
kind: msg.type,
|
|
265
|
+
browserSessionId: route.browserSessionId,
|
|
266
|
+
});
|
|
267
|
+
if (!request) return fail(client, msg, 'browser_request_conflict');
|
|
268
|
+
await sendToAgent(agent, {
|
|
269
|
+
type: msg.type,
|
|
270
|
+
agentId,
|
|
271
|
+
requestId: request.serverRequestId,
|
|
272
|
+
browserSessionId: route.browserSessionId,
|
|
273
|
+
...(msg.type === 'browser_session_close' ? { expectedRevision: Number(msg.expectedRevision) || route.revision } : {}),
|
|
274
|
+
serverIdentity: identity,
|
|
275
|
+
});
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const peer = getBrowserPeer(msg.peerId);
|
|
280
|
+
if (!browserPeerMatchesClient(peer, client, msg)) return fail(client, msg, 'browser_peer_stale');
|
|
281
|
+
if (msg.type === 'browser_peer_answer') {
|
|
282
|
+
const description = msg.description;
|
|
283
|
+
if (description?.type !== 'answer' || typeof description.sdp !== 'string' || description.sdp.length > 96 * 1024) {
|
|
284
|
+
return fail(client, msg, 'browser_sdp_invalid');
|
|
285
|
+
}
|
|
286
|
+
peer.state = 'answering';
|
|
287
|
+
await sendToAgent(agent, {
|
|
288
|
+
type: msg.type,
|
|
289
|
+
agentId,
|
|
290
|
+
browserSessionId: peer.browserSessionId,
|
|
291
|
+
peerId: peer.peerId,
|
|
292
|
+
connectionGeneration: peer.connectionGeneration,
|
|
293
|
+
description: { type: 'answer', sdp: description.sdp },
|
|
294
|
+
serverIdentity: identity,
|
|
295
|
+
});
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
if (msg.type === 'browser_peer_ice_candidate') {
|
|
299
|
+
if (peer.webCandidateCount >= 128) return fail(client, msg, 'browser_candidate_limit');
|
|
300
|
+
const candidate = msg.candidate;
|
|
301
|
+
if (candidate != null && (typeof candidate !== 'object'
|
|
302
|
+
|| typeof candidate.candidate !== 'string'
|
|
303
|
+
|| candidate.candidate.length > 4096)) return fail(client, msg, 'browser_candidate_invalid');
|
|
304
|
+
peer.webCandidateCount += 1;
|
|
305
|
+
await sendToAgent(agent, {
|
|
306
|
+
type: msg.type,
|
|
307
|
+
agentId,
|
|
308
|
+
browserSessionId: peer.browserSessionId,
|
|
309
|
+
peerId: peer.peerId,
|
|
310
|
+
connectionGeneration: peer.connectionGeneration,
|
|
311
|
+
candidate: candidate == null ? null : {
|
|
312
|
+
candidate: candidate.candidate,
|
|
313
|
+
sdpMid: clean(candidate.sdpMid, 256) || null,
|
|
314
|
+
sdpMLineIndex: Number.isInteger(candidate.sdpMLineIndex) ? candidate.sdpMLineIndex : null,
|
|
315
|
+
usernameFragment: clean(candidate.usernameFragment, 256) || null,
|
|
316
|
+
},
|
|
317
|
+
serverIdentity: identity,
|
|
318
|
+
});
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
if (msg.type === 'browser_peer_detach') {
|
|
322
|
+
deleteBrowserPeer(peer.peerId);
|
|
323
|
+
await sendToAgent(agent, {
|
|
324
|
+
type: msg.type,
|
|
325
|
+
agentId,
|
|
326
|
+
requestId,
|
|
327
|
+
browserSessionId: peer.browserSessionId,
|
|
328
|
+
peerId: peer.peerId,
|
|
329
|
+
connectionGeneration: peer.connectionGeneration,
|
|
330
|
+
serverIdentity: identity,
|
|
331
|
+
});
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export { CLIENT_BROWSER_TYPES };
|
|
@@ -16,6 +16,8 @@ import { handleAgentFileTerminal } from './handlers/agent-file-terminal.js';
|
|
|
16
16
|
import { handleAgentSync } from './handlers/agent-sync.js';
|
|
17
17
|
import { recordPerfTraceEvent } from './perf-trace.js';
|
|
18
18
|
import { clearWorkbenchCorrelationsForAgent } from './workbench-correlation.js';
|
|
19
|
+
import { clearBrowserRuntimeForAgent } from './browser-runtime-routes.js';
|
|
20
|
+
import { handleAgentBrowser } from './handlers/agent-browser.js';
|
|
19
21
|
import { markAgentHeartbeatSeen } from './heartbeat-policy.js';
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -229,6 +231,7 @@ function handleAgentDisconnect(agentId, agentName, ws) {
|
|
|
229
231
|
// Phase 4: 清理目录缓存
|
|
230
232
|
clearAgentDirCache(agentId);
|
|
231
233
|
clearWorkbenchCorrelationsForAgent(agentId);
|
|
234
|
+
clearBrowserRuntimeForAgent(agentId);
|
|
232
235
|
// Phase 1: 清理同步超时
|
|
233
236
|
if (agent._syncTimeout) {
|
|
234
237
|
clearTimeout(agent._syncTimeout);
|
|
@@ -294,6 +297,7 @@ function completeAgentRegistration(ws, agentId, agentName, workDir, sessionKey,
|
|
|
294
297
|
agents.get(agentId)._syncTimeout = syncTimeout;
|
|
295
298
|
|
|
296
299
|
if (existingAgent?.ws && existingAgent.ws !== ws) {
|
|
300
|
+
clearBrowserRuntimeForAgent(agentId);
|
|
297
301
|
existingAgent.ws.close(1008, 'Superseded by a newer Agent connection');
|
|
298
302
|
}
|
|
299
303
|
|
|
@@ -347,7 +351,10 @@ async function handleAgentMessage(agentId, msg, ws) {
|
|
|
347
351
|
'yeaft_history_chunk', 'yeaft_history_outline', 'yeaft_history_search_result', 'yeaft_history_window', 'slash_commands_update', 'agent_metrics',
|
|
348
352
|
'file_content', 'file_saved', 'file_op_result', 'file_search_result',
|
|
349
353
|
'git_status_result', 'git_diff_result', 'git_op_result',
|
|
350
|
-
'terminal_created', 'terminal_output', 'terminal_closed', 'terminal_error'
|
|
354
|
+
'terminal_created', 'terminal_output', 'terminal_closed', 'terminal_error',
|
|
355
|
+
'agent_capabilities_updated', 'browser_runtime_status_result', 'browser_runtime_install_progress', 'browser_runtime_error',
|
|
356
|
+
'browser_session_created', 'browser_session_error', 'browser_session_snapshot', 'browser_session_list_result',
|
|
357
|
+
'browser_peer_prepared', 'browser_peer_offer', 'browser_peer_ice_candidate', 'browser_peer_state', 'browser_peer_error'
|
|
351
358
|
]);
|
|
352
359
|
if (msg.conversationId && !CONV_EXEMPT_TYPES.has(msg.type)) {
|
|
353
360
|
if (!agent.conversations.has(msg.conversationId)) {
|
|
@@ -358,6 +365,7 @@ async function handleAgentMessage(agentId, msg, ws) {
|
|
|
358
365
|
|
|
359
366
|
// Dispatch to handler sub-modules
|
|
360
367
|
if (await handleAgentConversation(agentId, agent, msg)) return;
|
|
368
|
+
if (await handleAgentBrowser(agentId, agent, msg)) return;
|
|
361
369
|
if (await handleAgentWorkCenter(agentId, msg)) return;
|
|
362
370
|
if (await handleAgentOutput(agentId, agent, msg)) return;
|
|
363
371
|
if (await handleAgentFileTerminal(agentId, agent, msg)) return;
|
|
@@ -6,14 +6,21 @@ import { authenticateRequest } from './auth/request-auth.js';
|
|
|
6
6
|
import { encodeKey } from './encryption.js';
|
|
7
7
|
import { userDb } from './database.js';
|
|
8
8
|
import { agents, clearYeaftDebugRequestsForClient, webClients, isHeartbeatMessageType, trackRequest } from './context.js';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
applyClientHello,
|
|
11
|
+
BROWSER_RUNTIME_PROTOCOL,
|
|
12
|
+
BROWSER_RUNTIME_SETUP_PROTOCOL,
|
|
13
|
+
WORKBENCH_ROUTE_PROTOCOL,
|
|
14
|
+
} from './client-protocol.js';
|
|
10
15
|
import { clearWorkbenchCorrelationsForClient } from './workbench-correlation.js';
|
|
16
|
+
import { clearBrowserRuntimeForClient } from './browser-runtime-routes.js';
|
|
11
17
|
import {
|
|
12
18
|
parseMessage, sendToWebClient, sendToAgent,
|
|
13
19
|
broadcastAgentList, resolveAgentAccessError
|
|
14
20
|
} from './ws-utils.js';
|
|
15
21
|
import { handleClientConversation } from './handlers/client-conversation.js';
|
|
16
22
|
import { handleClientWorkbench } from './handlers/client-workbench.js';
|
|
23
|
+
import { CLIENT_BROWSER_TYPES, handleClientBrowser } from './handlers/client-browser.js';
|
|
17
24
|
import { handleClientMisc } from './handlers/client-misc.js';
|
|
18
25
|
import { clearWorkCenterRequestsForClient, handleClientWorkCenter } from './handlers/client-work-center.js';
|
|
19
26
|
import { recordPerfTraceEvent } from './perf-trace.js';
|
|
@@ -64,7 +71,11 @@ export function handleWebConnection(ws, url, req = {}) {
|
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
const connectionId = randomUUID();
|
|
67
75
|
webClients.set(clientId, {
|
|
76
|
+
id: clientId,
|
|
77
|
+
connectionId,
|
|
78
|
+
connectionGeneration: connectionId,
|
|
68
79
|
ws,
|
|
69
80
|
authenticated,
|
|
70
81
|
username,
|
|
@@ -79,8 +90,10 @@ export function handleWebConnection(ws, url, req = {}) {
|
|
|
79
90
|
// `false` when the client sends `client_hello { plaintextOk: true }`
|
|
80
91
|
// — see early dispatch in handleWebMessage.
|
|
81
92
|
encryptOutbound: true,
|
|
82
|
-
// Explicit
|
|
93
|
+
// Explicit protocols have no omission-based downgrade for security fields.
|
|
83
94
|
workbenchRouteProtocol: 0,
|
|
95
|
+
browserRuntimeProtocol: 0,
|
|
96
|
+
browserRuntimeSetupProtocol: 0,
|
|
84
97
|
});
|
|
85
98
|
|
|
86
99
|
// 心跳响应处理
|
|
@@ -108,6 +121,9 @@ export function handleWebConnection(ws, url, req = {}) {
|
|
|
108
121
|
acceptPlaintext: true,
|
|
109
122
|
yeaftSessionInventoryComplete: true,
|
|
110
123
|
workbenchRouteProtocol: WORKBENCH_ROUTE_PROTOCOL,
|
|
124
|
+
browserRuntimeProtocol: BROWSER_RUNTIME_PROTOCOL,
|
|
125
|
+
browserRuntimeSetupProtocol: BROWSER_RUNTIME_SETUP_PROTOCOL,
|
|
126
|
+
browserRuntimeEnabled: CONFIG.browserRuntime.enabled,
|
|
111
127
|
}));
|
|
112
128
|
setTimeout(() => broadcastAgentList(), 100);
|
|
113
129
|
} else {
|
|
@@ -169,6 +185,24 @@ export function handleWebConnection(ws, url, req = {}) {
|
|
|
169
185
|
}
|
|
170
186
|
clearWorkCenterRequestsForClient(client);
|
|
171
187
|
clearYeaftDebugRequestsForClient(clientId);
|
|
188
|
+
const browserPeers = clearBrowserRuntimeForClient(client);
|
|
189
|
+
for (const peer of browserPeers) {
|
|
190
|
+
const agent = agents.get(peer.agentId);
|
|
191
|
+
if (!agent) continue;
|
|
192
|
+
void sendToAgent(agent, {
|
|
193
|
+
type: 'browser_peer_detach',
|
|
194
|
+
agentId: peer.agentId,
|
|
195
|
+
browserSessionId: peer.browserSessionId,
|
|
196
|
+
peerId: peer.peerId,
|
|
197
|
+
connectionGeneration: peer.connectionGeneration,
|
|
198
|
+
serverIdentity: {
|
|
199
|
+
ownerUserId: peer.ownerUserId,
|
|
200
|
+
clientId: peer.clientId,
|
|
201
|
+
webConnectionId: peer.webConnectionId,
|
|
202
|
+
webConnectionGeneration: peer.webConnectionGeneration,
|
|
203
|
+
},
|
|
204
|
+
}).catch(error => console.warn('[BrowserRuntime] peer disconnect cleanup failed:', error.message));
|
|
205
|
+
}
|
|
172
206
|
const ownedTerminals = clearWorkbenchCorrelationsForClient(clientId);
|
|
173
207
|
for (const owner of ownedTerminals) {
|
|
174
208
|
const agent = agents.get(owner.agentId);
|
|
@@ -192,6 +226,7 @@ export function handleWebConnection(ws, url, req = {}) {
|
|
|
192
226
|
|
|
193
227
|
// Workbench 功能(terminal、file、git、proxy)仅 admin/pro 可用
|
|
194
228
|
const WORKBENCH_TYPES = new Set([
|
|
229
|
+
...CLIENT_BROWSER_TYPES,
|
|
195
230
|
'terminal_create', 'terminal_input', 'terminal_resize', 'terminal_close',
|
|
196
231
|
'read_file', 'write_file', 'create_file', 'delete_files', 'move_files', 'copy_files', 'upload_to_dir', 'file_search',
|
|
197
232
|
'git_status', 'git_diff', 'git_add', 'git_reset', 'git_restore', 'git_commit', 'git_push',
|
|
@@ -220,6 +255,9 @@ async function handleWebMessage(clientId, msg) {
|
|
|
220
255
|
await sendToWebClient(client, {
|
|
221
256
|
type: 'client_hello_ack',
|
|
222
257
|
workbenchRouteProtocol: client.workbenchRouteProtocol,
|
|
258
|
+
browserRuntimeProtocol: client.browserRuntimeProtocol,
|
|
259
|
+
browserRuntimeSetupProtocol: client.browserRuntimeSetupProtocol,
|
|
260
|
+
browserRuntimeEnabled: CONFIG.browserRuntime.enabled,
|
|
223
261
|
});
|
|
224
262
|
return;
|
|
225
263
|
}
|
|
@@ -246,6 +284,7 @@ async function handleWebMessage(clientId, msg) {
|
|
|
246
284
|
|
|
247
285
|
// Dispatch to handler sub-modules
|
|
248
286
|
if (await handleClientConversation(clientId, client, msg, checkAgentAccess)) return;
|
|
287
|
+
if (await handleClientBrowser(client, msg, checkAgentAccess)) return;
|
|
249
288
|
if (await handleClientWorkbench(clientId, client, msg, checkAgentAccess)) return;
|
|
250
289
|
if (await handleClientWorkCenter(client, msg, checkAgentAccess)) return;
|
|
251
290
|
if (await handleClientMisc(clientId, client, msg, checkAgentAccess)) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.418"}
|