@yeaft/webchat-agent 1.0.188 → 1.0.189

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.
Files changed (121) hide show
  1. package/cli.js +12 -0
  2. package/index.js +10 -2
  3. package/local-run.js +218 -0
  4. package/local-runtime/server/.env.example +54 -0
  5. package/local-runtime/server/api.js +111 -0
  6. package/local-runtime/server/auth/aad.js +235 -0
  7. package/local-runtime/server/auth/login.js +156 -0
  8. package/local-runtime/server/auth/oauth-flow.js +277 -0
  9. package/local-runtime/server/auth/password-reset.js +134 -0
  10. package/local-runtime/server/auth/providers/alipay.js +125 -0
  11. package/local-runtime/server/auth/providers/github.js +82 -0
  12. package/local-runtime/server/auth/providers/google.js +60 -0
  13. package/local-runtime/server/auth/providers/microsoft.js +71 -0
  14. package/local-runtime/server/auth/providers/types.js +57 -0
  15. package/local-runtime/server/auth/providers/wechat.js +68 -0
  16. package/local-runtime/server/auth/register.js +91 -0
  17. package/local-runtime/server/auth/session-store.js +57 -0
  18. package/local-runtime/server/auth/token.js +85 -0
  19. package/local-runtime/server/auth/totp-auth.js +133 -0
  20. package/local-runtime/server/auth/utils.js +42 -0
  21. package/local-runtime/server/auth.js +8 -0
  22. package/local-runtime/server/check-node-version.js +74 -0
  23. package/local-runtime/server/config.js +298 -0
  24. package/local-runtime/server/context.js +140 -0
  25. package/local-runtime/server/create-user.js +59 -0
  26. package/local-runtime/server/database.js +12 -0
  27. package/local-runtime/server/db/connection.js +963 -0
  28. package/local-runtime/server/db/expert-db.js +171 -0
  29. package/local-runtime/server/db/identity-db.js +92 -0
  30. package/local-runtime/server/db/invitation-db.js +38 -0
  31. package/local-runtime/server/db/message-db.js +257 -0
  32. package/local-runtime/server/db/session-db.js +118 -0
  33. package/local-runtime/server/db/user-db.js +165 -0
  34. package/local-runtime/server/db/user-stats-db.js +185 -0
  35. package/local-runtime/server/db/yeaft-session-db.js +258 -0
  36. package/local-runtime/server/email.js +96 -0
  37. package/local-runtime/server/encryption.js +105 -0
  38. package/local-runtime/server/handlers/agent-conversation.js +347 -0
  39. package/local-runtime/server/handlers/agent-file-terminal.js +99 -0
  40. package/local-runtime/server/handlers/agent-output.js +854 -0
  41. package/local-runtime/server/handlers/agent-sync.js +399 -0
  42. package/local-runtime/server/handlers/agent-work-center.js +27 -0
  43. package/local-runtime/server/handlers/client-conversation.js +1182 -0
  44. package/local-runtime/server/handlers/client-misc.js +254 -0
  45. package/local-runtime/server/handlers/client-work-center.js +269 -0
  46. package/local-runtime/server/handlers/client-workbench.js +146 -0
  47. package/local-runtime/server/handlers/session-pin-router.js +61 -0
  48. package/local-runtime/server/heartbeat-policy.js +46 -0
  49. package/local-runtime/server/index.js +275 -0
  50. package/local-runtime/server/package.json +55 -0
  51. package/local-runtime/server/perf-trace.js +154 -0
  52. package/local-runtime/server/proxy.js +273 -0
  53. package/local-runtime/server/routes/admin-routes.js +207 -0
  54. package/local-runtime/server/routes/auth-routes.js +322 -0
  55. package/local-runtime/server/routes/expert-routes.js +117 -0
  56. package/local-runtime/server/routes/invitation-routes.js +60 -0
  57. package/local-runtime/server/routes/session-routes.js +112 -0
  58. package/local-runtime/server/routes/upload-routes.js +109 -0
  59. package/local-runtime/server/routes/user-routes.js +241 -0
  60. package/local-runtime/server/totp.js +74 -0
  61. package/local-runtime/server/work-item-attachment-policy.js +56 -0
  62. package/local-runtime/server/ws-agent.js +319 -0
  63. package/local-runtime/server/ws-client.js +214 -0
  64. package/local-runtime/server/ws-utils.js +394 -0
  65. package/local-runtime/server/yeaft-asset-store.js +339 -0
  66. package/local-runtime/version.json +1 -0
  67. package/local-runtime/web/app.bundle.js +7673 -0
  68. package/local-runtime/web/app.bundle.js.gz +0 -0
  69. package/local-runtime/web/assets/avatars/README.md +34 -0
  70. package/local-runtime/web/assets/avatars/ada.svg +1 -0
  71. package/local-runtime/web/assets/avatars/alan.svg +1 -0
  72. package/local-runtime/web/assets/avatars/alice.svg +1 -0
  73. package/local-runtime/web/assets/avatars/anders.svg +1 -0
  74. package/local-runtime/web/assets/avatars/bezos.svg +1 -0
  75. package/local-runtime/web/assets/avatars/borges.svg +1 -0
  76. package/local-runtime/web/assets/avatars/buffett.svg +1 -0
  77. package/local-runtime/web/assets/avatars/clausewitz.svg +1 -0
  78. package/local-runtime/web/assets/avatars/dalio.svg +1 -0
  79. package/local-runtime/web/assets/avatars/dieter.svg +1 -0
  80. package/local-runtime/web/assets/avatars/drucker.svg +1 -0
  81. package/local-runtime/web/assets/avatars/einstein.svg +1 -0
  82. package/local-runtime/web/assets/avatars/grace.svg +1 -0
  83. package/local-runtime/web/assets/avatars/harari.svg +1 -0
  84. package/local-runtime/web/assets/avatars/jung.svg +1 -0
  85. package/local-runtime/web/assets/avatars/kahneman.svg +1 -0
  86. package/local-runtime/web/assets/avatars/ken.svg +1 -0
  87. package/local-runtime/web/assets/avatars/kongzi.svg +1 -0
  88. package/local-runtime/web/assets/avatars/kubrick.svg +1 -0
  89. package/local-runtime/web/assets/avatars/linus.svg +1 -0
  90. package/local-runtime/web/assets/avatars/luxun.svg +1 -0
  91. package/local-runtime/web/assets/avatars/margaret.svg +1 -0
  92. package/local-runtime/web/assets/avatars/martin.svg +1 -0
  93. package/local-runtime/web/assets/avatars/miyazaki.svg +1 -0
  94. package/local-runtime/web/assets/avatars/munger.svg +1 -0
  95. package/local-runtime/web/assets/avatars/nietzsche.svg +1 -0
  96. package/local-runtime/web/assets/avatars/norman.svg +1 -0
  97. package/local-runtime/web/assets/avatars/shannon.svg +1 -0
  98. package/local-runtime/web/assets/avatars/simaqian.svg +1 -0
  99. package/local-runtime/web/assets/avatars/socrates.svg +1 -0
  100. package/local-runtime/web/assets/avatars/steve.svg +1 -0
  101. package/local-runtime/web/assets/avatars/sudongpo.svg +1 -0
  102. package/local-runtime/web/assets/avatars/sunzi.svg +1 -0
  103. package/local-runtime/web/docx-preview.min.js +2 -0
  104. package/local-runtime/web/docx-preview.min.js.gz +0 -0
  105. package/local-runtime/web/html-to-image.min.js +2 -0
  106. package/local-runtime/web/html-to-image.min.js.gz +0 -0
  107. package/local-runtime/web/index.html +21 -0
  108. package/local-runtime/web/jszip.min.js +13 -0
  109. package/local-runtime/web/jszip.min.js.gz +0 -0
  110. package/local-runtime/web/mermaid.min.js +2843 -0
  111. package/local-runtime/web/mermaid.min.js.gz +0 -0
  112. package/local-runtime/web/msal-browser.min.js +69 -0
  113. package/local-runtime/web/msal-browser.min.js.gz +0 -0
  114. package/local-runtime/web/style.bundle.css +10 -0
  115. package/local-runtime/web/style.bundle.css.gz +0 -0
  116. package/local-runtime/web/vendor.bundle.js +1522 -0
  117. package/local-runtime/web/vendor.bundle.js.gz +0 -0
  118. package/local-runtime/web/xlsx.min.js +24 -0
  119. package/local-runtime/web/xlsx.min.js.gz +0 -0
  120. package/package.json +13 -3
  121. package/scripts/prepare-local-runtime.js +25 -0
@@ -0,0 +1,399 @@
1
+ import { CONFIG } from '../config.js';
2
+ import { sessionDb, userStatsDb } from '../database.js';
3
+ import { agents, webClients } from '../context.js';
4
+ import { sendToWebClient, broadcastAgentList } from '../ws-utils.js';
5
+ import {
6
+ handleProxyResponse, handleProxyResponseChunk, handleProxyResponseEnd,
7
+ handleProxyWsAgentMessage
8
+ } from '../proxy.js';
9
+
10
+ const numberMetric = (value) => {
11
+ const n = Number(value);
12
+ return Number.isFinite(n) && n >= 0 ? n : 0;
13
+ };
14
+
15
+ function normalizeAgentMetrics(metrics = {}) {
16
+ const chatTurns = numberMetric(metrics.chatTurns);
17
+ const yeaftTurns = numberMetric(metrics.yeaftTurns);
18
+ const totalTurns = numberMetric(metrics.totalTurns) || chatTurns + yeaftTurns;
19
+ const inputTokens = numberMetric(metrics.inputTokens);
20
+ const outputTokens = numberMetric(metrics.outputTokens);
21
+ const cacheReadTokens = numberMetric(metrics.cacheReadTokens);
22
+ const cacheWriteTokens = numberMetric(metrics.cacheWriteTokens);
23
+ const totalTokens = numberMetric(metrics.totalTokens) || inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
24
+ return {
25
+ metricEpoch: typeof metrics.metricEpoch === 'string' ? metrics.metricEpoch : null,
26
+ chatTurns,
27
+ yeaftTurns,
28
+ totalTurns,
29
+ sessionsCreated: numberMetric(metrics.sessionsCreated),
30
+ inputTokens,
31
+ outputTokens,
32
+ cacheReadTokens,
33
+ cacheWriteTokens,
34
+ totalTokens,
35
+ lastUpdatedAt: numberMetric(metrics.lastUpdatedAt) || null,
36
+ };
37
+ }
38
+
39
+ async function forwardAgentMetrics(agentId, agent) {
40
+ const payload = {
41
+ type: 'agent_metrics',
42
+ agentId,
43
+ metrics: agent.metrics,
44
+ metricsUpdatedAt: agent.metricsUpdatedAt || null,
45
+ };
46
+ for (const [, client] of webClients) {
47
+ if (client.authenticated && (CONFIG.skipAuth ||
48
+ (agent.ownerId && client.userId === agent.ownerId) ||
49
+ (!agent.ownerId && client.role === 'admin')
50
+ )) {
51
+ await sendToWebClient(client, payload);
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Handle sync, proxy, and agent control messages from agent.
58
+ * Types: agent_sync_complete, sync_sessions, agent_metrics,
59
+ * proxy_response, proxy_response_chunk, proxy_response_end,
60
+ * proxy_ports_update, proxy_ws_opened/message/closed/error,
61
+ * restart_agent_ack, upgrade_agent_ack
62
+ */
63
+ export async function handleAgentSync(agentId, agent, msg) {
64
+ switch (msg.type) {
65
+ // Phase 1: Agent 同步完成
66
+ case 'agent_sync_complete': {
67
+ agent.status = 'ready';
68
+ if (agent._syncTimeout) {
69
+ clearTimeout(agent._syncTimeout);
70
+ delete agent._syncTimeout;
71
+ }
72
+ console.log(`[Sync] Agent ${agent.name} sync complete, status: ready`);
73
+ await broadcastAgentList();
74
+ break;
75
+ }
76
+
77
+ case 'agent_metrics': {
78
+ agent.metrics = normalizeAgentMetrics(msg.metrics || {});
79
+ agent.metricsUpdatedAt = Date.now();
80
+ if (agent.ownerId && agent.metrics.metricEpoch) {
81
+ try {
82
+ userStatsDb.recordAgentTokenSnapshot(
83
+ agent.ownerId,
84
+ agent.instanceId || agentId,
85
+ agent.metrics
86
+ );
87
+ } catch (error) {
88
+ console.error(`[AgentMetrics] Failed to persist token usage for ${agentId}:`, error.message);
89
+ }
90
+ }
91
+ await forwardAgentMetrics(agentId, agent);
92
+ break;
93
+ }
94
+
95
+ // Phase 2: Session 同步
96
+ case 'sync_sessions': {
97
+ const sessions = msg.sessions || [];
98
+ // Security: 限制单次同步的 session 数量
99
+ const MAX_SYNC_SESSIONS = 1000;
100
+ if (sessions.length > MAX_SYNC_SESSIONS) {
101
+ console.warn(`[Security] Agent ${agentId} tried to sync ${sessions.length} sessions (limit: ${MAX_SYNC_SESSIONS}), truncating`);
102
+ }
103
+ const safeSessions = sessions.slice(0, MAX_SYNC_SESSIONS);
104
+ console.log(`[Sync] Received ${safeSessions.length} sessions from agent ${agent.name}`);
105
+ let created = 0, updated = 0;
106
+ for (const s of safeSessions) {
107
+ // Security: 校验 sessionId 格式
108
+ if (!s.sessionId || typeof s.sessionId !== 'string' || s.sessionId.length > 200) continue;
109
+ try {
110
+ const existing = sessionDb.get(s.sessionId);
111
+ if (!existing) {
112
+ // Security: 强制使用 agent.ownerId
113
+ sessionDb.create(s.sessionId, agentId, agent.name, s.workDir, s.sessionId, s.title, agent.ownerId || null);
114
+ // Auto-deactivate old sessions synced from disk (not modified in 2 days)
115
+ if (s.lastModified && s.lastModified < Date.now() - 2 * 24 * 60 * 60 * 1000) {
116
+ sessionDb.setActive(s.sessionId, false);
117
+ }
118
+ created++;
119
+ } else {
120
+ // fix-chat-title-sticky: don't let the agent's bulk title
121
+ // sync overwrite a user-renamed session. The title in the
122
+ // DB is sticky once `customTitle` is set; only the
123
+ // agent's claudeSessionId / activity timestamp need to
124
+ // refresh on this path.
125
+ if (s.lastModified > existing.updated_at && !existing.customTitle) {
126
+ sessionDb.update(s.sessionId, { title: s.title });
127
+ }
128
+ updated++;
129
+ }
130
+ } catch (e) {
131
+ console.error(`[Sync] Error syncing session ${s.sessionId}:`, e.message);
132
+ }
133
+ }
134
+ console.log(`[Sync] Sessions synced: ${created} created, ${updated} existing`);
135
+ break;
136
+ }
137
+
138
+ // Port proxy responses
139
+ case 'proxy_response':
140
+ handleProxyResponse(msg);
141
+ break;
142
+
143
+ case 'proxy_response_chunk':
144
+ handleProxyResponseChunk(msg);
145
+ break;
146
+
147
+ case 'proxy_response_end':
148
+ handleProxyResponseEnd(msg);
149
+ break;
150
+
151
+ case 'proxy_ports_update': {
152
+ const a = agents.get(agentId);
153
+ if (a) {
154
+ a.proxyPorts = msg.ports || [];
155
+ await broadcastAgentList();
156
+ }
157
+ break;
158
+ }
159
+
160
+ case 'restart_agent_ack': {
161
+ // 只通知该 Agent 的 owner
162
+ for (const [, client] of webClients) {
163
+ if (client.authenticated && (CONFIG.skipAuth ||
164
+ (agent.ownerId && client.userId === agent.ownerId) ||
165
+ (!agent.ownerId && client.role === 'admin')
166
+ )) {
167
+ await sendToWebClient(client, { type: 'restart_agent_ack', agentId });
168
+ }
169
+ }
170
+ break;
171
+ }
172
+
173
+ case 'upgrade_agent_ack': {
174
+ for (const [, client] of webClients) {
175
+ if (client.authenticated && (CONFIG.skipAuth ||
176
+ (agent.ownerId && client.userId === agent.ownerId) ||
177
+ (!agent.ownerId && client.role === 'admin')
178
+ )) {
179
+ await sendToWebClient(client, { type: 'upgrade_agent_ack', agentId, success: msg.success, error: msg.error, alreadyLatest: msg.alreadyLatest, version: msg.version, reason: msg.reason, currentNode: msg.currentNode, requiredNode: msg.requiredNode });
180
+ }
181
+ }
182
+ break;
183
+ }
184
+
185
+ // Proxy WebSocket messages from agent to browser
186
+ case 'proxy_ws_opened':
187
+ case 'proxy_ws_message':
188
+ case 'proxy_ws_closed':
189
+ case 'proxy_ws_error':
190
+ handleProxyWsAgentMessage(msg);
191
+ break;
192
+
193
+ // MCP servers list from agent — store on agent and broadcast to owner clients
194
+ case 'mcp_servers_list': {
195
+ agent.mcpServers = msg.servers || [];
196
+ console.log(`[MCP] Agent ${agent.name} reported ${agent.mcpServers.length} MCP servers`);
197
+ for (const [, client] of webClients) {
198
+ if (client.authenticated && (CONFIG.skipAuth ||
199
+ (agent.ownerId && client.userId === agent.ownerId) ||
200
+ (!agent.ownerId && client.role === 'admin')
201
+ )) {
202
+ await sendToWebClient(client, {
203
+ type: 'mcp_servers_list',
204
+ agentId,
205
+ servers: agent.mcpServers
206
+ });
207
+ }
208
+ }
209
+ break;
210
+ }
211
+
212
+ // Expert roles definition from agent — forward to owner clients
213
+ case 'expert_roles_list': {
214
+ for (const [, client] of webClients) {
215
+ if (client.authenticated && (CONFIG.skipAuth ||
216
+ (agent.ownerId && client.userId === agent.ownerId) ||
217
+ (!agent.ownerId && client.role === 'admin')
218
+ )) {
219
+ await sendToWebClient(client, {
220
+ type: 'expert_roles_list',
221
+ agentId,
222
+ roles: msg.roles
223
+ });
224
+ }
225
+ }
226
+ break;
227
+ }
228
+
229
+ // MCP config updated acknowledgement from agent
230
+ case 'mcp_config_updated': {
231
+ agent.mcpServers = msg.servers || [];
232
+ for (const [, client] of webClients) {
233
+ if (client.authenticated && (CONFIG.skipAuth ||
234
+ (agent.ownerId && client.userId === agent.ownerId) ||
235
+ (!agent.ownerId && client.role === 'admin')
236
+ )) {
237
+ await sendToWebClient(client, {
238
+ type: 'mcp_config_updated',
239
+ agentId,
240
+ servers: agent.mcpServers
241
+ });
242
+ }
243
+ }
244
+ break;
245
+ }
246
+
247
+ // LLM config response from agent — relay to owner clients
248
+ case 'llm_config': {
249
+ for (const [, client] of webClients) {
250
+ if (client.authenticated && (CONFIG.skipAuth ||
251
+ (agent.ownerId && client.userId === agent.ownerId) ||
252
+ (!agent.ownerId && client.role === 'admin')
253
+ )) {
254
+ await sendToWebClient(client, {
255
+ type: 'llm_config',
256
+ agentId,
257
+ providers: msg.providers,
258
+ primaryModel: msg.primaryModel,
259
+ fastModel: msg.fastModel,
260
+ language: msg.language,
261
+ needsSetup: msg.needsSetup,
262
+ agentConfig: msg.agentConfig,
263
+ effectiveConfig: msg.effectiveConfig,
264
+ error: msg.error
265
+ });
266
+ }
267
+ }
268
+ break;
269
+ }
270
+
271
+ // LLM model discovery response from agent — relay to owner clients
272
+ case 'llm_models_discovered': {
273
+ for (const [, client] of webClients) {
274
+ if (client.authenticated && (CONFIG.skipAuth ||
275
+ (agent.ownerId && client.userId === agent.ownerId) ||
276
+ (!agent.ownerId && client.role === 'admin')
277
+ )) {
278
+ await sendToWebClient(client, {
279
+ type: 'llm_models_discovered',
280
+ agentId,
281
+ requestId: msg.requestId,
282
+ providerType: msg.providerType,
283
+ provider: msg.provider,
284
+ models: msg.models || [],
285
+ providerModels: msg.providerModels || [],
286
+ source: msg.source,
287
+ warning: msg.warning,
288
+ error: msg.error,
289
+ });
290
+ }
291
+ }
292
+ break;
293
+ }
294
+
295
+ case 'llm_config_updated': {
296
+ for (const [, client] of webClients) {
297
+ if (client.authenticated && (CONFIG.skipAuth ||
298
+ (agent.ownerId && client.userId === agent.ownerId) ||
299
+ (!agent.ownerId && client.role === 'admin')
300
+ )) {
301
+ await sendToWebClient(client, {
302
+ type: 'llm_config_updated',
303
+ agentId,
304
+ providers: msg.providers,
305
+ primaryModel: msg.primaryModel,
306
+ fastModel: msg.fastModel,
307
+ language: msg.language,
308
+ agentConfig: msg.agentConfig,
309
+ effectiveConfig: msg.effectiveConfig,
310
+ error: msg.error
311
+ });
312
+ }
313
+ }
314
+ break;
315
+ }
316
+
317
+ // models.dev registry response from agent — relay to owner clients
318
+ case 'models_dev_registry': {
319
+ for (const [, client] of webClients) {
320
+ if (client.authenticated && (CONFIG.skipAuth ||
321
+ (agent.ownerId && client.userId === agent.ownerId) ||
322
+ (!agent.ownerId && client.role === 'admin')
323
+ )) {
324
+ await sendToWebClient(client, {
325
+ type: 'models_dev_registry',
326
+ agentId,
327
+ requestId: msg.requestId,
328
+ registry: msg.registry,
329
+ fetchedAt: msg.fetchedAt,
330
+ error: msg.error,
331
+ });
332
+ }
333
+ }
334
+ break;
335
+ }
336
+
337
+ // task-318: Yeaft runtime settings read / update ack — relay to owner
338
+ case 'yeaft_settings':
339
+ case 'yeaft_settings_updated': {
340
+ for (const [, client] of webClients) {
341
+ if (client.authenticated && (CONFIG.skipAuth ||
342
+ (agent.ownerId && client.userId === agent.ownerId) ||
343
+ (!agent.ownerId && client.role === 'admin')
344
+ )) {
345
+ await sendToWebClient(client, {
346
+ type: msg.type,
347
+ agentId,
348
+ maxConcurrentThreads: msg.maxConcurrentThreads,
349
+ autoArchiveIdleDays: msg.autoArchiveIdleDays,
350
+ error: msg.error
351
+ });
352
+ }
353
+ }
354
+ break;
355
+ }
356
+
357
+ // Search settings + Tavily usage relays. We pass the whole msg
358
+ // through (minus agentId, which we set ourselves) — the payload
359
+ // shapes differ per type and the front-end already filters on
360
+ // `type`, so a generic forward is simpler than three nearly-
361
+ // identical branches.
362
+ case 'search_settings':
363
+ case 'search_settings_updated':
364
+ case 'tavily_usage': {
365
+ for (const [, client] of webClients) {
366
+ if (client.authenticated && (CONFIG.skipAuth ||
367
+ (agent.ownerId && client.userId === agent.ownerId) ||
368
+ (!agent.ownerId && client.role === 'admin')
369
+ )) {
370
+ await sendToWebClient(client, { ...msg, agentId });
371
+ }
372
+ }
373
+ break;
374
+ }
375
+
376
+ // Yeaft MCP CRUD result + live-update broadcast. Same generic pass-
377
+ // through as the search-settings branch above — the payload shape
378
+ // is documented in web-bridge.js handlers (`handleYeaftMcp*`).
379
+ case 'yeaft_mcp_list_result':
380
+ case 'yeaft_mcp_add_result':
381
+ case 'yeaft_mcp_remove_result':
382
+ case 'yeaft_mcp_reload_result':
383
+ case 'yeaft_mcp_updated': {
384
+ for (const [, client] of webClients) {
385
+ if (client.authenticated && (CONFIG.skipAuth ||
386
+ (agent.ownerId && client.userId === agent.ownerId) ||
387
+ (!agent.ownerId && client.role === 'admin')
388
+ )) {
389
+ await sendToWebClient(client, { ...msg, agentId });
390
+ }
391
+ }
392
+ break;
393
+ }
394
+
395
+ default:
396
+ return false; // Not handled
397
+ }
398
+ return true; // Handled
399
+ }
@@ -0,0 +1,27 @@
1
+ import { forwardAgentEvent } from '../ws-utils.js';
2
+ import { deliverWorkCenterResponse } from './client-work-center.js';
3
+
4
+ const WORK_CENTER_TYPES = new Set([
5
+ 'work_center_response',
6
+ 'work_center_event',
7
+ ]);
8
+
9
+ /**
10
+ * Forward Agent-local Work Center responses and projection events.
11
+ *
12
+ * Responses use a server-owned request map. Events have no requester and are
13
+ * therefore projected only through the authenticated Agent owner boundary.
14
+ */
15
+ export async function handleAgentWorkCenter(agentId, msg) {
16
+ if (!WORK_CENTER_TYPES.has(msg?.type)) return false;
17
+
18
+ if (msg.type === 'work_center_response') {
19
+ await deliverWorkCenterResponse(agentId, msg);
20
+ return true;
21
+ }
22
+
23
+ const { agentId: _untrustedAgentId, _requestUserId, ...payload } = msg;
24
+ const outgoing = { ...payload, agentId };
25
+ await forwardAgentEvent(agentId, outgoing);
26
+ return true;
27
+ }