voice-vibecoder 2.17.6__tar.gz → 2.18.0__tar.gz
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.
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/CHANGELOG.md +8 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/PKG-INFO +1 -1
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/pyproject.toml +1 -1
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/__init__.py +1 -1
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/server.py +36 -14
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/session.py +6 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/.github/workflows/ci.yml +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/.github/workflows/release.yml +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/.gitignore +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/LICENSE +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/README.md +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/app.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/index.html +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/app_config.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/audio.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/cli_serve.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/config.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/instances.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/tools/__init__.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/tools/agent_task.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/tools/definitions.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/tools/dispatch.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/tools/handlers.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/__init__.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/callbacks.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/help_modal.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/panels.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/screen.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/setup.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/state.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/styles.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/ui/wizard.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/voice_providers/gemini.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
- {voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/worktrees.py +0 -0
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v2.18.0 (2026-02-23)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **server**: Text-only mode bypasses voice provider
|
|
10
|
+
([`1b62fcf`](https://github.com/snokam/voice-vibecoder/commit/1b62fcfffa15ce44a911781761610c533a69bac8))
|
|
11
|
+
|
|
12
|
+
|
|
5
13
|
## v2.17.6 (2026-02-23)
|
|
6
14
|
|
|
7
15
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-vibecoder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.18.0
|
|
4
4
|
Summary: Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code
|
|
5
5
|
Project-URL: Homepage, https://github.com/snokam/voice-vibecoder
|
|
6
6
|
Project-URL: Repository, https://github.com/snokam/voice-vibecoder
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "voice-vibecoder"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.18.0"
|
|
8
8
|
description = "Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -132,18 +132,16 @@ class WebSocketBridge:
|
|
|
132
132
|
on_ui_command=self._on_ui_command,
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
+
# Set event loop so _send works even before voice provider connects
|
|
136
|
+
self._session._loop = asyncio.get_event_loop()
|
|
137
|
+
|
|
135
138
|
# Load saved instances — panels appear dynamically as sessions/instances are created
|
|
136
139
|
restored = self._session.registry.load_state()
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
# Don't sync state here — the frontend sends sync_state after
|
|
141
|
-
# connecting (and after auth), which triggers _sync_state_to_frontend.
|
|
142
|
-
# Sending it here too would cause duplicate output lines.
|
|
141
|
+
# Don't connect voice provider yet — wait for sync_state with audio: true.
|
|
142
|
+
# Text-only clients skip the voice provider entirely.
|
|
143
143
|
self._send({"type": "status", "status": "ready"})
|
|
144
144
|
|
|
145
|
-
self._messages_task = asyncio.create_task(self._session.handle_messages())
|
|
146
|
-
|
|
147
145
|
try:
|
|
148
146
|
await self._receive_loop()
|
|
149
147
|
finally:
|
|
@@ -156,6 +154,14 @@ class WebSocketBridge:
|
|
|
156
154
|
if self._session.connected:
|
|
157
155
|
await self._session.disconnect()
|
|
158
156
|
|
|
157
|
+
async def _connect_voice_provider(self) -> None:
|
|
158
|
+
"""Lazily connect the voice provider when audio is needed."""
|
|
159
|
+
if self._session.connected:
|
|
160
|
+
return
|
|
161
|
+
await self._session.connect()
|
|
162
|
+
await self._session.configure_session()
|
|
163
|
+
self._messages_task = asyncio.create_task(self._session.handle_messages())
|
|
164
|
+
|
|
159
165
|
async def _receive_loop(self) -> None:
|
|
160
166
|
try:
|
|
161
167
|
async for raw in self._ws:
|
|
@@ -167,32 +173,48 @@ class WebSocketBridge:
|
|
|
167
173
|
msg_type = msg.get("type")
|
|
168
174
|
if msg_type == "audio":
|
|
169
175
|
data = msg.get("data", "")
|
|
170
|
-
if data and self._session:
|
|
176
|
+
if data and self._session and self._session.connected:
|
|
171
177
|
await self._session.send_audio(data)
|
|
172
178
|
elif msg_type == "audio.commit":
|
|
173
|
-
if self._session:
|
|
179
|
+
if self._session and self._session.connected:
|
|
174
180
|
await self._session.commit_audio()
|
|
175
181
|
elif msg_type == "text":
|
|
176
|
-
# Text-only mode: inject user message directly
|
|
177
182
|
text = msg.get("text", "") or msg.get("message", "")
|
|
178
183
|
if text and self._session:
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
if self._session.connected:
|
|
185
|
+
# Voice mode: route through voice provider
|
|
186
|
+
await self._session.inject_user_message(text)
|
|
187
|
+
else:
|
|
188
|
+
# Text-only mode: route directly to agent
|
|
189
|
+
await self._handle_text_only(text)
|
|
181
190
|
elif msg_type == "update_settings":
|
|
182
191
|
await self._handle_settings_update(msg.get("settings", {}))
|
|
183
192
|
elif msg_type == "sync_state":
|
|
184
|
-
#
|
|
185
|
-
# Opt-in to ui_command messages (file_tree, show_diff, etc.)
|
|
193
|
+
# Opt-in flags
|
|
186
194
|
if msg.get("ui_commands"):
|
|
187
195
|
self._ui_commands_enabled = True
|
|
188
196
|
if msg.get("audio"):
|
|
189
197
|
self._audio_enabled = True
|
|
198
|
+
# Connect voice provider now (lazy)
|
|
199
|
+
await self._connect_voice_provider()
|
|
190
200
|
self._send({"type": "status", "status": "syncing"})
|
|
191
201
|
await self._sync_state_to_frontend()
|
|
192
202
|
self._send({"type": "status", "status": "ready"})
|
|
193
203
|
except websockets.ConnectionClosed:
|
|
194
204
|
pass
|
|
195
205
|
|
|
206
|
+
async def _handle_text_only(self, text: str) -> None:
|
|
207
|
+
"""Route text directly to an agent session, bypassing the voice provider.
|
|
208
|
+
|
|
209
|
+
Used for text-only clients that don't need audio. The agent streams
|
|
210
|
+
output via on_agent_output and sends the final result as a transcript.
|
|
211
|
+
"""
|
|
212
|
+
from voice_vibecoder.tools.agent_task import _handle_send_to_session
|
|
213
|
+
|
|
214
|
+
self._session.text_only = True
|
|
215
|
+
self._send({"type": "transcript", "role": "user", "text": text})
|
|
216
|
+
_handle_send_to_session(text)
|
|
217
|
+
|
|
196
218
|
async def _sync_state_to_frontend(self) -> None:
|
|
197
219
|
"""Send full backend state to frontend - all instances with their conversation history."""
|
|
198
220
|
if not self._session:
|
|
@@ -85,6 +85,7 @@ class RealtimeSession:
|
|
|
85
85
|
self._on_ui_command = on_ui_command
|
|
86
86
|
self._running = False
|
|
87
87
|
self._assistant_transcript = ""
|
|
88
|
+
self.text_only = False
|
|
88
89
|
self._response_pending = False
|
|
89
90
|
self._fallback_task: asyncio.Task | None = None
|
|
90
91
|
self._loop: asyncio.AbstractEventLoop | None = None
|
|
@@ -358,6 +359,11 @@ class RealtimeSession:
|
|
|
358
359
|
# Show only the first line as summary — full result was already streamed
|
|
359
360
|
first_line = result.strip().split("\n", 1)[0][:120]
|
|
360
361
|
self._registry.on_output(instance_id, f"{prefix} {first_line}")
|
|
362
|
+
if self.text_only:
|
|
363
|
+
# Text-only mode: send result as transcript directly (no voice)
|
|
364
|
+
if self._on_transcript:
|
|
365
|
+
self._on_transcript("assistant", result)
|
|
366
|
+
return
|
|
361
367
|
if self._loop and self._running:
|
|
362
368
|
inst = self._registry.get_by_id(instance_id)
|
|
363
369
|
display_key = inst.display_key if inst else "unknown"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/claude.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/cursor.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/openclaw.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/code_providers/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/voice_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/voice_providers/gemini.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.17.6 → voice_vibecoder-2.18.0}/src/voice_vibecoder/voice_providers/openai.py
RENAMED
|
File without changes
|
|
File without changes
|