@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
+ /**
2
+ * Type definitions for multi-turn conversation execution.
3
+ */
4
+ import type { BotConfig } from "./types";
5
+ import type { CustomActivityTemplateType } from "schema";
6
+ import type { CardValidationError } from "./cardValidator";
7
+ /**
8
+ * Identity for a user persona in notification activities.
9
+ */
10
+ export interface PersonaConfig {
11
+ id: string;
12
+ name: string;
13
+ email?: string;
14
+ }
15
+ /**
16
+ * Server configuration passed per /run-conversation request.
17
+ */
18
+ export interface E2EConfig {
19
+ /** Bot endpoint URL (e.g., "http://localhost:3978/api/messages") */
20
+ botEndpoint: string;
21
+ /** Response timeout in milliseconds (default: 120000) */
22
+ timeout?: number;
23
+ /** Activity delivery mode (default: "expectReplies") */
24
+ deliveryMode?: "expectReplies" | "default";
25
+ /** Bot identity configuration */
26
+ bot?: BotConfig;
27
+ /** Named personas for notification activities */
28
+ personas?: Record<string, PersonaConfig>;
29
+ /**
30
+ * Default chat context for all "chat" turns in this conversation.
31
+ * Can be overridden per-turn via turn.chat_type.
32
+ * Default: "personal"
33
+ */
34
+ chatType?: "personal" | "group" | "channel";
35
+ /**
36
+ * Quiet-period fallback (ms) for streaming responses.
37
+ * Primary: resolves on streamType:"final" event (teams-ai / teams.ts SDK).
38
+ * Fallback: resolves after this many ms with no new updateActivity calls.
39
+ * Default: 800. Has no effect for bots with stream: false.
40
+ */
41
+ streamingSettleDelayMs?: number;
42
+ }
43
+ /**
44
+ * A single turn to execute in a conversation.
45
+ */
46
+ export interface Turn {
47
+ /** Unique identifier for this turn */
48
+ test_id: string;
49
+ /** Message text or notification body to send */
50
+ prompt: string;
51
+ /**
52
+ * Activity type. Default: "chat" (standard message).
53
+ * Any CustomActivityTemplateType value (e.g., "sendEmail", "mentionInWord", "meetingStart")
54
+ * sends the corresponding notification activity.
55
+ */
56
+ turn_type?: "chat" | "card_action" | CustomActivityTemplateType;
57
+ /**
58
+ * Chat context for "chat" turn_type. Default: "personal".
59
+ * - "personal": 1:1 personal chat (default)
60
+ * - "group": group chat
61
+ * - "channel": team channel
62
+ */
63
+ chat_type?: "personal" | "group" | "channel";
64
+ /** Persona name override for this turn (looks up in config.personas) */
65
+ persona?: string;
66
+ /** Extra metadata for the turn */
67
+ prompt_metadata?: Record<string, string>;
68
+ /**
69
+ * For "messageReaction" turn_type: the reaction emoji.
70
+ * Defaults to "like". Options: like, heart, laugh, surprised, sad, angry.
71
+ */
72
+ reaction_type?: "like" | "heart" | "laugh" | "surprised" | "sad" | "angry";
73
+ /**
74
+ * For "messageReaction" turn_type: the message ID to react to.
75
+ * If omitted, reacts to the last bot message in the conversation.
76
+ */
77
+ reply_to_id?: string;
78
+ /**
79
+ * For "card_action" turn_type: simulate clicking an Adaptive Card button.
80
+ * The server must already have a card message in this conversation to target.
81
+ */
82
+ card_action?: {
83
+ /**
84
+ * The verb of the Action.Execute button to click (required for Action.Execute).
85
+ */
86
+ verb?: string;
87
+ /**
88
+ * Data payload to send with the action (merged with button's own data).
89
+ */
90
+ data?: Record<string, unknown>;
91
+ /**
92
+ * test_id of the turn whose first bot response card to target.
93
+ * If omitted, uses the last bot message that has an attachment.
94
+ */
95
+ reply_to_turn?: string;
96
+ /**
97
+ * Action type. Default: "Action.Execute".
98
+ * Use "Action.Submit" for legacy bots.
99
+ */
100
+ action_type?: "Action.Execute" | "Action.Submit";
101
+ };
102
+ }
103
+ /**
104
+ * Input for a single conversation: a list of turns to execute sequentially.
105
+ */
106
+ export interface ConversationInput {
107
+ /** Default persona for all turns */
108
+ persona?: string;
109
+ /** Ordered turns to execute */
110
+ turns: Turn[];
111
+ }
112
+ /**
113
+ * A single validated Adaptive Card attachment returned with a turn result.
114
+ */
115
+ export interface TurnAttachment {
116
+ /** Attachment content type (e.g. "application/vnd.microsoft.card.adaptive") */
117
+ contentType: string;
118
+ /** The raw card JSON content */
119
+ content: Record<string, unknown>;
120
+ /** Schema validation errors for this card. Empty array means valid. */
121
+ card_errors: CardValidationError[];
122
+ }
123
+ /**
124
+ * Result of a single turn execution.
125
+ */
126
+ export interface TurnResult {
127
+ test_id: string;
128
+ prompt: string;
129
+ actual_response: string | null;
130
+ /** Adaptive Card attachments returned by the bot this turn, with validation results */
131
+ attachments: TurnAttachment[];
132
+ status: "Completed" | "TimedOut" | "Errored" | "Skipped";
133
+ error_message?: string;
134
+ duration_seconds: number;
135
+ }
136
+ /**
137
+ * Result of a full conversation execution.
138
+ */
139
+ export interface ConversationResult {
140
+ type: "conversation_result";
141
+ scenario: string;
142
+ status: "Completed" | "TimedOut" | "Errored";
143
+ duration_seconds: number;
144
+ turns: TurnResult[];
145
+ }
146
+ //# sourceMappingURL=conversationTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversationTypes.d.ts","sourceRoot":"","sources":["../src/conversationTypes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Type definitions for multi-turn conversation execution.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,14 @@
1
+ export { TestClient } from "./testClient";
2
+ export { ServerManager } from "./serverManager";
3
+ export { ResponseCapture } from "./responseCapture";
4
+ export type { TestClientConfig, BotConfig, BotResponse, PendingResponse } from "./types";
5
+ export { ActionType, LogActionType } from "./types";
6
+ export type { IAction, ICreateMessageAction, IUpdateMessageAction, ITypingAction, IActionMessage, ILogAction, IAppendLogAction, LogItem, } from "./types";
7
+ export { runConversation, log, logError } from "./runConversation";
8
+ export { sendNotificationAndWait } from "./notificationSender";
9
+ export { createConversationServer } from "./conversationServer";
10
+ export type { ConversationServer, ConversationServerOptions } from "./conversationServer";
11
+ export type { E2EConfig, PersonaConfig, Turn, TurnAttachment, ConversationInput, TurnResult, ConversationResult, } from "./conversationTypes";
12
+ export { validateAdaptiveCard } from "./cardValidator";
13
+ export type { CardValidationError } from "./cardValidator";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGzF,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpD,YAAY,EACV,OAAO,EACP,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,cAAc,EAEd,UAAU,EACV,gBAAgB,EAChB,OAAO,GACR,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,YAAY,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EACV,SAAS,EACT,aAAa,EACb,IAAI,EACJ,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateAdaptiveCard = exports.createConversationServer = exports.sendNotificationAndWait = exports.logError = exports.log = exports.runConversation = exports.LogActionType = exports.ActionType = exports.ResponseCapture = exports.ServerManager = exports.TestClient = void 0;
4
+ var testClient_1 = require("./testClient");
5
+ Object.defineProperty(exports, "TestClient", { enumerable: true, get: function () { return testClient_1.TestClient; } });
6
+ var serverManager_1 = require("./serverManager");
7
+ Object.defineProperty(exports, "ServerManager", { enumerable: true, get: function () { return serverManager_1.ServerManager; } });
8
+ var responseCapture_1 = require("./responseCapture");
9
+ Object.defineProperty(exports, "ResponseCapture", { enumerable: true, get: function () { return responseCapture_1.ResponseCapture; } });
10
+ // Re-export WebSocket event types for consumers
11
+ var types_1 = require("./types");
12
+ Object.defineProperty(exports, "ActionType", { enumerable: true, get: function () { return types_1.ActionType; } });
13
+ Object.defineProperty(exports, "LogActionType", { enumerable: true, get: function () { return types_1.LogActionType; } });
14
+ // Conversation runner
15
+ var runConversation_1 = require("./runConversation");
16
+ Object.defineProperty(exports, "runConversation", { enumerable: true, get: function () { return runConversation_1.runConversation; } });
17
+ Object.defineProperty(exports, "log", { enumerable: true, get: function () { return runConversation_1.log; } });
18
+ Object.defineProperty(exports, "logError", { enumerable: true, get: function () { return runConversation_1.logError; } });
19
+ var notificationSender_1 = require("./notificationSender");
20
+ Object.defineProperty(exports, "sendNotificationAndWait", { enumerable: true, get: function () { return notificationSender_1.sendNotificationAndWait; } });
21
+ var conversationServer_1 = require("./conversationServer");
22
+ Object.defineProperty(exports, "createConversationServer", { enumerable: true, get: function () { return conversationServer_1.createConversationServer; } });
23
+ // Adaptive Card validation
24
+ var cardValidator_1 = require("./cardValidator");
25
+ Object.defineProperty(exports, "validateAdaptiveCard", { enumerable: true, get: function () { return cardValidator_1.validateAdaptiveCard; } });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Notification sender for multi-turn conversation execution.
3
+ *
4
+ * Sends custom notification activities through the playground's Factory
5
+ * infrastructure and polls ConversationManager for bot responses.
6
+ */
7
+ import type { PersonaConfig, Turn } from "./conversationTypes";
8
+ /**
9
+ * Send a notification activity and wait for the bot's response.
10
+ *
11
+ * The turn's turn_type must be a valid CustomActivityTemplateType
12
+ * (e.g., "sendEmail", "mentionInWord"). It is passed directly to
13
+ * the playground's CustomActivityService.
14
+ */
15
+ export declare function sendNotificationAndWait(conversationId: string, turn: Turn, persona: PersonaConfig | undefined, timeout: number): Promise<string>;
16
+ //# sourceMappingURL=notificationSender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notificationSender.d.ts","sourceRoot":"","sources":["../src/notificationSender.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAQ/D;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAyGjB"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * Notification sender for multi-turn conversation execution.
4
+ *
5
+ * Sends custom notification activities through the playground's Factory
6
+ * infrastructure and polls ConversationManager for bot responses.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.sendNotificationAndWait = void 0;
10
+ const server_1 = require("server");
11
+ const POLL_INTERVAL_MS = 200;
12
+ function log(msg) {
13
+ process.stderr.write(`[notification] ${msg}\n`);
14
+ }
15
+ /**
16
+ * Send a notification activity and wait for the bot's response.
17
+ *
18
+ * The turn's turn_type must be a valid CustomActivityTemplateType
19
+ * (e.g., "sendEmail", "mentionInWord"). It is passed directly to
20
+ * the playground's CustomActivityService.
21
+ */
22
+ async function sendNotificationAndWait(conversationId, turn, persona, timeout) {
23
+ const templateType = turn.turn_type;
24
+ const customActivityService = server_1.Factory.getCustomActivityService();
25
+ const accessor = server_1.Factory.getBaseUserBotAccessor();
26
+ const conversationManager = server_1.Factory.getConversationManager();
27
+ // Count existing messages before sending.
28
+ const messagesBefore = conversationManager.listMessages(conversationId);
29
+ const countBefore = messagesBefore.length;
30
+ // Generate the activity template as a mutable plain object.
31
+ const template = customActivityService.generateTemplate(conversationId, templateType);
32
+ // Override the from field if a persona is provided.
33
+ if (persona) {
34
+ const from = (template.from ?? {});
35
+ template.from = {
36
+ ...from,
37
+ id: persona.email ?? persona.id,
38
+ name: persona.name,
39
+ };
40
+ }
41
+ // Customize the activity body based on turn type.
42
+ if (templateType === "sendEmail") {
43
+ const entities = template.entities;
44
+ const emailEntity = entities?.find((e) => e.type === "emailNotification");
45
+ if (emailEntity) {
46
+ emailEntity.htmlBody = turn.prompt;
47
+ }
48
+ }
49
+ else if (templateType === "mentionInWord") {
50
+ template.text = turn.prompt;
51
+ if (turn.prompt_metadata?.documentUrl) {
52
+ const attachments = template.attachments;
53
+ if (attachments?.[0]) {
54
+ attachments[0].contentUrl = turn.prompt_metadata.documentUrl;
55
+ }
56
+ }
57
+ if (turn.prompt_metadata?.documentName) {
58
+ const attachments = template.attachments;
59
+ if (attachments?.[0]) {
60
+ attachments[0].name = turn.prompt_metadata.documentName;
61
+ }
62
+ }
63
+ }
64
+ else if (templateType === "meetingStart" || templateType === "meetingEnd") {
65
+ if (turn.prompt_metadata?.meetingTitle) {
66
+ const value = (template.value ?? {});
67
+ value.Title = turn.prompt_metadata.meetingTitle;
68
+ template.value = value;
69
+ }
70
+ }
71
+ else if (templateType === "participantJoin" || templateType === "participantLeave") {
72
+ if (turn.prompt) {
73
+ const value = (template.value ?? {});
74
+ const members = (value.members ?? []);
75
+ if (members[0]) {
76
+ const user = (members[0].user ?? {});
77
+ user.name = turn.prompt;
78
+ members[0].user = user;
79
+ value.members = members;
80
+ template.value = value;
81
+ }
82
+ }
83
+ }
84
+ else if (templateType === "messageReaction") {
85
+ const reaction = turn.reaction_type ?? "like";
86
+ template.reactionsAdded = [{ type: reaction }];
87
+ template.reactionsRemoved = [];
88
+ if (turn.reply_to_id) {
89
+ template.replyToId = turn.reply_to_id;
90
+ }
91
+ else {
92
+ // Default: react to last bot message
93
+ const messages = conversationManager.listMessages(conversationId);
94
+ const lastBotMsg = [...messages].reverse().find((m) => m.createdBy === "bot");
95
+ if (lastBotMsg) {
96
+ template.replyToId = lastBotMsg.content.id;
97
+ }
98
+ }
99
+ }
100
+ log(`Sending ${templateType} activity to conversation ${conversationId}`);
101
+ // sendActivity expects the Activity class from the server's dependency tree.
102
+ // We cast through unknown to bridge the inferred template type.
103
+ await accessor.sendActivity(template);
104
+ // Poll for new bot messages.
105
+ const deadline = Date.now() + timeout;
106
+ while (Date.now() < deadline) {
107
+ const messages = conversationManager.listMessages(conversationId);
108
+ const newBotMessages = messages.slice(countBefore).filter((m) => m.createdBy === "bot");
109
+ if (newBotMessages.length > 0) {
110
+ const responseText = newBotMessages
111
+ .map((m) => m.content.text ?? "")
112
+ .filter((t) => t.length > 0)
113
+ .join("\n\n");
114
+ return responseText;
115
+ }
116
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
117
+ }
118
+ throw new Error(`Timeout waiting for bot response to ${templateType} after ${timeout}ms`);
119
+ }
120
+ exports.sendNotificationAndWait = sendNotificationAndWait;
@@ -0,0 +1,29 @@
1
+ import { BotConnectorService, ConversationManager, Message } from "server";
2
+ /**
3
+ * Captures bot responses by hooking into BotConnectorService.processActivity
4
+ */
5
+ export declare class ResponseCapture {
6
+ private pendingResponses;
7
+ private originalProcessActivity?;
8
+ /**
9
+ * Hook into BotConnectorService to intercept responses
10
+ */
11
+ hookIntoBotConnectorService(service: BotConnectorService): void;
12
+ /**
13
+ * Wait for a response in the given conversation
14
+ */
15
+ waitForResponse(conversationId: string, conversationManager: ConversationManager, timeout: number): Promise<Message[]>;
16
+ /**
17
+ * Resolve waiting promises when a response arrives
18
+ */
19
+ private resolveWaiting;
20
+ /**
21
+ * Clear all pending responses
22
+ */
23
+ clear(): void;
24
+ /**
25
+ * Clear pending responses for a specific conversation only
26
+ */
27
+ clearConversation(conversationId: string): void;
28
+ }
29
+ //# sourceMappingURL=responseCapture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseCapture.d.ts","sourceRoot":"","sources":["../src/responseCapture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAI3E;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,gBAAgB,CAA2C;IACnE,OAAO,CAAC,uBAAuB,CAAC,CAAuD;IAEvF;;OAEG;IACH,2BAA2B,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAkD/D;;OAEG;IACG,eAAe,CACnB,cAAc,EAAE,MAAM,EACtB,mBAAmB,EAAE,mBAAmB,EACxC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,EAAE,CAAC;IA6BrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;CAOhD"}
@@ -0,0 +1,119 @@
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;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Conversation execution logic for the test harness.
3
+ *
4
+ * Runs a sequence of turns against a bot through a fresh TestClient
5
+ * and returns the bot's responses. No eval logic — just execution.
6
+ */
7
+ import type { E2EConfig, ConversationInput, ConversationResult } from "./conversationTypes";
8
+ export declare function log(msg: string): void;
9
+ export declare function logError(msg: string): void;
10
+ /**
11
+ * Run a single conversation through a fresh TestClient.
12
+ *
13
+ * Creates an isolated TestClient, sends each turn sequentially, and
14
+ * collects the bot's responses. On failure, remaining turns are skipped.
15
+ */
16
+ export declare function runConversation(config: E2EConfig, scenario: string, input: ConversationInput): Promise<ConversationResult>;
17
+ //# sourceMappingURL=runConversation.d.ts.map
@@ -0,0 +1 @@
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;AAiHD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CA+N7B"}