voice-vibecoder 2.27.0__tar.gz → 2.28.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.
Files changed (51) hide show
  1. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/CHANGELOG.md +26 -0
  2. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/PKG-INFO +1 -1
  3. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/pyproject.toml +1 -1
  4. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/__init__.py +1 -1
  5. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/instances.py +55 -12
  6. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/.github/workflows/ci.yml +0 -0
  7. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/.github/workflows/release.yml +0 -0
  8. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/.gitignore +0 -0
  9. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/CLAUDE.md +0 -0
  10. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/LICENSE +0 -0
  11. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/README.md +0 -0
  12. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/app.py +0 -0
  13. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/app_config.py +0 -0
  14. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/audio.py +0 -0
  15. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/audio_macos.py +0 -0
  16. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/cli_serve.py +0 -0
  17. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
  18. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
  19. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
  20. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
  21. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
  22. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/config.py +0 -0
  23. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/server.py +0 -0
  24. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/session.py +0 -0
  25. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/__init__.py +0 -0
  26. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/agent_task.py +0 -0
  27. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/definitions.py +0 -0
  28. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/dispatch.py +0 -0
  29. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
  30. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/handlers.py +0 -0
  31. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/__init__.py +0 -0
  32. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/callbacks.py +0 -0
  33. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
  34. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/help_modal.py +0 -0
  35. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/panels.py +0 -0
  36. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/screen.py +0 -0
  37. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/setup.py +0 -0
  38. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/state.py +0 -0
  39. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/styles.py +0 -0
  40. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
  41. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/wizard.py +0 -0
  42. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
  43. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/voice_providers/gemini.py +0 -0
  44. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
  45. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/src/voice_vibecoder/worktrees.py +0 -0
  46. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/tests/test_audio_gating.py +0 -0
  47. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/tests/test_file_tree.py +0 -0
  48. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/tests/test_merge_branch.py +0 -0
  49. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/tests/test_openai_events.py +0 -0
  50. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/tests/test_session_interrupts.py +0 -0
  51. {voice_vibecoder-2.27.0 → voice_vibecoder-2.28.0}/tests/test_startup_context.py +0 -0
@@ -2,6 +2,32 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v2.28.0 (2026-07-22)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Never run a disabled agent for a persisted instance
10
+ ([#10](https://github.com/snokam/voice-vibecoder/pull/10),
11
+ [`393ed17`](https://github.com/snokam/voice-vibecoder/commit/393ed17ea2d0ed12d38abc37acc8f51a56bda280))
12
+
13
+ - Per-connection dispatch state via ContextVars
14
+ ([#10](https://github.com/snokam/voice-vibecoder/pull/10),
15
+ [`393ed17`](https://github.com/snokam/voice-vibecoder/commit/393ed17ea2d0ed12d38abc37acc8f51a56bda280))
16
+
17
+ ### Features
18
+
19
+ - Page context via session instructions, not baked into messages
20
+ ([#10](https://github.com/snokam/voice-vibecoder/pull/10),
21
+ [`393ed17`](https://github.com/snokam/voice-vibecoder/commit/393ed17ea2d0ed12d38abc37acc8f51a56bda280))
22
+
23
+ - Pluggable InstanceRegistry state store ([#10](https://github.com/snokam/voice-vibecoder/pull/10),
24
+ [`393ed17`](https://github.com/snokam/voice-vibecoder/commit/393ed17ea2d0ed12d38abc37acc8f51a56bda280))
25
+
26
+ - Pluggable InstanceRegistry state store (file default, DB override)
27
+ ([#10](https://github.com/snokam/voice-vibecoder/pull/10),
28
+ [`393ed17`](https://github.com/snokam/voice-vibecoder/commit/393ed17ea2d0ed12d38abc37acc8f51a56bda280))
29
+
30
+
5
31
  ## v2.27.0 (2026-07-22)
6
32
 
7
33
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-vibecoder
3
- Version: 2.27.0
3
+ Version: 2.28.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.27.0"
7
+ version = "2.28.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"
@@ -3,7 +3,7 @@
3
3
  A voice-controlled coding assistant using OpenAI Realtime API + Claude Code.
4
4
  """
5
5
 
6
- __version__ = "2.27.0"
6
+ __version__ = "2.28.0"
7
7
 
8
8
  from voice_vibecoder.app_config import VoiceConfig
9
9
  from voice_vibecoder.ui.screen import VoiceCodingScreen
@@ -33,6 +33,58 @@ def configure_paths(data_dir: Path) -> None:
33
33
  STATE_PATH = data_dir / "realtime-instances.json"
34
34
 
35
35
 
36
+ class StateStore:
37
+ """Pluggable persistence for InstanceRegistry state (instances + transcripts).
38
+
39
+ ``key`` scopes the state (e.g. per user email). Default is a local file;
40
+ a consumer can override with a database-backed store via set_state_store().
41
+ """
42
+
43
+ def save(self, key: str, data: dict) -> None:
44
+ raise NotImplementedError
45
+
46
+ def load(self, key: str) -> dict | None:
47
+ raise NotImplementedError
48
+
49
+
50
+ class FileStateStore(StateStore):
51
+ def _path(self, key: str) -> Path:
52
+ if key:
53
+ safe = key.replace("@", "-").replace(".", "-")
54
+ return STATE_PATH.parent / f"realtime-instances-{safe}.json"
55
+ return STATE_PATH
56
+
57
+ def save(self, key: str, data: dict) -> None:
58
+ path = self._path(key)
59
+ path.parent.mkdir(parents=True, exist_ok=True)
60
+ tmp = path.with_suffix(".tmp")
61
+ tmp.write_text(json.dumps(data, indent=2))
62
+ tmp.replace(path)
63
+
64
+ def load(self, key: str) -> dict | None:
65
+ path = self._path(key)
66
+ if not path.exists():
67
+ return None
68
+ try:
69
+ return json.loads(path.read_text())
70
+ except (json.JSONDecodeError, OSError) as e:
71
+ logger.warning("Failed to load instance state: %s", e)
72
+ return None
73
+
74
+
75
+ _state_store: StateStore = FileStateStore()
76
+
77
+
78
+ def set_state_store(store: StateStore) -> None:
79
+ """Override where InstanceRegistry persists its state (default: local file)."""
80
+ global _state_store
81
+ _state_store = store
82
+
83
+
84
+ def get_state_store() -> StateStore:
85
+ return _state_store
86
+
87
+
36
88
  class InstanceStatus(Enum):
37
89
  IDLE = "idle"
38
90
  RUNNING = "running"
@@ -329,11 +381,7 @@ class InstanceRegistry:
329
381
  "instances": instances_data,
330
382
  "transcripts": self._transcript_buffer[-100:], # Keep last 100
331
383
  }
332
- state_path = self._get_state_path()
333
- state_path.parent.mkdir(parents=True, exist_ok=True)
334
- tmp = state_path.with_suffix(".tmp")
335
- tmp.write_text(json.dumps(data, indent=2))
336
- tmp.replace(state_path)
384
+ get_state_store().save(self._user_email or "", data)
337
385
  except Exception as e:
338
386
  logger.warning("Failed to save instance state: %s", e)
339
387
 
@@ -343,13 +391,8 @@ class InstanceRegistry:
343
391
  Returns the loaded instances (already registered). Validates that
344
392
  worktree paths still exist on disk before restoring.
345
393
  """
346
- state_path = self._get_state_path()
347
- if not state_path.exists():
348
- return []
349
- try:
350
- raw_data = json.loads(state_path.read_text())
351
- except (json.JSONDecodeError, OSError) as e:
352
- logger.warning("Failed to load instance state: %s", e)
394
+ raw_data = get_state_store().load(self._user_email or "")
395
+ if raw_data is None:
353
396
  return []
354
397
 
355
398
  # Handle both old format (list) and new format (dict)