voicecc 1.2.11 → 1.2.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voicecc",
3
- "version": "1.2.11",
3
+ "version": "1.2.12",
4
4
  "description": "Voice Agent Platform running on Claude Code -- create and deploy conversational voice agents with ElevenLabs STT/TTS and VAD",
5
5
  "repository": {
6
6
  "type": "git",
package/server/index.ts CHANGED
@@ -43,6 +43,18 @@ const VOICE_SERVER_DIR = join(import.meta.dirname ?? ".", "..", "voice-server");
43
43
  /** Reference to the Python voice server child process */
44
44
  let pythonProcess: ChildProcess | null = null;
45
45
 
46
+ /** Maximum number of automatic restart attempts after an unexpected crash */
47
+ const PYTHON_MAX_RESTART_ATTEMPTS = 5;
48
+
49
+ /** Delay before attempting a restart (ms) */
50
+ const PYTHON_RESTART_DELAY_MS = 3_000;
51
+
52
+ /** Number of consecutive restart attempts since last successful start */
53
+ let pythonRestartAttempts = 0;
54
+
55
+ /** Whether the Python server was intentionally stopped (skip auto-restart) */
56
+ let pythonManuallyStopped = false;
57
+
46
58
  /**
47
59
  * Start the Python voice server as a child process.
48
60
  * Waits for the health endpoint to respond before returning.
@@ -63,6 +75,7 @@ async function startPythonVoiceServer(): Promise<void> {
63
75
  pythonProcess.on("exit", (code) => {
64
76
  console.error(`Python voice server exited with code ${code}`);
65
77
  pythonProcess = null;
78
+ schedulePythonRestart();
66
79
  });
67
80
 
68
81
  // Wait for health endpoint (up to 15s)
@@ -72,6 +85,7 @@ async function startPythonVoiceServer(): Promise<void> {
72
85
  const res = await fetch(`${VOICE_SERVER_API_URL}/health`);
73
86
  if (res.ok) {
74
87
  console.log("Python voice server is ready");
88
+ pythonRestartAttempts = 0;
75
89
  return;
76
90
  }
77
91
  } catch {
@@ -83,15 +97,67 @@ async function startPythonVoiceServer(): Promise<void> {
83
97
  }
84
98
 
85
99
  /**
86
- * Stop the Python voice server child process.
100
+ * Stop the Python voice server child process. Prevents auto-restart.
87
101
  */
88
102
  function stopPythonVoiceServer(): void {
103
+ pythonManuallyStopped = true;
89
104
  if (pythonProcess) {
90
105
  pythonProcess.kill("SIGTERM");
91
106
  pythonProcess = null;
92
107
  }
93
108
  }
94
109
 
110
+ /**
111
+ * Schedule an automatic restart of the Python voice server after an unexpected exit.
112
+ * Skips restart if manually stopped or max attempts exceeded.
113
+ */
114
+ function schedulePythonRestart(): void {
115
+ if (pythonManuallyStopped) {
116
+ return;
117
+ }
118
+
119
+ pythonRestartAttempts++;
120
+
121
+ if (pythonRestartAttempts > PYTHON_MAX_RESTART_ATTEMPTS) {
122
+ console.error(`[voice-server] Giving up after ${PYTHON_MAX_RESTART_ATTEMPTS} restart attempts`);
123
+ return;
124
+ }
125
+
126
+ console.log(
127
+ `[voice-server] Restarting in ${PYTHON_RESTART_DELAY_MS / 1000}s ` +
128
+ `(attempt ${pythonRestartAttempts}/${PYTHON_MAX_RESTART_ATTEMPTS})...`
129
+ );
130
+
131
+ setTimeout(async () => {
132
+ if (pythonManuallyStopped || pythonProcess) {
133
+ return;
134
+ }
135
+
136
+ try {
137
+ await startPythonVoiceServer();
138
+ pythonRestartAttempts = 0;
139
+ console.log("[voice-server] Restarted successfully");
140
+
141
+ // Re-notify of tunnel URL if tunnel is running
142
+ const currentTunnelUrl = getTunnelUrl();
143
+ if (currentTunnelUrl) {
144
+ try {
145
+ await fetch(`${VOICE_SERVER_API_URL}/config/tunnel-url`, {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/json" },
148
+ body: JSON.stringify({ url: currentTunnelUrl }),
149
+ });
150
+ } catch {
151
+ console.warn("[voice-server] Failed to re-notify tunnel URL after restart");
152
+ }
153
+ }
154
+ } catch (err) {
155
+ console.error(`[voice-server] Restart failed: ${err}`);
156
+ schedulePythonRestart();
157
+ }
158
+ }, PYTHON_RESTART_DELAY_MS);
159
+ }
160
+
95
161
  // Use VOICECC_DIR env var if set (passed by CLI when dropping root privileges),
96
162
  // otherwise fall back to ~/.voicecc.
97
163
  const VOICECC_DIR = process.env.VOICECC_DIR ?? join(homedir(), ".voicecc");
@@ -17,6 +17,7 @@ Responsibilities:
17
17
  """
18
18
 
19
19
  import asyncio
20
+ import gc
20
21
  import json
21
22
  import logging
22
23
  import os
@@ -289,6 +290,9 @@ async def check_single_agent(agent: Agent) -> HeartbeatResult:
289
290
  pass
290
291
  _in_flight_checks.discard(agent.id)
291
292
 
293
+ # Force garbage collection to reclaim memory from the Claude subprocess
294
+ gc.collect()
295
+
292
296
 
293
297
  async def _run_heartbeat_session(
294
298
  agent: Agent, timeout_s: float