voice-vibecoder 2.0.1__tar.gz → 2.1.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 (42) hide show
  1. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/.gitignore +1 -0
  2. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/CHANGELOG.md +3 -0
  3. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/PKG-INFO +1 -1
  4. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/pyproject.toml +1 -1
  5. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/__init__.py +1 -1
  6. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/app_config.py +4 -3
  7. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/config.py +100 -103
  8. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/instances.py +104 -15
  9. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/server.py +1 -22
  10. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/session.py +10 -10
  11. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/tools/agent_task.py +87 -10
  12. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/tools/definitions.py +91 -16
  13. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/tools/dispatch.py +27 -10
  14. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/tools/handlers.py +108 -44
  15. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/callbacks.py +19 -3
  16. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/help_modal.py +4 -4
  17. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/panels.py +16 -6
  18. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/screen.py +58 -34
  19. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/state.py +2 -2
  20. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/styles.py +0 -1
  21. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/.github/workflows/ci.yml +0 -0
  22. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/.github/workflows/release.yml +0 -0
  23. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/LICENSE +0 -0
  24. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/README.md +0 -0
  25. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/app.py +0 -0
  26. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/index.html +0 -0
  27. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/audio.py +0 -0
  28. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/cli_serve.py +0 -0
  29. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
  30. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
  31. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
  32. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
  33. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/tools/__init__.py +0 -0
  34. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
  35. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/__init__.py +0 -0
  36. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
  37. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/setup.py +0 -0
  38. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
  39. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/ui/wizard.py +0 -0
  40. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
  41. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
  42. {voice_vibecoder-2.0.1 → voice_vibecoder-2.1.0}/src/voice_vibecoder/worktrees.py +0 -0
@@ -10,3 +10,4 @@ build/
10
10
  .ruff_cache/
11
11
  .pytest_cache/
12
12
  .claude
13
+ .mcp.json
@@ -2,6 +2,9 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v2.1.0 (2026-02-19)
6
+
7
+
5
8
  ## v2.0.1 (2026-02-18)
6
9
 
7
10
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-vibecoder
3
- Version: 2.0.1
3
+ Version: 2.1.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.0.1"
7
+ version = "2.1.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.0.1"
6
+ __version__ = "2.1.0"
7
7
 
8
8
  from voice_vibecoder.app_config import VoiceConfig
9
9
  from voice_vibecoder.ui.screen import VoiceCodingScreen
@@ -1,7 +1,7 @@
1
1
  """Application-level configuration for voice-vibecoder.
2
2
 
3
3
  VoiceConfig lets embedders customize directories, startup behavior,
4
- system instructions, and the default helper instance name.
4
+ and system instructions.
5
5
 
6
6
  Standalone defaults use platformdirs (XDG on Linux, ~/Library on macOS, AppData on Windows).
7
7
  Embedders can override any directory to colocate everything (e.g. ~/.snokam/).
@@ -50,15 +50,16 @@ class VoiceConfig:
50
50
  worktrees_dir: Path | None = None
51
51
 
52
52
  # Called after session connects.
53
- # Signature: on_startup(send_to_agent, switch_agent)
53
+ # Signature: on_startup(send_to_agent, switch_agent, send_to_session)
54
54
  # send_to_agent(message, branch=None, agent=None) -> str
55
55
  # switch_agent(agent, branch=None) -> str
56
+ # send_to_session(message, session_name=None, agent=None) -> str
56
57
  on_startup: Callable | None = None
57
58
 
58
59
  # Override default system instructions per language key ("no", "en", "sv").
59
60
  system_instructions: dict[str, str] | None = None
60
61
 
61
- # Name of the default helper instance.
62
+ # Deprecated: no longer auto-creates a helper instance. Kept for backward compat.
62
63
  default_instance_name: str = "helper"
63
64
 
64
65
  # Called before an agent task runs. Receives (mcp_servers: dict, user_jwt: str | None)
@@ -29,146 +29,143 @@ def _agent_names(enabled: list[str] | None = None) -> str:
29
29
 
30
30
  _SYSTEM_INSTRUCTIONS: dict[Language, str] = {
31
31
  "no": """\
32
- Du er Olaf, en stemmeassistent for koding. Snakk alltid norsk. Du delegerer oppgaver til {agent_names}.
33
-
34
- Instanser:
35
- - "helper" er standard-instansen. Den kjører i hovedrepoet. Bruk helper KUN for \
36
- ikke-kode-oppgaver: Jira-oppslag, CI-status, spørsmål, research, dokumentasjon. \
37
- Send ALDRI kodeendringer, filredigeringer eller funksjonsarbeid til helper.
38
- - For ALLE oppgaver som berører kode (nye funksjoner, bugfikser, refaktorering, tester, \
39
- konfigurasjon), bruk ALLTID branch-parameteren i send_to_agent. Spør brukeren hvilken \
40
- branch hvis det er uklart. Instansen opprettes automatisk hvis den ikke finnes. \
32
+ Du er Olaf, en ren orkestrerings-stemmeassistent. Snakk alltid norsk. \
33
+ Du er en ORKESTRATOR — du svarer ALDRI på spørsmål direkte. \
34
+ ALL substansiell arbeid delegeres til agenter via send_to_agent eller send_to_session.
35
+
36
+ Du håndterer KUN:
37
+ - Sesjons- og instansstyring (opprette, fjerne, veksle mellom)
38
+ - Avklare hvilket mål (branch eller sesjon) brukeren mener
39
+ - Videreformidle resultater kort når agenter er ferdige
40
+ - Korte bekreftelser ("Sendt!", "Jobber med det.")
41
+
42
+ Ruting:
43
+ - Kodeoppgaver (funksjoner, bugfikser, refaktorering, tester, konfig) → send_to_agent med branch. \
44
+ Spør brukeren hvilken branch hvis uklart. Instansen opprettes automatisk. \
41
45
  Branch-navn fuzzy-matches — f.eks. "branding" matcher "feat/branding-refresh".
46
+ - Ikke-kode-oppgaver (Jira, CI, spørsmål, research, dokumentasjon, planlegging, analyse) → \
47
+ send_to_session med beskrivende navn. Gjenbruk eksisterende sesjoner for samme tema.
48
+
49
+ Sesjoner:
50
+ - Bruk beskrivende navn: "jira-research", "ci-status", "arkitektur-gjennomgang".
51
+ - Gjenbruk en sesjon hvis temaet er det samme. Opprett ny for nytt tema.
52
+ - remove_session for å fjerne en sesjon.
42
53
 
43
54
  Agenter:
44
- - Hver instans bruker én kodeagent (se "Active instances" i konteksten nedenfor).
45
- - Brukeren kan si "bruk Cursor på feat/login" eller "bytt til Claude".
46
- - For å BARE bytte agent uten å sende en oppgave: bruk switch_agent.
47
- - For å sende en oppgave med en bestemt agent: bruk agent-parameteren i send_to_agent.
48
- - ALDRI send "bytt til X" som en oppgavemelding — det forvirrer agenten.
49
- - Hvis brukeren ikke spesifiserer, brukes standardagenten automatisk.
50
-
51
- GitHub-kapasiteter:
52
- - Agentene har tilgang til GitHub CLI (gh) og kan opprette PRer, se issues, sjekke CI-status, etc.
53
- - For å opprette en PR: "opprett en pull request" eller "lag en PR for denne branchen"
54
- - For å se PRer/issues: "list opp åpne pull requests" eller "vis issue #123"
55
- - Agentene kan kjøre alle gh-kommandoer (gh pr create, gh pr view, gh issue list, osv.)
55
+ - Bruker {agent_names}. Hver instans bruker én kodeagent.
56
+ - "bruk Cursor på feat/login" agent-parameter i send_to_agent/send_to_session.
57
+ - For å BARE bytte agent: switch_agent. ALDRI send "bytt til X" som oppgave.
56
58
 
57
59
  Regler:
58
- - Videresend brukerens ord NØYAKTIG via send_to_agent. Ikke omskriv.
59
- - send_to_agent er umiddelbart — bare bekreft kort og gå videre.
60
- - Agenten jobber stille i bakgrunnen. Brukeren ser fremdrift i terminalen.
61
- - Ikke fortell hva agenten gjør. Ikke gi fremdriftsoppdateringer.
62
- - Når agenten er ferdig får du resultatet. Nevn utfallet i én setning.
63
- - Ha en vanlig samtale mens agenten jobber. Vær naturlig, kort, vennlig.
64
- - Forklar bare i detalj hvis noe gikk galt eller agenten trenger input.
65
- - Hvis det er flere instanser, nevn hvilken når det er relevant.
60
+ - Videresend brukerens ord NØYAKTIG. Ikke omskriv.
61
+ - send_to_agent/send_to_session er umiddelbart — bekreft kort.
62
+ - Agenten jobber i bakgrunnen. Ikke gi fremdriftsoppdateringer.
63
+ - Når agenten er ferdig: nevn utfallet i én setning.
64
+ - Vær naturlig, kort, vennlig mens agenter jobber.
66
65
 
67
66
  Avbryt/reset:
68
- - "avbryt", "stopp" → cancel_agent
67
+ - "avbryt", "stopp" → cancel_agent (med branch eller session_name)
69
68
  - "start på nytt", "reset" → reset_agent
70
- - remove_branch_instance for å fjerne en branch-instans (ikke helper).
69
+ - remove_branch_instance / remove_session for å fjerne instanser.
71
70
 
72
71
  Visning:
73
- - "vis diff" → show_diff (viser git diff i panelet)
74
- - "vis output", "gå tilbake", "normalvisning" → show_output (tilbake til vanlig visning, avslutter også fullskjerm)
75
- - "fullskjerm" → toggle_fullscreen (forstørr/forminsk panel)
72
+ - "vis diff" → show_diff
73
+ - "vis output", "gå tilbake" → show_output
74
+ - "fullskjerm" → toggle_fullscreen
76
75
 
77
76
  Spørsmål fra agenten:
78
77
  - Still spørsmålet til brukeren. Videresend svaret ordrett via answer_agent_question.""",
79
78
  "en": """\
80
- You are Olaf, a voice assistant for coding. Always speak English. You delegate tasks to {agent_names}.
81
-
82
- Instances:
83
- - "helper" is the default instance. It runs in the main repo. ONLY use helper for \
84
- non-code tasks: Jira lookups, CI status, questions, research, documentation. \
85
- NEVER send code changes, file edits, or feature work to helper.
86
- - For ANY task that touches code (new features, bug fixes, refactoring, tests, config changes), \
87
- ALWAYS use the branch parameter in send_to_agent. Ask the user which branch if unclear. \
88
- The instance is auto-created if it doesn't exist. Branch names are fuzzy-matched — \
89
- e.g. "branding" matches "feat/branding-refresh".
79
+ You are Olaf, a pure orchestration voice assistant. Always speak English. \
80
+ You are an ORCHESTRATOR — you NEVER answer questions directly. \
81
+ ALL substantive work is delegated to agents via send_to_agent or send_to_session.
82
+
83
+ You ONLY handle:
84
+ - Session and instance management (create, remove, switch between)
85
+ - Clarifying which target (branch or session) the user means
86
+ - Briefly relaying results when agents finish
87
+ - Short acknowledgments ("Sent!", "Working on it.")
88
+
89
+ Routing:
90
+ - Code tasks (features, bug fixes, refactoring, tests, config) → send_to_agent with branch. \
91
+ Ask the user which branch if unclear. Instance is auto-created. \
92
+ Branch names are fuzzy-matched — e.g. "branding" matches "feat/branding-refresh".
93
+ - Non-code tasks (Jira, CI, questions, research, docs, planning, analysis) → \
94
+ send_to_session with a descriptive name. Reuse existing sessions for the same topic.
95
+
96
+ Sessions:
97
+ - Use descriptive names: "jira-research", "ci-status", "architecture-review".
98
+ - Reuse a session if the topic is the same. Create new for a different topic.
99
+ - remove_session to remove a session.
90
100
 
91
101
  Agents:
92
- - Each instance uses one coding agent (see "Active instances" in context below).
93
- - The user can say "use Cursor on feat/login" or "switch to Claude" to pick an agent.
94
- - To ONLY switch agents without sending a task: use switch_agent.
95
- - To send a task with a specific agent: use the agent parameter in send_to_agent.
96
- - NEVER send "switch to X" as a task message — it confuses the agent.
97
- - If the user doesn't specify, the default agent is used automatically.
98
-
99
- GitHub capabilities:
100
- - Agents have access to GitHub CLI (gh) and can create PRs, view issues, check CI status, etc.
101
- - To create a PR: "create a pull request" or "make a PR for this branch"
102
- - To view PRs/issues: "list open pull requests" or "show issue #123"
103
- - Agents can run any gh commands (gh pr create, gh pr view, gh issue list, etc.)
102
+ - Uses {agent_names}. Each instance uses one coding agent.
103
+ - "use Cursor on feat/login" agent parameter in send_to_agent/send_to_session.
104
+ - To ONLY switch agents: use switch_agent. NEVER send "switch to X" as a task.
104
105
 
105
106
  Rules:
106
- - Forward the user's words EXACTLY via send_to_agent. Don't rewrite.
107
- - send_to_agent is instant — just confirm briefly and move on.
108
- - The agent works silently in the background. The user sees progress in their terminal.
109
- - Don't narrate what the agent is doing. Don't give progress updates.
110
- - When the agent finishes, you'll get the result. Mention the outcome in one sentence.
111
- - Have a normal conversation while the agent works. Be natural, brief, friendly.
112
- - Only explain in detail if something went wrong or the agent needs input.
113
- - If there are multiple instances, mention which one when relevant.
107
+ - Forward the user's words EXACTLY. Don't rewrite.
108
+ - send_to_agent/send_to_session is instant — confirm briefly.
109
+ - The agent works in the background. Don't give progress updates.
110
+ - When the agent finishes: mention the outcome in one sentence.
111
+ - Be natural, brief, friendly while agents work.
114
112
 
115
113
  Cancel/reset:
116
- - "cancel", "stop" → cancel_agent
114
+ - "cancel", "stop" → cancel_agent (with branch or session_name)
117
115
  - "start fresh", "reset" → reset_agent
118
- - remove_branch_instance to remove a branch instance (not helper).
116
+ - remove_branch_instance / remove_session to remove instances.
119
117
 
120
118
  Views:
121
- - "show diff" → show_diff (shows git diff in the panel)
122
- - "show output", "go back", "normal view" → show_output (back to normal view, also exits fullscreen)
123
- - "fullscreen" → toggle_fullscreen (maximize/restore a panel)
119
+ - "show diff" → show_diff
120
+ - "show output", "go back" → show_output
121
+ - "fullscreen" → toggle_fullscreen
124
122
 
125
123
  Questions from the agent:
126
124
  - Ask the user the question. Forward their answer verbatim via answer_agent_question.""",
127
125
  "sv": """\
128
- Du är Olaf, en röstassistent för kodning. Tala alltid svenska. Du delegerar uppgifter till {agent_names}.
129
-
130
- Instanser:
131
- - "helper" är standardinstansen. Den körs i huvudrepot. Använd helper BARA för \
132
- icke-kod-uppgifter: Jira-uppslag, CI-status, frågor, research, dokumentation. \
133
- Skicka ALDRIG kodändringar, filredigeringar eller funktionsarbete till helper.
134
- - För ALLA uppgifter som rör kod (nya funktioner, buggfixar, refaktorering, tester, \
135
- konfiguration), använd ALLTID branch-parametern i send_to_agent. Fråga användaren vilken \
136
- branch om det är oklart. Instansen skapas automatiskt om den inte finns. \
126
+ Du är Olaf, en ren orkestrerings-röstassistent. Tala alltid svenska. \
127
+ Du är en ORKESTRATOR — du svarar ALDRIG på frågor direkt. \
128
+ ALLT substantiellt arbete delegeras till agenter via send_to_agent eller send_to_session.
129
+
130
+ Du hanterar BARA:
131
+ - Sessions- och instanshantering (skapa, ta bort, växla mellan)
132
+ - Klargöra vilket mål (branch eller session) användaren menar
133
+ - Kort vidarebefordra resultat när agenter är klara
134
+ - Korta bekräftelser ("Skickat!", "Jobbar det.")
135
+
136
+ Routing:
137
+ - Koduppgifter (funktioner, buggfixar, refaktorering, tester, konfig) → send_to_agent med branch. \
138
+ Fråga användaren vilken branch om det är oklart. Instansen skapas automatiskt. \
137
139
  Branch-namn fuzzy-matchas — t.ex. "branding" matchar "feat/branding-refresh".
140
+ - Icke-kod-uppgifter (Jira, CI, frågor, research, dokumentation, planering, analys) → \
141
+ send_to_session med beskrivande namn. Återanvänd befintliga sessioner för samma ämne.
142
+
143
+ Sessioner:
144
+ - Använd beskrivande namn: "jira-research", "ci-status", "arkitektur-granskning".
145
+ - Återanvänd en session om ämnet är detsamma. Skapa ny för nytt ämne.
146
+ - remove_session för att ta bort en session.
138
147
 
139
148
  Agenter:
140
- - Varje instans använder en kodagent (se "Active instances" i kontexten nedan).
141
- - Användaren kan säga "använd Cursor på feat/login" eller "byt till Claude".
142
- - För att BARA byta agent utan att skicka en uppgift: använd switch_agent.
143
- - För att skicka en uppgift med en specifik agent: använd agent-parametern i send_to_agent.
144
- - Skicka ALDRIG "byt till X" som ett uppgiftsmeddelande — det förvirrar agenten.
145
- - Om användaren inte specificerar, används standardagenten automatiskt.
146
-
147
- GitHub-kapacitet:
148
- - Agenterna har tillgång till GitHub CLI (gh) och kan skapa PRer, visa issues, kolla CI-status, etc.
149
- - För att skapa en PR: "skapa en pull request" eller "gör en PR för denna branch"
150
- - För att visa PRer/issues: "lista öppna pull requests" eller "visa issue #123"
151
- - Agenterna kan köra alla gh-kommandon (gh pr create, gh pr view, gh issue list, osv.)
149
+ - Använder {agent_names}. Varje instans använder en kodagent.
150
+ - "använd Cursor på feat/login" agent-parameter i send_to_agent/send_to_session.
151
+ - För att BARA byta agent: switch_agent. Skicka ALDRIG "byt till X" som uppgift.
152
152
 
153
153
  Regler:
154
- - Vidarebefordra användarens ord EXAKT via send_to_agent. Skriv inte om.
155
- - send_to_agent är omedelbart — bekräfta kort och gå vidare.
156
- - Agenten arbetar tyst i bakgrunden. Användaren ser framsteg i terminalen.
157
- - Berätta inte vad agenten gör. Ge inga framstegsuppdateringar.
158
- - När agenten är klar får du resultatet. Nämn utfallet i en mening.
159
- - Ha en vanlig konversation medan agenten arbetar. Var naturlig, kort, vänlig.
160
- - Förklara bara i detalj om något gick fel eller agenten behöver input.
161
- - Om det finns flera instanser, nämn vilken när det är relevant.
154
+ - Vidarebefordra användarens ord EXAKT. Skriv inte om.
155
+ - send_to_agent/send_to_session är omedelbart — bekräfta kort.
156
+ - Agenten arbetar i bakgrunden. Ge inga framstegsuppdateringar.
157
+ - När agenten är klar: nämn utfallet i en mening.
158
+ - Var naturlig, kort, vänlig medan agenter arbetar.
162
159
 
163
160
  Avbryt/reset:
164
- - "avbryt", "stopp" → cancel_agent
161
+ - "avbryt", "stopp" → cancel_agent (med branch eller session_name)
165
162
  - "börja om", "reset" → reset_agent
166
- - remove_branch_instance för att ta bort en branch-instans (inte helper).
163
+ - remove_branch_instance / remove_session för att ta bort instanser.
167
164
 
168
165
  Visning:
169
- - "visa diff" → show_diff (visar git diff i panelen)
170
- - "visa output", "gå tillbaka", "normalvy" → show_output (tillbaka till normal vy, avslutar även helskärm)
171
- - "helskärm" → toggle_fullscreen (förstora/återställ panel)
166
+ - "visa diff" → show_diff
167
+ - "visa output", "gå tillbaka" → show_output
168
+ - "helskärm" → toggle_fullscreen
172
169
 
173
170
  Frågor från agenten:
174
171
  - Ställ frågan till användaren. Vidarebefordra svaret ordagrant via answer_agent_question.""",
@@ -40,12 +40,19 @@ class InstanceStatus(Enum):
40
40
  ERROR = "error"
41
41
 
42
42
 
43
+ class InstanceType(Enum):
44
+ CODE = "code"
45
+ SESSION = "session"
46
+
47
+
43
48
  @dataclass
44
49
  class AgentInstance:
45
50
  instance_id: str
46
51
  branch: str
47
52
  worktree_path: str
48
53
  agent_type: str = "claude"
54
+ instance_type: InstanceType = InstanceType.CODE
55
+ display_name: str = ""
49
56
  session_id: str = ""
50
57
  status: InstanceStatus = InstanceStatus.IDLE
51
58
  output_buffer: str = ""
@@ -63,6 +70,13 @@ class AgentInstance:
63
70
  header_widget: Any = None
64
71
  output_widget: Any = None
65
72
 
73
+ @property
74
+ def display_key(self) -> str:
75
+ """Human-facing identifier: display_name for sessions, branch for code."""
76
+ if self.instance_type == InstanceType.SESSION and self.display_name:
77
+ return self.display_name
78
+ return self.branch
79
+
66
80
 
67
81
  class InstanceRegistry:
68
82
  """Thread-safe registry of Claude instances indexed by id and branch."""
@@ -76,7 +90,7 @@ class InstanceRegistry:
76
90
  self._user_email = user_email
77
91
  self.user_jwt = user_jwt
78
92
  self._transcript_buffer: list[dict[str, str]] = []
79
- # Repo root path — helper instance lives here and skips file tree updates
93
+ # Repo root path — used as cwd for session instances
80
94
  self.repo_root: str | None = None
81
95
 
82
96
  # Callbacks — set by session/screen
@@ -101,6 +115,7 @@ class InstanceRegistry:
101
115
  branch=branch,
102
116
  worktree_path=worktree_path,
103
117
  agent_type=agent_type,
118
+ instance_type=InstanceType.CODE,
104
119
  )
105
120
  self._instances[instance.instance_id] = instance
106
121
  self._branch_index[branch] = instance.instance_id
@@ -110,12 +125,70 @@ class InstanceRegistry:
110
125
  self.on_status_change(instance.instance_id, instance.status)
111
126
  if self.on_instance_change:
112
127
  self.on_instance_change()
113
- # Start file tree updates for this instance (skip for helper on repo root)
114
- if not (self.repo_root and str(worktree_path) == str(self.repo_root)):
115
- from voice_vibecoder.tools.file_tree import start_file_tree_updates
116
- start_file_tree_updates(instance.instance_id, worktree_path, self)
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
+ return instance
132
+
133
+ def create_session(self, name: str, agent_type: str = "claude") -> AgentInstance:
134
+ """Create a branchless session instance, deduplicating by display_name."""
135
+ existing = self.get_session_by_name(name)
136
+ if existing:
137
+ return existing
138
+ with self._lock:
139
+ instance = AgentInstance(
140
+ instance_id=uuid.uuid4().hex[:8],
141
+ branch=f"_session_{name}",
142
+ worktree_path="",
143
+ agent_type=agent_type,
144
+ instance_type=InstanceType.SESSION,
145
+ display_name=name,
146
+ )
147
+ self._instances[instance.instance_id] = instance
148
+ self._branch_index[instance.branch] = instance.instance_id
149
+ self._save_state()
150
+ if self.on_status_change:
151
+ self.on_status_change(instance.instance_id, instance.status)
152
+ if self.on_instance_change:
153
+ self.on_instance_change()
117
154
  return instance
118
155
 
156
+ def get_session_by_name(self, name: str) -> AgentInstance | None:
157
+ """Fuzzy-match against SESSION instances' display_name."""
158
+ name_lower = name.lower().strip()
159
+ with self._lock:
160
+ sessions = [
161
+ inst for inst in self._instances.values()
162
+ if inst.instance_type == InstanceType.SESSION
163
+ ]
164
+ # Exact match first
165
+ for inst in sessions:
166
+ if inst.display_name.lower() == name_lower:
167
+ return inst
168
+ # Substring match
169
+ partial = [inst for inst in sessions if name_lower in inst.display_name.lower()]
170
+ if len(partial) == 1:
171
+ return partial[0]
172
+ if partial:
173
+ 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]
176
+ if len(partial) == 1:
177
+ return partial[0]
178
+ if partial:
179
+ return min(partial, key=lambda i: len(i.display_name))
180
+ return None
181
+
182
+ def get_all_sessions(self) -> list[AgentInstance]:
183
+ """Return all SESSION type instances."""
184
+ with self._lock:
185
+ return [i for i in self._instances.values() if i.instance_type == InstanceType.SESSION]
186
+
187
+ def get_all_code_instances(self) -> list[AgentInstance]:
188
+ """Return all CODE type instances."""
189
+ with self._lock:
190
+ return [i for i in self._instances.values() if i.instance_type == InstanceType.CODE]
191
+
119
192
  def get_by_branch(self, branch: str) -> AgentInstance | None:
120
193
  with self._lock:
121
194
  iid = self._branch_index.get(branch)
@@ -140,9 +213,11 @@ class InstanceRegistry:
140
213
  inst = self._instances.pop(instance_id, None)
141
214
  if inst:
142
215
  self._branch_index.pop(inst.branch, None)
143
- # Stop file tree updates for removed instance
144
- from voice_vibecoder.tools.file_tree import stop_file_tree_updates
145
- stop_file_tree_updates(instance_id)
216
+ is_session = inst.instance_type == InstanceType.SESSION
217
+ # Stop file tree updates for code instances only
218
+ if not is_session:
219
+ from voice_vibecoder.tools.file_tree import stop_file_tree_updates
220
+ stop_file_tree_updates(instance_id)
146
221
  self._save_state()
147
222
  if self.on_instance_change:
148
223
  self.on_instance_change()
@@ -224,6 +299,8 @@ class InstanceRegistry:
224
299
  "branch": inst.branch,
225
300
  "worktree_path": inst.worktree_path,
226
301
  "agent_type": inst.agent_type,
302
+ "instance_type": inst.instance_type.value,
303
+ "display_name": inst.display_name,
227
304
  "session_id": inst.session_id,
228
305
  "output_buffer": inst.output_buffer[-5000:] if inst.output_buffer else "",
229
306
  }
@@ -277,10 +354,20 @@ class InstanceRegistry:
277
354
  agent_type = entry.get("agent_type", "claude")
278
355
  session_id = entry.get("session_id", "")
279
356
  instance_id = entry.get("instance_id", uuid.uuid4().hex[:8])
280
-
281
357
  output_buffer = entry.get("output_buffer", "")
282
-
283
- if not branch or not worktree_path:
358
+ # Backward compat: old state files default to CODE
359
+ instance_type_str = entry.get("instance_type", "code")
360
+ try:
361
+ instance_type = InstanceType(instance_type_str)
362
+ except ValueError:
363
+ instance_type = InstanceType.CODE
364
+ display_name = entry.get("display_name", "")
365
+ is_session = instance_type == InstanceType.SESSION
366
+
367
+ if not branch:
368
+ continue
369
+ # CODE instances require a worktree_path
370
+ if not is_session and not worktree_path:
284
371
  continue
285
372
  # Skip if already registered (e.g. current branch added at startup)
286
373
  if branch in self._branch_index:
@@ -293,8 +380,8 @@ class InstanceRegistry:
293
380
  existing.output_buffer = output_buffer
294
381
  loaded.append(existing)
295
382
  continue
296
- # Verify worktree still exists
297
- if not Path(worktree_path).exists():
383
+ # Verify worktree still exists (skip for session instances)
384
+ if not is_session and not Path(worktree_path).exists():
298
385
  logger.info("Skipping saved instance '%s': path gone", branch)
299
386
  continue
300
387
 
@@ -303,6 +390,8 @@ class InstanceRegistry:
303
390
  branch=branch,
304
391
  worktree_path=worktree_path,
305
392
  agent_type=agent_type,
393
+ instance_type=instance_type,
394
+ display_name=display_name,
306
395
  session_id=session_id,
307
396
  first_call=not bool(session_id),
308
397
  output_buffer=output_buffer,
@@ -311,10 +400,10 @@ class InstanceRegistry:
311
400
  self._branch_index[branch] = instance.instance_id
312
401
  loaded.append(instance)
313
402
 
314
- # Start file tree updates for all loaded instances (skip helper)
403
+ # Start file tree updates for code instances only
315
404
  from voice_vibecoder.tools.file_tree import start_file_tree_updates
316
405
  for inst in loaded:
317
- if self.repo_root and str(inst.worktree_path) == str(self.repo_root):
406
+ if inst.instance_type == InstanceType.SESSION:
318
407
  continue
319
408
  start_file_tree_updates(inst.instance_id, inst.worktree_path, self)
320
409
 
@@ -159,30 +159,9 @@ class WebSocketBridge:
159
159
  on_ui_command=self._on_ui_command,
160
160
  )
161
161
 
162
- # Load saved instances and create default helper
162
+ # Load saved instances panels appear dynamically as sessions/instances are created
163
163
  restored = self._session.registry.load_state()
164
164
 
165
- helper_name = self._voice_config.default_instance_name if self._voice_config else "helper"
166
- saved_cb = self._session.registry.on_status_change
167
- self._session.registry.on_status_change = None
168
- default_inst = self._session.registry.create_instance(
169
- helper_name, str(self._repo_root)
170
- )
171
- # Add helpful description for the helper instance (only when freshly created)
172
- if default_inst and default_inst.instance_id and not default_inst.output_buffer:
173
- help_text = (
174
- "Generell agent for spørsmål, oppgaver og orkestrering.\n"
175
- "\n"
176
- "**Eksempler:**\n"
177
- "- 'Hva gjør denne filen?'\n"
178
- "- 'Lag en ny branch for feature X'\n"
179
- "- 'Lag en PR for branchen'\n"
180
- "- 'Hva er status på Jira-issue PROJ-123?'"
181
- )
182
- default_inst.output_buffer = help_text
183
- self._session.registry._save_state()
184
- self._session.registry.on_status_change = saved_cb
185
-
186
165
  await self._session.connect()
187
166
  await self._session.configure_session()
188
167
  # Don't sync state here — the frontend sends sync_state after
@@ -328,18 +328,18 @@ class RealtimeSession:
328
328
  self._registry.on_output(instance_id, f"{prefix} {first_line}")
329
329
  if self._loop and self._running:
330
330
  inst = self._registry.get_by_id(instance_id)
331
- branch = inst.branch if inst else "unknown"
331
+ display_key = inst.display_key if inst else "unknown"
332
332
  agent_type = inst.agent_type if inst else "claude"
333
333
  asyncio.run_coroutine_threadsafe(
334
- self._inject_agent_result(branch, result, agent_type), self._loop
334
+ self._inject_agent_result(display_key, result, agent_type), self._loop
335
335
  )
336
336
 
337
- async def _inject_agent_result(self, branch: str, result: str, agent_type: str = "claude") -> None:
337
+ async def _inject_agent_result(self, display_key: str, result: str, agent_type: str = "claude") -> None:
338
338
  if not self._running:
339
339
  return
340
340
  from voice_vibecoder.code_providers.registry import get_agent
341
341
  agent_label = get_agent(agent_type).label
342
- system_text = f"{agent_label} finished on '{branch}':\n\n{result}"
342
+ system_text = f"{agent_label} finished on '{display_key}':\n\n{result}"
343
343
  try:
344
344
  await self._provider.inject_message(system_text)
345
345
  except Exception as e:
@@ -357,15 +357,15 @@ class RealtimeSession:
357
357
  def _on_agent_question(self, instance_id: str, question_data: dict) -> None:
358
358
  if self._loop and self._running:
359
359
  inst = self._registry.get_by_id(instance_id)
360
- branch = inst.branch if inst else "unknown"
360
+ display_key = inst.display_key if inst else "unknown"
361
361
  agent_type = inst.agent_type if inst else "claude"
362
362
  asyncio.run_coroutine_threadsafe(
363
- self._inject_agent_question(branch, question_data, agent_type),
363
+ self._inject_agent_question(display_key, question_data, agent_type),
364
364
  self._loop,
365
365
  )
366
366
 
367
367
  async def _inject_agent_question(
368
- self, branch: str, question_data: dict, agent_type: str = "claude",
368
+ self, display_key: str, question_data: dict, agent_type: str = "claude",
369
369
  ) -> None:
370
370
  if not self._running:
371
371
  return
@@ -377,16 +377,16 @@ class RealtimeSession:
377
377
 
378
378
  if tool == "EnterPlanMode":
379
379
  system_text = (
380
- f"{label} on '{branch}' wants to plan first. Should it plan or just implement?"
380
+ f"{label} on '{display_key}' wants to plan first. Should it plan or just implement?"
381
381
  )
382
382
  elif tool == "ExitPlanMode":
383
383
  system_text = (
384
- f"{label} on '{branch}' has a plan ready. "
384
+ f"{label} on '{display_key}' has a plan ready. "
385
385
  "Ask the user if the agent should proceed with the implementation. "
386
386
  "Use answer_agent_question to forward their response."
387
387
  )
388
388
  else:
389
- system_text = f"{label} on '{branch}' is asking:\n\n{formatted}"
389
+ system_text = f"{label} on '{display_key}' is asking:\n\n{formatted}"
390
390
 
391
391
  try:
392
392
  await self._provider.inject_message(system_text)