@microsoft/m365agentsplayground-cli 0.2.26 → 0.2.27-alpha.20260518-462fbee.0
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/README.md +8 -21
- package/{build → dist}/conversationTypes.d.ts +5 -0
- package/{build → dist}/index.d.ts +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.LICENSE.txt +157 -0
- package/dist/responseCapture.d.ts +42 -0
- package/{build → dist}/serverManager.d.ts +1 -2
- package/dist/start-server.js +9 -0
- package/dist/start-server.js.LICENSE.txt +157 -0
- package/{build → dist}/testClient.d.ts +1 -1
- package/{build → dist}/types.d.ts +9 -0
- package/package.json +22 -12
- package/build/cardValidator.d.ts.map +0 -1
- package/build/cardValidator.js +0 -46
- package/build/conversationServer.d.ts.map +0 -1
- package/build/conversationServer.js +0 -136
- package/build/conversationTypes.d.ts.map +0 -1
- package/build/conversationTypes.js +0 -5
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -25
- package/build/notificationSender.d.ts.map +0 -1
- package/build/notificationSender.js +0 -83
- package/build/responseCapture.d.ts +0 -29
- package/build/responseCapture.d.ts.map +0 -1
- package/build/responseCapture.js +0 -119
- package/build/runConversation.d.ts.map +0 -1
- package/build/runConversation.js +0 -351
- package/build/serverManager.d.ts.map +0 -1
- package/build/serverManager.js +0 -149
- package/build/start-server.d.ts.map +0 -1
- package/build/start-server.js +0 -23
- package/build/testClient.d.ts.map +0 -1
- package/build/testClient.js +0 -434
- package/build/types.d.ts.map +0 -1
- package/build/types.js +0 -7
- package/build/websocketClient.d.ts.map +0 -1
- package/build/websocketClient.js +0 -129
- package/src/cardValidator.ts +0 -56
- package/src/conversationServer.ts +0 -147
- package/src/conversationTypes.ts +0 -157
- package/src/index.ts +0 -37
- package/src/notificationSender.ts +0 -103
- package/src/responseCapture.ts +0 -145
- package/src/runConversation.ts +0 -382
- package/src/serverManager.ts +0 -172
- package/src/start-server.ts +0 -26
- package/src/testClient.ts +0 -512
- package/src/types.ts +0 -155
- package/src/websocketClient.ts +0 -153
- package/tsconfig.json +0 -14
- /package/{build → dist}/cardValidator.d.ts +0 -0
- /package/{build → dist}/conversationServer.d.ts +0 -0
- /package/{build → dist}/notificationSender.d.ts +0 -0
- /package/{build → dist}/runConversation.d.ts +0 -0
- /package/{build → dist}/start-server.d.ts +0 -0
- /package/{build → dist}/websocketClient.d.ts +0 -0
package/build/testClient.js
DELETED
|
@@ -1,434 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TestClient = void 0;
|
|
4
|
-
const events_1 = require("events");
|
|
5
|
-
const schema_1 = require("schema");
|
|
6
|
-
const server_1 = require("server");
|
|
7
|
-
const serverManager_1 = require("./serverManager");
|
|
8
|
-
const websocketClient_1 = require("./websocketClient");
|
|
9
|
-
/**
|
|
10
|
-
* Test client for sending messages to a bot and receiving responses.
|
|
11
|
-
* Designed for testing multi-turn conversations.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* const client = new TestClient({
|
|
16
|
-
* botEndpoint: 'http://localhost:3978/api/messages',
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* await client.start();
|
|
20
|
-
*
|
|
21
|
-
* // Multi-turn conversation
|
|
22
|
-
* const [r1] = await client.sendMessage('Hello');
|
|
23
|
-
* expect(r1.text).to.include('Hi');
|
|
24
|
-
*
|
|
25
|
-
* const [r2] = await client.sendMessage('Book a flight');
|
|
26
|
-
* expect(r2.text).to.include('Where');
|
|
27
|
-
*
|
|
28
|
-
* await client.stop();
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```typescript
|
|
33
|
-
* // With WebSocket events
|
|
34
|
-
* const client = new TestClient({
|
|
35
|
-
* botEndpoint: 'http://localhost:3978/api/messages',
|
|
36
|
-
* });
|
|
37
|
-
*
|
|
38
|
-
* client.on('message:created', (event) => {
|
|
39
|
-
* console.log('New message:', event.message.text);
|
|
40
|
-
* });
|
|
41
|
-
*
|
|
42
|
-
* client.on('typing', (event) => {
|
|
43
|
-
* console.log('Bot is typing...');
|
|
44
|
-
* });
|
|
45
|
-
*
|
|
46
|
-
* await client.start();
|
|
47
|
-
* await client.sendMessage('Hello');
|
|
48
|
-
* await client.stop();
|
|
49
|
-
* ```
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```typescript
|
|
53
|
-
* // With Log WebSocket events
|
|
54
|
-
* const client = new TestClient({
|
|
55
|
-
* botEndpoint: 'http://localhost:3978/api/messages',
|
|
56
|
-
* });
|
|
57
|
-
*
|
|
58
|
-
* client.on('log:append', (event) => {
|
|
59
|
-
* console.log('Log entry:', event.logItem);
|
|
60
|
-
* });
|
|
61
|
-
*
|
|
62
|
-
* await client.start();
|
|
63
|
-
* await client.sendMessage('Hello');
|
|
64
|
-
* await client.stop();
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
class TestClient extends events_1.EventEmitter {
|
|
68
|
-
/** The placeholder text teams-ai sends as the first streaming activity */
|
|
69
|
-
static STREAMING_PLACEHOLDER = "Loading stream results...";
|
|
70
|
-
serverManager;
|
|
71
|
-
responseCapture;
|
|
72
|
-
wsClient = null;
|
|
73
|
-
logWsClient = null;
|
|
74
|
-
config;
|
|
75
|
-
conversationId = "";
|
|
76
|
-
userId = "";
|
|
77
|
-
started = false;
|
|
78
|
-
constructor(config) {
|
|
79
|
-
super();
|
|
80
|
-
this.config = {
|
|
81
|
-
timeout: 5000,
|
|
82
|
-
...config,
|
|
83
|
-
};
|
|
84
|
-
this.serverManager = new serverManager_1.ServerManager();
|
|
85
|
-
this.responseCapture = serverManager_1.ServerManager.getResponseCapture();
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Start the test client and server
|
|
89
|
-
*/
|
|
90
|
-
async start() {
|
|
91
|
-
if (this.started) {
|
|
92
|
-
throw new Error("TestClient is already started");
|
|
93
|
-
}
|
|
94
|
-
await this.serverManager.start(this.config);
|
|
95
|
-
// Create a unique conversation for this client
|
|
96
|
-
this.registerUniqueConversation();
|
|
97
|
-
// Connect to WebSocket for real-time events
|
|
98
|
-
this.wsClient = new websocketClient_1.WebSocketClient({
|
|
99
|
-
port: this.serverManager.port,
|
|
100
|
-
onMessage: (action) => {
|
|
101
|
-
switch (action.action) {
|
|
102
|
-
case schema_1.ActionType.CreateMessage:
|
|
103
|
-
this.emit("message:created", action);
|
|
104
|
-
break;
|
|
105
|
-
case schema_1.ActionType.UpdateMessage:
|
|
106
|
-
this.emit("message:updated", action);
|
|
107
|
-
break;
|
|
108
|
-
case schema_1.ActionType.Typing:
|
|
109
|
-
this.emit("typing", action);
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
onError: (err) => {
|
|
114
|
-
this.emit("websocket:error", err);
|
|
115
|
-
},
|
|
116
|
-
onClose: () => {
|
|
117
|
-
this.emit("websocket:close");
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
await this.wsClient.connect();
|
|
121
|
-
// Connect to Log WebSocket for log events
|
|
122
|
-
this.logWsClient = new websocketClient_1.LogWebSocketClient({
|
|
123
|
-
port: this.serverManager.port,
|
|
124
|
-
onLogMessage: (action) => {
|
|
125
|
-
if (action.logAction === schema_1.LogActionType.AppendLog) {
|
|
126
|
-
this.emit("log:append", action);
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
onError: (err) => {
|
|
130
|
-
this.emit("log:websocket:error", err);
|
|
131
|
-
},
|
|
132
|
-
onClose: () => {
|
|
133
|
-
this.emit("log:websocket:close");
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
await this.logWsClient.connect();
|
|
137
|
-
this.started = true;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Stop the test client and server
|
|
141
|
-
*/
|
|
142
|
-
async stop() {
|
|
143
|
-
if (!this.started) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
// Close WebSocket connections
|
|
147
|
-
if (this.wsClient) {
|
|
148
|
-
this.wsClient.close();
|
|
149
|
-
this.wsClient = null;
|
|
150
|
-
}
|
|
151
|
-
if (this.logWsClient) {
|
|
152
|
-
this.logWsClient.close();
|
|
153
|
-
this.logWsClient = null;
|
|
154
|
-
}
|
|
155
|
-
this.responseCapture.clearConversation(this.conversationId);
|
|
156
|
-
await this.serverManager.stop();
|
|
157
|
-
this.started = false;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Send a message to the bot and wait for response(s)
|
|
161
|
-
*
|
|
162
|
-
* @param text The message text to send
|
|
163
|
-
* @returns Array of bot responses
|
|
164
|
-
*/
|
|
165
|
-
async sendMessage(text) {
|
|
166
|
-
if (!this.started) {
|
|
167
|
-
throw new Error("TestClient is not started. Call start() first.");
|
|
168
|
-
}
|
|
169
|
-
const conversationManager = server_1.Factory.getConversationManager();
|
|
170
|
-
const messageService = server_1.Factory.getMessageService();
|
|
171
|
-
// Get the conversation
|
|
172
|
-
const conversation = conversationManager.getConversation(this.conversationId);
|
|
173
|
-
// Track message updates received via WebSocket (for streaming bots).
|
|
174
|
-
const messageUpdates = new Map(); // messageId → latest text
|
|
175
|
-
let streamFinalReceived = false; // set when streamType:"final" arrives
|
|
176
|
-
let lastUpdateTime = Date.now(); // epoch ms of last update (or conversation start)
|
|
177
|
-
const updateListener = (action) => {
|
|
178
|
-
const id = action.message?.messageId;
|
|
179
|
-
const msg = action.message;
|
|
180
|
-
if (id && msg.text !== undefined) {
|
|
181
|
-
messageUpdates.set(id, msg.text);
|
|
182
|
-
lastUpdateTime = Date.now();
|
|
183
|
-
if (msg.streamEntity?.streamType === "final") {
|
|
184
|
-
streamFinalReceived = true;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
this.on("message:updated", updateListener);
|
|
189
|
-
// Create the message request
|
|
190
|
-
const messagePayload = {
|
|
191
|
-
text,
|
|
192
|
-
textFormat: "plain",
|
|
193
|
-
from: { id: this.userId },
|
|
194
|
-
};
|
|
195
|
-
// Start waiting for response before sending (to avoid race conditions)
|
|
196
|
-
const responsePromise = this.responseCapture.waitForResponse(this.conversationId, conversationManager, this.config.timeout ?? 5000);
|
|
197
|
-
// Send the message
|
|
198
|
-
const { afterAll } = messageService.createMessage(conversation, messagePayload);
|
|
199
|
-
// Execute the async send
|
|
200
|
-
if (afterAll) {
|
|
201
|
-
afterAll();
|
|
202
|
-
}
|
|
203
|
-
// Wait for the first bot response (may be a streaming placeholder)
|
|
204
|
-
await responsePromise;
|
|
205
|
-
// Get the initial bot messages to check for streaming placeholder
|
|
206
|
-
const allMessages = conversationManager.listMessages(this.conversationId);
|
|
207
|
-
const lastUserIdx = allMessages.reduce((lastIdx, msg, idx) => (msg.createdBy === "client" ? idx : lastIdx), -1);
|
|
208
|
-
const initialBotMessages = allMessages
|
|
209
|
-
.slice(lastUserIdx + 1)
|
|
210
|
-
.filter((m) => m.createdBy === "bot");
|
|
211
|
-
const isStreamingPlaceholder = initialBotMessages.some((m) => m.content.text === TestClient.STREAMING_PLACEHOLDER);
|
|
212
|
-
if (isStreamingPlaceholder) {
|
|
213
|
-
// Streaming bot detected.
|
|
214
|
-
// Primary signal: wait for streamType:"final" WS event (teams-ai / teams.ts SDK).
|
|
215
|
-
// Fallback: quiet-period in case the bot doesn't send streamType at all.
|
|
216
|
-
const quietPeriodMs = this.config.streamingSettleDelayMs ?? 800;
|
|
217
|
-
const maxWaitMs = this.config.timeout ?? 30000;
|
|
218
|
-
await this.waitForStreamFinal(() => streamFinalReceived, () => lastUpdateTime, quietPeriodMs, maxWaitMs);
|
|
219
|
-
}
|
|
220
|
-
this.off("message:updated", updateListener);
|
|
221
|
-
// Re-read messages after any streaming updates have landed
|
|
222
|
-
const messages = conversationManager.listMessages(this.conversationId);
|
|
223
|
-
const lastUserMessageIndex = messages.reduce((lastIndex, msg, index) => {
|
|
224
|
-
if (msg.createdBy === "client") {
|
|
225
|
-
return index;
|
|
226
|
-
}
|
|
227
|
-
return lastIndex;
|
|
228
|
-
}, -1);
|
|
229
|
-
const recentBotMessages = messages
|
|
230
|
-
.slice(lastUserMessageIndex + 1)
|
|
231
|
-
.filter((m) => m.createdBy === "bot");
|
|
232
|
-
return recentBotMessages.map((msg) => {
|
|
233
|
-
const response = this.toBotResponse(msg);
|
|
234
|
-
// If this message was updated via streaming, use the final streamed text
|
|
235
|
-
const streamedText = messageUpdates.get(response.messageId);
|
|
236
|
-
if (streamedText !== undefined) {
|
|
237
|
-
response.text = streamedText;
|
|
238
|
-
}
|
|
239
|
-
return response;
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Wait for streaming to complete.
|
|
244
|
-
*
|
|
245
|
-
* Primary: resolves immediately when `streamType:"final"` WS event arrives.
|
|
246
|
-
* Fallback: resolves after `quietPeriodMs` of no new updateActivity events,
|
|
247
|
-
* for bots that don't send streamType (e.g. non-teams-ai streaming bots).
|
|
248
|
-
* Hard cap: always resolves after `maxWaitMs`.
|
|
249
|
-
*/
|
|
250
|
-
async waitForStreamFinal(isFinalReceived, getLastUpdateTime, quietPeriodMs, maxWaitMs) {
|
|
251
|
-
return new Promise((resolve) => {
|
|
252
|
-
const deadline = Date.now() + maxWaitMs;
|
|
253
|
-
const poll = () => {
|
|
254
|
-
// Primary signal: streamType:"final" received
|
|
255
|
-
if (isFinalReceived()) {
|
|
256
|
-
resolve();
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const now = Date.now();
|
|
260
|
-
if (now >= deadline) {
|
|
261
|
-
resolve();
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const lastUpdate = getLastUpdateTime();
|
|
265
|
-
// Fallback: quiet period after last update
|
|
266
|
-
if (lastUpdate > 0 && now - lastUpdate >= quietPeriodMs) {
|
|
267
|
-
resolve();
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
setTimeout(poll, 50);
|
|
271
|
-
};
|
|
272
|
-
setTimeout(poll, 50);
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Start a new conversation (resets conversation state)
|
|
277
|
-
*/
|
|
278
|
-
newConversation() {
|
|
279
|
-
if (!this.started) {
|
|
280
|
-
throw new Error("TestClient is not started. Call start() first.");
|
|
281
|
-
}
|
|
282
|
-
// Clear pending responses for the old conversation
|
|
283
|
-
this.responseCapture.clearConversation(this.conversationId);
|
|
284
|
-
// Create a fresh unique conversation
|
|
285
|
-
this.registerUniqueConversation();
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Get all messages in the current conversation
|
|
289
|
-
*/
|
|
290
|
-
getMessages() {
|
|
291
|
-
if (!this.started) {
|
|
292
|
-
throw new Error("TestClient is not started. Call start() first.");
|
|
293
|
-
}
|
|
294
|
-
const conversationManager = server_1.Factory.getConversationManager();
|
|
295
|
-
return conversationManager.listMessages(this.conversationId);
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Get the last bot message in the current conversation
|
|
299
|
-
*/
|
|
300
|
-
getLastBotMessage() {
|
|
301
|
-
const messages = this.getMessages();
|
|
302
|
-
const botMessages = messages.filter((m) => m.createdBy === "bot");
|
|
303
|
-
return botMessages[botMessages.length - 1];
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Get the current conversation ID
|
|
307
|
-
*/
|
|
308
|
-
getConversationId() {
|
|
309
|
-
return this.conversationId;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Get the port the test server is running on
|
|
313
|
-
*/
|
|
314
|
-
getPort() {
|
|
315
|
-
return this.serverManager.port;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Simulate clicking an Adaptive Card button (Action.Execute).
|
|
319
|
-
*
|
|
320
|
-
* @param messageId The message ID of the card message (from BotResponse.messageId)
|
|
321
|
-
* @param verb The Action.Execute verb defined on the button
|
|
322
|
-
* @param data Optional extra data to merge with the button's own data
|
|
323
|
-
* @returns The invoke response body from the bot
|
|
324
|
-
*/
|
|
325
|
-
async clickCardButton(messageId, verb, data = {}) {
|
|
326
|
-
if (!this.started) {
|
|
327
|
-
throw new Error("TestClient is not started. Call start() first.");
|
|
328
|
-
}
|
|
329
|
-
const port = this.serverManager.port;
|
|
330
|
-
const meId = server_1.Factory.getTenantManager().getMe().id;
|
|
331
|
-
const body = {
|
|
332
|
-
name: "adaptiveCard/action",
|
|
333
|
-
conversation: { id: this.conversationId },
|
|
334
|
-
from: { id: meId },
|
|
335
|
-
replyToId: messageId,
|
|
336
|
-
value: {
|
|
337
|
-
action: {
|
|
338
|
-
type: "Action.Execute",
|
|
339
|
-
verb,
|
|
340
|
-
data,
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
};
|
|
344
|
-
const res = await fetch(`http://localhost:${port}/_conversation/v1/invoke`, {
|
|
345
|
-
method: "POST",
|
|
346
|
-
headers: { "Content-Type": "application/json" },
|
|
347
|
-
body: JSON.stringify(body),
|
|
348
|
-
});
|
|
349
|
-
if (!res.ok) {
|
|
350
|
-
throw new Error(`Card action invoke failed: HTTP ${res.status}`);
|
|
351
|
-
}
|
|
352
|
-
return res.json();
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Simulate submitting an Adaptive Card form (Action.Submit / legacy bots).
|
|
356
|
-
*
|
|
357
|
-
* @param messageId The message ID of the card message
|
|
358
|
-
* @param data The form data to submit
|
|
359
|
-
*/
|
|
360
|
-
async submitCardForm(messageId, data) {
|
|
361
|
-
if (!this.started) {
|
|
362
|
-
throw new Error("TestClient is not started. Call start() first.");
|
|
363
|
-
}
|
|
364
|
-
const port = this.serverManager.port;
|
|
365
|
-
const meId = server_1.Factory.getTenantManager().getMe().id;
|
|
366
|
-
const body = {
|
|
367
|
-
name: "message/submitAction",
|
|
368
|
-
conversation: { id: this.conversationId },
|
|
369
|
-
from: { id: meId },
|
|
370
|
-
replyToId: messageId,
|
|
371
|
-
value: data,
|
|
372
|
-
};
|
|
373
|
-
const res = await fetch(`http://localhost:${port}/_conversation/v1/invoke`, {
|
|
374
|
-
method: "POST",
|
|
375
|
-
headers: { "Content-Type": "application/json" },
|
|
376
|
-
body: JSON.stringify(body),
|
|
377
|
-
});
|
|
378
|
-
if (!res.ok) {
|
|
379
|
-
throw new Error(`Card form submit failed: HTTP ${res.status}`);
|
|
380
|
-
}
|
|
381
|
-
return res.json();
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Create a unique conversation for this client based on chatType config.
|
|
385
|
-
* - personal: creates a new personal chat with a unique userId
|
|
386
|
-
* - group: creates a new group chat
|
|
387
|
-
* - channel: uses the team's general channel
|
|
388
|
-
*/
|
|
389
|
-
registerUniqueConversation() {
|
|
390
|
-
const chatType = this.config.chatType ?? "personal";
|
|
391
|
-
const chatManager = server_1.Factory.getChatManager();
|
|
392
|
-
const conversationManager = server_1.Factory.getConversationManager();
|
|
393
|
-
if (chatType === "group") {
|
|
394
|
-
const chat = chatManager.createGroupChat(`test-group-${Date.now()}`);
|
|
395
|
-
const conversation = conversationManager.createConversation(chat);
|
|
396
|
-
this.conversationId = conversation.id;
|
|
397
|
-
this.userId = server_1.Factory.getTenantManager().getMe().id;
|
|
398
|
-
}
|
|
399
|
-
else if (chatType === "channel") {
|
|
400
|
-
const teamManager = server_1.Factory.getTeamManager();
|
|
401
|
-
const channel = teamManager.getGeneralChannel();
|
|
402
|
-
const conversation = conversationManager.createConversation(channel);
|
|
403
|
-
this.conversationId = conversation.id;
|
|
404
|
-
this.userId = server_1.Factory.getTenantManager().getMe().id;
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
this.userId = `test-user-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
408
|
-
const chat = chatManager.createPersonalChat(this.userId, "Test Chat");
|
|
409
|
-
const conversation = conversationManager.createConversation(chat);
|
|
410
|
-
this.conversationId = conversation.id;
|
|
411
|
-
// Replace the fake userId in the chat's user set with the real "me" user so that:
|
|
412
|
-
// 1. isOtherUserChat() returns false → WebSocket events are broadcast to this client
|
|
413
|
-
// 2. getChatUsers() doesn't throw when looking up the fake userId in the tenant
|
|
414
|
-
// Using 'as any' is intentional — ChatManager has no public addUserToChat API and
|
|
415
|
-
// this is simulator-only setup code, not production logic.
|
|
416
|
-
const meId = server_1.Factory.getTenantManager().getMe().id;
|
|
417
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
418
|
-
chatManager._chatUsers.set(chat.id, new Set([meId]));
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Convert a Message to a BotResponse
|
|
423
|
-
*/
|
|
424
|
-
toBotResponse(message) {
|
|
425
|
-
return {
|
|
426
|
-
messageId: message.content.id,
|
|
427
|
-
text: message.content.text,
|
|
428
|
-
attachments: message.content.attachments,
|
|
429
|
-
timestamp: message.content.timestamp.getTime(),
|
|
430
|
-
raw: message,
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
exports.TestClient = TestClient;
|
package/build/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGzC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACnD,YAAY,EACV,OAAO,EACP,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,cAAc,EAEd,UAAU,EACV,gBAAgB,EAChB,OAAO,GACR,MAAM,QAAQ,CAAC;AAEhB;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,GAAG,CAAC,EAAE,SAAS,CAAC;IAEhB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;IAE5C;;;;OAIG;IACH,YAAY,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IAE3C;;;;;;;;;;;;;;;;;OAiBG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAE3B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,GAAG,EAAE,OAAO,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACvC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;CAC5B"}
|
package/build/types.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogActionType = exports.ActionType = void 0;
|
|
4
|
-
// Re-export action types for consumers
|
|
5
|
-
var schema_1 = require("schema");
|
|
6
|
-
Object.defineProperty(exports, "ActionType", { enumerable: true, get: function () { return schema_1.ActionType; } });
|
|
7
|
-
Object.defineProperty(exports, "LogActionType", { enumerable: true, get: function () { return schema_1.LogActionType; } });
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"websocketClient.d.ts","sourceRoot":"","sources":["../src/websocketClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAK7C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAyB;gBAE5B,OAAO,EAAE,sBAAsB;IAI3C;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCxB;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,WAAW,IAAI,OAAO;CAGvB;AAED;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAA4B;gBAE/B,OAAO,EAAE,yBAAyB;IAI9C;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCxB;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,WAAW,IAAI,OAAO;CAGvB"}
|
package/build/websocketClient.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.LogWebSocketClient = exports.WebSocketClient = void 0;
|
|
7
|
-
const ws_1 = __importDefault(require("ws"));
|
|
8
|
-
const CONVERSATION_WS_PATH = "/_ws_conversation/v1/conversations/stream";
|
|
9
|
-
const LOG_WS_PATH = "/_ws/log/stream";
|
|
10
|
-
/**
|
|
11
|
-
* WebSocket client for connecting to the conversation WebSocket endpoint.
|
|
12
|
-
* Receives real-time message and typing events from the server.
|
|
13
|
-
*/
|
|
14
|
-
class WebSocketClient {
|
|
15
|
-
ws = null;
|
|
16
|
-
options;
|
|
17
|
-
constructor(options) {
|
|
18
|
-
this.options = options;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Connect to the WebSocket server
|
|
22
|
-
*/
|
|
23
|
-
connect() {
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
const url = `ws://localhost:${this.options.port}${CONVERSATION_WS_PATH}`;
|
|
26
|
-
this.ws = new ws_1.default(url);
|
|
27
|
-
this.ws.on("open", () => {
|
|
28
|
-
// Small delay to ensure server has processed the connection
|
|
29
|
-
setTimeout(resolve, 50);
|
|
30
|
-
});
|
|
31
|
-
this.ws.on("message", (data) => {
|
|
32
|
-
try {
|
|
33
|
-
const message = JSON.parse(data.toString());
|
|
34
|
-
this.options.onMessage(message);
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
console.error("Failed to parse WebSocket message:", err);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
this.ws.on("error", (err) => {
|
|
41
|
-
if (this.options.onError) {
|
|
42
|
-
this.options.onError(err);
|
|
43
|
-
}
|
|
44
|
-
reject(err);
|
|
45
|
-
});
|
|
46
|
-
this.ws.on("close", () => {
|
|
47
|
-
if (this.options.onClose) {
|
|
48
|
-
this.options.onClose();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Close the WebSocket connection
|
|
55
|
-
*/
|
|
56
|
-
close() {
|
|
57
|
-
if (this.ws) {
|
|
58
|
-
this.ws.close();
|
|
59
|
-
this.ws = null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Check if the WebSocket is connected
|
|
64
|
-
*/
|
|
65
|
-
isConnected() {
|
|
66
|
-
return this.ws !== null && this.ws.readyState === ws_1.default.OPEN;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
exports.WebSocketClient = WebSocketClient;
|
|
70
|
-
/**
|
|
71
|
-
* WebSocket client for connecting to the log WebSocket endpoint.
|
|
72
|
-
* Receives real-time log events from the server.
|
|
73
|
-
*/
|
|
74
|
-
class LogWebSocketClient {
|
|
75
|
-
ws = null;
|
|
76
|
-
options;
|
|
77
|
-
constructor(options) {
|
|
78
|
-
this.options = options;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Connect to the log WebSocket server
|
|
82
|
-
*/
|
|
83
|
-
connect() {
|
|
84
|
-
return new Promise((resolve, reject) => {
|
|
85
|
-
const url = `ws://localhost:${this.options.port}${LOG_WS_PATH}`;
|
|
86
|
-
this.ws = new ws_1.default(url);
|
|
87
|
-
this.ws.on("open", () => {
|
|
88
|
-
// Small delay to ensure server has processed the connection
|
|
89
|
-
setTimeout(resolve, 50);
|
|
90
|
-
});
|
|
91
|
-
this.ws.on("message", (data) => {
|
|
92
|
-
try {
|
|
93
|
-
const message = JSON.parse(data.toString());
|
|
94
|
-
this.options.onLogMessage(message);
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
console.error("Failed to parse log WebSocket message:", err);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
this.ws.on("error", (err) => {
|
|
101
|
-
if (this.options.onError) {
|
|
102
|
-
this.options.onError(err);
|
|
103
|
-
}
|
|
104
|
-
reject(err);
|
|
105
|
-
});
|
|
106
|
-
this.ws.on("close", () => {
|
|
107
|
-
if (this.options.onClose) {
|
|
108
|
-
this.options.onClose();
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Close the WebSocket connection
|
|
115
|
-
*/
|
|
116
|
-
close() {
|
|
117
|
-
if (this.ws) {
|
|
118
|
-
this.ws.close();
|
|
119
|
-
this.ws = null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Check if the WebSocket is connected
|
|
124
|
-
*/
|
|
125
|
-
isConnected() {
|
|
126
|
-
return this.ws !== null && this.ws.readyState === ws_1.default.OPEN;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
exports.LogWebSocketClient = LogWebSocketClient;
|
package/src/cardValidator.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adaptive Card schema validation using the official `adaptivecards` package.
|
|
3
|
-
*
|
|
4
|
-
* Validates that a card payload is a well-formed Adaptive Card that Teams can
|
|
5
|
-
* parse. Does NOT check visual rendering — only structural/schema correctness.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { AdaptiveCard, SerializationContext } from "adaptivecards";
|
|
9
|
-
|
|
10
|
-
export interface CardValidationError {
|
|
11
|
-
/** Human-readable description of the issue */
|
|
12
|
-
message: string;
|
|
13
|
-
/** "parsing" | "schema" */
|
|
14
|
-
phase: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Validate a raw Adaptive Card JSON payload.
|
|
19
|
-
* Returns an empty array if the card is valid.
|
|
20
|
-
*/
|
|
21
|
-
export function validateAdaptiveCard(
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
-
payload: Record<string, unknown>
|
|
24
|
-
): CardValidationError[] {
|
|
25
|
-
const errors: CardValidationError[] = [];
|
|
26
|
-
|
|
27
|
-
// Basic type guard: must have type === "AdaptiveCard"
|
|
28
|
-
if (payload.type !== "AdaptiveCard") {
|
|
29
|
-
errors.push({
|
|
30
|
-
message: `Expected type "AdaptiveCard", got "${String(payload.type ?? "(missing)")}"`,
|
|
31
|
-
phase: "schema",
|
|
32
|
-
});
|
|
33
|
-
return errors; // No point parsing further
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const card = new AdaptiveCard();
|
|
38
|
-
const context = new SerializationContext();
|
|
39
|
-
card.parse(payload, context);
|
|
40
|
-
|
|
41
|
-
for (let i = 0; i < context.eventCount; i++) {
|
|
42
|
-
const event = context.getEventAt(i);
|
|
43
|
-
errors.push({
|
|
44
|
-
message: event.message ?? String(event),
|
|
45
|
-
phase: event.phase !== undefined ? String(event.phase) : "parsing",
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
errors.push({
|
|
50
|
-
message: err instanceof Error ? err.message : String(err),
|
|
51
|
-
phase: "parsing",
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return errors;
|
|
56
|
-
}
|