@axhub/genie 0.2.9 → 0.2.10

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 (96) hide show
  1. package/dist/api-docs.html +2 -2
  2. package/dist/assets/App-CYCCsgwf.js +264 -0
  3. package/dist/assets/{ReviewApp-C9K--AQE.js → ReviewApp-0srHIXwb.js} +1 -1
  4. package/dist/assets/{_basePickBy-DR_8uFCo.js → _basePickBy-DVVb07UV.js} +1 -1
  5. package/dist/assets/{_baseUniq-D0njlQ_7.js → _baseUniq-BtbziL5G.js} +1 -1
  6. package/dist/assets/{arc-CKlr_Rec.js → arc-BsCC8yBD.js} +1 -1
  7. package/dist/assets/{architectureDiagram-2XIMDMQ5-BmO_uLUH.js → architectureDiagram-2XIMDMQ5-woFp6eNI.js} +1 -1
  8. package/dist/assets/{blockDiagram-WCTKOSBZ-DhAeO-56.js → blockDiagram-WCTKOSBZ-ya8VAc2k.js} +1 -1
  9. package/dist/assets/{c4Diagram-IC4MRINW-C67kFoXx.js → c4Diagram-IC4MRINW-CY1dZmIZ.js} +1 -1
  10. package/dist/assets/channel-BMhScXFe.js +1 -0
  11. package/dist/assets/{chunk-4BX2VUAB-mLLagvJi.js → chunk-4BX2VUAB-CR1lAd74.js} +1 -1
  12. package/dist/assets/{chunk-55IACEB6-Lx-hOjlM.js → chunk-55IACEB6-CP98WcFC.js} +1 -1
  13. package/dist/assets/{chunk-FMBD7UC4-Bt-XmVUV.js → chunk-FMBD7UC4-D9c7ijAB.js} +1 -1
  14. package/dist/assets/{chunk-JSJVCQXG-Cya6gaDV.js → chunk-JSJVCQXG-DQAGYOn-.js} +1 -1
  15. package/dist/assets/{chunk-KX2RTZJC-Bd7Ig6tF.js → chunk-KX2RTZJC-BbTXiDq7.js} +1 -1
  16. package/dist/assets/{chunk-NQ4KR5QH-5UAE0Vg-.js → chunk-NQ4KR5QH-BI6AX0dr.js} +1 -1
  17. package/dist/assets/{chunk-QZHKN3VN-BAxZ8m7w.js → chunk-QZHKN3VN-DB3V2Ifo.js} +1 -1
  18. package/dist/assets/{chunk-WL4C6EOR-DjDPvUUP.js → chunk-WL4C6EOR-DhzTthv6.js} +1 -1
  19. package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +1 -0
  20. package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +1 -0
  21. package/dist/assets/clone-BPqOt4r3.js +1 -0
  22. package/dist/assets/{cose-bilkent-S5V4N54A-D-60XrkJ.js → cose-bilkent-S5V4N54A-BQ09ZE2j.js} +1 -1
  23. package/dist/assets/{dagre-KLK3FWXG-bqu3ZS4K.js → dagre-KLK3FWXG-Dc2ueD_R.js} +1 -1
  24. package/dist/assets/{diagram-E7M64L7V-BueeqoYm.js → diagram-E7M64L7V-DP-LsQoL.js} +1 -1
  25. package/dist/assets/{diagram-IFDJBPK2-D4fDv2E7.js → diagram-IFDJBPK2-Cg6r42cB.js} +1 -1
  26. package/dist/assets/{diagram-P4PSJMXO-WqipY3fN.js → diagram-P4PSJMXO-aHsfoUZE.js} +1 -1
  27. package/dist/assets/{erDiagram-INFDFZHY-D0oVnO-x.js → erDiagram-INFDFZHY-qBXJ4aAz.js} +1 -1
  28. package/dist/assets/{flowDiagram-PKNHOUZH-DzbGyxrr.js → flowDiagram-PKNHOUZH-D_13emJM.js} +1 -1
  29. package/dist/assets/{ganttDiagram-A5KZAMGK-BwhbbgCP.js → ganttDiagram-A5KZAMGK-BvIcOLwz.js} +1 -1
  30. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js → gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js} +1 -1
  31. package/dist/assets/{graph-DzKos-N0.js → graph-CeJCMjan.js} +1 -1
  32. package/dist/assets/{highlighted-body-TPN3WLV5-CKDMgz3X.js → highlighted-body-TPN3WLV5-B_novwSz.js} +1 -1
  33. package/dist/assets/index-C514cLyb.js +2 -0
  34. package/dist/assets/index-h1DBl_g3.css +1 -0
  35. package/dist/assets/{infoDiagram-LFFYTUFH-BFicZbTf.js → infoDiagram-LFFYTUFH-lOxAqb3m.js} +1 -1
  36. package/dist/assets/{ishikawaDiagram-PHBUUO56-CtihxDxl.js → ishikawaDiagram-PHBUUO56-DIr-51gj.js} +1 -1
  37. package/dist/assets/{journeyDiagram-4ABVD52K-Du00J8_d.js → journeyDiagram-4ABVD52K-CYcIW0ZU.js} +1 -1
  38. package/dist/assets/{kanban-definition-K7BYSVSG-BJi9S0iQ.js → kanban-definition-K7BYSVSG-C1ZK616a.js} +1 -1
  39. package/dist/assets/{layout-B80Sityu.js → layout-CI2RM-v6.js} +1 -1
  40. package/dist/assets/{linear-sRQLOf5H.js → linear-DE7bISck.js} +1 -1
  41. package/dist/assets/{mermaid-O7DHMXV3-CBuVs4eJ.js → mermaid-O7DHMXV3-XxAJo8EK.js} +6 -6
  42. package/dist/assets/{mindmap-definition-YRQLILUH-C5IL_xi-.js → mindmap-definition-YRQLILUH-Dz6EFjmn.js} +1 -1
  43. package/dist/assets/{pieDiagram-SKSYHLDU-CeTwlJ8z.js → pieDiagram-SKSYHLDU-DPpEzUed.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-337W2JSQ-COfUcLWt.js → quadrantDiagram-337W2JSQ-xdoXNet7.js} +1 -1
  45. package/dist/assets/{requirementDiagram-Z7DCOOCP-DSb-CJ5B.js → requirementDiagram-Z7DCOOCP-DUq8H3CL.js} +1 -1
  46. package/dist/assets/{sankeyDiagram-WA2Y5GQK-8jtuVb45.js → sankeyDiagram-WA2Y5GQK-CmqEUxRu.js} +1 -1
  47. package/dist/assets/{sequenceDiagram-2WXFIKYE-C2VpkMwA.js → sequenceDiagram-2WXFIKYE-DhtXRNiH.js} +1 -1
  48. package/dist/assets/{stateDiagram-RAJIS63D-fmwMqxxc.js → stateDiagram-RAJIS63D-Dj0HOlbN.js} +1 -1
  49. package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +1 -0
  50. package/dist/assets/{timeline-definition-YZTLITO2-Dx1hP5lg.js → timeline-definition-YZTLITO2-DUuJzZB5.js} +1 -1
  51. package/dist/assets/{treemap-KZPCXAKY-CkLOdYCZ.js → treemap-KZPCXAKY-DpYBQ0qr.js} +1 -1
  52. package/dist/assets/vendor-codemirror-CMHSJ_9p.js +9 -0
  53. package/dist/assets/{vennDiagram-LZ73GAT5-D6KWcnln.js → vennDiagram-LZ73GAT5-DpePUyOd.js} +1 -1
  54. package/dist/assets/{xychartDiagram-JWTSCODW-6fh6qmzN.js → xychartDiagram-JWTSCODW-Cfp1I4_U.js} +1 -1
  55. package/dist/index.html +3 -3
  56. package/package.json +6 -5
  57. package/server/acp-runtime/client.js +120 -14
  58. package/server/acp-runtime/index.js +54 -0
  59. package/server/acp-runtime/registry.js +2 -2
  60. package/server/acp-runtime/session-store.js +75 -1
  61. package/server/cli.js +32 -8
  62. package/server/database/db.js +20 -0
  63. package/server/external-agent/ws.js +477 -24
  64. package/server/index.js +78 -146
  65. package/server/lan-access/core.js +79 -0
  66. package/server/lan-access/state.js +102 -0
  67. package/server/middleware/auth.js +57 -14
  68. package/server/projects.js +423 -535
  69. package/server/routes/auth.js +24 -4
  70. package/server/routes/cli-auth.js +21 -25
  71. package/server/routes/codex.js +84 -298
  72. package/server/routes/commands.js +322 -407
  73. package/server/routes/lan-access.js +231 -0
  74. package/server/routes/projects.js +154 -158
  75. package/server/routes/session-core.js +13 -7
  76. package/server/routes/settings.js +113 -99
  77. package/server/session-core/eventStore.js +15 -2
  78. package/server/session-core/providerAdapters.js +28 -28
  79. package/server/session-core/sessionListMerge.js +47 -0
  80. package/shared/conversationEvents.js +96 -1
  81. package/shared/modelConstants.js +79 -99
  82. package/dist/assets/App-GBcTeeUS.js +0 -460
  83. package/dist/assets/channel-V3MBjKys.js +0 -1
  84. package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +0 -1
  85. package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +0 -1
  86. package/dist/assets/clone-BbMGfZwt.js +0 -1
  87. package/dist/assets/index-DiQlHzGj.js +0 -2
  88. package/dist/assets/index-Drat2nB9.css +0 -1
  89. package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +0 -1
  90. package/dist/assets/vendor-codemirror-BxPY6emf.js +0 -39
  91. package/server/routes/git.js +0 -1110
  92. package/server/routes/mcp-utils.js +0 -48
  93. package/server/routes/mcp.js +0 -536
  94. package/server/routes/taskmaster.js +0 -1963
  95. package/server/utils/mcp-detector.js +0 -198
  96. package/server/utils/taskmaster-websocket.js +0 -129
@@ -15,7 +15,11 @@ import {
15
15
  seedSessionRuntimeSubscriptionSnapshot,
16
16
  subscribeToSessionRuntimeStateChanges
17
17
  } from '../session-core/runtimeState.js';
18
- import { SUPPORTED_PROVIDERS } from '../../shared/conversationEvents.js';
18
+ import {
19
+ CONVERSATION_EVENT_KINDS,
20
+ stripAssistantProtocolTags,
21
+ SUPPORTED_PROVIDERS
22
+ } from '../../shared/conversationEvents.js';
19
23
  import { materializeDataUrlImageToFile, sanitizeFileName } from '../utils/agentImages.js';
20
24
 
21
25
  const INTEGRATION_ROLE_FRONTEND_PAGE = 'frontend-page';
@@ -39,6 +43,9 @@ const pendingIntegrationRequests = new Map();
39
43
  const pendingIntegrationPings = new Map();
40
44
  const sessionStateSubscriptionsByWs = new Map();
41
45
  const sessionStateSubscribersByKey = new Map();
46
+ const sessionActivitySubscriptionsByWs = new Map();
47
+ const sessionActivitySubscribersByKey = new Map();
48
+ const assistantActivityBuffersByRequestId = new Map();
42
49
 
43
50
  // Editing state cache for resilience across connection drops
44
51
  const recentEditingStates = new Map();
@@ -111,6 +118,296 @@ function normalizePositiveInteger(value) {
111
118
  return next;
112
119
  }
113
120
 
121
+ function buildSessionActivitySubscriptionKey({ requestId = null, provider = null, sessionId = null } = {}) {
122
+ if (requestId) {
123
+ return `request:${requestId}`;
124
+ }
125
+ if (provider && sessionId) {
126
+ return `session:${provider}:${sessionId}`;
127
+ }
128
+ return null;
129
+ }
130
+
131
+ function normalizeSessionActivityTarget(payload) {
132
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
133
+ throw Object.assign(new Error('payload must be an object'), { statusCode: 400 });
134
+ }
135
+
136
+ const requestId = normalizeNonEmptyString(payload.requestId);
137
+ const sessionId = normalizeNonEmptyString(payload.sessionId);
138
+ const provider = normalizeNonEmptyString(payload.provider)?.toLowerCase() || null;
139
+
140
+ if (provider && !SUPPORTED_PROVIDERS.includes(provider)) {
141
+ throw Object.assign(new Error(`provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}`), {
142
+ statusCode: 400
143
+ });
144
+ }
145
+
146
+ if (!requestId && !(sessionId && provider)) {
147
+ throw Object.assign(new Error('requestId or sessionId+provider is required'), {
148
+ statusCode: 400
149
+ });
150
+ }
151
+
152
+ return {
153
+ requestId,
154
+ sessionId,
155
+ provider
156
+ };
157
+ }
158
+
159
+ function addSessionActivitySubscription(ws, target) {
160
+ const key = buildSessionActivitySubscriptionKey(target);
161
+ if (!key) {
162
+ return;
163
+ }
164
+
165
+ if (!sessionActivitySubscriptionsByWs.has(ws)) {
166
+ sessionActivitySubscriptionsByWs.set(ws, new Map());
167
+ }
168
+ sessionActivitySubscriptionsByWs.get(ws).set(key, target);
169
+
170
+ if (!sessionActivitySubscribersByKey.has(key)) {
171
+ sessionActivitySubscribersByKey.set(key, new Set());
172
+ }
173
+ sessionActivitySubscribersByKey.get(key).add(ws);
174
+ }
175
+
176
+ function removeSessionActivitySubscription(ws, target) {
177
+ const key = buildSessionActivitySubscriptionKey(target);
178
+ if (!key) {
179
+ return;
180
+ }
181
+
182
+ const targets = sessionActivitySubscriptionsByWs.get(ws);
183
+ if (targets) {
184
+ targets.delete(key);
185
+ if (targets.size === 0) {
186
+ sessionActivitySubscriptionsByWs.delete(ws);
187
+ }
188
+ }
189
+
190
+ const subscribers = sessionActivitySubscribersByKey.get(key);
191
+ if (!subscribers) {
192
+ return;
193
+ }
194
+
195
+ subscribers.delete(ws);
196
+ if (subscribers.size === 0) {
197
+ sessionActivitySubscribersByKey.delete(key);
198
+ }
199
+ }
200
+
201
+ function removeSessionActivitySubscriptionsForWs(ws) {
202
+ const targets = sessionActivitySubscriptionsByWs.get(ws);
203
+ if (!targets) {
204
+ return;
205
+ }
206
+
207
+ for (const key of targets.keys()) {
208
+ const subscribers = sessionActivitySubscribersByKey.get(key);
209
+ if (!subscribers) {
210
+ continue;
211
+ }
212
+ subscribers.delete(ws);
213
+ if (subscribers.size === 0) {
214
+ sessionActivitySubscribersByKey.delete(key);
215
+ }
216
+ }
217
+
218
+ sessionActivitySubscriptionsByWs.delete(ws);
219
+ }
220
+
221
+ function clearAssistantActivityBuffers(requestId) {
222
+ if (!requestId) {
223
+ return;
224
+ }
225
+ assistantActivityBuffersByRequestId.delete(requestId);
226
+ }
227
+
228
+ function getAssistantActivityBuffer(requestId, messageId) {
229
+ if (!requestId || !messageId) {
230
+ return null;
231
+ }
232
+
233
+ if (!assistantActivityBuffersByRequestId.has(requestId)) {
234
+ assistantActivityBuffersByRequestId.set(requestId, new Map());
235
+ }
236
+
237
+ const buffers = assistantActivityBuffersByRequestId.get(requestId);
238
+ if (!buffers.has(messageId)) {
239
+ buffers.set(messageId, {
240
+ text: '',
241
+ updatedAt: Date.now()
242
+ });
243
+ }
244
+
245
+ return buffers.get(messageId);
246
+ }
247
+
248
+ function consumeAssistantActivityBuffer(requestId, messageId) {
249
+ if (!requestId || !messageId) {
250
+ return null;
251
+ }
252
+
253
+ const buffers = assistantActivityBuffersByRequestId.get(requestId);
254
+ if (!buffers) {
255
+ return null;
256
+ }
257
+
258
+ const buffer = buffers.get(messageId) || null;
259
+ buffers.delete(messageId);
260
+ if (buffers.size === 0) {
261
+ assistantActivityBuffersByRequestId.delete(requestId);
262
+ }
263
+ return buffer;
264
+ }
265
+
266
+ function summarizeSessionActivityText(text) {
267
+ const normalized = stripAssistantProtocolTags(String(text || ''))
268
+ .replace(/\s+/g, ' ')
269
+ .trim();
270
+ if (!normalized) {
271
+ return '';
272
+ }
273
+ if (normalized.length <= 160) {
274
+ return normalized;
275
+ }
276
+ return `${normalized.slice(0, 157).trimEnd()}...`;
277
+ }
278
+
279
+ function createSessionActivityItem({
280
+ id,
281
+ timestamp,
282
+ kind,
283
+ text,
284
+ requestId,
285
+ sessionId,
286
+ provider,
287
+ toolName
288
+ }) {
289
+ const normalizedText = summarizeSessionActivityText(text);
290
+ if (!normalizedText) {
291
+ return null;
292
+ }
293
+
294
+ const parsedTimestamp = timestamp ? Date.parse(timestamp) : NaN;
295
+ return {
296
+ id: normalizeNonEmptyString(id) || createRuntimeId('session_activity'),
297
+ timestamp: Number.isFinite(parsedTimestamp) ? parsedTimestamp : Date.now(),
298
+ kind,
299
+ text: normalizedText,
300
+ requestId: requestId || null,
301
+ sessionId: sessionId || null,
302
+ provider: provider || null,
303
+ ...(toolName ? { toolName } : {})
304
+ };
305
+ }
306
+
307
+ function buildSessionActivityFromConversationEvent({
308
+ requestId,
309
+ provider,
310
+ sessionId,
311
+ event
312
+ }) {
313
+ if (!event || typeof event !== 'object') {
314
+ return null;
315
+ }
316
+
317
+ const eventKind = normalizeNonEmptyString(event.kind);
318
+ const payload = isPlainObject(event.payload) ? event.payload : {};
319
+ const resolvedProvider = normalizeNonEmptyString(event.provider)?.toLowerCase() || provider || null;
320
+ const resolvedSessionId = normalizeNonEmptyString(event.sessionId) || sessionId || null;
321
+ const messageId =
322
+ normalizeNonEmptyString(payload.messageId)
323
+ || normalizeNonEmptyString(event.extensions?.messageId);
324
+
325
+ if (eventKind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_START) {
326
+ getAssistantActivityBuffer(requestId, messageId);
327
+ return null;
328
+ }
329
+
330
+ if (eventKind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_DELTA) {
331
+ const buffer = getAssistantActivityBuffer(requestId, messageId);
332
+ if (!buffer) {
333
+ return null;
334
+ }
335
+ buffer.text += String(payload.text || '');
336
+ buffer.updatedAt = Date.now();
337
+ return null;
338
+ }
339
+
340
+ if (eventKind === CONVERSATION_EVENT_KINDS.ASSISTANT_TEXT_END) {
341
+ const buffer = consumeAssistantActivityBuffer(requestId, messageId);
342
+ if (!buffer?.text) {
343
+ return null;
344
+ }
345
+ return createSessionActivityItem({
346
+ id: event.eventId,
347
+ timestamp: event.timestamp,
348
+ kind: 'assistant',
349
+ text: buffer.text,
350
+ requestId,
351
+ sessionId: resolvedSessionId,
352
+ provider: resolvedProvider
353
+ });
354
+ }
355
+
356
+ if (eventKind === CONVERSATION_EVENT_KINDS.TOOL_CALL_START) {
357
+ const toolName = normalizeNonEmptyString(payload.toolName) || 'unknown';
358
+ return createSessionActivityItem({
359
+ id: event.eventId,
360
+ timestamp: event.timestamp,
361
+ kind: 'tool',
362
+ text: `调用工具:${toolName}`,
363
+ requestId,
364
+ sessionId: resolvedSessionId,
365
+ provider: resolvedProvider,
366
+ toolName
367
+ });
368
+ }
369
+
370
+ return null;
371
+ }
372
+
373
+ function publishSessionActivity(item) {
374
+ if (!item || typeof item !== 'object') {
375
+ return;
376
+ }
377
+
378
+ const keys = new Set();
379
+ const requestKey = buildSessionActivitySubscriptionKey({ requestId: item.requestId });
380
+ const sessionKey = buildSessionActivitySubscriptionKey({
381
+ provider: item.provider,
382
+ sessionId: item.sessionId
383
+ });
384
+ if (requestKey) {
385
+ keys.add(requestKey);
386
+ }
387
+ if (sessionKey) {
388
+ keys.add(sessionKey);
389
+ }
390
+ if (keys.size === 0) {
391
+ return;
392
+ }
393
+
394
+ const recipients = new Set();
395
+ for (const key of keys) {
396
+ const subscribers = sessionActivitySubscribersByKey.get(key);
397
+ if (!subscribers) {
398
+ continue;
399
+ }
400
+ subscribers.forEach((ws) => recipients.add(ws));
401
+ }
402
+
403
+ recipients.forEach((ws) => {
404
+ sendJson(ws, {
405
+ type: 'session.activity.event',
406
+ payload: item
407
+ });
408
+ });
409
+ }
410
+
114
411
  function isEditorResultMessageType(type) {
115
412
  return typeof type === 'string'
116
413
  && type.startsWith(INTEGRATION_EDITOR_TYPE_PREFIX)
@@ -303,6 +600,56 @@ async function handleAgentStateSubscribe(ws, data) {
303
600
  });
304
601
  }
305
602
 
603
+ function handleSessionActivitySubscribe(ws, data) {
604
+ const requestId = getRequestId(data);
605
+
606
+ let normalized = null;
607
+ try {
608
+ normalized = normalizeSessionActivityTarget(data?.payload);
609
+ } catch (error) {
610
+ sendRequestError(ws, {
611
+ requestId,
612
+ provider: data?.payload?.provider || null,
613
+ sessionId: data?.payload?.sessionId || null,
614
+ error: error.message,
615
+ statusCode: error.statusCode || 400
616
+ });
617
+ return;
618
+ }
619
+
620
+ addSessionActivitySubscription(ws, normalized);
621
+ sendJson(ws, {
622
+ type: 'session.activity.subscribed',
623
+ requestId,
624
+ payload: normalized
625
+ });
626
+ }
627
+
628
+ function handleSessionActivityUnsubscribe(ws, data) {
629
+ const requestId = getRequestId(data);
630
+
631
+ let normalized = null;
632
+ try {
633
+ normalized = normalizeSessionActivityTarget(data?.payload);
634
+ } catch (error) {
635
+ sendRequestError(ws, {
636
+ requestId,
637
+ provider: data?.payload?.provider || null,
638
+ sessionId: data?.payload?.sessionId || null,
639
+ error: error.message,
640
+ statusCode: error.statusCode || 400
641
+ });
642
+ return;
643
+ }
644
+
645
+ removeSessionActivitySubscription(ws, normalized);
646
+ sendJson(ws, {
647
+ type: 'session.activity.unsubscribed',
648
+ requestId,
649
+ payload: normalized
650
+ });
651
+ }
652
+
306
653
  function cleanupExpiredPendingIntegrationRequests() {
307
654
  const now = Date.now();
308
655
  for (const [requestId, entry] of pendingIntegrationRequests.entries()) {
@@ -850,6 +1197,69 @@ function getTargetFrontendConnections(channel, targetClientId) {
850
1197
  });
851
1198
  }
852
1199
 
1200
+ function resolveForwardTargets(originWs, { channel, targetClientId }) {
1201
+ const explicitTargetId = normalizeNonEmptyString(targetClientId);
1202
+ if (explicitTargetId) {
1203
+ return {
1204
+ targets: getTargetFrontendConnections(channel, explicitTargetId),
1205
+ resolvedTargetClientId: explicitTargetId,
1206
+ error: null
1207
+ };
1208
+ }
1209
+
1210
+ const channelTargets = getTargetFrontendConnections(channel, null);
1211
+ if (channelTargets.length === 0) {
1212
+ return {
1213
+ targets: [],
1214
+ resolvedTargetClientId: null,
1215
+ error: {
1216
+ code: 'FRONTEND_NOT_ONLINE',
1217
+ message: 'Target frontend page is not connected'
1218
+ }
1219
+ };
1220
+ }
1221
+
1222
+ if (channelTargets.length === 1) {
1223
+ return {
1224
+ targets: channelTargets,
1225
+ resolvedTargetClientId: channelTargets[0].clientId,
1226
+ error: null
1227
+ };
1228
+ }
1229
+
1230
+ const originMeta = integrationConnections.get(originWs) || null;
1231
+ const sessionMatchedTargets = originMeta?.sessionId
1232
+ ? channelTargets.filter((target) => target.sessionId && target.sessionId === originMeta.sessionId)
1233
+ : [];
1234
+ if (sessionMatchedTargets.length === 1) {
1235
+ return {
1236
+ targets: sessionMatchedTargets,
1237
+ resolvedTargetClientId: sessionMatchedTargets[0].clientId,
1238
+ error: null
1239
+ };
1240
+ }
1241
+
1242
+ const pageMatchedTargets = originMeta?.pageUrl
1243
+ ? channelTargets.filter((target) => target.pageUrl && target.pageUrl === originMeta.pageUrl)
1244
+ : [];
1245
+ if (pageMatchedTargets.length === 1) {
1246
+ return {
1247
+ targets: pageMatchedTargets,
1248
+ resolvedTargetClientId: pageMatchedTargets[0].clientId,
1249
+ error: null
1250
+ };
1251
+ }
1252
+
1253
+ return {
1254
+ targets: [],
1255
+ resolvedTargetClientId: null,
1256
+ error: {
1257
+ code: 'AMBIGUOUS_TARGET',
1258
+ message: 'Multiple frontend pages are online for this channel; specify targetClientId or close extra pages'
1259
+ }
1260
+ };
1261
+ }
1262
+
853
1263
  function validateEditorClientsListPayload(payload) {
854
1264
  if (payload === undefined || payload === null) {
855
1265
  return {
@@ -884,16 +1294,11 @@ function validateEditorTargetPayload(payload, extras = {}) {
884
1294
  return { ok: false, code: 'INVALID_PAYLOAD', message: 'channel is required' };
885
1295
  }
886
1296
 
887
- const targetClientId = normalizeNonEmptyString(payload.targetClientId);
888
- if (!targetClientId) {
889
- return { ok: false, code: 'INVALID_TARGET', message: 'targetClientId is required' };
890
- }
891
-
892
1297
  return {
893
1298
  ok: true,
894
1299
  payload: {
895
1300
  channel,
896
- targetClientId,
1301
+ targetClientId: normalizeNonEmptyString(payload.targetClientId),
897
1302
  ...extras
898
1303
  }
899
1304
  };
@@ -1036,11 +1441,6 @@ function validateIntegrationContextUpdatePayload(payload) {
1036
1441
  return { ok: false, code: 'INVALID_PAYLOAD', message: 'channel is required' };
1037
1442
  }
1038
1443
 
1039
- const targetClientId = normalizeNonEmptyString(payload.targetClientId);
1040
- if (!targetClientId) {
1041
- return { ok: false, code: 'INVALID_TARGET', message: 'targetClientId is required' };
1042
- }
1043
-
1044
1444
  const mode = normalizeNonEmptyString(payload.mode) || 'replace';
1045
1445
  if (mode !== 'replace' && mode !== 'append') {
1046
1446
  return { ok: false, code: 'INVALID_PAYLOAD', message: 'mode must be replace or append' };
@@ -1054,7 +1454,7 @@ function validateIntegrationContextUpdatePayload(payload) {
1054
1454
  ok: true,
1055
1455
  payload: {
1056
1456
  channel,
1057
- targetClientId,
1457
+ targetClientId: normalizeNonEmptyString(payload.targetClientId),
1058
1458
  mode,
1059
1459
  context: payload.context
1060
1460
  }
@@ -1071,11 +1471,6 @@ function validateIntegrationPromptUpdatePayload(payload) {
1071
1471
  return { ok: false, code: 'INVALID_PAYLOAD', message: 'channel is required' };
1072
1472
  }
1073
1473
 
1074
- const targetClientId = normalizeNonEmptyString(payload.targetClientId);
1075
- if (!targetClientId) {
1076
- return { ok: false, code: 'INVALID_TARGET', message: 'targetClientId is required' };
1077
- }
1078
-
1079
1474
  if (typeof payload.prompt !== 'string') {
1080
1475
  return { ok: false, code: 'INVALID_PAYLOAD', message: 'prompt must be a string' };
1081
1476
  }
@@ -1084,7 +1479,7 @@ function validateIntegrationPromptUpdatePayload(payload) {
1084
1479
  ok: true,
1085
1480
  payload: {
1086
1481
  channel,
1087
- targetClientId,
1482
+ targetClientId: normalizeNonEmptyString(payload.targetClientId),
1088
1483
  prompt: payload.prompt,
1089
1484
  autoSend: payload.autoSend === true
1090
1485
  }
@@ -1104,8 +1499,18 @@ function forwardIntegrationMessage(ws, data, { validator, type, awaitResult = fa
1104
1499
  }
1105
1500
 
1106
1501
  const { channel, targetClientId } = validation.payload;
1107
- const targets = getTargetFrontendConnections(channel, targetClientId);
1108
- if (targets.length === 0) {
1502
+ const resolution = resolveForwardTargets(ws, { channel, targetClientId });
1503
+ if (resolution.error) {
1504
+ sendIntegrationError(ws, {
1505
+ requestId,
1506
+ code: resolution.error.code,
1507
+ message: resolution.error.message
1508
+ });
1509
+ return;
1510
+ }
1511
+
1512
+ const { targets, resolvedTargetClientId } = resolution;
1513
+ if (targets.length === 0 || !resolvedTargetClientId) {
1109
1514
  sendIntegrationError(ws, {
1110
1515
  requestId,
1111
1516
  code: 'FRONTEND_NOT_ONLINE',
@@ -1127,17 +1532,22 @@ function forwardIntegrationMessage(ws, data, { validator, type, awaitResult = fa
1127
1532
  }
1128
1533
  }
1129
1534
 
1535
+ const resolvedPayload = {
1536
+ ...validation.payload,
1537
+ targetClientId: resolvedTargetClientId
1538
+ };
1539
+
1130
1540
  storePendingIntegrationRequest(requestId, ws, {
1131
1541
  awaitResult,
1132
1542
  requestType: type,
1133
1543
  timeoutMs: data?.payload?.timeoutMs,
1134
- requestPayload: validation.payload
1544
+ requestPayload: resolvedPayload
1135
1545
  });
1136
1546
 
1137
1547
  const outgoingPayload = {
1138
1548
  type,
1139
1549
  requestId,
1140
- payload: validation.payload
1550
+ payload: resolvedPayload
1141
1551
  };
1142
1552
 
1143
1553
  for (const target of targets) {
@@ -1243,14 +1653,25 @@ class ExternalAgentWebSocketWriter {
1243
1653
  if (sessionId) {
1244
1654
  this.sessionId = sessionId;
1245
1655
  }
1656
+ const resolvedProvider = payload.provider || payload.event?.provider || this.provider;
1246
1657
 
1247
1658
  sendJson(this.ws, {
1248
1659
  type: 'agent.event',
1249
1660
  requestId: this.requestId,
1250
- provider: payload.provider || payload.event?.provider || this.provider,
1661
+ provider: resolvedProvider,
1251
1662
  sessionId,
1252
1663
  event: payload.event
1253
1664
  });
1665
+
1666
+ const activity = buildSessionActivityFromConversationEvent({
1667
+ requestId: this.requestId,
1668
+ provider: resolvedProvider,
1669
+ sessionId,
1670
+ event: payload.event
1671
+ });
1672
+ if (activity) {
1673
+ publishSessionActivity(activity);
1674
+ }
1254
1675
  }
1255
1676
  }
1256
1677
 
@@ -1353,6 +1774,8 @@ async function handleAgentRun(ws, request, data) {
1353
1774
  error: error.message,
1354
1775
  statusCode: error.statusCode || 500
1355
1776
  });
1777
+ } finally {
1778
+ clearAssistantActivityBuffers(requestId);
1356
1779
  }
1357
1780
  }
1358
1781
 
@@ -1557,6 +1980,16 @@ export function handleExternalAgentWebSocketConnection(ws, request) {
1557
1980
  return;
1558
1981
  }
1559
1982
 
1983
+ if (data?.type === 'session.activity.subscribe') {
1984
+ handleSessionActivitySubscribe(ws, data);
1985
+ return;
1986
+ }
1987
+
1988
+ if (data?.type === 'session.activity.unsubscribe') {
1989
+ handleSessionActivityUnsubscribe(ws, data);
1990
+ return;
1991
+ }
1992
+
1560
1993
  if (data?.type === 'agent.abort') {
1561
1994
  void handleAgentAbort(ws, data);
1562
1995
  return;
@@ -1581,6 +2014,26 @@ export function handleExternalAgentWebSocketConnection(ws, request) {
1581
2014
  });
1582
2015
 
1583
2016
  ws.on('close', () => {
2017
+ removeSessionActivitySubscriptionsForWs(ws);
1584
2018
  unregisterIntegrationConnection(ws);
1585
2019
  });
1586
2020
  }
2021
+
2022
+ export function __buildSessionActivityFromConversationEventForTest(input) {
2023
+ return buildSessionActivityFromConversationEvent(input);
2024
+ }
2025
+
2026
+ export function __publishSessionActivityForTest(item) {
2027
+ publishSessionActivity(item);
2028
+ }
2029
+
2030
+ export function __resetExternalAgentWsTestState() {
2031
+ integrationConnections.clear();
2032
+ pendingIntegrationRequests.clear();
2033
+ pendingIntegrationPings.clear();
2034
+ sessionStateSubscriptionsByWs.clear();
2035
+ sessionStateSubscribersByKey.clear();
2036
+ sessionActivitySubscriptionsByWs.clear();
2037
+ sessionActivitySubscribersByKey.clear();
2038
+ assistantActivityBuffersByRequestId.clear();
2039
+ }