voice-vibecoder 2.5.1__tar.gz → 2.5.3__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 (42) hide show
  1. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/CHANGELOG.md +16 -0
  2. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/PKG-INFO +1 -1
  3. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/pyproject.toml +1 -1
  4. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/__init__.py +1 -1
  5. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/config.py +47 -4
  6. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/instances.py +22 -7
  7. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/session.py +4 -2
  8. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/tools/agent_task.py +15 -2
  9. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/tools/handlers.py +20 -9
  10. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/.github/workflows/ci.yml +0 -0
  11. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/.github/workflows/release.yml +0 -0
  12. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/.gitignore +0 -0
  13. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/LICENSE +0 -0
  14. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/README.md +0 -0
  15. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/app.py +0 -0
  16. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/index.html +0 -0
  17. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/app_config.py +0 -0
  18. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/audio.py +0 -0
  19. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/cli_serve.py +0 -0
  20. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/code_providers/__init__.py +0 -0
  21. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/code_providers/claude.py +0 -0
  22. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/code_providers/cursor.py +0 -0
  23. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/code_providers/registry.py +0 -0
  24. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/server.py +0 -0
  25. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/tools/__init__.py +0 -0
  26. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/tools/definitions.py +0 -0
  27. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/tools/dispatch.py +0 -0
  28. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/tools/file_tree.py +0 -0
  29. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/__init__.py +0 -0
  30. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/callbacks.py +0 -0
  31. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/diff_view.py +0 -0
  32. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/help_modal.py +0 -0
  33. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/panels.py +0 -0
  34. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/screen.py +0 -0
  35. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/setup.py +0 -0
  36. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/state.py +0 -0
  37. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/styles.py +0 -0
  38. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/update_modal.py +0 -0
  39. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/ui/wizard.py +0 -0
  40. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
  41. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/voice_providers/openai.py +0 -0
  42. {voice_vibecoder-2.5.1 → voice_vibecoder-2.5.3}/src/voice_vibecoder/worktrees.py +0 -0
@@ -2,6 +2,22 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v2.5.3 (2026-02-20)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Prevent duplicate sessions by embedding active instances in instructions
10
+ ([`7f493dc`](https://github.com/snokam/voice-vibecoder/commit/7f493dc275d7913edc760afc8ac3b2c066062954))
11
+
12
+
13
+ ## v2.5.2 (2026-02-20)
14
+
15
+ ### Bug Fixes
16
+
17
+ - Let user freely continue conversations in any session/agent
18
+ ([`0d43f1c`](https://github.com/snokam/voice-vibecoder/commit/0d43f1c0acca831d9b51da81a029cc45e04a8eea))
19
+
20
+
5
21
  ## v2.5.1 (2026-02-19)
6
22
 
7
23
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-vibecoder
3
- Version: 2.5.1
3
+ Version: 2.5.3
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.5.1"
7
+ version = "2.5.3"
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.5.1"
6
+ __version__ = "2.5.3"
7
7
 
8
8
  from voice_vibecoder.app_config import VoiceConfig
9
9
  from voice_vibecoder.ui.screen import VoiceCodingScreen
@@ -47,9 +47,12 @@ Branch-navn fuzzy-matches — f.eks. "branding" matcher "feat/branding-refresh".
47
47
  send_to_session med beskrivende navn. Gjenbruk eksisterende sesjoner for samme tema.
48
48
 
49
49
  Sesjoner:
50
+ - SJEKK ALLTID listen over aktive sesjoner nedenfor før du oppretter en ny.
51
+ - Hvis en sesjon dekker samme tema, GJENBRUK den ved å bruke det eksakte navnet.
52
+ - Opprett kun en ny sesjon for et genuint annet tema.
50
53
  - Bruk beskrivende navn: "jira-research", "ci-status", "arkitektur-gjennomgang".
51
- - Gjenbruk en sesjon hvis temaet er det samme. Opprett ny for nytt tema.
52
54
  - remove_session for å fjerne en sesjon.
55
+ {active_instances}
53
56
 
54
57
  Agenter:
55
58
  - Tilgjengelige agenter: {agent_names}.
@@ -62,6 +65,13 @@ Regler:
62
65
  - Når agenten er ferdig: nevn utfallet i én setning.
63
66
  - Vær naturlig, kort, vennlig mens agenter jobber.
64
67
 
68
+ Brukeren bestemmer:
69
+ - Når brukeren spesifikt sier hvilken sesjon eller agent de vil sende til, \
70
+ BARE videresend meldingen. ALDRI nekt eller forklar at sesjonen er "feil" for den oppgaven.
71
+ - Brukeren har ALLTID rett til å sende hva som helst til hvilken som helst sesjon/agent.
72
+ - Naturlig videreføring: hvis brukeren stiller et oppfølgingsspørsmål relatert til en nylig sesjon, \
73
+ send det til samme sesjon uten å spørre.
74
+
65
75
  Avbryt/reset:
66
76
  - "avbryt", "stopp" → cancel_agent (med branch eller session_name)
67
77
  - "start på nytt", "reset" → reset_agent
@@ -93,9 +103,12 @@ Branch names are fuzzy-matched — e.g. "branding" matches "feat/branding-refres
93
103
  send_to_session with a descriptive name. Reuse existing sessions for the same topic.
94
104
 
95
105
  Sessions:
106
+ - ALWAYS check the active sessions list below before creating a new one.
107
+ - If a session covers the same topic, REUSE it by using its exact name.
108
+ - Only create a new session for a genuinely different topic.
96
109
  - Use descriptive names: "jira-research", "ci-status", "architecture-review".
97
- - Reuse a session if the topic is the same. Create new for a different topic.
98
110
  - remove_session to remove a session.
111
+ {active_instances}
99
112
 
100
113
  Agents:
101
114
  - Available agents: {agent_names}.
@@ -108,6 +121,13 @@ Rules:
108
121
  - When the agent finishes: mention the outcome in one sentence.
109
122
  - Be natural, brief, friendly while agents work.
110
123
 
124
+ User is in control:
125
+ - When the user specifically says which session or agent to send to, \
126
+ JUST forward the message. NEVER refuse or explain that the session is "wrong" for the task.
127
+ - The user ALWAYS has the right to send anything to any session/agent.
128
+ - Natural continuation: if the user asks a follow-up question related to a recent session, \
129
+ send it to the same session without asking.
130
+
111
131
  Cancel/reset:
112
132
  - "cancel", "stop" → cancel_agent (with branch or session_name)
113
133
  - "start fresh", "reset" → reset_agent
@@ -139,9 +159,12 @@ Branch-namn fuzzy-matchas — t.ex. "branding" matchar "feat/branding-refresh".
139
159
  send_to_session med beskrivande namn. Återanvänd befintliga sessioner för samma ämne.
140
160
 
141
161
  Sessioner:
162
+ - KONTROLLERA ALLTID listan över aktiva sessioner nedan innan du skapar en ny.
163
+ - Om en session täcker samma ämne, ÅTERANVÄND den genom att använda det exakta namnet.
164
+ - Skapa bara en ny session för ett genuint annorlunda ämne.
142
165
  - Använd beskrivande namn: "jira-research", "ci-status", "arkitektur-granskning".
143
- - Återanvänd en session om ämnet är detsamma. Skapa ny för nytt ämne.
144
166
  - remove_session för att ta bort en session.
167
+ {active_instances}
145
168
 
146
169
  Agenter:
147
170
  - Tillgängliga agenter: {agent_names}.
@@ -154,6 +177,13 @@ Regler:
154
177
  - När agenten är klar: nämn utfallet i en mening.
155
178
  - Var naturlig, kort, vänlig medan agenter arbetar.
156
179
 
180
+ Användaren bestämmer:
181
+ - När användaren specifikt säger vilken session eller agent att skicka till, \
182
+ BARA vidarebefordra meddelandet. ALDRIG vägra eller förklara att sessionen är "fel" för uppgiften.
183
+ - Användaren har ALLTID rätt att skicka vad som helst till vilken session/agent som helst.
184
+ - Naturlig fortsättning: om användaren ställer en uppföljningsfråga relaterad till en nylig session, \
185
+ skicka den till samma session utan att fråga.
186
+
157
187
  Avbryt/reset:
158
188
  - "avbryt", "stopp" → cancel_agent (med branch eller session_name)
159
189
  - "börja om", "reset" → reset_agent
@@ -174,12 +204,25 @@ def get_system_instructions(
174
204
  context: str = "",
175
205
  overrides: dict[str, str] | None = None,
176
206
  enabled_agents: list[str] | None = None,
207
+ active_instances: str = "",
177
208
  ) -> str:
178
209
  if overrides and language in overrides:
179
210
  base = overrides[language]
180
211
  else:
181
212
  base = _SYSTEM_INSTRUCTIONS.get(language, _SYSTEM_INSTRUCTIONS["en"])
182
- base = base.format(agent_names=_agent_names(enabled_agents))
213
+ instances_text = active_instances or "(No active sessions or agents.)"
214
+ has_instances_placeholder = "{active_instances}" in base
215
+ try:
216
+ base = base.format(
217
+ agent_names=_agent_names(enabled_agents),
218
+ active_instances=instances_text,
219
+ )
220
+ except KeyError:
221
+ # Override instructions may not have all placeholders
222
+ base = base.format(agent_names=_agent_names(enabled_agents))
223
+ # If the template didn't have {active_instances}, append inline
224
+ if not has_instances_placeholder and active_instances:
225
+ base += f"\n\n{instances_text}"
183
226
  if context:
184
227
  return f"{base}\n\n{context}"
185
228
  return base
@@ -154,25 +154,40 @@ class InstanceRegistry:
154
154
  return instance
155
155
 
156
156
  def get_session_by_name(self, name: str) -> AgentInstance | None:
157
- """Fuzzy-match against SESSION instances' display_name."""
157
+ """Fuzzy-match against SESSION instances' display_name and session key.
158
+
159
+ Matches against both the display_name and the original session key
160
+ (stored in branch as ``_session_{key}``), so lookups work even after
161
+ the display_name is updated to a human-friendly label.
162
+ """
158
163
  name_lower = name.lower().strip()
159
164
  with self._lock:
160
165
  sessions = [
161
166
  inst for inst in self._instances.values()
162
167
  if inst.instance_type == InstanceType.SESSION
163
168
  ]
164
- # Exact match first
169
+
170
+ def _session_key(inst: AgentInstance) -> str:
171
+ return inst.branch.removeprefix("_session_").lower()
172
+
173
+ # Exact match on display_name or session key
165
174
  for inst in sessions:
166
- if inst.display_name.lower() == name_lower:
175
+ if inst.display_name.lower() == name_lower or _session_key(inst) == name_lower:
167
176
  return inst
168
- # Substring match
169
- partial = [inst for inst in sessions if name_lower in inst.display_name.lower()]
177
+ # Substring match on display_name or session key
178
+ partial = [
179
+ inst for inst in sessions
180
+ if name_lower in inst.display_name.lower() or name_lower in _session_key(inst)
181
+ ]
170
182
  if len(partial) == 1:
171
183
  return partial[0]
172
184
  if partial:
173
185
  return min(partial, key=lambda i: len(i.display_name))
174
- # Reverse substring (session name in query)
175
- partial = [inst for inst in sessions if inst.display_name.lower() in name_lower]
186
+ # Reverse substring (session name or key in query)
187
+ partial = [
188
+ inst for inst in sessions
189
+ if inst.display_name.lower() in name_lower or _session_key(inst) in name_lower
190
+ ]
176
191
  if len(partial) == 1:
177
192
  return partial[0]
178
193
  if partial:
@@ -128,13 +128,14 @@ class RealtimeSession:
128
128
  self._on_status("connected")
129
129
 
130
130
  async def configure_session(self) -> None:
131
- from voice_vibecoder.tools.handlers import get_startup_context
131
+ from voice_vibecoder.tools.handlers import get_active_instances_text, get_startup_context
132
132
 
133
133
  s = self._settings
134
134
  overrides = self._voice_config.system_instructions if self._voice_config else None
135
135
  instructions = get_system_instructions(
136
136
  s.language, context=get_startup_context(),
137
137
  overrides=overrides, enabled_agents=s.enabled_agents,
138
+ active_instances=get_active_instances_text(),
138
139
  )
139
140
 
140
141
  vad: VADSettings | None = None
@@ -304,12 +305,13 @@ class RealtimeSession:
304
305
  asyncio.run_coroutine_threadsafe(self._refresh_instructions(), self._loop)
305
306
 
306
307
  async def _refresh_instructions(self) -> None:
307
- from voice_vibecoder.tools.handlers import get_startup_context
308
+ from voice_vibecoder.tools.handlers import get_active_instances_text, get_startup_context
308
309
 
309
310
  overrides = self._voice_config.system_instructions if self._voice_config else None
310
311
  instructions = get_system_instructions(
311
312
  self._settings.language, context=get_startup_context(),
312
313
  overrides=overrides, enabled_agents=self._settings.enabled_agents,
314
+ active_instances=get_active_instances_text(),
313
315
  )
314
316
  try:
315
317
  await self._provider.update_session(instructions=instructions)
@@ -198,6 +198,7 @@ def _handle_send_to_session(message: str, session_name: str | None = None, agent
198
198
  session_name = "session"
199
199
 
200
200
  instance = _registry.get_session_by_name(session_name)
201
+ is_new_session = instance is None
201
202
  if not instance:
202
203
  instance = _registry.create_session(session_name, agent_type=target_agent)
203
204
  # Update display_name if a better one was provided by the LLM
@@ -239,7 +240,13 @@ def _handle_send_to_session(message: str, session_name: str | None = None, agent
239
240
  daemon=True,
240
241
  )
241
242
  thread.start()
242
- return f"Sent to session '{instance.display_name}'."
243
+ result = f"Sent to session '{instance.display_name}'."
244
+ if is_new_session:
245
+ all_sessions = registry.get_all_sessions()
246
+ if len(all_sessions) > 1:
247
+ names = [s.display_name for s in all_sessions]
248
+ result += f" (Active sessions: {', '.join(names)})"
249
+ return result
243
250
 
244
251
  thread = threading.Thread(
245
252
  target=_run_agent_background,
@@ -248,7 +255,13 @@ def _handle_send_to_session(message: str, session_name: str | None = None, agent
248
255
  )
249
256
  thread.start()
250
257
 
251
- return f"Sent to session '{instance.display_name}'."
258
+ result = f"Sent to session '{instance.display_name}'."
259
+ if is_new_session:
260
+ all_sessions = registry.get_all_sessions()
261
+ if len(all_sessions) > 1:
262
+ names = [s.display_name for s in all_sessions]
263
+ result += f" (Active sessions: {', '.join(names)})"
264
+ return result
252
265
 
253
266
 
254
267
  def _run_agent_background(instance: AgentInstance, message: str, registry: "InstanceRegistry") -> None:
@@ -15,19 +15,12 @@ from voice_vibecoder.tools.dispatch import (
15
15
  from voice_vibecoder.worktrees import remove_worktree, resolve_worktree_path
16
16
 
17
17
 
18
- def get_startup_context() -> str:
19
- """Build a context string with current instances, agents, and worktrees for the system prompt."""
18
+ def get_active_instances_text() -> str:
19
+ """Return a text summary of active sessions and code instances for inline insertion."""
20
20
  from voice_vibecoder.code_providers.registry import get_agent
21
21
 
22
22
  parts = []
23
23
 
24
- enabled = _dispatch._enabled_agents
25
- if len(enabled) > 1:
26
- labels = [get_agent(a).label for a in enabled]
27
- parts.append(f"Enabled agents: {', '.join(labels)}")
28
- parts.append(f"Default agent: {get_agent(enabled[0]).label}")
29
- parts.append("Use the 'agent' parameter in send_to_agent/send_to_session to pick a specific agent.")
30
-
31
24
  if _dispatch._registry:
32
25
  code_instances = _dispatch._registry.get_all_code_instances()
33
26
  sessions = _dispatch._registry.get_all_sessions()
@@ -44,6 +37,24 @@ def get_startup_context() -> str:
44
37
  agent_label = get_agent(inst.agent_type).label
45
38
  parts.append(f" - {inst.display_name} ({inst.status.value}) [{agent_label}]")
46
39
 
40
+ if not parts:
41
+ return ""
42
+ return "\n".join(parts)
43
+
44
+
45
+ def get_startup_context() -> str:
46
+ """Build a context string with agent config and worktrees for the system prompt."""
47
+ from voice_vibecoder.code_providers.registry import get_agent
48
+
49
+ parts = []
50
+
51
+ enabled = _dispatch._enabled_agents
52
+ if len(enabled) > 1:
53
+ labels = [get_agent(a).label for a in enabled]
54
+ parts.append(f"Enabled agents: {', '.join(labels)}")
55
+ parts.append(f"Default agent: {get_agent(enabled[0]).label}")
56
+ parts.append("Use the 'agent' parameter in send_to_agent/send_to_session to pick a specific agent.")
57
+
47
58
  if _dispatch._repo_root:
48
59
  from voice_vibecoder.worktrees import list_worktrees
49
60
 
File without changes
File without changes