mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04:00
feat(voice-call): add configurable stale call reaper
Adds a periodic reaper that automatically ends calls older than a configurable threshold. This catches calls stuck in unexpected states, such as notify-mode calls that never receive a terminal webhook from the provider. New config option: staleCallReaperSeconds: number (default: 0 = disabled) When enabled, checks every 30 seconds and ends calls exceeding the max age. Recommended value: 120-300 for production deployments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
47f8c9209f
commit
390c503b56
@@ -273,6 +273,14 @@ export const VoiceCallConfigSchema = z
|
||||
/** Maximum call duration in seconds */
|
||||
maxDurationSeconds: z.number().int().positive().default(300),
|
||||
|
||||
/**
|
||||
* Maximum age of a call in seconds before it is automatically reaped.
|
||||
* Catches calls stuck in unexpected states (e.g., notify-mode calls that
|
||||
* never receive a terminal webhook). Set to 0 to disable.
|
||||
* Default: 0 (disabled). Recommended: 120-300 for production.
|
||||
*/
|
||||
staleCallReaperSeconds: z.number().int().nonnegative().default(0),
|
||||
|
||||
/** Silence timeout for end-of-speech detection (ms) */
|
||||
silenceTimeoutMs: z.number().int().positive().default(800),
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export class VoiceCallWebhookServer {
|
||||
private manager: CallManager;
|
||||
private provider: VoiceCallProvider;
|
||||
private coreConfig: CoreConfig | null;
|
||||
private staleCallReaperInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
/** Media stream handler for bidirectional audio (when streaming enabled) */
|
||||
private mediaStreamHandler: MediaStreamHandler | null = null;
|
||||
@@ -229,14 +230,49 @@ export class VoiceCallWebhookServer {
|
||||
console.log(`[voice-call] Media stream WebSocket on ws://${bind}:${port}${streamPath}`);
|
||||
}
|
||||
resolve(url);
|
||||
|
||||
// Start the stale call reaper if configured
|
||||
this.startStaleCallReaper();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a periodic reaper that ends calls older than the configured threshold.
|
||||
* Catches calls stuck in unexpected states (e.g., notify-mode calls that never
|
||||
* receive a terminal webhook from the provider).
|
||||
*/
|
||||
private startStaleCallReaper(): void {
|
||||
const maxAgeSeconds = this.config.staleCallReaperSeconds;
|
||||
if (!maxAgeSeconds || maxAgeSeconds <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const CHECK_INTERVAL_MS = 30_000; // Check every 30 seconds
|
||||
const maxAgeMs = maxAgeSeconds * 1000;
|
||||
|
||||
this.staleCallReaperInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const call of this.manager.getActiveCalls()) {
|
||||
const age = now - call.startedAt;
|
||||
if (age > maxAgeMs) {
|
||||
console.log(
|
||||
`[voice-call] Reaping stale call ${call.callId} (age: ${Math.round(age / 1000)}s, state: ${call.state})`,
|
||||
);
|
||||
void this.manager.endCall(call.callId).catch(() => {});
|
||||
}
|
||||
}
|
||||
}, CHECK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the webhook server.
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
if (this.staleCallReaperInterval) {
|
||||
clearInterval(this.staleCallReaperInterval);
|
||||
this.staleCallReaperInterval = null;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
if (this.server) {
|
||||
this.server.close(() => {
|
||||
|
||||
Reference in New Issue
Block a user