@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/responseCapture.js
DELETED
|
@@ -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"}
|
package/build/runConversation.js
DELETED
|
@@ -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"}
|
package/build/serverManager.js
DELETED
|
@@ -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"}
|
package/build/start-server.js
DELETED
|
@@ -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"}
|