voice-vibecoder 2.28.3__tar.gz → 2.29.1__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.28.3 → voice_vibecoder-2.29.1}/CHANGELOG.md +16 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/PKG-INFO +1 -1
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/pyproject.toml +1 -1
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/__init__.py +1 -1
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/server.py +17 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/agent_task.py +29 -13
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/.github/workflows/ci.yml +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/.github/workflows/release.yml +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/.gitignore +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/CLAUDE.md +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/LICENSE +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/README.md +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/app.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/app_config.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/audio.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/audio_macos.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/cli_serve.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/__init__.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/claude.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/cursor.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/registry.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/config.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/instances.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/session.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/__init__.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/definitions.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/dispatch.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/file_tree.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/handlers.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/__init__.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/callbacks.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/diff_view.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/help_modal.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/panels.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/screen.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/setup.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/state.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/styles.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/update_modal.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/wizard.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/gemini.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/openai.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/worktrees.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_audio_gating.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_file_tree.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_merge_branch.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_openai_events.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_session_interrupts.py +0 -0
- {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_startup_context.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v2.29.1 (2026-07-22)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Restore full worktree transcript and re-push file tree on reconnect
|
|
10
|
+
([`006b8ac`](https://github.com/snokam/voice-vibecoder/commit/006b8ac3e23718cce635a3ff28cbdb5b84650a20))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## v2.29.0 (2026-07-22)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- Include worktree directory name in instance_status
|
|
18
|
+
([`7b289ff`](https://github.com/snokam/voice-vibecoder/commit/7b289ffdff8f527bb5a97200edf94e146060edc1))
|
|
19
|
+
|
|
20
|
+
|
|
5
21
|
## v2.28.3 (2026-07-22)
|
|
6
22
|
|
|
7
23
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-vibecoder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.29.1
|
|
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.29.1"
|
|
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"
|
|
@@ -239,6 +239,9 @@ class WebSocketBridge:
|
|
|
239
239
|
# Send instance status and history (skip for text-only)
|
|
240
240
|
if self._session.text_only:
|
|
241
241
|
return
|
|
242
|
+
from voice_vibecoder.instances import InstanceType
|
|
243
|
+
from voice_vibecoder.tools.file_tree import start_file_tree_updates
|
|
244
|
+
|
|
242
245
|
for inst in self._session.registry.get_all():
|
|
243
246
|
self._send({
|
|
244
247
|
"type": "instance_status",
|
|
@@ -247,6 +250,7 @@ class WebSocketBridge:
|
|
|
247
250
|
"branch": inst.branch,
|
|
248
251
|
"agentType": inst.agent_type,
|
|
249
252
|
"displayName": inst.display_key,
|
|
253
|
+
"worktree": Path(inst.worktree_path).name if inst.worktree_path else "",
|
|
250
254
|
})
|
|
251
255
|
# Send buffered output from previous session
|
|
252
256
|
if inst.output_buffer:
|
|
@@ -255,6 +259,17 @@ class WebSocketBridge:
|
|
|
255
259
|
"instanceId": inst.instance_id,
|
|
256
260
|
"line": f"text:{inst.output_buffer}",
|
|
257
261
|
})
|
|
262
|
+
# Re-push the file tree: the periodic timer's first tick may have
|
|
263
|
+
# fired before this reconnecting client enabled ui_commands, so it
|
|
264
|
+
# would otherwise never see the tree. Restarting fires immediately.
|
|
265
|
+
if (
|
|
266
|
+
inst.instance_type != InstanceType.SESSION
|
|
267
|
+
and inst.worktree_path
|
|
268
|
+
and Path(inst.worktree_path).exists()
|
|
269
|
+
):
|
|
270
|
+
start_file_tree_updates(
|
|
271
|
+
inst.instance_id, inst.worktree_path, self._session.registry
|
|
272
|
+
)
|
|
258
273
|
|
|
259
274
|
# Send current settings so the frontend can render voice controls
|
|
260
275
|
if not self._session.text_only:
|
|
@@ -342,6 +357,7 @@ class WebSocketBridge:
|
|
|
342
357
|
branch = inst.branch if inst else ""
|
|
343
358
|
agent_type = inst.agent_type if inst else "claude"
|
|
344
359
|
display_name = inst.display_key if inst else branch
|
|
360
|
+
worktree = Path(inst.worktree_path).name if inst and inst.worktree_path else ""
|
|
345
361
|
self._send({
|
|
346
362
|
"type": "instance_status",
|
|
347
363
|
"instanceId": instance_id,
|
|
@@ -349,6 +365,7 @@ class WebSocketBridge:
|
|
|
349
365
|
"branch": branch,
|
|
350
366
|
"agentType": agent_type,
|
|
351
367
|
"displayName": display_name,
|
|
368
|
+
"worktree": worktree,
|
|
352
369
|
})
|
|
353
370
|
|
|
354
371
|
def _on_tool_call(self, name: str, arguments: dict) -> None:
|
|
@@ -33,6 +33,20 @@ def _start_background(target, *args) -> None:
|
|
|
33
33
|
|
|
34
34
|
_CLAUDE_JSON = Path.home() / ".claude.json"
|
|
35
35
|
|
|
36
|
+
# The panel's output_buffer is a running, multi-turn transcript persisted for
|
|
37
|
+
# restore. It accumulates across turns (never reset per run) and is trimmed from
|
|
38
|
+
# the front once it grows past the cap so restore shows recent history, not just
|
|
39
|
+
# the last line.
|
|
40
|
+
_BUFFER_MAX = 50_000
|
|
41
|
+
_BUFFER_KEEP = 30_000
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _append_buffer(instance: AgentInstance, text: str) -> None:
|
|
45
|
+
instance.output_buffer += text
|
|
46
|
+
if len(instance.output_buffer) > _BUFFER_MAX:
|
|
47
|
+
instance.output_buffer = instance.output_buffer[-_BUFFER_KEEP:]
|
|
48
|
+
|
|
49
|
+
|
|
36
50
|
# Cached MCP servers — loaded once per session, files rarely change
|
|
37
51
|
_mcp_cache: dict[str, dict] | None = None
|
|
38
52
|
_mcp_lock = threading.Lock()
|
|
@@ -170,13 +184,13 @@ def _handle_send_to_agent(message: str, branch: str | None = None, agent: str |
|
|
|
170
184
|
# overwrites the module-level _registry.
|
|
171
185
|
registry = _registry
|
|
172
186
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
187
|
+
# Show the user's prompt and a separator in the panel, and start a new turn
|
|
188
|
+
# in the persisted transcript (kept across turns for restore).
|
|
189
|
+
preview = message[:200] + ("..." if len(message) > 200 else "")
|
|
176
190
|
if registry and registry.on_output:
|
|
177
191
|
registry.on_output(instance.instance_id, "separator:")
|
|
178
|
-
preview = message[:200] + ("..." if len(message) > 200 else "")
|
|
179
192
|
registry.on_output(instance.instance_id, f"user_prompt:{preview}")
|
|
193
|
+
_append_buffer(instance, f"\n\n> {preview}\n\n")
|
|
180
194
|
|
|
181
195
|
if registry:
|
|
182
196
|
registry.update_status(instance.instance_id, InstanceStatus.RUNNING)
|
|
@@ -247,13 +261,13 @@ def _handle_send_to_session(message: str, session_name: str | None = None, agent
|
|
|
247
261
|
|
|
248
262
|
registry = _registry
|
|
249
263
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
264
|
+
# Show the user's prompt and a separator in the panel, and start a new turn
|
|
265
|
+
# in the persisted transcript (kept across turns for restore).
|
|
266
|
+
preview = message[:200] + ("..." if len(message) > 200 else "")
|
|
253
267
|
if registry and registry.on_output:
|
|
254
268
|
registry.on_output(instance.instance_id, "separator:")
|
|
255
|
-
preview = message[:200] + ("..." if len(message) > 200 else "")
|
|
256
269
|
registry.on_output(instance.instance_id, f"user_prompt:{preview}")
|
|
270
|
+
_append_buffer(instance, f"\n\n> {preview}\n\n")
|
|
257
271
|
|
|
258
272
|
if registry:
|
|
259
273
|
registry.update_status(instance.instance_id, InstanceStatus.RUNNING)
|
|
@@ -346,7 +360,7 @@ async def _run_helper_override_async(
|
|
|
346
360
|
return
|
|
347
361
|
|
|
348
362
|
instance._bg_task = None
|
|
349
|
-
instance
|
|
363
|
+
_append_buffer(instance, result)
|
|
350
364
|
|
|
351
365
|
registry.update_status(iid, InstanceStatus.IDLE)
|
|
352
366
|
|
|
@@ -409,9 +423,7 @@ async def _run_agent_async(
|
|
|
409
423
|
if registry.on_output:
|
|
410
424
|
registry.on_output(iid, f"{output.category}:{output.content}")
|
|
411
425
|
instance.last_activity = output.content[:200]
|
|
412
|
-
instance
|
|
413
|
-
if len(instance.output_buffer) > 50_000:
|
|
414
|
-
instance.output_buffer = instance.output_buffer[-30_000:]
|
|
426
|
+
_append_buffer(instance, output.content + "\n")
|
|
415
427
|
|
|
416
428
|
async def can_use_tool(tool_name: str, input_data: dict, context: Any) -> Any:
|
|
417
429
|
if tool_name == "AskUserQuestion":
|
|
@@ -423,6 +435,7 @@ async def _run_agent_async(
|
|
|
423
435
|
# Use repo root as cwd for session instances (they have no worktree)
|
|
424
436
|
cwd = str(instance.worktree_path) if instance.worktree_path else str(_repo_root)
|
|
425
437
|
|
|
438
|
+
turn_start_len = len(instance.output_buffer)
|
|
426
439
|
try:
|
|
427
440
|
result = await runner.run(
|
|
428
441
|
message=message,
|
|
@@ -464,7 +477,10 @@ async def _run_agent_async(
|
|
|
464
477
|
instance.session_id = result.session_id
|
|
465
478
|
instance.first_call = False
|
|
466
479
|
registry._save_state_now() # session_id must persist immediately
|
|
467
|
-
|
|
480
|
+
# Streaming agents already accumulated their output via on_output; only add
|
|
481
|
+
# the final text when nothing streamed this turn (non-streaming agents).
|
|
482
|
+
if len(instance.output_buffer) == turn_start_len and result.text:
|
|
483
|
+
_append_buffer(instance, result.text)
|
|
468
484
|
|
|
469
485
|
registry.update_status(iid, InstanceStatus.IDLE)
|
|
470
486
|
|
|
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.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/claude.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/cursor.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/openclaw.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/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.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/gemini.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/openai.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
|