@vapi-ai/web 2.3.7 → 2.3.9
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/api.ts +10641 -1229
- package/dist/api.d.ts +10025 -1097
- package/dist/api.js +190 -59
- package/dist/vapi.d.ts +28 -1
- package/dist/vapi.js +332 -34
- package/package.json +1 -1
- package/vapi.ts +390 -36
package/vapi.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { client } from './client';
|
|
|
25
25
|
export interface AddMessageMessage {
|
|
26
26
|
type: 'add-message';
|
|
27
27
|
message: ChatCompletionMessageParam;
|
|
28
|
+
triggerResponseEnabled?: boolean;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export interface ControlMessages {
|
|
@@ -55,7 +56,36 @@ type VapiEventNames =
|
|
|
55
56
|
| 'message'
|
|
56
57
|
| 'video'
|
|
57
58
|
| 'error'
|
|
58
|
-
| '
|
|
59
|
+
| 'camera-error'
|
|
60
|
+
| 'network-quality-change'
|
|
61
|
+
| 'network-connection'
|
|
62
|
+
| 'daily-participant-updated'
|
|
63
|
+
| 'call-start-progress'
|
|
64
|
+
| 'call-start-success'
|
|
65
|
+
| 'call-start-failed';
|
|
66
|
+
|
|
67
|
+
interface CallStartProgressEvent {
|
|
68
|
+
stage: string;
|
|
69
|
+
status: 'started' | 'completed' | 'failed';
|
|
70
|
+
duration?: number;
|
|
71
|
+
timestamp: string;
|
|
72
|
+
metadata?: Record<string, any>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface CallStartSuccessEvent {
|
|
76
|
+
totalDuration: number;
|
|
77
|
+
callId?: string;
|
|
78
|
+
timestamp: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface CallStartFailedEvent {
|
|
82
|
+
stage: string;
|
|
83
|
+
totalDuration: number;
|
|
84
|
+
error: string;
|
|
85
|
+
errorStack?: string;
|
|
86
|
+
timestamp: string;
|
|
87
|
+
context: Record<string, any>;
|
|
88
|
+
}
|
|
59
89
|
|
|
60
90
|
type VapiEventListeners = {
|
|
61
91
|
'call-end': () => void;
|
|
@@ -66,7 +96,13 @@ type VapiEventListeners = {
|
|
|
66
96
|
video: (track: MediaStreamTrack) => void;
|
|
67
97
|
message: (message: any) => void;
|
|
68
98
|
error: (error: any) => void;
|
|
99
|
+
'camera-error': (error: any) => void;
|
|
100
|
+
'network-quality-change': (event: any) => void;
|
|
101
|
+
'network-connection': (event: any) => void;
|
|
69
102
|
'daily-participant-updated': (participant: DailyParticipant) => void;
|
|
103
|
+
'call-start-progress': (event: CallStartProgressEvent) => void;
|
|
104
|
+
'call-start-success': (event: CallStartSuccessEvent) => void;
|
|
105
|
+
'call-start-failed': (event: CallStartFailedEvent) => void;
|
|
70
106
|
};
|
|
71
107
|
|
|
72
108
|
async function startAudioPlayer(
|
|
@@ -203,16 +239,53 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
203
239
|
workflow?: CreateWorkflowDTO | string,
|
|
204
240
|
workflowOverrides?: WorkflowOverrides,
|
|
205
241
|
): Promise<Call | null> {
|
|
242
|
+
const startTime = Date.now();
|
|
243
|
+
|
|
244
|
+
// Input validation with detailed error messages
|
|
206
245
|
if (!assistant && !squad && !workflow) {
|
|
207
|
-
|
|
246
|
+
const error = new Error('Assistant or Squad or Workflow must be provided.');
|
|
247
|
+
this.emit('error', {
|
|
248
|
+
type: 'validation-error',
|
|
249
|
+
stage: 'input-validation',
|
|
250
|
+
message: error.message,
|
|
251
|
+
timestamp: new Date().toISOString()
|
|
252
|
+
});
|
|
253
|
+
throw error;
|
|
208
254
|
}
|
|
209
255
|
|
|
210
256
|
if (this.started) {
|
|
257
|
+
this.emit('call-start-progress', {
|
|
258
|
+
stage: 'validation',
|
|
259
|
+
status: 'failed',
|
|
260
|
+
timestamp: new Date().toISOString(),
|
|
261
|
+
metadata: { reason: 'already-started' }
|
|
262
|
+
});
|
|
211
263
|
return null;
|
|
212
264
|
}
|
|
265
|
+
|
|
266
|
+
this.emit('call-start-progress', {
|
|
267
|
+
stage: 'initialization',
|
|
268
|
+
status: 'started',
|
|
269
|
+
timestamp: new Date().toISOString(),
|
|
270
|
+
metadata: {
|
|
271
|
+
hasAssistant: !!assistant,
|
|
272
|
+
hasSquad: !!squad,
|
|
273
|
+
hasWorkflow: !!workflow
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
213
277
|
this.started = true;
|
|
214
278
|
|
|
215
279
|
try {
|
|
280
|
+
// Stage 1: Create web call
|
|
281
|
+
this.emit('call-start-progress', {
|
|
282
|
+
stage: 'web-call-creation',
|
|
283
|
+
status: 'started',
|
|
284
|
+
timestamp: new Date().toISOString()
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const webCallStartTime = Date.now();
|
|
288
|
+
|
|
216
289
|
const webCall = (
|
|
217
290
|
await client.call.callControllerCreateWebCall({
|
|
218
291
|
assistant: typeof assistant === 'string' ? undefined : assistant,
|
|
@@ -225,8 +298,27 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
225
298
|
workflowOverrides,
|
|
226
299
|
})
|
|
227
300
|
).data;
|
|
301
|
+
|
|
302
|
+
const webCallDuration = Date.now() - webCallStartTime;
|
|
303
|
+
this.emit('call-start-progress', {
|
|
304
|
+
stage: 'web-call-creation',
|
|
305
|
+
status: 'completed',
|
|
306
|
+
duration: webCallDuration,
|
|
307
|
+
timestamp: new Date().toISOString(),
|
|
308
|
+
metadata: {
|
|
309
|
+
callId: webCall?.id || 'unknown',
|
|
310
|
+
videoRecordingEnabled: webCall?.artifactPlan?.videoRecordingEnabled ?? false,
|
|
311
|
+
voiceProvider: webCall?.assistant?.voice?.provider || 'unknown'
|
|
312
|
+
}
|
|
313
|
+
});
|
|
228
314
|
|
|
229
315
|
if (this.call) {
|
|
316
|
+
this.emit('call-start-progress', {
|
|
317
|
+
stage: 'daily-call-object-creation',
|
|
318
|
+
status: 'started',
|
|
319
|
+
timestamp: new Date().toISOString(),
|
|
320
|
+
metadata: { action: 'cleanup-existing' }
|
|
321
|
+
});
|
|
230
322
|
this.cleanup();
|
|
231
323
|
}
|
|
232
324
|
|
|
@@ -235,11 +327,53 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
235
327
|
|
|
236
328
|
const isVideoEnabled = webCall?.assistant?.voice?.provider === 'tavus';
|
|
237
329
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
330
|
+
// Stage 2: Create Daily call object
|
|
331
|
+
this.emit('call-start-progress', {
|
|
332
|
+
stage: 'daily-call-object-creation',
|
|
333
|
+
status: 'started',
|
|
334
|
+
timestamp: new Date().toISOString(),
|
|
335
|
+
metadata: {
|
|
336
|
+
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
337
|
+
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
338
|
+
isVideoRecordingEnabled,
|
|
339
|
+
isVideoEnabled
|
|
340
|
+
}
|
|
242
341
|
});
|
|
342
|
+
|
|
343
|
+
const dailyCallStartTime = Date.now();
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
this.call = DailyIframe.createCallObject({
|
|
347
|
+
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
348
|
+
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
349
|
+
dailyConfig: this.dailyCallConfig,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const dailyCallDuration = Date.now() - dailyCallStartTime;
|
|
353
|
+
this.emit('call-start-progress', {
|
|
354
|
+
stage: 'daily-call-object-creation',
|
|
355
|
+
status: 'completed',
|
|
356
|
+
duration: dailyCallDuration,
|
|
357
|
+
timestamp: new Date().toISOString()
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
const dailyCallDuration = Date.now() - dailyCallStartTime;
|
|
361
|
+
this.emit('call-start-progress', {
|
|
362
|
+
stage: 'daily-call-object-creation',
|
|
363
|
+
status: 'failed',
|
|
364
|
+
duration: dailyCallDuration,
|
|
365
|
+
timestamp: new Date().toISOString(),
|
|
366
|
+
metadata: { error: error?.toString() }
|
|
367
|
+
});
|
|
368
|
+
this.emit('error', {
|
|
369
|
+
type: 'daily-call-object-creation-error',
|
|
370
|
+
stage: 'daily-call-object-creation',
|
|
371
|
+
error,
|
|
372
|
+
timestamp: new Date().toISOString()
|
|
373
|
+
});
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
|
|
243
377
|
this.call.iframe()?.style.setProperty('display', 'none');
|
|
244
378
|
|
|
245
379
|
this.call.on('left-meeting', () => {
|
|
@@ -266,7 +400,15 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
266
400
|
});
|
|
267
401
|
|
|
268
402
|
this.call.on('camera-error', (error: any) => {
|
|
269
|
-
this.emit('error', error);
|
|
403
|
+
this.emit('camera-error', error);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
this.call.on('network-quality-change', (event: any) => {
|
|
407
|
+
this.emit('network-quality-change', event);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
this.call.on('network-connection', (event: any) => {
|
|
411
|
+
this.emit('network-connection', event);
|
|
270
412
|
});
|
|
271
413
|
|
|
272
414
|
this.call.on('track-started', async (e) => {
|
|
@@ -312,41 +454,182 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
312
454
|
destroyAudioPlayer(e.participant.session_id);
|
|
313
455
|
});
|
|
314
456
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
457
|
+
// Stage 3: Mobile device handling and permissions
|
|
458
|
+
const isMobile = this.isMobileDevice();
|
|
459
|
+
this.emit('call-start-progress', {
|
|
460
|
+
stage: 'mobile-permissions',
|
|
461
|
+
status: 'started',
|
|
462
|
+
timestamp: new Date().toISOString(),
|
|
463
|
+
metadata: { isMobile }
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (isMobile) {
|
|
467
|
+
const mobileWaitStartTime = Date.now();
|
|
318
468
|
await this.sleep(1000);
|
|
469
|
+
const mobileWaitDuration = Date.now() - mobileWaitStartTime;
|
|
470
|
+
this.emit('call-start-progress', {
|
|
471
|
+
stage: 'mobile-permissions',
|
|
472
|
+
status: 'completed',
|
|
473
|
+
duration: mobileWaitDuration,
|
|
474
|
+
timestamp: new Date().toISOString(),
|
|
475
|
+
metadata: { action: 'permissions-wait' }
|
|
476
|
+
});
|
|
477
|
+
} else {
|
|
478
|
+
this.emit('call-start-progress', {
|
|
479
|
+
stage: 'mobile-permissions',
|
|
480
|
+
status: 'completed',
|
|
481
|
+
timestamp: new Date().toISOString(),
|
|
482
|
+
metadata: { action: 'skipped-not-mobile' }
|
|
483
|
+
});
|
|
319
484
|
}
|
|
320
485
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
486
|
+
// Stage 4: Join the call
|
|
487
|
+
this.emit('call-start-progress', {
|
|
488
|
+
stage: 'daily-call-join',
|
|
489
|
+
status: 'started',
|
|
490
|
+
timestamp: new Date().toISOString()
|
|
325
491
|
});
|
|
492
|
+
|
|
493
|
+
const joinStartTime = Date.now();
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
await this.call.join({
|
|
497
|
+
// @ts-expect-error This exists
|
|
498
|
+
url: webCall.webCallUrl,
|
|
499
|
+
subscribeToTracksAutomatically: false,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const joinDuration = Date.now() - joinStartTime;
|
|
503
|
+
this.emit('call-start-progress', {
|
|
504
|
+
stage: 'daily-call-join',
|
|
505
|
+
status: 'completed',
|
|
506
|
+
duration: joinDuration,
|
|
507
|
+
timestamp: new Date().toISOString()
|
|
508
|
+
});
|
|
509
|
+
} catch (error) {
|
|
510
|
+
const joinDuration = Date.now() - joinStartTime;
|
|
511
|
+
this.emit('call-start-progress', {
|
|
512
|
+
stage: 'daily-call-join',
|
|
513
|
+
status: 'failed',
|
|
514
|
+
duration: joinDuration,
|
|
515
|
+
timestamp: new Date().toISOString(),
|
|
516
|
+
metadata: { error: error?.toString() }
|
|
517
|
+
});
|
|
518
|
+
this.emit('error', {
|
|
519
|
+
type: 'daily-call-join-error',
|
|
520
|
+
stage: 'daily-call-join',
|
|
521
|
+
error,
|
|
522
|
+
duration: joinDuration,
|
|
523
|
+
timestamp: new Date().toISOString()
|
|
524
|
+
});
|
|
525
|
+
throw error;
|
|
526
|
+
}
|
|
326
527
|
|
|
528
|
+
// Stage 5: Video recording setup (if enabled)
|
|
327
529
|
if (isVideoRecordingEnabled) {
|
|
530
|
+
this.emit('call-start-progress', {
|
|
531
|
+
stage: 'video-recording-setup',
|
|
532
|
+
status: 'started',
|
|
533
|
+
timestamp: new Date().toISOString()
|
|
534
|
+
});
|
|
535
|
+
|
|
328
536
|
const recordingRequestedTime = new Date().getTime();
|
|
537
|
+
const recordingStartTime = Date.now();
|
|
329
538
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
539
|
+
try {
|
|
540
|
+
this.call.startRecording({
|
|
541
|
+
width: 1280,
|
|
542
|
+
height: 720,
|
|
543
|
+
backgroundColor: '#FF1F2D3D',
|
|
544
|
+
layout: {
|
|
545
|
+
preset: 'default',
|
|
546
|
+
},
|
|
547
|
+
});
|
|
338
548
|
|
|
339
|
-
|
|
340
|
-
this.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
549
|
+
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
550
|
+
this.emit('call-start-progress', {
|
|
551
|
+
stage: 'video-recording-setup',
|
|
552
|
+
status: 'completed',
|
|
553
|
+
duration: recordingSetupDuration,
|
|
554
|
+
timestamp: new Date().toISOString()
|
|
345
555
|
});
|
|
556
|
+
|
|
557
|
+
this.call.on('recording-started', () => {
|
|
558
|
+
const totalRecordingDelay = (new Date().getTime() - recordingRequestedTime) / 1000;
|
|
559
|
+
this.emit('call-start-progress', {
|
|
560
|
+
stage: 'video-recording-started',
|
|
561
|
+
status: 'completed',
|
|
562
|
+
timestamp: new Date().toISOString(),
|
|
563
|
+
metadata: { delaySeconds: totalRecordingDelay }
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
this.send({
|
|
567
|
+
type: 'control',
|
|
568
|
+
control: 'say-first-message',
|
|
569
|
+
videoRecordingStartDelaySeconds: totalRecordingDelay,
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
} catch (error) {
|
|
573
|
+
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
574
|
+
this.emit('call-start-progress', {
|
|
575
|
+
stage: 'video-recording-setup',
|
|
576
|
+
status: 'failed',
|
|
577
|
+
duration: recordingSetupDuration,
|
|
578
|
+
timestamp: new Date().toISOString(),
|
|
579
|
+
metadata: { error: error?.toString() }
|
|
580
|
+
});
|
|
581
|
+
this.emit('error', {
|
|
582
|
+
type: 'video-recording-setup-error',
|
|
583
|
+
stage: 'video-recording-setup',
|
|
584
|
+
error,
|
|
585
|
+
timestamp: new Date().toISOString()
|
|
586
|
+
});
|
|
587
|
+
// Don't throw here, video recording is optional
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
this.emit('call-start-progress', {
|
|
591
|
+
stage: 'video-recording-setup',
|
|
592
|
+
status: 'completed',
|
|
593
|
+
timestamp: new Date().toISOString(),
|
|
594
|
+
metadata: { action: 'skipped-not-enabled' }
|
|
346
595
|
});
|
|
347
596
|
}
|
|
348
597
|
|
|
349
|
-
|
|
598
|
+
// Stage 6: Audio level observer setup
|
|
599
|
+
this.emit('call-start-progress', {
|
|
600
|
+
stage: 'audio-observer-setup',
|
|
601
|
+
status: 'started',
|
|
602
|
+
timestamp: new Date().toISOString()
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const audioObserverStartTime = Date.now();
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
609
|
+
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
610
|
+
this.emit('call-start-progress', {
|
|
611
|
+
stage: 'audio-observer-setup',
|
|
612
|
+
status: 'completed',
|
|
613
|
+
duration: audioObserverDuration,
|
|
614
|
+
timestamp: new Date().toISOString()
|
|
615
|
+
});
|
|
616
|
+
} catch (error) {
|
|
617
|
+
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
618
|
+
this.emit('call-start-progress', {
|
|
619
|
+
stage: 'audio-observer-setup',
|
|
620
|
+
status: 'failed',
|
|
621
|
+
duration: audioObserverDuration,
|
|
622
|
+
timestamp: new Date().toISOString(),
|
|
623
|
+
metadata: { error: error?.toString() }
|
|
624
|
+
});
|
|
625
|
+
this.emit('error', {
|
|
626
|
+
type: 'audio-observer-setup-error',
|
|
627
|
+
stage: 'audio-observer-setup',
|
|
628
|
+
error,
|
|
629
|
+
timestamp: new Date().toISOString()
|
|
630
|
+
});
|
|
631
|
+
// Don't throw here, this is non-critical
|
|
632
|
+
}
|
|
350
633
|
|
|
351
634
|
this.call.on('remote-participants-audio-level', (e) => {
|
|
352
635
|
if (e) this.handleRemoteParticipantsAudioLevel(e);
|
|
@@ -371,18 +654,89 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
371
654
|
}
|
|
372
655
|
});
|
|
373
656
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
657
|
+
// Stage 7: Audio processing setup
|
|
658
|
+
this.emit('call-start-progress', {
|
|
659
|
+
stage: 'audio-processing-setup',
|
|
660
|
+
status: 'started',
|
|
661
|
+
timestamp: new Date().toISOString()
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
const audioProcessingStartTime = Date.now();
|
|
665
|
+
|
|
666
|
+
try {
|
|
667
|
+
this.call.updateInputSettings({
|
|
668
|
+
audio: {
|
|
669
|
+
processor: {
|
|
670
|
+
type: 'noise-cancellation',
|
|
671
|
+
},
|
|
378
672
|
},
|
|
379
|
-
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
676
|
+
this.emit('call-start-progress', {
|
|
677
|
+
stage: 'audio-processing-setup',
|
|
678
|
+
status: 'completed',
|
|
679
|
+
duration: audioProcessingDuration,
|
|
680
|
+
timestamp: new Date().toISOString()
|
|
681
|
+
});
|
|
682
|
+
} catch (error) {
|
|
683
|
+
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
684
|
+
this.emit('call-start-progress', {
|
|
685
|
+
stage: 'audio-processing-setup',
|
|
686
|
+
status: 'failed',
|
|
687
|
+
duration: audioProcessingDuration,
|
|
688
|
+
timestamp: new Date().toISOString(),
|
|
689
|
+
metadata: { error: error?.toString() }
|
|
690
|
+
});
|
|
691
|
+
this.emit('error', {
|
|
692
|
+
type: 'audio-processing-setup-error',
|
|
693
|
+
stage: 'audio-processing-setup',
|
|
694
|
+
error,
|
|
695
|
+
timestamp: new Date().toISOString()
|
|
696
|
+
});
|
|
697
|
+
// Don't throw here, this is non-critical
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const totalDuration = Date.now() - startTime;
|
|
701
|
+
this.emit('call-start-success', {
|
|
702
|
+
totalDuration,
|
|
703
|
+
callId: webCall?.id || 'unknown',
|
|
704
|
+
timestamp: new Date().toISOString()
|
|
380
705
|
});
|
|
381
706
|
|
|
382
707
|
return webCall;
|
|
383
708
|
} catch (e) {
|
|
384
|
-
|
|
385
|
-
|
|
709
|
+
const totalDuration = Date.now() - startTime;
|
|
710
|
+
|
|
711
|
+
this.emit('call-start-failed', {
|
|
712
|
+
stage: 'unknown',
|
|
713
|
+
totalDuration,
|
|
714
|
+
error: e?.toString() || 'Unknown error occurred',
|
|
715
|
+
errorStack: e instanceof Error ? e.stack : 'No stack trace available',
|
|
716
|
+
timestamp: new Date().toISOString(),
|
|
717
|
+
context: {
|
|
718
|
+
hasAssistant: !!assistant,
|
|
719
|
+
hasSquad: !!squad,
|
|
720
|
+
hasWorkflow: !!workflow,
|
|
721
|
+
isMobile: this.isMobileDevice()
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// Also emit the generic error event for backward compatibility
|
|
726
|
+
this.emit('error', {
|
|
727
|
+
type: 'start-method-error',
|
|
728
|
+
stage: 'unknown',
|
|
729
|
+
error: e,
|
|
730
|
+
totalDuration,
|
|
731
|
+
timestamp: new Date().toISOString(),
|
|
732
|
+
context: {
|
|
733
|
+
hasAssistant: !!assistant,
|
|
734
|
+
hasSquad: !!squad,
|
|
735
|
+
hasWorkflow: !!workflow,
|
|
736
|
+
isMobile: this.isMobileDevice()
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
|
|
386
740
|
this.cleanup();
|
|
387
741
|
return null;
|
|
388
742
|
}
|