voice-vibecoder 2.9.4__tar.gz → 2.9.6__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 (43) hide show
  1. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/CHANGELOG.md +16 -0
  2. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/PKG-INFO +1 -1
  3. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/pyproject.toml +1 -1
  4. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/__init__.py +1 -1
  5. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/code_providers/openclaw.py +47 -34
  6. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/.github/workflows/ci.yml +0 -0
  7. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/.github/workflows/release.yml +0 -0
  8. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/.gitignore +0 -0
  9. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/LICENSE +0 -0
  10. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/README.md +0 -0
  11. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/app.py +0 -0
  12. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/index.html +0 -0
  13. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/app_config.py +0 -0
  14. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/audio.py +0 -0
  15. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/cli_serve.py +0 -0
  16. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/code_providers/__init__.py +0 -0
  17. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/code_providers/claude.py +0 -0
  18. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/code_providers/cursor.py +0 -0
  19. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/code_providers/registry.py +0 -0
  20. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/config.py +0 -0
  21. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/instances.py +0 -0
  22. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/server.py +0 -0
  23. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/session.py +0 -0
  24. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/tools/__init__.py +0 -0
  25. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/tools/agent_task.py +0 -0
  26. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/tools/definitions.py +0 -0
  27. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/tools/dispatch.py +0 -0
  28. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/tools/file_tree.py +0 -0
  29. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/tools/handlers.py +0 -0
  30. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/__init__.py +0 -0
  31. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/callbacks.py +0 -0
  32. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/diff_view.py +0 -0
  33. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/help_modal.py +0 -0
  34. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/panels.py +0 -0
  35. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/screen.py +0 -0
  36. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/setup.py +0 -0
  37. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/state.py +0 -0
  38. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/styles.py +0 -0
  39. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/update_modal.py +0 -0
  40. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/ui/wizard.py +0 -0
  41. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/voice_providers/__init__.py +0 -0
  42. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/voice_providers/openai.py +0 -0
  43. {voice_vibecoder-2.9.4 → voice_vibecoder-2.9.6}/src/voice_vibecoder/worktrees.py +0 -0
@@ -2,6 +2,22 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v2.9.6 (2026-02-21)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Handle agent/chat events correctly for streaming
10
+ ([`2abd488`](https://github.com/snokam/voice-vibecoder/commit/2abd488a7e408a4227375bb332f9e2d76ae8df85))
11
+
12
+
13
+ ## v2.9.5 (2026-02-21)
14
+
15
+ ### Bug Fixes
16
+
17
+ - Use chat.send method for gateway streaming
18
+ ([`a955747`](https://github.com/snokam/voice-vibecoder/commit/a955747a793514463f932376f0866cb140e364c6))
19
+
20
+
5
21
  ## v2.9.4 (2026-02-21)
6
22
 
7
23
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-vibecoder
3
- Version: 2.9.4
3
+ Version: 2.9.6
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.9.4"
7
+ version = "2.9.6"
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.9.4"
6
+ __version__ = "2.9.6"
7
7
 
8
8
  from voice_vibecoder.app_config import VoiceConfig
9
9
  from voice_vibecoder.ui.screen import VoiceCodingScreen
@@ -68,11 +68,7 @@ def _build_auth_payload(
68
68
  token: str | None,
69
69
  nonce: str | None,
70
70
  ) -> str:
71
- """Build the payload to sign for device auth.
72
-
73
- Format (v2 with nonce):
74
- v2|deviceId|clientId|clientMode|role|scopes|signedAtMs|token|nonce
75
- """
71
+ """Build the payload to sign for device auth."""
76
72
  scopes_str = ",".join(scopes)
77
73
  token_str = token or ""
78
74
 
@@ -136,7 +132,6 @@ class OpenClawRunner:
136
132
  timeout=5.0
137
133
  )
138
134
 
139
- # Wait for challenge
140
135
  challenge_msg = await asyncio.wait_for(ws.recv(), timeout=5.0)
141
136
  challenge = json.loads(challenge_msg)
142
137
 
@@ -148,7 +143,6 @@ class OpenClawRunner:
148
143
  nonce = challenge["payload"]["nonce"]
149
144
  signed_at_ms = int(time.time() * 1000)
150
145
 
151
- # Build auth payload and sign
152
146
  client_id = "cli"
153
147
  client_mode = "cli"
154
148
  role = "operator"
@@ -166,14 +160,12 @@ class OpenClawRunner:
166
160
  )
167
161
  signature = _sign_payload(self._private_key, payload)
168
162
 
169
- # Get public key in base64
170
163
  public_key_bytes = self._private_key.public_key().public_bytes(
171
164
  encoding=serialization.Encoding.Raw,
172
165
  format=serialization.PublicFormat.Raw
173
166
  )
174
167
  pub_b64 = base64.urlsafe_b64encode(public_key_bytes).decode().rstrip("=")
175
168
 
176
- # Send connect request
177
169
  connect_req = {
178
170
  "type": "req",
179
171
  "id": str(uuid.uuid4()),
@@ -207,7 +199,6 @@ class OpenClawRunner:
207
199
 
208
200
  await ws.send(json.dumps(connect_req))
209
201
 
210
- # Wait for response
211
202
  response_msg = await asyncio.wait_for(ws.recv(), timeout=5.0)
212
203
  response = json.loads(response_msg)
213
204
 
@@ -235,51 +226,71 @@ class OpenClawRunner:
235
226
  """Run agent via WebSocket for streaming output."""
236
227
  try:
237
228
  req_id = str(uuid.uuid4())
238
- agent_req = {
229
+ idempotency_key = f"voice-{uuid.uuid4().hex[:16]}"
230
+
231
+ chat_req = {
239
232
  "type": "req",
240
233
  "id": req_id,
241
- "method": "agent.run",
234
+ "method": "chat.send",
242
235
  "params": {
243
- "sessionId": session_id,
236
+ "sessionKey": session_id,
244
237
  "message": message,
245
- "cwd": cwd,
246
- "local": True
238
+ "idempotencyKey": idempotency_key,
247
239
  }
248
240
  }
249
241
 
250
- await ws.send(json.dumps(agent_req))
242
+ await ws.send(json.dumps(chat_req))
251
243
 
252
244
  result_text = ""
245
+ last_text = ""
253
246
 
254
247
  while True:
255
248
  msg = await asyncio.wait_for(ws.recv(), timeout=300.0)
256
249
  data = json.loads(msg)
257
250
 
258
- if data.get("type") == "event":
259
- event = data.get("event", "")
260
- payload = data.get("payload", {})
251
+ event = data.get("event")
252
+ payload = data.get("payload", {})
253
+
254
+ if event == "agent":
255
+ stream = payload.get("stream")
256
+ agent_data = payload.get("data", {})
261
257
 
262
- if event == "agent.output":
263
- text = payload.get("text", "")
264
- if text and on_output:
265
- on_output(AgentOutput(category="text", content=text))
266
- elif event == "agent.tool":
267
- tool = payload.get("tool", "")
258
+ if stream == "assistant" and "text" in agent_data:
259
+ text = agent_data["text"]
260
+ # Send delta (new text since last)
261
+ if text and len(text) > len(last_text):
262
+ delta = text[len(last_text):]
263
+ if on_output and delta:
264
+ on_output(AgentOutput(category="text", content=delta))
265
+ last_text = text
266
+ result_text = text
267
+
268
+ elif stream == "tool_use" and "name" in agent_data:
269
+ tool = agent_data["name"]
268
270
  if on_output:
269
- on_output(AgentOutput(category="tool_call", content=f"[{tool}]"))
271
+ on_output(AgentOutput(category="tool_call", content=f"\n[{tool}] "))
272
+
273
+ elif event == "chat":
274
+ state = payload.get("state")
275
+ if state == "final":
276
+ # Get final message
277
+ msg_content = payload.get("message", {})
278
+ if isinstance(msg_content, dict) and "content" in msg_content:
279
+ result_text = msg_content["content"]
280
+ break
281
+ elif state == "error":
282
+ error = payload.get("error", "Unknown error")
283
+ raise RuntimeError(f"Chat error: {error}")
270
284
 
271
285
  elif data.get("type") == "res" and data.get("id") == req_id:
272
- if data.get("ok"):
273
- result_text = data.get("payload", {}).get("result", "")
274
- else:
286
+ if not data.get("ok"):
275
287
  error = data.get("error", {}).get("message", "Unknown error")
276
- raise RuntimeError(f"Agent error: {error}")
277
- break
288
+ raise RuntimeError(f"Chat send error: {error}")
278
289
 
279
290
  return AgentResult(text=result_text.strip(), session_id=session_id)
280
291
 
281
292
  except Exception as e:
282
- logger.warning("WebSocket agent run failed: %s", e)
293
+ logger.warning("WebSocket chat failed: %s", e)
283
294
  return None
284
295
 
285
296
  async def _run_via_cli(
@@ -337,11 +348,13 @@ class OpenClawRunner:
337
348
  effective_session = session_id or self._session_id
338
349
 
339
350
  if on_output:
340
- on_output(AgentOutput(category="text", content="🦞 Connecting to OpenClaw..."))
351
+ on_output(AgentOutput(category="text", content="🦞 Connecting to OpenClaw...\n"))
341
352
 
342
353
  ws = await self._connect_websocket()
343
354
  if ws:
344
355
  try:
356
+ if on_output:
357
+ on_output(AgentOutput(category="text", content="🦞 Connected! Streaming...\n"))
345
358
  result = await self._run_via_websocket(ws, message, cwd, effective_session, on_output)
346
359
  if result:
347
360
  return result
@@ -349,7 +362,7 @@ class OpenClawRunner:
349
362
  await ws.close()
350
363
 
351
364
  if on_output:
352
- on_output(AgentOutput(category="text", content="WebSocket unavailable, using CLI..."))
365
+ on_output(AgentOutput(category="text", content="WebSocket unavailable, using CLI...\n"))
353
366
 
354
367
  return await self._run_via_cli(message, cwd, effective_session, on_output)
355
368
 
File without changes
File without changes