@achieveai/hitl-mcp-server 1.0.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.
@@ -0,0 +1,477 @@
1
+ import express from 'express';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import open from 'open';
4
+ import { EventEmitter } from 'events';
5
+ export class DialogManager extends EventEmitter {
6
+ app;
7
+ port;
8
+ pendingDialogs;
9
+ server;
10
+ isInitialized = false;
11
+ constructor(port = 0) {
12
+ super();
13
+ this.port = port;
14
+ this.app = express();
15
+ this.pendingDialogs = new Map();
16
+ this.setupRoutes();
17
+ }
18
+ setupRoutes() {
19
+ this.app.use(express.json());
20
+ this.app.use(express.static('public'));
21
+ this.app.get('/dialog/:id', (req, res) => {
22
+ const dialogId = req.params.id;
23
+ const dialog = this.pendingDialogs.get(dialogId);
24
+ if (!dialog) {
25
+ res.status(404).send('Dialog not found or expired');
26
+ return;
27
+ }
28
+ const html = this.generateDialogHTML(dialog.request);
29
+ res.send(html);
30
+ });
31
+ this.app.post('/dialog/:id/response', (req, res) => {
32
+ const dialogId = req.params.id;
33
+ const dialog = this.pendingDialogs.get(dialogId);
34
+ if (!dialog) {
35
+ res.status(404).json({ error: 'Dialog not found or expired' });
36
+ return;
37
+ }
38
+ const response = {
39
+ id: dialogId,
40
+ selectedValues: req.body.selectedValues || [],
41
+ otherText: req.body.otherText,
42
+ timestamp: Date.now()
43
+ };
44
+ if (dialog.timeout) {
45
+ clearTimeout(dialog.timeout);
46
+ }
47
+ dialog.resolve(response);
48
+ this.pendingDialogs.delete(dialogId);
49
+ res.json({ success: true });
50
+ });
51
+ this.app.get('/health', (_req, res) => {
52
+ res.json({ status: 'healthy', pendingDialogs: this.pendingDialogs.size });
53
+ });
54
+ }
55
+ generateDialogHTML(request) {
56
+ return `<!DOCTYPE html>
57
+ <html lang="en">
58
+ <head>
59
+ <meta charset="UTF-8">
60
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
61
+ <title>Human In The Loop - Response Required</title>
62
+ <style>
63
+ * {
64
+ margin: 0;
65
+ padding: 0;
66
+ box-sizing: border-box;
67
+ }
68
+
69
+ body {
70
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
71
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
72
+ min-height: 100vh;
73
+ display: flex;
74
+ justify-content: center;
75
+ align-items: center;
76
+ padding: 20px;
77
+ }
78
+
79
+ .dialog-container {
80
+ background: white;
81
+ border-radius: 12px;
82
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
83
+ max-width: 600px;
84
+ width: 100%;
85
+ padding: 32px;
86
+ animation: slideIn 0.3s ease-out;
87
+ }
88
+
89
+ @keyframes slideIn {
90
+ from {
91
+ opacity: 0;
92
+ transform: translateY(-20px);
93
+ }
94
+ to {
95
+ opacity: 1;
96
+ transform: translateY(0);
97
+ }
98
+ }
99
+
100
+ .header {
101
+ margin-bottom: 24px;
102
+ }
103
+
104
+ .title {
105
+ font-size: 24px;
106
+ font-weight: 600;
107
+ color: #1a202c;
108
+ margin-bottom: 8px;
109
+ }
110
+
111
+ .subtitle {
112
+ color: #718096;
113
+ font-size: 14px;
114
+ }
115
+
116
+ .question {
117
+ font-size: 18px;
118
+ color: #2d3748;
119
+ margin-bottom: 8px;
120
+ font-weight: 500;
121
+ }
122
+
123
+ .context {
124
+ background: #f7fafc;
125
+ border-left: 4px solid #667eea;
126
+ padding: 12px;
127
+ margin-bottom: 24px;
128
+ font-size: 14px;
129
+ color: #4a5568;
130
+ border-radius: 4px;
131
+ }
132
+
133
+ .options-container {
134
+ margin-bottom: 24px;
135
+ }
136
+
137
+ .option {
138
+ display: flex;
139
+ align-items: flex-start;
140
+ margin-bottom: 16px;
141
+ padding: 12px;
142
+ border: 2px solid #e2e8f0;
143
+ border-radius: 8px;
144
+ cursor: pointer;
145
+ transition: all 0.2s;
146
+ }
147
+
148
+ .option:hover {
149
+ border-color: #667eea;
150
+ background: #f7fafc;
151
+ }
152
+
153
+ .option.selected {
154
+ border-color: #667eea;
155
+ background: #edf2ff;
156
+ }
157
+
158
+ .option input {
159
+ margin-right: 12px;
160
+ margin-top: 2px;
161
+ cursor: pointer;
162
+ }
163
+
164
+ .option-content {
165
+ flex: 1;
166
+ }
167
+
168
+ .option-label {
169
+ font-weight: 500;
170
+ color: #2d3748;
171
+ margin-bottom: 4px;
172
+ }
173
+
174
+ .option-description {
175
+ font-size: 14px;
176
+ color: #718096;
177
+ }
178
+
179
+ .other-section {
180
+ margin-bottom: 24px;
181
+ }
182
+
183
+ .other-label {
184
+ display: block;
185
+ margin-bottom: 8px;
186
+ font-weight: 500;
187
+ color: #2d3748;
188
+ }
189
+
190
+ .other-input {
191
+ width: 100%;
192
+ padding: 12px;
193
+ border: 2px solid #e2e8f0;
194
+ border-radius: 8px;
195
+ font-size: 16px;
196
+ transition: border-color 0.2s;
197
+ }
198
+
199
+ .other-input:focus {
200
+ outline: none;
201
+ border-color: #667eea;
202
+ }
203
+
204
+ .button-container {
205
+ display: flex;
206
+ gap: 12px;
207
+ }
208
+
209
+ .button {
210
+ flex: 1;
211
+ padding: 12px 24px;
212
+ border: none;
213
+ border-radius: 8px;
214
+ font-size: 16px;
215
+ font-weight: 500;
216
+ cursor: pointer;
217
+ transition: all 0.2s;
218
+ }
219
+
220
+ .button-primary {
221
+ background: #667eea;
222
+ color: white;
223
+ }
224
+
225
+ .button-primary:hover {
226
+ background: #5a67d8;
227
+ }
228
+
229
+ .button-secondary {
230
+ background: #e2e8f0;
231
+ color: #4a5568;
232
+ }
233
+
234
+ .button-secondary:hover {
235
+ background: #cbd5e0;
236
+ }
237
+
238
+ .button:disabled {
239
+ opacity: 0.5;
240
+ cursor: not-allowed;
241
+ }
242
+
243
+ .error-message {
244
+ color: #e53e3e;
245
+ font-size: 14px;
246
+ margin-top: 8px;
247
+ display: none;
248
+ }
249
+ </style>
250
+ </head>
251
+ <body>
252
+ <div class="dialog-container">
253
+ <div class="header">
254
+ <h1 class="title">Human Input Required</h1>
255
+ <p class="subtitle">An AI agent needs your guidance to continue</p>
256
+ </div>
257
+
258
+ <div class="question">${this.escapeHtml(request.question)}</div>
259
+
260
+ ${request.context ? `
261
+ <div class="context">
262
+ <strong>Context:</strong> ${this.escapeHtml(request.context)}
263
+ </div>
264
+ ` : ''}
265
+
266
+ <div class="options-container">
267
+ ${request.options.map((option, index) => `
268
+ <div class="option" onclick="toggleOption(${index})">
269
+ <input type="${request.allowMultiple ? 'checkbox' : 'radio'}"
270
+ name="options"
271
+ id="option-${index}"
272
+ value="${this.escapeHtml(option.value)}">
273
+ <div class="option-content">
274
+ <div class="option-label">${this.escapeHtml(option.label)}</div>
275
+ ${option.description ? `
276
+ <div class="option-description">${this.escapeHtml(option.description)}</div>
277
+ ` : ''}
278
+ </div>
279
+ </div>
280
+ `).join('')}
281
+ </div>
282
+
283
+ ${request.allowOther ? `
284
+ <div class="other-section">
285
+ <label class="other-label" for="other-input">Other (please specify):</label>
286
+ <textarea class="other-input"
287
+ id="other-input"
288
+ rows="3"
289
+ placeholder="Enter your custom response here..."></textarea>
290
+ </div>
291
+ ` : ''}
292
+
293
+ <div class="error-message" id="error-message"></div>
294
+
295
+ <div class="button-container">
296
+ <button class="button button-secondary" onclick="skipDialog()">Skip</button>
297
+ <button class="button button-primary" onclick="submitResponse()">Submit Response</button>
298
+ </div>
299
+ </div>
300
+
301
+ <script>
302
+ const dialogId = '${request.id}';
303
+ const allowMultiple = ${request.allowMultiple};
304
+ const allowOther = ${request.allowOther};
305
+
306
+ function toggleOption(index) {
307
+ const option = document.getElementById('option-' + index);
308
+ const optionDiv = option.closest('.option');
309
+
310
+ if (!allowMultiple) {
311
+ document.querySelectorAll('.option').forEach(el => {
312
+ el.classList.remove('selected');
313
+ });
314
+ document.querySelectorAll('input[name="options"]').forEach(el => {
315
+ el.checked = false;
316
+ });
317
+ }
318
+
319
+ option.checked = !option.checked;
320
+ if (option.checked) {
321
+ optionDiv.classList.add('selected');
322
+ } else {
323
+ optionDiv.classList.remove('selected');
324
+ }
325
+ }
326
+
327
+ function getSelectedValues() {
328
+ const selected = [];
329
+ document.querySelectorAll('input[name="options"]:checked').forEach(el => {
330
+ selected.push(el.value);
331
+ });
332
+ return selected;
333
+ }
334
+
335
+ async function submitResponse() {
336
+ const selectedValues = getSelectedValues();
337
+ const otherText = allowOther ? document.getElementById('other-input').value.trim() : '';
338
+
339
+ if (selectedValues.length === 0 && !otherText) {
340
+ showError('Please select at least one option or provide a custom response');
341
+ return;
342
+ }
343
+
344
+ try {
345
+ const response = await fetch(\`/dialog/\${dialogId}/response\`, {
346
+ method: 'POST',
347
+ headers: { 'Content-Type': 'application/json' },
348
+ body: JSON.stringify({ selectedValues, otherText })
349
+ });
350
+
351
+ if (response.ok) {
352
+ showSuccess();
353
+ } else {
354
+ showError('Failed to submit response. Please try again.');
355
+ }
356
+ } catch (error) {
357
+ showError('Network error. Please check your connection.');
358
+ }
359
+ }
360
+
361
+ async function skipDialog() {
362
+ try {
363
+ const response = await fetch(\`/dialog/\${dialogId}/response\`, {
364
+ method: 'POST',
365
+ headers: { 'Content-Type': 'application/json' },
366
+ body: JSON.stringify({ selectedValues: [], otherText: 'SKIPPED' })
367
+ });
368
+
369
+ if (response.ok) {
370
+ showSuccess('Response skipped');
371
+ }
372
+ } catch (error) {
373
+ showError('Failed to skip. Please try again.');
374
+ }
375
+ }
376
+
377
+ function showError(message) {
378
+ const errorEl = document.getElementById('error-message');
379
+ errorEl.textContent = message;
380
+ errorEl.style.display = 'block';
381
+ setTimeout(() => {
382
+ errorEl.style.display = 'none';
383
+ }, 5000);
384
+ }
385
+
386
+ function showSuccess(message = 'Response submitted successfully!') {
387
+ document.querySelector('.dialog-container').innerHTML = \`
388
+ <div style="text-align: center; padding: 48px;">
389
+ <div style="font-size: 48px; margin-bottom: 16px;">✓</div>
390
+ <h2 style="font-size: 24px; color: #2d3748; margin-bottom: 8px;">\${message}</h2>
391
+ <p style="color: #718096;">You can close this window now.</p>
392
+ </div>
393
+ \`;
394
+ setTimeout(() => {
395
+ window.close();
396
+ }, 2000);
397
+ }
398
+
399
+ // Allow Enter key to submit when other input is focused
400
+ if (allowOther) {
401
+ document.getElementById('other-input').addEventListener('keydown', (e) => {
402
+ if (e.key === 'Enter' && e.ctrlKey) {
403
+ submitResponse();
404
+ }
405
+ });
406
+ }
407
+ </script>
408
+ </body>
409
+ </html>`;
410
+ }
411
+ escapeHtml(text) {
412
+ const map = {
413
+ '&': '&amp;',
414
+ '<': '&lt;',
415
+ '>': '&gt;',
416
+ '"': '&quot;',
417
+ "'": '&#039;'
418
+ };
419
+ return text.replace(/[&<>"']/g, m => map[m]);
420
+ }
421
+ async initialize() {
422
+ if (this.isInitialized) {
423
+ return this.port;
424
+ }
425
+ return new Promise((resolve, reject) => {
426
+ this.server = this.app.listen(this.port, () => {
427
+ const addr = this.server.address();
428
+ this.port = typeof addr === 'object' && addr !== null ? addr.port : this.port;
429
+ this.isInitialized = true;
430
+ console.error(`Dialog server running on port ${this.port}`);
431
+ resolve(this.port);
432
+ });
433
+ this.server.on('error', (error) => {
434
+ reject(error);
435
+ });
436
+ });
437
+ }
438
+ async showDialog(request) {
439
+ if (!this.isInitialized) {
440
+ await this.initialize();
441
+ }
442
+ return new Promise((resolve, reject) => {
443
+ const dialogId = request.id || uuidv4();
444
+ const fullRequest = { ...request, id: dialogId };
445
+ let timeout;
446
+ if (request.timeout) {
447
+ timeout = setTimeout(() => {
448
+ this.pendingDialogs.delete(dialogId);
449
+ reject(new Error('Dialog timeout'));
450
+ }, request.timeout);
451
+ }
452
+ this.pendingDialogs.set(dialogId, {
453
+ request: fullRequest,
454
+ resolve,
455
+ reject,
456
+ timeout
457
+ });
458
+ const dialogUrl = `http://localhost:${this.port}/dialog/${dialogId}`;
459
+ console.error(`Opening dialog: ${dialogUrl}`);
460
+ open(dialogUrl).catch((err) => {
461
+ console.error('Failed to open browser:', err);
462
+ console.error(`Please manually open: ${dialogUrl}`);
463
+ });
464
+ });
465
+ }
466
+ async close() {
467
+ if (this.server) {
468
+ return new Promise((resolve) => {
469
+ this.server.close(() => {
470
+ this.isInitialized = false;
471
+ resolve();
472
+ });
473
+ });
474
+ }
475
+ }
476
+ }
477
+ //# sourceMappingURL=dialog-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-manager.js","sourceRoot":"","sources":["../src/dialog-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dialog-manager.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-manager.test.d.ts","sourceRoot":"","sources":["../src/dialog-manager.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,156 @@
1
+ // Simple test to verify the dialog manager works
2
+ import { DialogManager } from './dialog-manager.js';
3
+ async function runTests() {
4
+ console.log('Starting DialogManager tests...\n');
5
+ const manager = new DialogManager();
6
+ let testsPassed = 0;
7
+ let testsFailed = 0;
8
+ // Test 1: Initialization
9
+ try {
10
+ const port = await manager.initialize();
11
+ console.log('✓ Test 1 - Initialization: Server started on port', port);
12
+ testsPassed++;
13
+ }
14
+ catch (error) {
15
+ console.error('✗ Test 1 - Initialization failed:', error);
16
+ testsFailed++;
17
+ }
18
+ // Test 2: HTML generation with XSS protection
19
+ try {
20
+ const html = manager.generateDialogHTML({
21
+ id: 'test',
22
+ question: '<script>alert("XSS")</script>',
23
+ options: [
24
+ {
25
+ label: '<b>Bold</b>',
26
+ value: 'test',
27
+ description: '"Quotes" & \'apostrophes\''
28
+ }
29
+ ],
30
+ allowMultiple: false,
31
+ allowOther: true
32
+ });
33
+ if (html.includes('<script>alert') || html.includes('<b>Bold</b>')) {
34
+ throw new Error('XSS protection failed');
35
+ }
36
+ if (!html.includes('&lt;script&gt;') || !html.includes('&lt;b&gt;')) {
37
+ throw new Error('HTML should be escaped');
38
+ }
39
+ console.log('✓ Test 2 - XSS Protection: HTML properly escaped');
40
+ testsPassed++;
41
+ }
42
+ catch (error) {
43
+ console.error('✗ Test 2 - XSS Protection failed:', error);
44
+ testsFailed++;
45
+ }
46
+ // Test 3: Radio vs Checkbox rendering
47
+ try {
48
+ const radioHtml = manager.generateDialogHTML({
49
+ id: 'test',
50
+ question: 'Choose one',
51
+ options: [
52
+ { label: 'A', value: 'a' },
53
+ { label: 'B', value: 'b' }
54
+ ],
55
+ allowMultiple: false,
56
+ allowOther: false
57
+ });
58
+ const checkboxHtml = manager.generateDialogHTML({
59
+ id: 'test',
60
+ question: 'Choose many',
61
+ options: [
62
+ { label: 'A', value: 'a' },
63
+ { label: 'B', value: 'b' }
64
+ ],
65
+ allowMultiple: true,
66
+ allowOther: false
67
+ });
68
+ if (!radioHtml.includes('type="radio"') || radioHtml.includes('type="checkbox"')) {
69
+ throw new Error('Single choice should use radio buttons');
70
+ }
71
+ if (!checkboxHtml.includes('type="checkbox"') || checkboxHtml.includes('type="radio"')) {
72
+ throw new Error('Multiple choice should use checkboxes');
73
+ }
74
+ console.log('✓ Test 3 - Input Types: Correct radio/checkbox rendering');
75
+ testsPassed++;
76
+ }
77
+ catch (error) {
78
+ console.error('✗ Test 3 - Input Types failed:', error);
79
+ testsFailed++;
80
+ }
81
+ // Test 4: Other field inclusion
82
+ try {
83
+ const withOther = manager.generateDialogHTML({
84
+ id: 'test',
85
+ question: 'Choose',
86
+ options: [{ label: 'A', value: 'a' }],
87
+ allowMultiple: false,
88
+ allowOther: true
89
+ });
90
+ const withoutOther = manager.generateDialogHTML({
91
+ id: 'test',
92
+ question: 'Choose',
93
+ options: [{ label: 'A', value: 'a' }],
94
+ allowMultiple: false,
95
+ allowOther: false
96
+ });
97
+ if (!withOther.includes('other-input')) {
98
+ throw new Error('Should include other field when allowed');
99
+ }
100
+ if (withoutOther.includes('Other (please specify)')) {
101
+ throw new Error('Should not include other field when not allowed');
102
+ }
103
+ console.log('✓ Test 4 - Other Field: Correctly included/excluded');
104
+ testsPassed++;
105
+ }
106
+ catch (error) {
107
+ console.error('✗ Test 4 - Other Field failed:', error);
108
+ testsFailed++;
109
+ }
110
+ // Test 5: Context inclusion
111
+ try {
112
+ const withContext = manager.generateDialogHTML({
113
+ id: 'test',
114
+ question: 'Test question',
115
+ options: [{ label: 'Option 1', value: 'opt1' }],
116
+ allowMultiple: false,
117
+ allowOther: false,
118
+ context: 'Important context information'
119
+ });
120
+ const withoutContext = manager.generateDialogHTML({
121
+ id: 'test',
122
+ question: 'Test question',
123
+ options: [{ label: 'Option 1', value: 'opt1' }],
124
+ allowMultiple: false,
125
+ allowOther: false
126
+ });
127
+ if (!withContext.includes('Important context information')) {
128
+ throw new Error('Should include context when provided');
129
+ }
130
+ if (withoutContext.includes('Context:')) {
131
+ throw new Error('Should not include context section when not provided');
132
+ }
133
+ console.log('✓ Test 5 - Context: Correctly included/excluded');
134
+ testsPassed++;
135
+ }
136
+ catch (error) {
137
+ console.error('✗ Test 5 - Context failed:', error);
138
+ testsFailed++;
139
+ }
140
+ // Cleanup
141
+ await manager.close();
142
+ // Summary
143
+ console.log(`\n========== Test Summary ==========`);
144
+ console.log(`Passed: ${testsPassed}`);
145
+ console.log(`Failed: ${testsFailed}`);
146
+ console.log(`Total: ${testsPassed + testsFailed}`);
147
+ if (testsFailed > 0) {
148
+ process.exit(1);
149
+ }
150
+ }
151
+ // Run tests
152
+ runTests().catch(error => {
153
+ console.error('Test suite failed:', error);
154
+ process.exit(1);
155
+ });
156
+ //# sourceMappingURL=dialog-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-manager.test.js","sourceRoot":"","sources":["../src/dialog-manager.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}