voice-vibecoder 2.0.1__tar.gz → 2.2.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.0.1 → voice_vibecoder-2.2.0}/.gitignore +1 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/CHANGELOG.md +6 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/PKG-INFO +1 -1
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/pyproject.toml +1 -1
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/__init__.py +1 -1
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/app_config.py +4 -3
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/config.py +100 -103
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/instances.py +104 -15
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/server.py +1 -22
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/session.py +10 -10
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/tools/agent_task.py +87 -10
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/tools/definitions.py +91 -16
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/tools/dispatch.py +27 -10
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/tools/handlers.py +108 -44
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/callbacks.py +19 -3
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/help_modal.py +4 -4
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/panels.py +32 -6
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/screen.py +58 -34
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/state.py +2 -2
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/styles.py +0 -1
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/.github/workflows/ci.yml +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/.github/workflows/release.yml +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/LICENSE +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/README.md +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/app.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/index.html +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/audio.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/cli_serve.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/tools/__init__.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/__init__.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/setup.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/ui/wizard.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
- {voice_vibecoder-2.0.1 → voice_vibecoder-2.2.0}/src/voice_vibecoder/worktrees.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-vibecoder
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.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
|
|
7
|
+
version = "2.2.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"
|
|
@@ -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
|
|
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
|
-
#
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
45
|
-
-
|
|
46
|
-
- For å BARE bytte 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
|
|
59
|
-
- send_to_agent er umiddelbart —
|
|
60
|
-
- Agenten jobber
|
|
61
|
-
-
|
|
62
|
-
-
|
|
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
|
|
69
|
+
- remove_branch_instance / remove_session for å fjerne instanser.
|
|
71
70
|
|
|
72
71
|
Visning:
|
|
73
|
-
- "vis diff" → show_diff
|
|
74
|
-
- "vis output", "gå tilbake"
|
|
75
|
-
- "fullskjerm" → toggle_fullscreen
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
93
|
-
-
|
|
94
|
-
- To ONLY switch agents
|
|
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
|
|
107
|
-
- send_to_agent is instant —
|
|
108
|
-
- The agent works
|
|
109
|
-
-
|
|
110
|
-
-
|
|
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
|
|
116
|
+
- remove_branch_instance / remove_session to remove instances.
|
|
119
117
|
|
|
120
118
|
Views:
|
|
121
|
-
- "show diff" → show_diff
|
|
122
|
-
- "show output", "go back"
|
|
123
|
-
- "fullscreen" → toggle_fullscreen
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
|
|
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 på 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
|
|
141
|
-
-
|
|
142
|
-
- För att BARA byta 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
|
|
155
|
-
- send_to_agent är omedelbart — bekräfta kort
|
|
156
|
-
- Agenten arbetar
|
|
157
|
-
-
|
|
158
|
-
-
|
|
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
|
|
163
|
+
- remove_branch_instance / remove_session för att ta bort instanser.
|
|
167
164
|
|
|
168
165
|
Visning:
|
|
169
|
-
- "visa diff" → show_diff
|
|
170
|
-
- "visa output", "gå tillbaka"
|
|
171
|
-
- "helskärm" → toggle_fullscreen
|
|
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 —
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
334
|
+
self._inject_agent_result(display_key, result, agent_type), self._loop
|
|
335
335
|
)
|
|
336
336
|
|
|
337
|
-
async def _inject_agent_result(self,
|
|
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 '{
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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 '{
|
|
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 '{
|
|
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 '{
|
|
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)
|