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.
Files changed (51) hide show
  1. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/CHANGELOG.md +16 -0
  2. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/PKG-INFO +1 -1
  3. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/pyproject.toml +1 -1
  4. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/__init__.py +1 -1
  5. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/server.py +17 -0
  6. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/agent_task.py +29 -13
  7. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/.github/workflows/ci.yml +0 -0
  8. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/.github/workflows/release.yml +0 -0
  9. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/.gitignore +0 -0
  10. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/CLAUDE.md +0 -0
  11. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/LICENSE +0 -0
  12. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/README.md +0 -0
  13. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/app.py +0 -0
  14. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/app_config.py +0 -0
  15. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/audio.py +0 -0
  16. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/audio_macos.py +0 -0
  17. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/cli_serve.py +0 -0
  18. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/__init__.py +0 -0
  19. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/claude.py +0 -0
  20. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/cursor.py +0 -0
  21. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
  22. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/code_providers/registry.py +0 -0
  23. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/config.py +0 -0
  24. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/instances.py +0 -0
  25. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/session.py +0 -0
  26. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/__init__.py +0 -0
  27. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/definitions.py +0 -0
  28. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/dispatch.py +0 -0
  29. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/file_tree.py +0 -0
  30. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/tools/handlers.py +0 -0
  31. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/__init__.py +0 -0
  32. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/callbacks.py +0 -0
  33. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/diff_view.py +0 -0
  34. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/help_modal.py +0 -0
  35. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/panels.py +0 -0
  36. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/screen.py +0 -0
  37. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/setup.py +0 -0
  38. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/state.py +0 -0
  39. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/styles.py +0 -0
  40. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/update_modal.py +0 -0
  41. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/ui/wizard.py +0 -0
  42. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
  43. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/gemini.py +0 -0
  44. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/voice_providers/openai.py +0 -0
  45. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/src/voice_vibecoder/worktrees.py +0 -0
  46. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_audio_gating.py +0 -0
  47. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_file_tree.py +0 -0
  48. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_merge_branch.py +0 -0
  49. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_openai_events.py +0 -0
  50. {voice_vibecoder-2.28.3 → voice_vibecoder-2.29.1}/tests/test_session_interrupts.py +0 -0
  51. {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.28.3
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.28.3"
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"
@@ -3,7 +3,7 @@
3
3
  A voice-controlled coding assistant using OpenAI Realtime API + Claude Code.
4
4
  """
5
5
 
6
- __version__ = "2.28.3"
6
+ __version__ = "2.29.1"
7
7
 
8
8
  from voice_vibecoder.app_config import VoiceConfig
9
9
  from voice_vibecoder.ui.screen import VoiceCodingScreen
@@ -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
- instance.output_buffer = ""
174
-
175
- # Show the user's prompt and a separator in the panel
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
- instance.output_buffer = ""
251
-
252
- # Show the user's prompt and a separator in the panel
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.output_buffer = result
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.output_buffer += output.content + "\n"
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
- instance.output_buffer = result.text
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