@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.
- package/dist/api-docs.html +2 -2
- package/dist/assets/App-CYCCsgwf.js +264 -0
- package/dist/assets/{ReviewApp-C9K--AQE.js → ReviewApp-0srHIXwb.js} +1 -1
- package/dist/assets/{_basePickBy-DR_8uFCo.js → _basePickBy-DVVb07UV.js} +1 -1
- package/dist/assets/{_baseUniq-D0njlQ_7.js → _baseUniq-BtbziL5G.js} +1 -1
- package/dist/assets/{arc-CKlr_Rec.js → arc-BsCC8yBD.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-BmO_uLUH.js → architectureDiagram-2XIMDMQ5-woFp6eNI.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-DhAeO-56.js → blockDiagram-WCTKOSBZ-ya8VAc2k.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-C67kFoXx.js → c4Diagram-IC4MRINW-CY1dZmIZ.js} +1 -1
- package/dist/assets/channel-BMhScXFe.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-mLLagvJi.js → chunk-4BX2VUAB-CR1lAd74.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Lx-hOjlM.js → chunk-55IACEB6-CP98WcFC.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-Bt-XmVUV.js → chunk-FMBD7UC4-D9c7ijAB.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-Cya6gaDV.js → chunk-JSJVCQXG-DQAGYOn-.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-Bd7Ig6tF.js → chunk-KX2RTZJC-BbTXiDq7.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-5UAE0Vg-.js → chunk-NQ4KR5QH-BI6AX0dr.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BAxZ8m7w.js → chunk-QZHKN3VN-DB3V2Ifo.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-DjDPvUUP.js → chunk-WL4C6EOR-DhzTthv6.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +1 -0
- package/dist/assets/clone-BPqOt4r3.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-D-60XrkJ.js → cose-bilkent-S5V4N54A-BQ09ZE2j.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-bqu3ZS4K.js → dagre-KLK3FWXG-Dc2ueD_R.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-BueeqoYm.js → diagram-E7M64L7V-DP-LsQoL.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-D4fDv2E7.js → diagram-IFDJBPK2-Cg6r42cB.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-WqipY3fN.js → diagram-P4PSJMXO-aHsfoUZE.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-D0oVnO-x.js → erDiagram-INFDFZHY-qBXJ4aAz.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-DzbGyxrr.js → flowDiagram-PKNHOUZH-D_13emJM.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-BwhbbgCP.js → ganttDiagram-A5KZAMGK-BvIcOLwz.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js → gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js} +1 -1
- package/dist/assets/{graph-DzKos-N0.js → graph-CeJCMjan.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-CKDMgz3X.js → highlighted-body-TPN3WLV5-B_novwSz.js} +1 -1
- package/dist/assets/index-C514cLyb.js +2 -0
- package/dist/assets/index-h1DBl_g3.css +1 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-BFicZbTf.js → infoDiagram-LFFYTUFH-lOxAqb3m.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-CtihxDxl.js → ishikawaDiagram-PHBUUO56-DIr-51gj.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-Du00J8_d.js → journeyDiagram-4ABVD52K-CYcIW0ZU.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-BJi9S0iQ.js → kanban-definition-K7BYSVSG-C1ZK616a.js} +1 -1
- package/dist/assets/{layout-B80Sityu.js → layout-CI2RM-v6.js} +1 -1
- package/dist/assets/{linear-sRQLOf5H.js → linear-DE7bISck.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-CBuVs4eJ.js → mermaid-O7DHMXV3-XxAJo8EK.js} +6 -6
- package/dist/assets/{mindmap-definition-YRQLILUH-C5IL_xi-.js → mindmap-definition-YRQLILUH-Dz6EFjmn.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-CeTwlJ8z.js → pieDiagram-SKSYHLDU-DPpEzUed.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-COfUcLWt.js → quadrantDiagram-337W2JSQ-xdoXNet7.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-DSb-CJ5B.js → requirementDiagram-Z7DCOOCP-DUq8H3CL.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-8jtuVb45.js → sankeyDiagram-WA2Y5GQK-CmqEUxRu.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-C2VpkMwA.js → sequenceDiagram-2WXFIKYE-DhtXRNiH.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-fmwMqxxc.js → stateDiagram-RAJIS63D-Dj0HOlbN.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-Dx1hP5lg.js → timeline-definition-YZTLITO2-DUuJzZB5.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-CkLOdYCZ.js → treemap-KZPCXAKY-DpYBQ0qr.js} +1 -1
- package/dist/assets/vendor-codemirror-CMHSJ_9p.js +9 -0
- package/dist/assets/{vennDiagram-LZ73GAT5-D6KWcnln.js → vennDiagram-LZ73GAT5-DpePUyOd.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-6fh6qmzN.js → xychartDiagram-JWTSCODW-Cfp1I4_U.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +6 -5
- package/server/acp-runtime/client.js +120 -14
- package/server/acp-runtime/index.js +54 -0
- package/server/acp-runtime/registry.js +2 -2
- package/server/acp-runtime/session-store.js +75 -1
- package/server/cli.js +32 -8
- package/server/database/db.js +20 -0
- package/server/external-agent/ws.js +477 -24
- package/server/index.js +78 -146
- package/server/lan-access/core.js +79 -0
- package/server/lan-access/state.js +102 -0
- package/server/middleware/auth.js +57 -14
- package/server/projects.js +423 -535
- package/server/routes/auth.js +24 -4
- package/server/routes/cli-auth.js +21 -25
- package/server/routes/codex.js +84 -298
- package/server/routes/commands.js +322 -407
- package/server/routes/lan-access.js +231 -0
- package/server/routes/projects.js +154 -158
- package/server/routes/session-core.js +13 -7
- package/server/routes/settings.js +113 -99
- package/server/session-core/eventStore.js +15 -2
- package/server/session-core/providerAdapters.js +28 -28
- package/server/session-core/sessionListMerge.js +47 -0
- package/shared/conversationEvents.js +96 -1
- package/shared/modelConstants.js +79 -99
- package/dist/assets/App-GBcTeeUS.js +0 -460
- package/dist/assets/channel-V3MBjKys.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +0 -1
- package/dist/assets/clone-BbMGfZwt.js +0 -1
- package/dist/assets/index-DiQlHzGj.js +0 -2
- package/dist/assets/index-Drat2nB9.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +0 -1
- package/dist/assets/vendor-codemirror-BxPY6emf.js +0 -39
- package/server/routes/git.js +0 -1110
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -536
- package/server/routes/taskmaster.js +0 -1963
- package/server/utils/mcp-detector.js +0 -198
- 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 {
|
|
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
|
|
1108
|
-
if (
|
|
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:
|
|
1544
|
+
requestPayload: resolvedPayload
|
|
1135
1545
|
});
|
|
1136
1546
|
|
|
1137
1547
|
const outgoingPayload = {
|
|
1138
1548
|
type,
|
|
1139
1549
|
requestId,
|
|
1140
|
-
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:
|
|
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
|
+
}
|