voice-vibecoder 2.5.2__tar.gz → 2.6.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.
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/CHANGELOG.md +21 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/PKG-INFO +1 -1
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/pyproject.toml +1 -1
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/__init__.py +1 -1
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/config.py +26 -4
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/instances.py +6 -12
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/session.py +8 -2
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/tools/agent_task.py +68 -40
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/tools/file_tree.py +24 -42
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/tools/handlers.py +20 -9
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/.github/workflows/ci.yml +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/.github/workflows/release.yml +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/.gitignore +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/LICENSE +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/README.md +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/app.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/index.html +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/app_config.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/audio.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/cli_serve.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/server.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/tools/__init__.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/tools/definitions.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/tools/dispatch.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/__init__.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/callbacks.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/help_modal.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/panels.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/screen.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/setup.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/state.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/styles.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/ui/wizard.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
- {voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/worktrees.py +0 -0
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v2.6.0 (2026-02-20)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **agent_task**: Support matching by display name in session lookup
|
|
10
|
+
([`a97e0e6`](https://github.com/snokam/voice-vibecoder/commit/a97e0e67fdf7178ddee182acc275957a471e2691))
|
|
11
|
+
|
|
12
|
+
### Refactoring
|
|
13
|
+
|
|
14
|
+
- **instances**: Simplify branch key assignment logic
|
|
15
|
+
([`58f5d34`](https://github.com/snokam/voice-vibecoder/commit/58f5d3475d1696dd3b92252e3843ee2e4a04826b))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## v2.5.3 (2026-02-20)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- Prevent duplicate sessions by embedding active instances in instructions
|
|
23
|
+
([`7f493dc`](https://github.com/snokam/voice-vibecoder/commit/7f493dc275d7913edc760afc8ac3b2c066062954))
|
|
24
|
+
|
|
25
|
+
|
|
5
26
|
## v2.5.2 (2026-02-20)
|
|
6
27
|
|
|
7
28
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-vibecoder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.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.
|
|
7
|
+
version = "2.6.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"
|
|
@@ -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}.
|
|
@@ -100,9 +103,12 @@ Branch names are fuzzy-matched — e.g. "branding" matches "feat/branding-refres
|
|
|
100
103
|
send_to_session with a descriptive name. Reuse existing sessions for the same topic.
|
|
101
104
|
|
|
102
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.
|
|
103
109
|
- Use descriptive names: "jira-research", "ci-status", "architecture-review".
|
|
104
|
-
- Reuse a session if the topic is the same. Create new for a different topic.
|
|
105
110
|
- remove_session to remove a session.
|
|
111
|
+
{active_instances}
|
|
106
112
|
|
|
107
113
|
Agents:
|
|
108
114
|
- Available agents: {agent_names}.
|
|
@@ -153,9 +159,12 @@ Branch-namn fuzzy-matchas — t.ex. "branding" matchar "feat/branding-refresh".
|
|
|
153
159
|
send_to_session med beskrivande namn. Återanvänd befintliga sessioner för samma ämne.
|
|
154
160
|
|
|
155
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.
|
|
156
165
|
- Använd beskrivande namn: "jira-research", "ci-status", "arkitektur-granskning".
|
|
157
|
-
- Återanvänd en session om ämnet är detsamma. Skapa ny för nytt ämne.
|
|
158
166
|
- remove_session för att ta bort en session.
|
|
167
|
+
{active_instances}
|
|
159
168
|
|
|
160
169
|
Agenter:
|
|
161
170
|
- Tillgängliga agenter: {agent_names}.
|
|
@@ -195,12 +204,25 @@ def get_system_instructions(
|
|
|
195
204
|
context: str = "",
|
|
196
205
|
overrides: dict[str, str] | None = None,
|
|
197
206
|
enabled_agents: list[str] | None = None,
|
|
207
|
+
active_instances: str = "",
|
|
198
208
|
) -> str:
|
|
199
209
|
if overrides and language in overrides:
|
|
200
210
|
base = overrides[language]
|
|
201
211
|
else:
|
|
202
212
|
base = _SYSTEM_INSTRUCTIONS.get(language, _SYSTEM_INSTRUCTIONS["en"])
|
|
203
|
-
|
|
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}"
|
|
204
226
|
if context:
|
|
205
227
|
return f"{base}\n\n{context}"
|
|
206
228
|
return base
|
|
@@ -125,9 +125,6 @@ class InstanceRegistry:
|
|
|
125
125
|
self.on_status_change(instance.instance_id, instance.status)
|
|
126
126
|
if self.on_instance_change:
|
|
127
127
|
self.on_instance_change()
|
|
128
|
-
# Start file tree updates for code instances (skip for sessions)
|
|
129
|
-
from voice_vibecoder.tools.file_tree import start_file_tree_updates
|
|
130
|
-
start_file_tree_updates(instance.instance_id, worktree_path, self)
|
|
131
128
|
return instance
|
|
132
129
|
|
|
133
130
|
def create_session(self, name: str, agent_type: str = "claude") -> AgentInstance:
|
|
@@ -136,16 +133,20 @@ class InstanceRegistry:
|
|
|
136
133
|
if existing:
|
|
137
134
|
return existing
|
|
138
135
|
with self._lock:
|
|
136
|
+
# Re-check under lock to prevent race between concurrent creates
|
|
137
|
+
branch_key = f"_session_{name}"
|
|
138
|
+
if branch_key in self._branch_index:
|
|
139
|
+
return self._instances[self._branch_index[branch_key]]
|
|
139
140
|
instance = AgentInstance(
|
|
140
141
|
instance_id=uuid.uuid4().hex[:8],
|
|
141
|
-
branch=
|
|
142
|
+
branch=branch_key,
|
|
142
143
|
worktree_path="",
|
|
143
144
|
agent_type=agent_type,
|
|
144
145
|
instance_type=InstanceType.SESSION,
|
|
145
146
|
display_name=name,
|
|
146
147
|
)
|
|
147
148
|
self._instances[instance.instance_id] = instance
|
|
148
|
-
self._branch_index[
|
|
149
|
+
self._branch_index[branch_key] = instance.instance_id
|
|
149
150
|
self._save_state()
|
|
150
151
|
if self.on_status_change:
|
|
151
152
|
self.on_status_change(instance.instance_id, instance.status)
|
|
@@ -415,11 +416,4 @@ class InstanceRegistry:
|
|
|
415
416
|
self._branch_index[branch] = instance.instance_id
|
|
416
417
|
loaded.append(instance)
|
|
417
418
|
|
|
418
|
-
# Start file tree updates for code instances only
|
|
419
|
-
from voice_vibecoder.tools.file_tree import start_file_tree_updates
|
|
420
|
-
for inst in loaded:
|
|
421
|
-
if inst.instance_type == InstanceType.SESSION:
|
|
422
|
-
continue
|
|
423
|
-
start_file_tree_updates(inst.instance_id, inst.worktree_path, self)
|
|
424
|
-
|
|
425
419
|
return loaded
|
|
@@ -128,13 +128,18 @@ class RealtimeSession:
|
|
|
128
128
|
self._on_status("connected")
|
|
129
129
|
|
|
130
130
|
async def configure_session(self) -> None:
|
|
131
|
-
from voice_vibecoder.tools.
|
|
131
|
+
from voice_vibecoder.tools.agent_task import warm_mcp_cache
|
|
132
|
+
from voice_vibecoder.tools.handlers import get_active_instances_text, get_startup_context
|
|
133
|
+
|
|
134
|
+
# Pre-warm MCP server cache so first agent spawn doesn't pay the cost
|
|
135
|
+
warm_mcp_cache()
|
|
132
136
|
|
|
133
137
|
s = self._settings
|
|
134
138
|
overrides = self._voice_config.system_instructions if self._voice_config else None
|
|
135
139
|
instructions = get_system_instructions(
|
|
136
140
|
s.language, context=get_startup_context(),
|
|
137
141
|
overrides=overrides, enabled_agents=s.enabled_agents,
|
|
142
|
+
active_instances=get_active_instances_text(),
|
|
138
143
|
)
|
|
139
144
|
|
|
140
145
|
vad: VADSettings | None = None
|
|
@@ -304,12 +309,13 @@ class RealtimeSession:
|
|
|
304
309
|
asyncio.run_coroutine_threadsafe(self._refresh_instructions(), self._loop)
|
|
305
310
|
|
|
306
311
|
async def _refresh_instructions(self) -> None:
|
|
307
|
-
from voice_vibecoder.tools.handlers import get_startup_context
|
|
312
|
+
from voice_vibecoder.tools.handlers import get_active_instances_text, get_startup_context
|
|
308
313
|
|
|
309
314
|
overrides = self._voice_config.system_instructions if self._voice_config else None
|
|
310
315
|
instructions = get_system_instructions(
|
|
311
316
|
self._settings.language, context=get_startup_context(),
|
|
312
317
|
overrides=overrides, enabled_agents=self._settings.enabled_agents,
|
|
318
|
+
active_instances=get_active_instances_text(),
|
|
313
319
|
)
|
|
314
320
|
try:
|
|
315
321
|
await self._provider.update_session(instructions=instructions)
|
|
@@ -23,10 +23,11 @@ _CLAUDE_JSON = Path.home() / ".claude.json"
|
|
|
23
23
|
|
|
24
24
|
# Cached MCP servers — loaded once per session, files rarely change
|
|
25
25
|
_mcp_cache: dict[str, dict] | None = None
|
|
26
|
+
_mcp_lock = threading.Lock()
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def _load_mcp_servers() -> dict[str, dict]:
|
|
29
|
-
"""Load MCP servers (cached after first call).
|
|
30
|
+
"""Load MCP servers (cached after first call, thread-safe).
|
|
30
31
|
|
|
31
32
|
Priority order:
|
|
32
33
|
1. MCP_SERVERS_JSON environment variable (for Azure/production)
|
|
@@ -37,50 +38,60 @@ def _load_mcp_servers() -> dict[str, dict]:
|
|
|
37
38
|
if _mcp_cache is not None:
|
|
38
39
|
return _mcp_cache
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
with _mcp_lock:
|
|
42
|
+
# Double-check after acquiring the lock
|
|
43
|
+
if _mcp_cache is not None:
|
|
44
|
+
return _mcp_cache
|
|
45
|
+
|
|
46
|
+
import os
|
|
47
|
+
from voice_vibecoder.tools.dispatch import _repo_root
|
|
48
|
+
|
|
49
|
+
servers: dict[str, dict] = {}
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
# Load from .mcp.json in repo root
|
|
52
|
+
if _repo_root:
|
|
53
|
+
mcp_json = Path(_repo_root) / ".mcp.json"
|
|
54
|
+
if mcp_json.exists():
|
|
55
|
+
try:
|
|
56
|
+
data = json.loads(mcp_json.read_text())
|
|
57
|
+
servers.update(data.get("mcpServers", {}))
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.warning("Failed to read .mcp.json: %s", e)
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
mcp_json = Path(_repo_root) / ".mcp.json"
|
|
48
|
-
if mcp_json.exists():
|
|
61
|
+
# Load from ~/.claude.json
|
|
62
|
+
if _repo_root and _CLAUDE_JSON.exists():
|
|
49
63
|
try:
|
|
50
|
-
data = json.loads(
|
|
51
|
-
|
|
64
|
+
data = json.loads(_CLAUDE_JSON.read_text())
|
|
65
|
+
project_key = str(Path(_repo_root))
|
|
66
|
+
project_data = data.get("projects", {}).get(project_key, {})
|
|
67
|
+
local_servers = project_data.get("mcpServers", {})
|
|
68
|
+
servers.update(local_servers)
|
|
52
69
|
except Exception as e:
|
|
53
|
-
logger.warning("Failed to read .
|
|
70
|
+
logger.warning("Failed to read ~/.claude.json: %s", e)
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
except Exception as e:
|
|
74
|
-
logger.warning("Failed to parse MCP_SERVERS_JSON: %s", e)
|
|
72
|
+
# MCP_SERVERS_JSON takes precedence (for Azure Container Apps)
|
|
73
|
+
mcp_env = os.environ.get("MCP_SERVERS_JSON")
|
|
74
|
+
if mcp_env:
|
|
75
|
+
try:
|
|
76
|
+
env_servers = json.loads(mcp_env)
|
|
77
|
+
servers.update(env_servers.get("mcpServers", env_servers))
|
|
78
|
+
logger.info("Loaded MCP servers from MCP_SERVERS_JSON environment variable")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.warning("Failed to parse MCP_SERVERS_JSON: %s", e)
|
|
81
|
+
|
|
82
|
+
# Ensure each MCP server inherits process env vars (the Claude CLI may
|
|
83
|
+
# not forward its own env to MCP server subprocesses).
|
|
84
|
+
for _name, cfg in servers.items():
|
|
85
|
+
if "env" not in cfg:
|
|
86
|
+
cfg["env"] = dict(os.environ)
|
|
87
|
+
|
|
88
|
+
_mcp_cache = servers
|
|
89
|
+
return servers
|
|
75
90
|
|
|
76
|
-
# Ensure each MCP server inherits process env vars (the Claude CLI may
|
|
77
|
-
# not forward its own env to MCP server subprocesses).
|
|
78
|
-
for _name, cfg in servers.items():
|
|
79
|
-
if "env" not in cfg:
|
|
80
|
-
cfg["env"] = dict(os.environ)
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
def warm_mcp_cache() -> None:
|
|
93
|
+
"""Pre-load MCP server config so it's ready when agents spawn."""
|
|
94
|
+
_load_mcp_servers()
|
|
84
95
|
|
|
85
96
|
|
|
86
97
|
def _create_runner(agent_type: str, permission_mode: str, mcp_servers: dict | None, env: dict | None = None) -> AgentRunner:
|
|
@@ -198,6 +209,11 @@ def _handle_send_to_session(message: str, session_name: str | None = None, agent
|
|
|
198
209
|
session_name = "session"
|
|
199
210
|
|
|
200
211
|
instance = _registry.get_session_by_name(session_name)
|
|
212
|
+
# Also try matching by display_name (LLM may use different session keys
|
|
213
|
+
# for the same logical session, e.g. "calendar-today" vs "today-s-calendar")
|
|
214
|
+
if not instance and display_name:
|
|
215
|
+
instance = _registry.get_session_by_name(display_name)
|
|
216
|
+
is_new_session = instance is None
|
|
201
217
|
if not instance:
|
|
202
218
|
instance = _registry.create_session(session_name, agent_type=target_agent)
|
|
203
219
|
# Update display_name if a better one was provided by the LLM
|
|
@@ -239,7 +255,13 @@ def _handle_send_to_session(message: str, session_name: str | None = None, agent
|
|
|
239
255
|
daemon=True,
|
|
240
256
|
)
|
|
241
257
|
thread.start()
|
|
242
|
-
|
|
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
|
|
243
265
|
|
|
244
266
|
thread = threading.Thread(
|
|
245
267
|
target=_run_agent_background,
|
|
@@ -248,7 +270,13 @@ def _handle_send_to_session(message: str, session_name: str | None = None, agent
|
|
|
248
270
|
)
|
|
249
271
|
thread.start()
|
|
250
272
|
|
|
251
|
-
|
|
273
|
+
result = f"Sent to session '{instance.display_name}'."
|
|
274
|
+
if is_new_session:
|
|
275
|
+
all_sessions = registry.get_all_sessions()
|
|
276
|
+
if len(all_sessions) > 1:
|
|
277
|
+
names = [s.display_name for s in all_sessions]
|
|
278
|
+
result += f" (Active sessions: {', '.join(names)})"
|
|
279
|
+
return result
|
|
252
280
|
|
|
253
281
|
|
|
254
282
|
def _run_agent_background(instance: AgentInstance, message: str, registry: "InstanceRegistry") -> None:
|
|
@@ -9,6 +9,7 @@ timer while an agent is running.
|
|
|
9
9
|
import logging
|
|
10
10
|
import subprocess
|
|
11
11
|
import threading
|
|
12
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
12
13
|
from pathlib import PurePosixPath
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
@@ -16,16 +17,15 @@ logger = logging.getLogger(__name__)
|
|
|
16
17
|
MAX_DEPTH = 10 # Maximum tree depth to prevent excessive nesting
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
["git",
|
|
23
|
-
cwd=
|
|
20
|
+
def _git(args: list[str], cwd: str) -> subprocess.CompletedProcess:
|
|
21
|
+
"""Run a git command and return the CompletedProcess."""
|
|
22
|
+
return subprocess.run(
|
|
23
|
+
["git", *args],
|
|
24
|
+
cwd=cwd,
|
|
24
25
|
capture_output=True,
|
|
25
26
|
text=True,
|
|
26
27
|
timeout=10,
|
|
27
28
|
)
|
|
28
|
-
return result.stdout.strip() if result.returncode == 0 else "main"
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _build_tree_node(path: str, status: str | None, is_folder: bool) -> dict:
|
|
@@ -112,26 +112,25 @@ def get_file_tree(worktree_path: str) -> dict:
|
|
|
112
112
|
"summary": {"modified": N, "new": N, "deleted": N, "unchanged": N}}
|
|
113
113
|
"""
|
|
114
114
|
try:
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
# Run independent git commands in parallel, then diff after merge-base
|
|
116
|
+
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
117
|
+
branch_fut = pool.submit(_git, ["rev-parse", "--abbrev-ref", "HEAD"], worktree_path)
|
|
118
|
+
base_fut = pool.submit(_git, ["merge-base", "main", "HEAD"], worktree_path)
|
|
119
|
+
untracked_fut = pool.submit(_git, ["ls-files", "--others", "--exclude-standard"], worktree_path)
|
|
120
|
+
all_files_fut = pool.submit(_git, ["ls-files"], worktree_path)
|
|
121
|
+
|
|
122
|
+
# Wait for merge-base, then submit diff (overlaps with ls-files)
|
|
123
|
+
base_result = base_fut.result()
|
|
124
|
+
base = base_result.stdout.strip() if base_result.returncode == 0 else "main"
|
|
125
|
+
diff_fut = pool.submit(_git, ["diff", "--name-status", base], worktree_path)
|
|
126
|
+
|
|
127
|
+
# Collect remaining results
|
|
128
|
+
branch_result = branch_fut.result()
|
|
129
|
+
diff_result = diff_fut.result()
|
|
130
|
+
untracked_result = untracked_fut.result()
|
|
131
|
+
all_files_result = all_files_fut.result()
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
# Get modified/deleted files vs merge-base
|
|
128
|
-
diff_result = subprocess.run(
|
|
129
|
-
["git", "diff", "--name-status", base],
|
|
130
|
-
cwd=worktree_path,
|
|
131
|
-
capture_output=True,
|
|
132
|
-
text=True,
|
|
133
|
-
timeout=10,
|
|
134
|
-
)
|
|
133
|
+
root = branch_result.stdout.strip() if branch_result.returncode == 0 else "unknown"
|
|
135
134
|
|
|
136
135
|
changed: dict[str, str] = {} # path -> status
|
|
137
136
|
if diff_result.returncode == 0:
|
|
@@ -151,29 +150,12 @@ def get_file_tree(worktree_path: str) -> dict:
|
|
|
151
150
|
elif status_code in ("R", "C"):
|
|
152
151
|
changed[path] = "modified"
|
|
153
152
|
|
|
154
|
-
# Get untracked files
|
|
155
|
-
untracked_result = subprocess.run(
|
|
156
|
-
["git", "ls-files", "--others", "--exclude-standard"],
|
|
157
|
-
cwd=worktree_path,
|
|
158
|
-
capture_output=True,
|
|
159
|
-
text=True,
|
|
160
|
-
timeout=10,
|
|
161
|
-
)
|
|
162
153
|
if untracked_result.returncode == 0:
|
|
163
154
|
for path in untracked_result.stdout.strip().splitlines():
|
|
164
155
|
path = path.strip()
|
|
165
156
|
if path and path not in changed:
|
|
166
157
|
changed[path] = "new"
|
|
167
158
|
|
|
168
|
-
# Get all tracked files to show complete project structure
|
|
169
|
-
all_files_result = subprocess.run(
|
|
170
|
-
["git", "ls-files"],
|
|
171
|
-
cwd=worktree_path,
|
|
172
|
-
capture_output=True,
|
|
173
|
-
text=True,
|
|
174
|
-
timeout=10,
|
|
175
|
-
)
|
|
176
|
-
|
|
177
159
|
all_paths: dict[str, str] = {} # path -> status
|
|
178
160
|
if all_files_result.returncode == 0:
|
|
179
161
|
for f in all_files_result.stdout.strip().splitlines():
|
|
@@ -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
|
|
19
|
-
"""
|
|
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
|
|
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.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/code_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/code_providers/claude.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/code_providers/cursor.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/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
|
{voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/voice_providers/__init__.py
RENAMED
|
File without changes
|
{voice_vibecoder-2.5.2 → voice_vibecoder-2.6.0}/src/voice_vibecoder/voice_providers/openai.py
RENAMED
|
File without changes
|
|
File without changes
|