@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.
Files changed (56) hide show
  1. package/README.md +8 -21
  2. package/{build → dist}/conversationTypes.d.ts +5 -0
  3. package/{build → dist}/index.d.ts +1 -1
  4. package/dist/index.js +8 -0
  5. package/dist/index.js.LICENSE.txt +157 -0
  6. package/dist/responseCapture.d.ts +42 -0
  7. package/{build → dist}/serverManager.d.ts +1 -2
  8. package/dist/start-server.js +9 -0
  9. package/dist/start-server.js.LICENSE.txt +157 -0
  10. package/{build → dist}/testClient.d.ts +1 -1
  11. package/{build → dist}/types.d.ts +9 -0
  12. package/package.json +22 -12
  13. package/build/cardValidator.d.ts.map +0 -1
  14. package/build/cardValidator.js +0 -46
  15. package/build/conversationServer.d.ts.map +0 -1
  16. package/build/conversationServer.js +0 -136
  17. package/build/conversationTypes.d.ts.map +0 -1
  18. package/build/conversationTypes.js +0 -5
  19. package/build/index.d.ts.map +0 -1
  20. package/build/index.js +0 -25
  21. package/build/notificationSender.d.ts.map +0 -1
  22. package/build/notificationSender.js +0 -83
  23. package/build/responseCapture.d.ts +0 -29
  24. package/build/responseCapture.d.ts.map +0 -1
  25. package/build/responseCapture.js +0 -119
  26. package/build/runConversation.d.ts.map +0 -1
  27. package/build/runConversation.js +0 -351
  28. package/build/serverManager.d.ts.map +0 -1
  29. package/build/serverManager.js +0 -149
  30. package/build/start-server.d.ts.map +0 -1
  31. package/build/start-server.js +0 -23
  32. package/build/testClient.d.ts.map +0 -1
  33. package/build/testClient.js +0 -434
  34. package/build/types.d.ts.map +0 -1
  35. package/build/types.js +0 -7
  36. package/build/websocketClient.d.ts.map +0 -1
  37. package/build/websocketClient.js +0 -129
  38. package/src/cardValidator.ts +0 -56
  39. package/src/conversationServer.ts +0 -147
  40. package/src/conversationTypes.ts +0 -157
  41. package/src/index.ts +0 -37
  42. package/src/notificationSender.ts +0 -103
  43. package/src/responseCapture.ts +0 -145
  44. package/src/runConversation.ts +0 -382
  45. package/src/serverManager.ts +0 -172
  46. package/src/start-server.ts +0 -26
  47. package/src/testClient.ts +0 -512
  48. package/src/types.ts +0 -155
  49. package/src/websocketClient.ts +0 -153
  50. package/tsconfig.json +0 -14
  51. /package/{build → dist}/cardValidator.d.ts +0 -0
  52. /package/{build → dist}/conversationServer.d.ts +0 -0
  53. /package/{build → dist}/notificationSender.d.ts +0 -0
  54. /package/{build → dist}/runConversation.d.ts +0 -0
  55. /package/{build → dist}/start-server.d.ts +0 -0
  56. /package/{build → dist}/websocketClient.d.ts +0 -0
@@ -1,119 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ResponseCapture = void 0;
4
- /**
5
- * Captures bot responses by hooking into BotConnectorService.processActivity
6
- */
7
- class ResponseCapture {
8
- pendingResponses = new Map();
9
- originalProcessActivity;
10
- /**
11
- * Hook into BotConnectorService to intercept responses
12
- */
13
- hookIntoBotConnectorService(service) {
14
- if (this.originalProcessActivity) {
15
- // Already hooked
16
- return;
17
- }
18
- this.originalProcessActivity = service.processActivity.bind(service);
19
- service.processActivity = async (conversationId, activity) => {
20
- if (this.originalProcessActivity === undefined) {
21
- throw new Error("BotConnectorService.processActivity is undefined");
22
- }
23
- try {
24
- const result = await this.originalProcessActivity(conversationId, activity);
25
- // Only resolve waiting when an actual message was created (resIds non-empty).
26
- // Typing indicators and trace activities return resIds:[] and must NOT
27
- // resolve the waiting promise prematurely.
28
- if (result.resIds.length > 0) {
29
- this.resolveWaiting(conversationId);
30
- }
31
- return result;
32
- }
33
- catch (error) {
34
- // Log the error for debugging — this is the actual error that causes 500s
35
- // when the bot POSTs back to the connector.
36
- console.error(`[ResponseCapture] processActivity error for conversation ${conversationId}:`, error);
37
- const activityAny = activity;
38
- const entities = Array.isArray(activityAny.entities) ? activityAny.entities : [];
39
- console.error(`[ResponseCapture] Activity type: ${String(activityAny.type)}, ` +
40
- `entities: ${JSON.stringify(entities.map((e) => e.type))}`);
41
- // Still resolve waiting on error — the test should see whatever messages
42
- // were created before the error, rather than timing out silently.
43
- this.resolveWaiting(conversationId);
44
- throw error;
45
- }
46
- };
47
- }
48
- /**
49
- * Wait for a response in the given conversation
50
- */
51
- async waitForResponse(conversationId, conversationManager, timeout) {
52
- const messageCountBefore = conversationManager.listMessages(conversationId).length;
53
- return new Promise((resolve, reject) => {
54
- const timer = setTimeout(() => {
55
- this.pendingResponses.delete(conversationId);
56
- // Check one more time before rejecting
57
- const messages = conversationManager.listMessages(conversationId);
58
- const newMessages = messages.slice(messageCountBefore);
59
- const botMessages = newMessages.filter((m) => m.createdBy === "bot");
60
- if (botMessages.length > 0) {
61
- resolve(botMessages);
62
- }
63
- else {
64
- reject(new Error(`Timeout waiting for bot response after ${timeout}ms`));
65
- }
66
- }, timeout);
67
- this.pendingResponses.set(conversationId, {
68
- resolve: (messages) => {
69
- clearTimeout(timer);
70
- resolve(messages);
71
- },
72
- reject,
73
- timer,
74
- messageCountBefore,
75
- });
76
- });
77
- }
78
- /**
79
- * Resolve waiting promises when a response arrives
80
- */
81
- resolveWaiting(conversationId) {
82
- const pending = this.pendingResponses.get(conversationId);
83
- if (!pending) {
84
- return;
85
- }
86
- // Use setImmediate to allow the message to be stored first
87
- setImmediate(() => {
88
- // The pending might have been removed by timeout
89
- if (!this.pendingResponses.has(conversationId)) {
90
- return;
91
- }
92
- // We don't have direct access to conversationManager here,
93
- // so we just signal completion. The TestClient will check for new messages.
94
- this.pendingResponses.delete(conversationId);
95
- clearTimeout(pending.timer);
96
- pending.resolve([]);
97
- });
98
- }
99
- /**
100
- * Clear all pending responses
101
- */
102
- clear() {
103
- this.pendingResponses.forEach((pending) => {
104
- clearTimeout(pending.timer);
105
- });
106
- this.pendingResponses.clear();
107
- }
108
- /**
109
- * Clear pending responses for a specific conversation only
110
- */
111
- clearConversation(conversationId) {
112
- const pending = this.pendingResponses.get(conversationId);
113
- if (pending) {
114
- clearTimeout(pending.timer);
115
- this.pendingResponses.delete(conversationId);
116
- }
117
- }
118
- }
119
- exports.ResponseCapture = ResponseCapture;
@@ -1 +0,0 @@
1
- {"version":3,"file":"runConversation.d.ts","sourceRoot":"","sources":["../src/runConversation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EACV,SAAS,EAET,iBAAiB,EAIjB,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAM7B,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAErC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE1C;AAoHD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CA+N7B"}
@@ -1,351 +0,0 @@
1
- "use strict";
2
- /**
3
- * Conversation execution logic for the test harness.
4
- *
5
- * Runs a sequence of turns against a bot through a fresh TestClient
6
- * and returns the bot's responses. No eval logic — just execution.
7
- */
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
- Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.log = log;
43
- exports.logError = logError;
44
- exports.runConversation = runConversation;
45
- const http = __importStar(require("http"));
46
- const https = __importStar(require("https"));
47
- const testClient_1 = require("./testClient");
48
- const notificationSender_1 = require("./notificationSender");
49
- const cardValidator_1 = require("./cardValidator");
50
- // ─────────────────────────────────────────────────────────────────────────────
51
- // Logging (stderr only — stdout is reserved for structured output)
52
- // ─────────────────────────────────────────────────────────────────────────────
53
- function log(msg) {
54
- process.stderr.write(`[agents-simulator] ${msg}\n`);
55
- }
56
- function logError(msg) {
57
- process.stderr.write(`[agents-simulator][ERROR] ${msg}\n`);
58
- }
59
- // ─────────────────────────────────────────────────────────────────────────────
60
- // Persona resolution
61
- // ─────────────────────────────────────────────────────────────────────────────
62
- function resolvePersona(turn, input, config) {
63
- const personaName = turn.persona ?? input.persona;
64
- if (!personaName || !config.personas)
65
- return undefined;
66
- const persona = config.personas[personaName];
67
- if (!persona) {
68
- log(`Warning: persona "${personaName}" not found in config, using default`);
69
- return undefined;
70
- }
71
- return persona;
72
- }
73
- // ─────────────────────────────────────────────────────────────────────────────
74
- // Bot auth preflight check
75
- // ─────────────────────────────────────────────────────────────────────────────
76
- /**
77
- * Send a bare probe request to the bot endpoint (no Authorization header).
78
- * Returns the HTTP status code, or throws if the bot is unreachable.
79
- *
80
- * Bot Framework SDK behaviour:
81
- * - MicrosoftAppId is empty → anonymous mode → accepts any request → 200/202
82
- * - MicrosoftAppId is set → validates JWT → rejects bare request → 401
83
- */
84
- async function probeBot(botEndpoint) {
85
- return new Promise((resolve, reject) => {
86
- const url = new URL(botEndpoint);
87
- const lib = url.protocol === "https:" ? https : http;
88
- // Use type:"typing" — the Bot Framework adapter acks it immediately without
89
- // invoking the AI planner (no LLM call, no sendActivity, instant response).
90
- // Bot returns 200/202 for unauthenticated bots, 401 for authenticated ones.
91
- const body = JSON.stringify({ type: "typing" });
92
- const req = lib.request({
93
- hostname: url.hostname,
94
- port: url.port || (url.protocol === "https:" ? 443 : 80),
95
- path: url.pathname,
96
- method: "POST",
97
- headers: {
98
- "Content-Type": "application/json",
99
- "Content-Length": Buffer.byteLength(body),
100
- },
101
- }, (res) => resolve(res.statusCode ?? 0));
102
- req.on("error", reject);
103
- req.setTimeout(5000, () => {
104
- req.destroy(new Error(`Connection to bot timed out: ${botEndpoint}`));
105
- });
106
- req.write(body);
107
- req.end();
108
- });
109
- }
110
- /**
111
- * Verify the bot is reachable and accepting unauthenticated requests.
112
- * Throws a descriptive error when the bot returns 401 or is unreachable.
113
- */
114
- async function checkBotReachable(botEndpoint) {
115
- let statusCode;
116
- try {
117
- statusCode = await probeBot(botEndpoint);
118
- }
119
- catch (err) {
120
- const msg = err instanceof Error ? err.message : String(err);
121
- throw new Error(`Cannot reach bot at ${botEndpoint}: ${msg}\n` +
122
- ` → Make sure the bot is running before starting a conversation.`);
123
- }
124
- if (statusCode === 401) {
125
- throw new Error(`Bot at ${botEndpoint} requires authentication (HTTP 401).\n` +
126
- ` → For local testing, start the bot with BOT_ID="" BOT_PASSWORD="" so it\n` +
127
- ` accepts requests without a Bot Framework JWT token.\n` +
128
- ` → Example: BOT_ID= BOT_PASSWORD= node ./src/index.ts`);
129
- }
130
- }
131
- // ─────────────────────────────────────────────────────────────────────────────
132
- // Conversation execution
133
- // ─────────────────────────────────────────────────────────────────────────────
134
- /**
135
- * Extract and validate Adaptive Card attachments from bot responses.
136
- * Only processes "application/vnd.microsoft.card.adaptive" attachments.
137
- */
138
- function extractAttachments(responses) {
139
- const result = [];
140
- for (const r of responses) {
141
- for (const att of r.attachments ?? []) {
142
- if (att.contentType === "application/vnd.microsoft.card.adaptive" && att.content) {
143
- const content = att.content;
144
- const card_errors = (0, cardValidator_1.validateAdaptiveCard)(content);
145
- result.push({ contentType: att.contentType, content, card_errors });
146
- }
147
- }
148
- }
149
- return result;
150
- }
151
- /**
152
- * Run a single conversation through a fresh TestClient.
153
- *
154
- * Creates an isolated TestClient, sends each turn sequentially, and
155
- * collects the bot's responses. On failure, remaining turns are skipped.
156
- */
157
- async function runConversation(config, scenario, input) {
158
- const results = [];
159
- const convStart = Date.now();
160
- const timeout = config.timeout ?? 120000;
161
- const turns = input.turns || [];
162
- if (turns.length === 0) {
163
- return {
164
- type: "conversation_result",
165
- scenario,
166
- status: "Errored",
167
- duration_seconds: 0,
168
- turns: [],
169
- };
170
- }
171
- // Preflight: verify the bot is reachable and not requiring auth before
172
- // spending timeout budget on the first real turn.
173
- try {
174
- await checkBotReachable(config.botEndpoint);
175
- }
176
- catch (err) {
177
- const message = err instanceof Error ? err.message : String(err);
178
- logError(message);
179
- return {
180
- type: "conversation_result",
181
- scenario,
182
- status: "Errored",
183
- duration_seconds: (Date.now() - convStart) / 1000,
184
- turns: turns.map((t) => ({
185
- test_id: t.test_id,
186
- prompt: t.prompt,
187
- actual_response: null,
188
- attachments: [],
189
- status: "Errored",
190
- error_message: message,
191
- duration_seconds: 0,
192
- })),
193
- };
194
- }
195
- const client = new testClient_1.TestClient({
196
- botEndpoint: config.botEndpoint,
197
- timeout,
198
- bot: config.bot,
199
- deliveryMode: config.deliveryMode ?? "expectReplies",
200
- chatType: config.chatType ?? "personal",
201
- streamingSettleDelayMs: config.streamingSettleDelayMs ?? 0,
202
- });
203
- try {
204
- log(`Starting client for: ${scenario}`);
205
- await client.start();
206
- log(`Client started on port ${client.getPort()}`);
207
- // Maps turn test_id → messageId of the bot's first card response in that turn
208
- const cardMessageIds = new Map();
209
- for (let i = 0; i < turns.length; i++) {
210
- const turn = turns[i];
211
- const turnType = turn.turn_type ?? "chat";
212
- const turnStart = Date.now();
213
- log(` Turn ${i + 1}/${turns.length} [${turn.test_id}] (${turnType}): ${turn.prompt.substring(0, 80)}...`);
214
- try {
215
- let responseText;
216
- let attachments = [];
217
- if (turnType === "chat") {
218
- const responses = await client.sendMessage(turn.prompt);
219
- responseText = responses
220
- .map((r) => r.text ?? "")
221
- .filter((t) => t.length > 0)
222
- .join("\n\n");
223
- attachments = extractAttachments(responses);
224
- // Track the first card-containing message ID for card_action turns
225
- const firstCardMsg = responses.find((r) => r.attachments?.some((a) => a.contentType === "application/vnd.microsoft.card.adaptive"));
226
- if (firstCardMsg?.messageId) {
227
- cardMessageIds.set(turn.test_id, firstCardMsg.messageId);
228
- }
229
- }
230
- else if (turnType === "card_action") {
231
- const cardAction = turn.card_action;
232
- if (!cardAction) {
233
- throw new Error(`Turn "${turn.test_id}" has turn_type "card_action" but no card_action field.`);
234
- }
235
- if (!cardAction.verb) {
236
- throw new Error(`Turn "${turn.test_id}" card_action is missing required "verb" field.`);
237
- }
238
- // Resolve the target message ID from the referenced turn
239
- let replyToId;
240
- if (cardAction.reply_to_turn != null) {
241
- replyToId = cardMessageIds.get(cardAction.reply_to_turn);
242
- if (!replyToId) {
243
- throw new Error(`card_action.reply_to_turn "${cardAction.reply_to_turn}" did not produce a card message. ` +
244
- `Ensure that turn sends a card before using card_action.`);
245
- }
246
- }
247
- else {
248
- // Use the most recently stored card messageId
249
- const entries = [...cardMessageIds.values()];
250
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
251
- replyToId = entries[entries.length - 1];
252
- }
253
- if (!replyToId) {
254
- throw new Error(`card_action for turn "${turn.test_id}" could not find any card message to target. ` +
255
- `Ensure a prior turn produces an Adaptive Card.`);
256
- }
257
- if (cardAction.action_type === "Action.Submit") {
258
- const invokeResponse = await client.submitCardForm(replyToId, cardAction.data ?? {});
259
- responseText = JSON.stringify(invokeResponse);
260
- }
261
- else {
262
- const invokeResponse = await client.clickCardButton(replyToId, cardAction.verb, cardAction.data ?? {});
263
- responseText = JSON.stringify(invokeResponse);
264
- }
265
- }
266
- else {
267
- const persona = resolvePersona(turn, input, config);
268
- responseText = await (0, notificationSender_1.sendNotificationAndWait)(client.getConversationId(), turn, persona, timeout);
269
- }
270
- const duration = (Date.now() - turnStart) / 1000;
271
- log(` [OK] (${duration.toFixed(1)}s): ${responseText.substring(0, 100)}...`);
272
- results.push({
273
- test_id: turn.test_id,
274
- prompt: turn.prompt,
275
- actual_response: responseText || null,
276
- attachments,
277
- status: "Completed",
278
- duration_seconds: duration,
279
- });
280
- }
281
- catch (err) {
282
- const duration = (Date.now() - turnStart) / 1000;
283
- const message = err instanceof Error ? err.message : String(err);
284
- const isTimeout = message.includes("Timeout");
285
- logError(` Turn ${turn.test_id} failed: ${message}`);
286
- results.push({
287
- test_id: turn.test_id,
288
- prompt: turn.prompt,
289
- actual_response: null,
290
- attachments: [],
291
- status: isTimeout ? "TimedOut" : "Errored",
292
- error_message: message,
293
- duration_seconds: duration,
294
- });
295
- for (let j = i + 1; j < turns.length; j++) {
296
- results.push({
297
- test_id: turns[j].test_id,
298
- prompt: turns[j].prompt,
299
- actual_response: null,
300
- attachments: [],
301
- status: "Skipped",
302
- duration_seconds: 0,
303
- });
304
- }
305
- break;
306
- }
307
- }
308
- }
309
- catch (err) {
310
- const message = err instanceof Error ? err.message : String(err);
311
- logError(`Client startup failed for ${scenario}: ${message}`);
312
- for (const turn of turns) {
313
- results.push({
314
- test_id: turn.test_id,
315
- prompt: turn.prompt,
316
- actual_response: null,
317
- attachments: [],
318
- status: "Errored",
319
- error_message: message,
320
- duration_seconds: 0,
321
- });
322
- }
323
- }
324
- finally {
325
- try {
326
- await client.stop();
327
- }
328
- catch {
329
- // Ignore stop errors.
330
- }
331
- }
332
- const convDuration = (Date.now() - convStart) / 1000;
333
- const statuses = new Set(results.map((t) => t.status));
334
- let overallStatus;
335
- if (statuses.has("TimedOut")) {
336
- overallStatus = "TimedOut";
337
- }
338
- else if (statuses.has("Errored")) {
339
- overallStatus = "Errored";
340
- }
341
- else {
342
- overallStatus = "Completed";
343
- }
344
- return {
345
- type: "conversation_result",
346
- scenario,
347
- status: overallStatus,
348
- duration_seconds: convDuration,
349
- turns: results,
350
- };
351
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"serverManager.d.ts","sourceRoot":"","sources":["../src/serverManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,OAAO,EAAiB,MAAM,QAAQ,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAiB3C,qBAAa,aAAa;IAExB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAC5C,OAAO,CAAC,MAAM,CAAC,GAAG,CAAwB;IAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAK;IACzB,OAAO,CAAC,MAAM,CAAC,WAAW,CAAM;IAChC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAS;IAChC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAgC;IAE/D;;;;;OAKG;IACG,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyFpD;;;;;;;;OAQG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,UAAU,IAAI,OAAO,OAAO;IAI5B;;;OAGG;IACH,MAAM,CAAC,kBAAkB,IAAI,eAAe;CAM7C"}
@@ -1,149 +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.ServerManager = void 0;
7
- const express_1 = __importDefault(require("express"));
8
- const server_1 = require("server");
9
- const schema_1 = require("schema");
10
- const responseCapture_1 = require("./responseCapture");
11
- /**
12
- * Manages the shared Express server lifecycle for the test harness.
13
- *
14
- * The server package's Factory is a process-wide singleton — calling
15
- * mountBackend() more than once replaces ConfigManager while leaving
16
- * every other cached manager/service stale. ServerManager therefore
17
- * starts the Express server exactly once and reuses it for all
18
- * TestClient instances in the same process.
19
- */
20
- // Fixed conversation ID used only for the initial ConfigManager bootstrap.
21
- // Individual TestClients register their own unique conversations in
22
- // ConversationManager (see TestClient.registerUniqueConversation).
23
- const BOOTSTRAP_CONVERSATION_ID = "playground-cli-bootstrap";
24
- class ServerManager {
25
- // ── Shared process-level state ───────────────────────────────────────
26
- static server = null;
27
- static app = null;
28
- static _port = 0;
29
- static _serviceUrl = "";
30
- static _started = false;
31
- static _responseCapture = null;
32
- /**
33
- * Start the shared test server (no-op if already running).
34
- *
35
- * The first call boots Express + mountBackend. Subsequent calls
36
- * return immediately, reusing the existing server.
37
- */
38
- async start(config) {
39
- if (ServerManager._started) {
40
- return;
41
- }
42
- const app = (0, express_1.default)();
43
- ServerManager.app = app;
44
- // Create HTTP server first
45
- ServerManager.server = app.listen(config.port ?? 0);
46
- // Wait for server to be listening
47
- await new Promise((resolve, reject) => {
48
- ServerManager.server?.once("listening", resolve);
49
- ServerManager.server?.once("error", reject);
50
- });
51
- // Get the assigned port
52
- const address = ServerManager.server?.address();
53
- ServerManager._port = address.port;
54
- ServerManager._serviceUrl = `http://localhost:${ServerManager._port}/_connector`;
55
- // Get default config and merge bot config if provided
56
- const defaultConfig = server_1.ConfigManager.getDefaultConfig({
57
- path: "",
58
- conversationId: BOOTSTRAP_CONVERSATION_ID,
59
- });
60
- if (config.bot) {
61
- defaultConfig.bot = {
62
- ...defaultConfig.bot,
63
- ...config.bot,
64
- };
65
- // Also update the root tenantId if bot.tenantId is provided
66
- if (config.bot.tenantId) {
67
- defaultConfig.tenantId = config.bot.tenantId;
68
- }
69
- }
70
- if (!ServerManager.server) {
71
- throw new Error("Server failed to start");
72
- }
73
- // Map delivery mode string to enum value
74
- const deliveryMode = config.deliveryMode === "expectReplies"
75
- ? schema_1.DeliveryMode.ExpectReplies
76
- : config.deliveryMode === "default"
77
- ? schema_1.DeliveryMode.Default
78
- : undefined;
79
- (0, server_1.mountBackend)(app, ServerManager.server, {
80
- configFileOptions: { configFile: defaultConfig },
81
- appConfig: {
82
- endpoint: config.botEndpoint,
83
- },
84
- debugConfig: {
85
- eventsRecordingEnabled: false,
86
- deliveryMode,
87
- },
88
- });
89
- // Add error-handling middleware for connector routes.
90
- app.use((err, _req, res,
91
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
92
- _next) => {
93
- console.error("[agents-simulator] Unhandled error in connector:", err);
94
- if (!res.headersSent) {
95
- res.status(500).json({
96
- error: {
97
- code: "InternalError",
98
- message: err.message || "Unknown error",
99
- },
100
- });
101
- }
102
- });
103
- // Hook the shared ResponseCapture into BotConnectorService once.
104
- const capture = ServerManager.getResponseCapture();
105
- capture.hookIntoBotConnectorService(server_1.Factory.getBotConnectorService());
106
- ServerManager._started = true;
107
- }
108
- /**
109
- * No-op — the shared server stays running for the process lifetime.
110
- *
111
- * Per-client cleanup (WebSockets, conversation messages) is handled
112
- * by TestClient.stop().
113
- *
114
- * TODO(server-package): Add a Factory.reset() so the harness can
115
- * fully tear down between test suites if needed.
116
- */
117
- async stop() {
118
- // Intentionally empty.
119
- }
120
- /**
121
- * Get the port the server is running on
122
- */
123
- get port() {
124
- return ServerManager._port;
125
- }
126
- /**
127
- * Get the service URL for the connector API
128
- */
129
- get serviceUrl() {
130
- return ServerManager._serviceUrl;
131
- }
132
- /**
133
- * Get the Factory instance for accessing services
134
- */
135
- getFactory() {
136
- return server_1.Factory;
137
- }
138
- /**
139
- * Shared ResponseCapture instance — keyed by conversationId, so
140
- * concurrent TestClients don't interfere with each other.
141
- */
142
- static getResponseCapture() {
143
- if (!ServerManager._responseCapture) {
144
- ServerManager._responseCapture = new responseCapture_1.ResponseCapture();
145
- }
146
- return ServerManager._responseCapture;
147
- }
148
- }
149
- exports.ServerManager = ServerManager;
@@ -1 +0,0 @@
1
- {"version":3,"file":"start-server.d.ts","sourceRoot":"","sources":["../src/start-server.ts"],"names":[],"mappings":";AAEA;;;;;GAKG"}
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /**
4
- * Starts the Conversation Server.
5
- *
6
- * Usage:
7
- * node build/start-server.js [--port <port>]
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- const conversationServer_1 = require("./conversationServer");
11
- function parsePort() {
12
- const args = process.argv.slice(2);
13
- for (let i = 0; i < args.length; i++) {
14
- if (args[i] === "--port" && args[i + 1]) {
15
- return parseInt(args[++i], 10);
16
- }
17
- }
18
- return 0;
19
- }
20
- const port = parsePort();
21
- void (0, conversationServer_1.createConversationServer)({ port }).then((server) => {
22
- process.stdout.write(JSON.stringify({ port: server.port }) + "\n");
23
- });
@@ -1 +0,0 @@
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,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BxF;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B;IAiClC;;OAEG;IACH,OAAO,CAAC,aAAa;CAStB"}