voice-vibecoder 2.26.2__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.26.2 → voice_vibecoder-2.28.0}/CHANGELOG.md +48 -0
  2. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/PKG-INFO +1 -1
  3. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/pyproject.toml +1 -1
  4. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/__init__.py +1 -1
  5. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/instances.py +55 -12
  6. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/server.py +3 -0
  7. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/session.py +13 -2
  8. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/.github/workflows/ci.yml +0 -0
  9. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/.github/workflows/release.yml +0 -0
  10. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/.gitignore +0 -0
  11. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/CLAUDE.md +0 -0
  12. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/LICENSE +0 -0
  13. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/README.md +0 -0
  14. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/app.py +0 -0
  15. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/app_config.py +0 -0
  16. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/audio.py +0 -0
  17. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/audio_macos.py +0 -0
  18. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/cli_serve.py +0 -0
  19. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
  20. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
  21. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
  22. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
  23. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
  24. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/config.py +0 -0
  25. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/__init__.py +0 -0
  26. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/agent_task.py +0 -0
  27. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/definitions.py +0 -0
  28. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/dispatch.py +0 -0
  29. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
  30. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/tools/handlers.py +0 -0
  31. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/__init__.py +0 -0
  32. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/callbacks.py +0 -0
  33. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
  34. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/help_modal.py +0 -0
  35. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/panels.py +0 -0
  36. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/screen.py +0 -0
  37. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/setup.py +0 -0
  38. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/state.py +0 -0
  39. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/styles.py +0 -0
  40. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
  41. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/ui/wizard.py +0 -0
  42. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
  43. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/voice_providers/gemini.py +0 -0
  44. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
  45. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/src/voice_vibecoder/worktrees.py +0 -0
  46. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/tests/test_audio_gating.py +0 -0
  47. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/tests/test_file_tree.py +0 -0
  48. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/tests/test_merge_branch.py +0 -0
  49. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/tests/test_openai_events.py +0 -0
  50. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/tests/test_session_interrupts.py +0 -0
  51. {voice_vibecoder-2.26.2 → voice_vibecoder-2.28.0}/tests/test_startup_context.py +0 -0
@@ -2,6 +2,54 @@
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
+
31
+ ## v2.27.0 (2026-07-22)
32
+
33
+ ### Bug Fixes
34
+
35
+ - Never run a disabled agent for a persisted instance
36
+ ([#9](https://github.com/snokam/voice-vibecoder/pull/9),
37
+ [`01cd4c0`](https://github.com/snokam/voice-vibecoder/commit/01cd4c0095ef871a022f42929f653e3fe76f5cc9))
38
+
39
+ - Per-connection dispatch state via ContextVars
40
+ ([#9](https://github.com/snokam/voice-vibecoder/pull/9),
41
+ [`01cd4c0`](https://github.com/snokam/voice-vibecoder/commit/01cd4c0095ef871a022f42929f653e3fe76f5cc9))
42
+
43
+ ### Features
44
+
45
+ - Page context via session instructions ([#9](https://github.com/snokam/voice-vibecoder/pull/9),
46
+ [`01cd4c0`](https://github.com/snokam/voice-vibecoder/commit/01cd4c0095ef871a022f42929f653e3fe76f5cc9))
47
+
48
+ - Page context via session instructions, not baked into messages
49
+ ([#9](https://github.com/snokam/voice-vibecoder/pull/9),
50
+ [`01cd4c0`](https://github.com/snokam/voice-vibecoder/commit/01cd4c0095ef871a022f42929f653e3fe76f5cc9))
51
+
52
+
5
53
  ## v2.26.2 (2026-07-22)
6
54
 
7
55
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-vibecoder
3
- Version: 2.26.2
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.26.2"
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.26.2"
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)
@@ -188,6 +188,9 @@ class WebSocketBridge:
188
188
  else:
189
189
  # Text-only mode: route directly to agent
190
190
  await self._handle_text_only(text)
191
+ elif msg_type == "context":
192
+ if self._session:
193
+ await self._session.set_page_context(msg.get("text", "") or "")
191
194
  elif msg_type == "update_settings":
192
195
  await self._handle_settings_update(msg.get("settings", {}))
193
196
  elif msg_type == "sync_state":
@@ -153,6 +153,7 @@ class RealtimeSession:
153
153
  self._on_response_active = on_response_active
154
154
  self._running = False
155
155
  self._assistant_transcript = ""
156
+ self._page_context = ""
156
157
  self.text_only = False
157
158
  self._response_pending = False
158
159
  self._fallback_task: asyncio.Task | None = None
@@ -227,7 +228,7 @@ class RealtimeSession:
227
228
  s = self._settings
228
229
  overrides = self._voice_config.system_instructions if self._voice_config else None
229
230
  instructions = get_system_instructions(
230
- s.language, context=get_startup_context(),
231
+ s.language, context=self._instructions_context(get_startup_context()),
231
232
  overrides=overrides, enabled_agents=s.enabled_agents,
232
233
  active_instances=get_active_instances_text(),
233
234
  )
@@ -488,12 +489,22 @@ class RealtimeSession:
488
489
  if self._loop and self._running:
489
490
  asyncio.run_coroutine_threadsafe(self._refresh_instructions(), self._loop)
490
491
 
492
+ def _instructions_context(self, base: str) -> str:
493
+ if self._page_context:
494
+ return f"{base}\n\n{self._page_context}" if base else self._page_context
495
+ return base
496
+
497
+ async def set_page_context(self, text: str) -> None:
498
+ self._page_context = (text or "").strip()
499
+ if self._running:
500
+ await self._refresh_instructions()
501
+
491
502
  async def _refresh_instructions(self) -> None:
492
503
  from voice_vibecoder.tools.handlers import get_active_instances_text, get_startup_context
493
504
 
494
505
  overrides = self._voice_config.system_instructions if self._voice_config else None
495
506
  instructions = get_system_instructions(
496
- self._settings.language, context=get_startup_context(),
507
+ self._settings.language, context=self._instructions_context(get_startup_context()),
497
508
  overrides=overrides, enabled_agents=self._settings.enabled_agents,
498
509
  active_instances=get_active_instances_text(),
499
510
  )