@microsoft/m365agentsplayground-cli 0.2.25-alpha.20260507-efe1416.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.
Files changed (51) hide show
  1. package/README.md +341 -0
  2. package/build/cardValidator.d.ts +18 -0
  3. package/build/cardValidator.d.ts.map +1 -0
  4. package/build/cardValidator.js +47 -0
  5. package/build/conversationServer.d.ts +29 -0
  6. package/build/conversationServer.d.ts.map +1 -0
  7. package/build/conversationServer.js +127 -0
  8. package/build/conversationTypes.d.ts +146 -0
  9. package/build/conversationTypes.d.ts.map +1 -0
  10. package/build/conversationTypes.js +5 -0
  11. package/build/index.d.ts +14 -0
  12. package/build/index.d.ts.map +1 -0
  13. package/build/index.js +25 -0
  14. package/build/notificationSender.d.ts +16 -0
  15. package/build/notificationSender.d.ts.map +1 -0
  16. package/build/notificationSender.js +120 -0
  17. package/build/responseCapture.d.ts +29 -0
  18. package/build/responseCapture.d.ts.map +1 -0
  19. package/build/responseCapture.js +119 -0
  20. package/build/runConversation.d.ts +17 -0
  21. package/build/runConversation.d.ts.map +1 -0
  22. package/build/runConversation.js +338 -0
  23. package/build/serverManager.d.ts +46 -0
  24. package/build/serverManager.d.ts.map +1 -0
  25. package/build/serverManager.js +149 -0
  26. package/build/start-server.d.ts +9 -0
  27. package/build/start-server.d.ts.map +1 -0
  28. package/build/start-server.js +23 -0
  29. package/build/testClient.d.ts +146 -0
  30. package/build/testClient.d.ts.map +1 -0
  31. package/build/testClient.js +434 -0
  32. package/build/types.d.ts +125 -0
  33. package/build/types.d.ts.map +1 -0
  34. package/build/types.js +7 -0
  35. package/build/websocketClient.d.ts +56 -0
  36. package/build/websocketClient.d.ts.map +1 -0
  37. package/build/websocketClient.js +129 -0
  38. package/package.json +36 -0
  39. package/src/cardValidator.ts +56 -0
  40. package/src/conversationServer.ts +147 -0
  41. package/src/conversationTypes.ts +169 -0
  42. package/src/index.ts +37 -0
  43. package/src/notificationSender.ts +135 -0
  44. package/src/responseCapture.ts +145 -0
  45. package/src/runConversation.ts +379 -0
  46. package/src/serverManager.ts +172 -0
  47. package/src/start-server.ts +26 -0
  48. package/src/testClient.ts +515 -0
  49. package/src/types.ts +155 -0
  50. package/src/websocketClient.ts +153 -0
  51. package/tsconfig.json +16 -0
@@ -0,0 +1,146 @@
1
+ import { EventEmitter } from "events";
2
+ import { Message } from "server";
3
+ import { BotResponse, TestClientConfig } from "./types";
4
+ /**
5
+ * Test client for sending messages to a bot and receiving responses.
6
+ * Designed for testing multi-turn conversations.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const client = new TestClient({
11
+ * botEndpoint: 'http://localhost:3978/api/messages',
12
+ * });
13
+ *
14
+ * await client.start();
15
+ *
16
+ * // Multi-turn conversation
17
+ * const [r1] = await client.sendMessage('Hello');
18
+ * expect(r1.text).to.include('Hi');
19
+ *
20
+ * const [r2] = await client.sendMessage('Book a flight');
21
+ * expect(r2.text).to.include('Where');
22
+ *
23
+ * await client.stop();
24
+ * ```
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // With WebSocket events
29
+ * const client = new TestClient({
30
+ * botEndpoint: 'http://localhost:3978/api/messages',
31
+ * });
32
+ *
33
+ * client.on('message:created', (event) => {
34
+ * console.log('New message:', event.message.text);
35
+ * });
36
+ *
37
+ * client.on('typing', (event) => {
38
+ * console.log('Bot is typing...');
39
+ * });
40
+ *
41
+ * await client.start();
42
+ * await client.sendMessage('Hello');
43
+ * await client.stop();
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * // With Log WebSocket events
49
+ * const client = new TestClient({
50
+ * botEndpoint: 'http://localhost:3978/api/messages',
51
+ * });
52
+ *
53
+ * client.on('log:append', (event) => {
54
+ * console.log('Log entry:', event.logItem);
55
+ * });
56
+ *
57
+ * await client.start();
58
+ * await client.sendMessage('Hello');
59
+ * await client.stop();
60
+ * ```
61
+ */
62
+ export declare class TestClient extends EventEmitter {
63
+ /** The placeholder text teams-ai sends as the first streaming activity */
64
+ static readonly STREAMING_PLACEHOLDER = "Loading stream results...";
65
+ private serverManager;
66
+ private responseCapture;
67
+ private wsClient;
68
+ private logWsClient;
69
+ private config;
70
+ private conversationId;
71
+ private userId;
72
+ private started;
73
+ constructor(config: TestClientConfig);
74
+ /**
75
+ * Start the test client and server
76
+ */
77
+ start(): Promise<void>;
78
+ /**
79
+ * Stop the test client and server
80
+ */
81
+ stop(): Promise<void>;
82
+ /**
83
+ * Send a message to the bot and wait for response(s)
84
+ *
85
+ * @param text The message text to send
86
+ * @returns Array of bot responses
87
+ */
88
+ sendMessage(text: string): Promise<BotResponse[]>;
89
+ /**
90
+ * Wait for streaming to complete.
91
+ *
92
+ * Primary: resolves immediately when `streamType:"final"` WS event arrives.
93
+ * Fallback: resolves after `quietPeriodMs` of no new updateActivity events,
94
+ * for bots that don't send streamType (e.g. non-teams-ai streaming bots).
95
+ * Hard cap: always resolves after `maxWaitMs`.
96
+ */
97
+ private waitForStreamFinal;
98
+ /**
99
+ * Start a new conversation (resets conversation state)
100
+ */
101
+ newConversation(): void;
102
+ /**
103
+ * Get all messages in the current conversation
104
+ */
105
+ getMessages(): Message[];
106
+ /**
107
+ * Get the last bot message in the current conversation
108
+ */
109
+ getLastBotMessage(): Message | undefined;
110
+ /**
111
+ * Get the current conversation ID
112
+ */
113
+ getConversationId(): string;
114
+ /**
115
+ * Get the port the test server is running on
116
+ */
117
+ getPort(): number;
118
+ /**
119
+ * Simulate clicking an Adaptive Card button (Action.Execute).
120
+ *
121
+ * @param messageId The message ID of the card message (from BotResponse.messageId)
122
+ * @param verb The Action.Execute verb defined on the button
123
+ * @param data Optional extra data to merge with the button's own data
124
+ * @returns The invoke response body from the bot
125
+ */
126
+ clickCardButton(messageId: string, verb: string, data?: Record<string, unknown>): Promise<unknown>;
127
+ /**
128
+ * Simulate submitting an Adaptive Card form (Action.Submit / legacy bots).
129
+ *
130
+ * @param messageId The message ID of the card message
131
+ * @param data The form data to submit
132
+ */
133
+ submitCardForm(messageId: string, data: Record<string, unknown>): Promise<unknown>;
134
+ /**
135
+ * Create a unique conversation for this client based on chatType config.
136
+ * - personal: creates a new personal chat with a unique userId
137
+ * - group: creates a new group chat
138
+ * - channel: uses the team's general channel
139
+ */
140
+ private registerUniqueConversation;
141
+ /**
142
+ * Convert a Message to a BotResponse
143
+ */
144
+ private toBotResponse;
145
+ }
146
+ //# sourceMappingURL=testClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testClient.d.ts","sourceRoot":"","sources":["../src/testClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAW,OAAO,EAAE,MAAM,QAAQ,CAAC;AAG1C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,qBAAa,UAAW,SAAQ,YAAY;IAC1C,0EAA0E;IAC1E,MAAM,CAAC,QAAQ,CAAC,qBAAqB,+BAA+B;IAEpE,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,gBAAgB;IAUpC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyD5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB3B;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA2GvD;;;;;;;OAOG;YACW,kBAAkB;IAoChC;;OAEG;IACH,eAAe,IAAI,IAAI;IAYvB;;OAEG;IACH,WAAW,IAAI,OAAO,EAAE;IASxB;;OAEG;IACH,iBAAiB,IAAI,OAAO,GAAG,SAAS;IAMxC;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;;;;;;OAOG;IACG,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,OAAO,CAAC;IAkCnB;;;;;OAKG;IACG,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC;IA4BnB;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B;IAiClC;;OAEG;IACH,OAAO,CAAC,aAAa;CAStB"}
@@ -0,0 +1,434 @@
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
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;