code-puppy 0.0.344__py3-none-any.whl → 0.0.346__py3-none-any.whl

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.
@@ -24,6 +24,7 @@ from typing import (
24
24
  import mcp
25
25
  import pydantic
26
26
  import pydantic_ai.models
27
+ from dbos import DBOS, SetWorkflowID
27
28
  from pydantic_ai import Agent as PydanticAgent
28
29
  from pydantic_ai import (
29
30
  BinaryContent,
@@ -34,6 +35,7 @@ from pydantic_ai import (
34
35
  UsageLimitExceeded,
35
36
  UsageLimits,
36
37
  )
38
+ from pydantic_ai.durable_exec.dbos import DBOSAgent
37
39
  from pydantic_ai.messages import (
38
40
  ModelMessage,
39
41
  ModelRequest,
@@ -54,6 +56,7 @@ from code_puppy.config import (
54
56
  get_global_model_name,
55
57
  get_message_limit,
56
58
  get_protected_token_count,
59
+ get_use_dbos,
57
60
  get_value,
58
61
  )
59
62
  from code_puppy.error_logging import log_error
@@ -1209,25 +1212,56 @@ class BaseAgent(ABC):
1209
1212
 
1210
1213
  self._last_model_name = resolved_model_name
1211
1214
  # expose for run_with_mcp
1215
+ # Wrap it with DBOS, but handle MCP servers separately to avoid serialization issues
1212
1216
  global _reload_count
1213
1217
  _reload_count += 1
1214
- # Include filtered MCP servers in the agent
1215
- p_agent = PydanticAgent(
1216
- model=model,
1217
- instructions=instructions,
1218
- output_type=str,
1219
- retries=3,
1220
- toolsets=filtered_mcp_servers if filtered_mcp_servers else [],
1221
- history_processors=[self.message_history_accumulator],
1222
- model_settings=model_settings,
1223
- )
1224
- # Register regular tools on the agent
1225
- agent_tools = self.get_available_tools()
1226
- register_tools_for_agent(p_agent, agent_tools)
1218
+ if get_use_dbos():
1219
+ # Don't pass MCP servers to the agent constructor when using DBOS
1220
+ # This prevents the "cannot pickle async_generator object" error
1221
+ # MCP servers will be handled separately in run_with_mcp
1222
+ agent_without_mcp = PydanticAgent(
1223
+ model=model,
1224
+ instructions=instructions,
1225
+ output_type=str,
1226
+ retries=3,
1227
+ toolsets=[], # Don't include MCP servers here
1228
+ history_processors=[self.message_history_accumulator],
1229
+ model_settings=model_settings,
1230
+ )
1231
+
1232
+ # Register regular tools (non-MCP) on the new agent
1233
+ agent_tools = self.get_available_tools()
1234
+ register_tools_for_agent(agent_without_mcp, agent_tools)
1235
+
1236
+ # Wrap with DBOS
1237
+ dbos_agent = DBOSAgent(
1238
+ agent_without_mcp, name=f"{self.name}-{_reload_count}"
1239
+ )
1240
+ self.pydantic_agent = dbos_agent
1241
+ self._code_generation_agent = dbos_agent
1227
1242
 
1228
- self.pydantic_agent = p_agent
1229
- self._code_generation_agent = p_agent
1230
- self._mcp_servers = filtered_mcp_servers
1243
+ # Store filtered MCP servers separately for runtime use
1244
+ self._mcp_servers = filtered_mcp_servers
1245
+ else:
1246
+ # Normal path without DBOS - include filtered MCP servers in the agent
1247
+ # Re-create agent with filtered MCP servers
1248
+ p_agent = PydanticAgent(
1249
+ model=model,
1250
+ instructions=instructions,
1251
+ output_type=str,
1252
+ retries=3,
1253
+ toolsets=filtered_mcp_servers,
1254
+ history_processors=[self.message_history_accumulator],
1255
+ model_settings=model_settings,
1256
+ )
1257
+ # Register regular tools on the agent
1258
+ agent_tools = self.get_available_tools()
1259
+ register_tools_for_agent(p_agent, agent_tools)
1260
+
1261
+ self.pydantic_agent = p_agent
1262
+ self._code_generation_agent = p_agent
1263
+ self._mcp_servers = filtered_mcp_servers
1264
+ self._mcp_servers = mcp_servers
1231
1265
  return self._code_generation_agent
1232
1266
 
1233
1267
  def _create_agent_with_output_type(self, output_type: Type[Any]) -> PydanticAgent:
@@ -1241,7 +1275,7 @@ class BaseAgent(ABC):
1241
1275
  output_type: The Pydantic model or type for structured output.
1242
1276
 
1243
1277
  Returns:
1244
- A configured PydanticAgent with the custom output_type.
1278
+ A configured PydanticAgent (or DBOSAgent wrapper) with the custom output_type.
1245
1279
  """
1246
1280
  from code_puppy.model_utils import prepare_prompt_for_model
1247
1281
  from code_puppy.tools import register_tools_for_agent
@@ -1268,19 +1302,38 @@ class BaseAgent(ABC):
1268
1302
  global _reload_count
1269
1303
  _reload_count += 1
1270
1304
 
1271
- temp_agent = PydanticAgent(
1272
- model=model,
1273
- instructions=instructions,
1274
- output_type=output_type,
1275
- retries=3,
1276
- toolsets=mcp_servers,
1277
- history_processors=[self.message_history_accumulator],
1278
- model_settings=model_settings,
1279
- )
1280
- agent_tools = self.get_available_tools()
1281
- register_tools_for_agent(temp_agent, agent_tools)
1282
- return temp_agent
1305
+ if get_use_dbos():
1306
+ temp_agent = PydanticAgent(
1307
+ model=model,
1308
+ instructions=instructions,
1309
+ output_type=output_type,
1310
+ retries=3,
1311
+ toolsets=[],
1312
+ history_processors=[self.message_history_accumulator],
1313
+ model_settings=model_settings,
1314
+ )
1315
+ agent_tools = self.get_available_tools()
1316
+ register_tools_for_agent(temp_agent, agent_tools)
1317
+ dbos_agent = DBOSAgent(
1318
+ temp_agent, name=f"{self.name}-structured-{_reload_count}"
1319
+ )
1320
+ return dbos_agent
1321
+ else:
1322
+ temp_agent = PydanticAgent(
1323
+ model=model,
1324
+ instructions=instructions,
1325
+ output_type=output_type,
1326
+ retries=3,
1327
+ toolsets=mcp_servers,
1328
+ history_processors=[self.message_history_accumulator],
1329
+ model_settings=model_settings,
1330
+ )
1331
+ agent_tools = self.get_available_tools()
1332
+ register_tools_for_agent(temp_agent, agent_tools)
1333
+ return temp_agent
1283
1334
 
1335
+ # It's okay to decorate it with DBOS.step even if not using DBOS; the decorator is a no-op in that case.
1336
+ @DBOS.step()
1284
1337
  def message_history_accumulator(self, ctx: RunContext, messages: List[Any]):
1285
1338
  _message_history = self.get_message_history()
1286
1339
  message_history_hashes = set([self.hash_message(m) for m in _message_history])
@@ -1788,14 +1841,49 @@ class BaseAgent(ABC):
1788
1841
 
1789
1842
  usage_limits = UsageLimits(request_limit=get_message_limit())
1790
1843
 
1791
- # MCP servers are already included in the agent
1792
- result_ = await pydantic_agent.run(
1793
- prompt_payload,
1794
- message_history=self.get_message_history(),
1795
- usage_limits=usage_limits,
1796
- event_stream_handler=self._event_stream_handler,
1797
- **kwargs,
1798
- )
1844
+ # Handle MCP servers - add them temporarily when using DBOS
1845
+ if (
1846
+ get_use_dbos()
1847
+ and hasattr(self, "_mcp_servers")
1848
+ and self._mcp_servers
1849
+ ):
1850
+ # Temporarily add MCP servers to the DBOS agent using internal _toolsets
1851
+ original_toolsets = pydantic_agent._toolsets
1852
+ pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1853
+ pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1854
+
1855
+ try:
1856
+ # Set the workflow ID for DBOS context so DBOS and Code Puppy ID match
1857
+ with SetWorkflowID(group_id):
1858
+ result_ = await pydantic_agent.run(
1859
+ prompt_payload,
1860
+ message_history=self.get_message_history(),
1861
+ usage_limits=usage_limits,
1862
+ event_stream_handler=self._event_stream_handler,
1863
+ **kwargs,
1864
+ )
1865
+ finally:
1866
+ # Always restore original toolsets
1867
+ pydantic_agent._toolsets = original_toolsets
1868
+ elif get_use_dbos():
1869
+ # DBOS without MCP servers
1870
+ with SetWorkflowID(group_id):
1871
+ result_ = await pydantic_agent.run(
1872
+ prompt_payload,
1873
+ message_history=self.get_message_history(),
1874
+ usage_limits=usage_limits,
1875
+ event_stream_handler=self._event_stream_handler,
1876
+ **kwargs,
1877
+ )
1878
+ else:
1879
+ # Non-DBOS path (MCP servers are already included)
1880
+ result_ = await pydantic_agent.run(
1881
+ prompt_payload,
1882
+ message_history=self.get_message_history(),
1883
+ usage_limits=usage_limits,
1884
+ event_stream_handler=self._event_stream_handler,
1885
+ **kwargs,
1886
+ )
1799
1887
  return result_
1800
1888
  except* UsageLimitExceeded as ule:
1801
1889
  emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
@@ -1811,8 +1899,12 @@ class BaseAgent(ABC):
1811
1899
  )
1812
1900
  except* asyncio.exceptions.CancelledError:
1813
1901
  emit_info("Cancelled")
1902
+ if get_use_dbos():
1903
+ await DBOS.cancel_workflow_async(group_id)
1814
1904
  except* InterruptedError as ie:
1815
1905
  emit_info(f"Interrupted: {str(ie)}")
1906
+ if get_use_dbos():
1907
+ await DBOS.cancel_workflow_async(group_id)
1816
1908
  except* Exception as other_error:
1817
1909
  # Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
1818
1910
  remaining_exceptions = []
code_puppy/cli_runner.py CHANGED
@@ -12,9 +12,11 @@ import argparse
12
12
  import asyncio
13
13
  import os
14
14
  import sys
15
+ import time
15
16
  import traceback
16
17
  from pathlib import Path
17
18
 
19
+ from dbos import DBOS, DBOSConfig
18
20
  from rich.console import Console
19
21
 
20
22
  from code_puppy import __version__, callbacks, plugins
@@ -24,8 +26,10 @@ from code_puppy.command_line.clipboard import get_clipboard_manager
24
26
  from code_puppy.config import (
25
27
  AUTOSAVE_DIR,
26
28
  COMMAND_HISTORY_FILE,
29
+ DBOS_DATABASE_URL,
27
30
  ensure_config_exists,
28
31
  finalize_autosave_session,
32
+ get_use_dbos,
29
33
  initialize_command_history_file,
30
34
  save_command_to_history,
31
35
  )
@@ -283,6 +287,33 @@ async def main():
283
287
 
284
288
  await callbacks.on_startup()
285
289
 
290
+ # Initialize DBOS if not disabled
291
+ if get_use_dbos():
292
+ # Append a Unix timestamp in ms to the version for uniqueness
293
+ dbos_app_version = os.environ.get(
294
+ "DBOS_APP_VERSION", f"{current_version}-{int(time.time() * 1000)}"
295
+ )
296
+ dbos_config: DBOSConfig = {
297
+ "name": "dbos-code-puppy",
298
+ "system_database_url": DBOS_DATABASE_URL,
299
+ "run_admin_server": False,
300
+ "conductor_key": os.environ.get(
301
+ "DBOS_CONDUCTOR_KEY"
302
+ ), # Optional, if set in env, connect to conductor
303
+ "log_level": os.environ.get(
304
+ "DBOS_LOG_LEVEL", "ERROR"
305
+ ), # Default to ERROR level to suppress verbose logs
306
+ "application_version": dbos_app_version, # Match DBOS app version to Code Puppy version
307
+ }
308
+ try:
309
+ DBOS(config=dbos_config)
310
+ DBOS.launch()
311
+ except Exception as e:
312
+ emit_error(f"Error initializing DBOS: {e}")
313
+ sys.exit(1)
314
+ else:
315
+ pass
316
+
286
317
  global shutdown_flag
287
318
  shutdown_flag = False
288
319
  try:
@@ -307,6 +338,8 @@ async def main():
307
338
  if bus_renderer:
308
339
  bus_renderer.stop()
309
340
  await callbacks.on_shutdown()
341
+ if get_use_dbos():
342
+ DBOS.destroy()
310
343
 
311
344
 
312
345
  async def interactive_mode(message_renderer, initial_command: str = None) -> None:
@@ -874,6 +907,8 @@ def main_entry():
874
907
  except KeyboardInterrupt:
875
908
  # Note: Using sys.stderr for crash output - messaging system may not be available
876
909
  sys.stderr.write(traceback.format_exc())
910
+ if get_use_dbos():
911
+ DBOS.destroy()
877
912
  return 0
878
913
  finally:
879
914
  # Reset terminal on Unix-like systems (not Windows)
@@ -17,6 +17,7 @@ from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
17
17
  from prompt_toolkit.layout.controls import FormattedTextControl
18
18
  from prompt_toolkit.widgets import Frame
19
19
 
20
+ from code_puppy.command_line.utils import safe_input
20
21
  from code_puppy.config import EXTRA_MODELS_FILE, set_config_value
21
22
  from code_puppy.messaging import emit_error, emit_info, emit_warning
22
23
  from code_puppy.models_dev_parser import ModelInfo, ModelsDevRegistry, ProviderInfo
@@ -724,8 +725,8 @@ class AddModelMenu:
724
725
  emit_info(f" {hint}")
725
726
 
726
727
  try:
727
- # Use regular input - simpler and works in threaded context
728
- value = input(f" Enter {env_var} (or press Enter to skip): ").strip()
728
+ # Use safe_input for cross-platform compatibility (Windows fix)
729
+ value = safe_input(f" Enter {env_var} (or press Enter to skip): ")
729
730
 
730
731
  if not value:
731
732
  emit_warning(
@@ -785,7 +786,7 @@ class AddModelMenu:
785
786
  )
786
787
 
787
788
  try:
788
- model_name = input(" Model ID: ").strip()
789
+ model_name = safe_input(" Model ID: ")
789
790
 
790
791
  if not model_name:
791
792
  emit_warning("No model name provided, cancelled.")
@@ -795,7 +796,7 @@ class AddModelMenu:
795
796
  emit_info("\n Enter the context window size (in tokens).")
796
797
  emit_info(" Common sizes: 8192, 32768, 128000, 200000, 1000000\n")
797
798
 
798
- context_input = input(" Context size [128000]: ").strip()
799
+ context_input = safe_input(" Context size [128000]: ")
799
800
 
800
801
  if not context_input:
801
802
  context_length = 128000 # Default
@@ -1045,11 +1046,9 @@ class AddModelMenu:
1045
1046
  f" It will be very limited for coding tasks."
1046
1047
  )
1047
1048
  try:
1048
- confirm = (
1049
- input("\n Are you sure you want to add this model? (y/N): ")
1050
- .strip()
1051
- .lower()
1052
- )
1049
+ confirm = safe_input(
1050
+ "\n Are you sure you want to add this model? (y/N): "
1051
+ ).lower()
1053
1052
  if confirm not in ("y", "yes"):
1054
1053
  emit_info("Model addition cancelled.")
1055
1054
  return False
@@ -43,6 +43,7 @@ def handle_show_command(command: str) -> bool:
43
43
  get_protected_token_count,
44
44
  get_puppy_name,
45
45
  get_temperature,
46
+ get_use_dbos,
46
47
  get_yolo_mode,
47
48
  )
48
49
  from code_puppy.keymap import get_cancel_agent_display_name
@@ -71,6 +72,7 @@ def handle_show_command(command: str) -> bool:
71
72
  [bold]default_agent:[/bold] [cyan]{default_agent}[/cyan]
72
73
  [bold]model:[/bold] [green]{model}[/green]
73
74
  [bold]YOLO_MODE:[/bold] {"[red]ON[/red]" if yolo_mode else "[yellow]off[/yellow]"}
75
+ [bold]DBOS:[/bold] {"[green]enabled[/green]" if get_use_dbos() else "[yellow]disabled[/yellow]"} (toggle: /set enable_dbos true|false)
74
76
  [bold]auto_save_session:[/bold] {"[green]enabled[/green]" if auto_save else "[yellow]disabled[/yellow]"}
75
77
  [bold]protected_tokens:[/bold] [cyan]{protected_tokens:,}[/cyan] recent tokens preserved
76
78
  [bold]compaction_threshold:[/bold] [cyan]{compaction_threshold:.1%}[/cyan] context usage triggers compaction
@@ -211,6 +213,14 @@ def handle_set_command(command: str) -> bool:
211
213
  )
212
214
  return True
213
215
  if key:
216
+ # Check if we're toggling DBOS enablement
217
+ if key == "enable_dbos":
218
+ emit_info(
219
+ Text.from_markup(
220
+ "[yellow]⚠️ DBOS configuration changed. Please restart Code Puppy for this change to take effect.[/yellow]"
221
+ )
222
+ )
223
+
214
224
  # Validate cancel_agent_key before setting
215
225
  if key == "cancel_agent_key":
216
226
  from code_puppy.keymap import VALID_CANCEL_KEYS
@@ -7,6 +7,7 @@ MCP servers from the catalog.
7
7
  import os
8
8
  from typing import Dict, Optional
9
9
 
10
+ from code_puppy.command_line.utils import safe_input
10
11
  from code_puppy.messaging import emit_info, emit_success, emit_warning
11
12
 
12
13
  # Helpful hints for common environment variables
@@ -52,7 +53,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
52
53
  # Get custom name
53
54
  default_name = server.name
54
55
  try:
55
- name_input = input(f" Server name [{default_name}]: ").strip()
56
+ name_input = safe_input(f" Server name [{default_name}]: ")
56
57
  server_name = name_input if name_input else default_name
57
58
  except (KeyboardInterrupt, EOFError):
58
59
  emit_info("")
@@ -63,9 +64,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
63
64
  existing = find_server_id_by_name(manager, server_name)
64
65
  if existing:
65
66
  try:
66
- override = input(
67
- f" Server '{server_name}' exists. Override? [y/N]: "
68
- ).strip()
67
+ override = safe_input(f" Server '{server_name}' exists. Override? [y/N]: ")
69
68
  if not override.lower().startswith("y"):
70
69
  emit_warning("Installation cancelled")
71
70
  return None
@@ -91,7 +90,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
91
90
  hint = get_env_var_hint(var)
92
91
  if hint:
93
92
  emit_info(f" {hint}")
94
- value = input(f" Enter {var}: ").strip()
93
+ value = safe_input(f" Enter {var}: ")
95
94
  if value:
96
95
  env_vars[var] = value
97
96
  # Save to config for future use
@@ -119,7 +118,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
119
118
  prompt_str += " (optional)"
120
119
 
121
120
  try:
122
- value = input(f"{prompt_str}: ").strip()
121
+ value = safe_input(f"{prompt_str}: ")
123
122
  if value:
124
123
  cmd_args[name] = value
125
124
  elif default:
@@ -7,6 +7,7 @@ custom MCP servers with JSON configuration.
7
7
  import json
8
8
  import os
9
9
 
10
+ from code_puppy.command_line.utils import safe_input
10
11
  from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
11
12
 
12
13
  # Example configurations for each server type
@@ -58,7 +59,7 @@ def prompt_and_install_custom_server(manager) -> bool:
58
59
 
59
60
  # Get server name
60
61
  try:
61
- server_name = input(" Server name: ").strip()
62
+ server_name = safe_input(" Server name: ")
62
63
  if not server_name:
63
64
  emit_warning("Server name is required")
64
65
  return False
@@ -71,9 +72,7 @@ def prompt_and_install_custom_server(manager) -> bool:
71
72
  existing = find_server_id_by_name(manager, server_name)
72
73
  if existing:
73
74
  try:
74
- override = input(
75
- f" Server '{server_name}' exists. Override? [y/N]: "
76
- ).strip()
75
+ override = safe_input(f" Server '{server_name}' exists. Override? [y/N]: ")
77
76
  if not override.lower().startswith("y"):
78
77
  emit_warning("Cancelled")
79
78
  return False
@@ -89,7 +88,7 @@ def prompt_and_install_custom_server(manager) -> bool:
89
88
  emit_info(" 3. 📡 sse - Server-Sent Events\n")
90
89
 
91
90
  try:
92
- type_choice = input(" Enter choice [1-3]: ").strip()
91
+ type_choice = safe_input(" Enter choice [1-3]: ")
93
92
  except (KeyboardInterrupt, EOFError):
94
93
  emit_info("")
95
94
  emit_warning("Cancelled")
@@ -115,8 +114,8 @@ def prompt_and_install_custom_server(manager) -> bool:
115
114
  empty_count = 0
116
115
  try:
117
116
  while True:
118
- line = input()
119
- if line.strip() == "":
117
+ line = safe_input("")
118
+ if line == "":
120
119
  empty_count += 1
121
120
  if empty_count >= 2:
122
121
  break
@@ -37,3 +37,57 @@ def make_directory_table(path: str = None) -> Table:
37
37
  for f in sorted(files):
38
38
  table.add_row("[yellow]file[/yellow]", f"{f}")
39
39
  return table
40
+
41
+
42
+ def _reset_windows_console() -> None:
43
+ """Reset Windows console to normal input mode.
44
+
45
+ After a prompt_toolkit Application exits on Windows, the console can be
46
+ left in a weird state where Enter doesn't work properly. This resets it.
47
+ """
48
+ import sys
49
+
50
+ if sys.platform != "win32":
51
+ return
52
+
53
+ try:
54
+ import ctypes
55
+
56
+ kernel32 = ctypes.windll.kernel32
57
+ # Get handle to stdin
58
+ STD_INPUT_HANDLE = -10
59
+ handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
60
+
61
+ # Enable line input and echo (normal console mode)
62
+ # ENABLE_LINE_INPUT = 0x0002
63
+ # ENABLE_ECHO_INPUT = 0x0004
64
+ # ENABLE_PROCESSED_INPUT = 0x0001
65
+ NORMAL_MODE = 0x0007 # Line input + echo + processed
66
+ kernel32.SetConsoleMode(handle, NORMAL_MODE)
67
+ except Exception:
68
+ pass # Silently ignore errors - this is best-effort
69
+
70
+
71
+ def safe_input(prompt_text: str = "") -> str:
72
+ """Cross-platform safe input that works after prompt_toolkit Applications.
73
+
74
+ On Windows, raw input() can fail after a prompt_toolkit Application exits
75
+ because the terminal can be left in a weird state. This function resets
76
+ the Windows console mode before calling input().
77
+
78
+ Args:
79
+ prompt_text: The prompt to display to the user
80
+
81
+ Returns:
82
+ The user's input string (stripped)
83
+
84
+ Raises:
85
+ KeyboardInterrupt: If user presses Ctrl+C
86
+ EOFError: If user presses Ctrl+D/Ctrl+Z
87
+ """
88
+ # Reset Windows console to normal mode before reading input
89
+ _reset_windows_console()
90
+
91
+ # Use standard input() - now that console is reset, it should work
92
+ result = input(prompt_text)
93
+ return result.strip() if result else ""
code_puppy/config.py CHANGED
@@ -47,6 +47,7 @@ MODELS_FILE = os.path.join(DATA_DIR, "models.json")
47
47
  EXTRA_MODELS_FILE = os.path.join(DATA_DIR, "extra_models.json")
48
48
  AGENTS_DIR = os.path.join(DATA_DIR, "agents")
49
49
  CONTEXTS_DIR = os.path.join(DATA_DIR, "contexts")
50
+ _DEFAULT_SQLITE_FILE = os.path.join(DATA_DIR, "dbos_store.sqlite")
50
51
 
51
52
  # OAuth plugin model files (XDG_DATA_HOME)
52
53
  GEMINI_MODELS_FILE = os.path.join(DATA_DIR, "gemini_models.json")
@@ -59,6 +60,21 @@ AUTOSAVE_DIR = os.path.join(CACHE_DIR, "autosaves")
59
60
 
60
61
  # State files (XDG_STATE_HOME)
61
62
  COMMAND_HISTORY_FILE = os.path.join(STATE_DIR, "command_history.txt")
63
+ DBOS_DATABASE_URL = os.environ.get(
64
+ "DBOS_SYSTEM_DATABASE_URL", f"sqlite:///{_DEFAULT_SQLITE_FILE}"
65
+ )
66
+ # DBOS enable switch is controlled solely via puppy.cfg using key 'enable_dbos'.
67
+ # Default: False (DBOS disabled) unless explicitly enabled.
68
+
69
+
70
+ def get_use_dbos() -> bool:
71
+ """Return True if DBOS should be used based on 'enable_dbos' (default False)."""
72
+ cfg_val = get_value("enable_dbos")
73
+ if cfg_val is None:
74
+ return False
75
+ return str(cfg_val).strip().lower() in {"1", "true", "yes", "on"}
76
+
77
+
62
78
  DEFAULT_SECTION = "puppy"
63
79
  REQUIRED_KEYS = ["puppy_name", "owner_name"]
64
80
 
@@ -193,6 +209,8 @@ def get_config_keys():
193
209
  "default_agent",
194
210
  "temperature",
195
211
  ]
212
+ # Add DBOS control key
213
+ default_keys.append("enable_dbos")
196
214
  # Add cancel agent key configuration
197
215
  default_keys.append("cancel_agent_key")
198
216
  # Add banner color keys
@@ -1029,6 +1047,11 @@ def set_http2(enabled: bool) -> None:
1029
1047
  set_config_value("http2", "true" if enabled else "false")
1030
1048
 
1031
1049
 
1050
+ def set_enable_dbos(enabled: bool) -> None:
1051
+ """Enable DBOS via config (true enables, default false)."""
1052
+ set_config_value("enable_dbos", "true" if enabled else "false")
1053
+
1054
+
1032
1055
  def get_message_limit(default: int = 1000) -> int:
1033
1056
  """
1034
1057
  Returns the user-configured message/request limit for the agent.
@@ -219,8 +219,9 @@ class ManagedMCPServer:
219
219
  stdio_kwargs["env"] = _expand_env_vars(config["env"])
220
220
  if "cwd" in config:
221
221
  stdio_kwargs["cwd"] = _expand_env_vars(config["cwd"])
222
- if "timeout" in config:
223
- stdio_kwargs["timeout"] = config["timeout"]
222
+ # Default timeout of 60s for stdio servers - some servers like Serena take a while to start
223
+ # Users can override this in their config
224
+ stdio_kwargs["timeout"] = config.get("timeout", 60)
224
225
  if "read_timeout" in config:
225
226
  stdio_kwargs["read_timeout"] = config["read_timeout"]
226
227
 
@@ -329,31 +329,19 @@ def emit_divider(content: str = "─" * 100 + "\n", **metadata):
329
329
 
330
330
 
331
331
  def emit_prompt(prompt_text: str, timeout: float = None) -> str:
332
- """Emit a human input request and wait for response."""
333
- # TUI mode has been removed, always use interactive mode input
334
- if True:
335
- # Emit the prompt as a message for display
336
- from code_puppy.messaging import emit_info
332
+ """Emit a human input request and wait for response.
337
333
 
338
- emit_info(prompt_text)
334
+ Uses safe_input for cross-platform compatibility, especially on Windows
335
+ where raw input() can fail after prompt_toolkit Applications.
336
+ """
337
+ from code_puppy.command_line.utils import safe_input
338
+ from code_puppy.messaging import emit_info
339
339
 
340
- # Get input directly
341
- try:
342
- # Try to use rich console for better formatting
343
- from rich.console import Console
344
-
345
- console = Console()
346
- response = console.input("[cyan]>>> [/cyan]")
347
- return response
348
- except Exception:
349
- # Fallback to basic input
350
- response = input(">>> ")
351
- return response
352
-
353
- # In TUI mode, use the queue system
354
- queue = get_global_queue()
355
- prompt_id = queue.create_prompt_request(prompt_text)
356
- return queue.wait_for_prompt_response(prompt_id, timeout)
340
+ emit_info(prompt_text)
341
+
342
+ # Use safe_input which resets Windows console state before reading
343
+ response = safe_input(">>> ")
344
+ return response
357
345
 
358
346
 
359
347
  def provide_prompt_response(prompt_id: str, response: str):
@@ -4,7 +4,10 @@ from typing import List
4
4
 
5
5
  from pydantic_ai import Agent
6
6
 
7
- from code_puppy.config import get_global_model_name
7
+ from code_puppy.config import (
8
+ get_global_model_name,
9
+ get_use_dbos,
10
+ )
8
11
  from code_puppy.model_factory import ModelFactory, make_model_settings
9
12
 
10
13
  # Keep a module-level agent reference to avoid rebuilding per call
@@ -103,6 +106,13 @@ def reload_summarization_agent():
103
106
  retries=1, # Fewer retries for summarization
104
107
  model_settings=model_settings,
105
108
  )
109
+ if get_use_dbos():
110
+ from pydantic_ai.durable_exec.dbos import DBOSAgent
111
+
112
+ global _reload_count
113
+ _reload_count += 1
114
+ dbos_agent = DBOSAgent(agent, name=f"summarization-agent-{_reload_count}")
115
+ return dbos_agent
106
116
  return agent
107
117
 
108
118
 
@@ -1,6 +1,7 @@
1
1
  # agent_tools.py
2
2
  import asyncio
3
3
  import hashlib
4
+ import itertools
4
5
  import json
5
6
  import pickle
6
7
  import re
@@ -9,6 +10,7 @@ from datetime import datetime
9
10
  from pathlib import Path
10
11
  from typing import List, Set
11
12
 
13
+ from dbos import DBOS, SetWorkflowID
12
14
  from pydantic import BaseModel
13
15
 
14
16
  # Import Agent from pydantic_ai to create temporary agents for invocation
@@ -18,6 +20,7 @@ from pydantic_ai.messages import ModelMessage
18
20
  from code_puppy.config import (
19
21
  DATA_DIR,
20
22
  get_message_limit,
23
+ get_use_dbos,
21
24
  )
22
25
  from code_puppy.messaging import (
23
26
  SubAgentInvocationMessage,
@@ -34,6 +37,27 @@ from code_puppy.tools.common import generate_group_id
34
37
  # Set to track active subagent invocation tasks
35
38
  _active_subagent_tasks: Set[asyncio.Task] = set()
36
39
 
40
+ # Atomic counter for DBOS workflow IDs - ensures uniqueness even in rapid back-to-back calls
41
+ # itertools.count() is thread-safe for next() calls
42
+ _dbos_workflow_counter = itertools.count()
43
+
44
+
45
+ def _generate_dbos_workflow_id(base_id: str) -> str:
46
+ """Generate a unique DBOS workflow ID by appending an atomic counter.
47
+
48
+ DBOS requires workflow IDs to be unique across all executions.
49
+ This function ensures uniqueness by combining the base_id with
50
+ an atomically incrementing counter.
51
+
52
+ Args:
53
+ base_id: The base identifier (e.g., group_id from generate_group_id)
54
+
55
+ Returns:
56
+ A unique workflow ID in format: {base_id}-wf-{counter}
57
+ """
58
+ counter = next(_dbos_workflow_counter)
59
+ return f"{base_id}-wf-{counter}"
60
+
37
61
 
38
62
  def _generate_session_hash_suffix() -> str:
39
63
  """Generate a short SHA1 hash suffix based on current timestamp for uniqueness.
@@ -444,11 +468,9 @@ def register_invoke_agent(agent):
444
468
  instructions = prepared.instructions
445
469
  prompt = prepared.user_prompt
446
470
 
471
+ subagent_name = f"temp-invoke-agent-{session_id}"
447
472
  model_settings = make_model_settings(model_name)
448
473
 
449
- # Load MCP servers so sub-agents have access to the same tools as the main agent
450
- mcp_servers = agent_config.load_mcp_servers()
451
-
452
474
  temp_agent = Agent(
453
475
  model=model,
454
476
  instructions=instructions,
@@ -456,7 +478,6 @@ def register_invoke_agent(agent):
456
478
  retries=3,
457
479
  history_processors=[agent_config.message_history_accumulator],
458
480
  model_settings=model_settings,
459
- toolsets=mcp_servers if mcp_servers else [],
460
481
  )
461
482
 
462
483
  # Register the tools that the agent needs
@@ -465,21 +486,44 @@ def register_invoke_agent(agent):
465
486
  agent_tools = agent_config.get_available_tools()
466
487
  register_tools_for_agent(temp_agent, agent_tools)
467
488
 
489
+ if get_use_dbos():
490
+ from pydantic_ai.durable_exec.dbos import DBOSAgent
491
+
492
+ dbos_agent = DBOSAgent(temp_agent, name=subagent_name)
493
+ temp_agent = dbos_agent
494
+
468
495
  # Run the temporary agent with the provided prompt as an asyncio task
469
496
  # Pass the message_history from the session to continue the conversation
470
- task = asyncio.create_task(
471
- temp_agent.run(
472
- prompt,
473
- message_history=message_history,
474
- usage_limits=UsageLimits(request_limit=get_message_limit()),
497
+ workflow_id = None # Track for potential cancellation
498
+ if get_use_dbos():
499
+ # Generate a unique workflow ID for DBOS - ensures no collisions in back-to-back calls
500
+ workflow_id = _generate_dbos_workflow_id(group_id)
501
+ with SetWorkflowID(workflow_id):
502
+ task = asyncio.create_task(
503
+ temp_agent.run(
504
+ prompt,
505
+ message_history=message_history,
506
+ usage_limits=UsageLimits(request_limit=get_message_limit()),
507
+ )
508
+ )
509
+ _active_subagent_tasks.add(task)
510
+ else:
511
+ task = asyncio.create_task(
512
+ temp_agent.run(
513
+ prompt,
514
+ message_history=message_history,
515
+ usage_limits=UsageLimits(request_limit=get_message_limit()),
516
+ )
475
517
  )
476
- )
477
- _active_subagent_tasks.add(task)
518
+ _active_subagent_tasks.add(task)
478
519
 
479
520
  try:
480
521
  result = await task
481
522
  finally:
482
523
  _active_subagent_tasks.discard(task)
524
+ if task.cancelled():
525
+ if get_use_dbos() and workflow_id:
526
+ DBOS.cancel_workflow(workflow_id)
483
527
 
484
528
  # Extract the response from the result
485
529
  response = result.output
@@ -7,7 +7,7 @@ from functools import lru_cache
7
7
  from pydantic import BaseModel, Field
8
8
  from pydantic_ai import Agent, BinaryContent
9
9
 
10
- from code_puppy.config import get_vqa_model_name
10
+ from code_puppy.config import get_use_dbos, get_vqa_model_name
11
11
  from code_puppy.model_factory import ModelFactory
12
12
 
13
13
 
@@ -50,6 +50,12 @@ def _load_vqa_agent(model_name: str) -> Agent[None, VisualAnalysisResult]:
50
50
  retries=2,
51
51
  )
52
52
 
53
+ if get_use_dbos():
54
+ from pydantic_ai.durable_exec.dbos import DBOSAgent
55
+
56
+ dbos_agent = DBOSAgent(vqa_agent, name="vqa-agent")
57
+ return dbos_agent
58
+
53
59
  return vqa_agent
54
60
 
55
61
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.344
3
+ Version: 0.0.346
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Software Development :: Code Generators
17
17
  Requires-Python: <3.14,>=3.11
18
18
  Requires-Dist: camoufox>=0.4.11
19
+ Requires-Dist: dbos>=2.5.0
19
20
  Requires-Dist: fastapi>=0.111.0
20
21
  Requires-Dist: httpx[http2]>=0.24.1
21
22
  Requires-Dist: json-repair>=0.46.2
@@ -173,6 +174,27 @@ These providers are automatically configured with correct OpenAI-compatible endp
173
174
  - **⚠️ Unsupported Providers** - Providers like Amazon Bedrock and Google Vertex that require special authentication are clearly marked
174
175
  - **⚠️ No Tool Calling** - Models without tool calling support show a big warning since they can't use Code Puppy's file/shell tools
175
176
 
177
+ ### Durable Execution
178
+
179
+ Code Puppy now supports **[DBOS](https://github.com/dbos-inc/dbos-transact-py)** durable execution.
180
+
181
+ When enabled, every agent is automatically wrapped as a `DBOSAgent`, checkpointing key interactions (including agent inputs, LLM responses, MCP calls, and tool calls) in a database for durability and recovery.
182
+
183
+ You can toggle DBOS via either of these options:
184
+
185
+ - CLI config (persists): `/set enable_dbos true` (or `false` to disable)
186
+
187
+
188
+ Config takes precedence if set; otherwise the environment variable is used.
189
+
190
+ ### Configuration
191
+
192
+ The following environment variables control DBOS behavior:
193
+ - `DBOS_CONDUCTOR_KEY`: If set, Code Puppy connects to the [DBOS Management Console](https://console.dbos.dev/). Make sure you first register an app named `dbos-code-puppy` on the console to generate a Conductor key. Default: `None`.
194
+ - `DBOS_LOG_LEVEL`: Logging verbosity: `CRITICAL`, `ERROR`, `WARNING`, `INFO`, or `DEBUG`. Default: `ERROR`.
195
+ - `DBOS_SYSTEM_DATABASE_URL`: Database URL used by DBOS. Can point to a local SQLite file or a Postgres instance. Example: `postgresql://postgres:dbos@localhost:5432/postgres`. Default: `dbos_store.sqlite` file in the config directory.
196
+ - `DBOS_APP_VERSION`: If set, Code Puppy uses it as the [DBOS application version](https://docs.dbos.dev/architecture#application-and-workflow-versions) and automatically tries to recover pending workflows for this version. Default: Code Puppy version + Unix timestamp in millisecond (disable automatic recovery).
197
+
176
198
  ### Custom Commands
177
199
  Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
178
200
 
@@ -3,8 +3,8 @@ code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
3
3
  code_puppy/callbacks.py,sha256=hqTV--dNxG5vwWWm3MrEjmb8MZuHFFdmHePl23NXPHk,8621
4
4
  code_puppy/chatgpt_codex_client.py,sha256=Om0ANB_kpHubhCwNzF9ENf8RvKBqs0IYzBLl_SNw0Vk,9833
5
5
  code_puppy/claude_cache_client.py,sha256=MLIRSJP428r9IK_aV6XyCXrCfQnNti32U60psPymLM4,14860
6
- code_puppy/cli_runner.py,sha256=ltUZJauK-fYaRT6qxCgmbJYFtXw1wDuk1rf8eu8LmD0,33676
7
- code_puppy/config.py,sha256=oCfxFfXvFgv7dEc4NObtQHvE-VG65DifEqaid342kTM,51544
6
+ code_puppy/cli_runner.py,sha256=BQu5Sa9y_ueqtgvbmuhWS-Tmd1FAjMbhTrtjFKbVZjM,34919
7
+ code_puppy/config.py,sha256=RlnrLkyFXm7h2Htf8rQA7vqoAyzLPMrESle417uLmFw,52373
8
8
  code_puppy/error_logging.py,sha256=a80OILCUtJhexI6a9GM-r5LqIdjvSRzggfgPp2jv1X0,3297
9
9
  code_puppy/gemini_code_assist.py,sha256=KGS7sO5OLc83nDF3xxS-QiU6vxW9vcm6hmzilu79Ef8,13867
10
10
  code_puppy/http_utils.py,sha256=H3N5Qz2B1CcsGUYOycGWAqoNMr2P1NCVluKX3aRwRqI,10358
@@ -20,7 +20,7 @@ code_puppy/reopenable_async_client.py,sha256=pD34chyBFcC7_OVPJ8fp6aRI5jYdN-7VDyc
20
20
  code_puppy/round_robin_model.py,sha256=kSawwPUiPgg0yg8r4AAVgvjzsWkptxpSORd75-HP7W4,5335
21
21
  code_puppy/session_storage.py,sha256=T4hOsAl9z0yz2JZCptjJBOnN8fCmkLZx5eLy1hTdv6Q,9631
22
22
  code_puppy/status_display.py,sha256=qHzIQGAPEa2_-4gQSg7_rE1ihOosBq8WO73MWFNmmlo,8938
23
- code_puppy/summarization_agent.py,sha256=YO_B1rd8jxX9S79HDFxhiiAKgn2Uv2fnt_hRkVvJ-dU,4209
23
+ code_puppy/summarization_agent.py,sha256=6Pu_Wp_rF-HAhoX9u2uXTabRVkOZUYwRoMP1lzNS4ew,4485
24
24
  code_puppy/terminal_utils.py,sha256=TaS19x7EZqudlBUAQwLMzBMNxBHBNInvQQREXqRGtkM,12984
25
25
  code_puppy/uvx_detection.py,sha256=tP9X9Nvzow--KIqtqjgrHQkSxMJ3EevfoaeoB9VLY2o,7224
26
26
  code_puppy/version_checker.py,sha256=aq2Mwxl1CR9sEFBgrPt3OQOowLOBUp9VaQYWJhuUv8Q,1780
@@ -40,18 +40,18 @@ code_puppy/agents/agent_qa_expert.py,sha256=5Ikb4U3SZQknUEfwlHZiyZXKqnffnOTQagr_
40
40
  code_puppy/agents/agent_qa_kitten.py,sha256=5PeFFSwCFlTUvP6h5bGntx0xv5NmRwBiw0HnMqY8nLI,9107
41
41
  code_puppy/agents/agent_security_auditor.py,sha256=SpiYNA0XAsIwBj7S2_EQPRslRUmF_-b89pIJyW7DYtY,12022
42
42
  code_puppy/agents/agent_typescript_reviewer.py,sha256=vsnpp98xg6cIoFAEJrRTUM_i4wLEWGm5nJxs6fhHobM,10275
43
- code_puppy/agents/base_agent.py,sha256=8ocPht_t87a83B7jVg6SExMnsq_f9qd-6KAelXoBB10,80354
43
+ code_puppy/agents/base_agent.py,sha256=QnPmROIw-rs5wopcVTZdBZusP0mGRC6EFZ7y_V00rMI,84825
44
44
  code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
45
45
  code_puppy/agents/prompt_reviewer.py,sha256=JJrJ0m5q0Puxl8vFsyhAbY9ftU9n6c6UxEVdNct1E-Q,5558
46
46
  code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
47
- code_puppy/command_line/add_model_menu.py,sha256=caXxSQc6dgx0qQ68RRFrDTsiH-wZjl4nUv2r0javhaM,43262
47
+ code_puppy/command_line/add_model_menu.py,sha256=CpURhxPvUhLHLBV_uwH1ODfJ-WAcGklvlsjEf5Vfvg4,43255
48
48
  code_puppy/command_line/attachments.py,sha256=4Q5I2Es4j0ltnz5wjw2z0QXMsiMJvEfWRkPf_lJeITM,13093
49
49
  code_puppy/command_line/autosave_menu.py,sha256=de7nOmFmEH6x5T7C95U8N8xgxxeF-l5lgaJzGJsF3ZY,19824
50
50
  code_puppy/command_line/clipboard.py,sha256=oe9bfAX5RnT81FiYrDmhvHaePS1tAT-NFG1fSXubSD4,16869
51
51
  code_puppy/command_line/colors_menu.py,sha256=LoFVfJ-Mo-Eq9hnb2Rj5mn7oBCnadAGr-8NNHsHlu18,17273
52
52
  code_puppy/command_line/command_handler.py,sha256=CY9F27eovZJK_kpU1YmbroYLWGTCuouCOQ-TXfDp-nw,10916
53
53
  code_puppy/command_line/command_registry.py,sha256=qFySsw1g8dol3kgi0p6cXrIDlP11_OhOoaQ5nAadWXg,4416
54
- code_puppy/command_line/config_commands.py,sha256=TrTijfP98cL88XD_my6rO9mFX2DX5Glxc81Y2tPIo38,25263
54
+ code_puppy/command_line/config_commands.py,sha256=qS9Cm758DPz2QGvHLhAV4Tp_Xfgo3PyoCoLDusbnmCw,25742
55
55
  code_puppy/command_line/core_commands.py,sha256=ujAPD4yDbXwYGJJfR2u4ei24eBV-Ps_-BVBjFMEoJy0,27668
56
56
  code_puppy/command_line/diff_menu.py,sha256=_Gr9SP9fbItk-08dya9WTAR53s_PlyAvEnbt-8VWKPk,24141
57
57
  code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
@@ -65,12 +65,12 @@ code_puppy/command_line/onboarding_wizard.py,sha256=U5lV_1P3IwDYZUHar0zKgdp121zz
65
65
  code_puppy/command_line/pin_command_completion.py,sha256=juSvdqRpk7AdfkPy1DJx5NzfEUU5KYGlChvP0hisM18,11667
66
66
  code_puppy/command_line/prompt_toolkit_completion.py,sha256=49GM3jVE89G1M3XroMZk2LhGgXpOO8XZ0Sg8h4a6LLw,32806
67
67
  code_puppy/command_line/session_commands.py,sha256=Jh8GGfhlfBAEVfucKLbcZjNaXYd0twImiOwq2ZnGdQQ,9902
68
- code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
68
+ code_puppy/command_line/utils.py,sha256=_upMrrmDp5hteUjrRiEgVR6SoeNEPNZGXb5lOWYqcUQ,2952
69
69
  code_puppy/command_line/mcp/__init__.py,sha256=0-OQuwjq_pLiTVJ1_NrirVwdRerghyKs_MTZkwPC7YY,315
70
70
  code_puppy/command_line/mcp/base.py,sha256=pPeNnSyM0GGqD6mhYN-qA22rAT9bEapxliwH_YiIu3Q,823
71
- code_puppy/command_line/mcp/catalog_server_installer.py,sha256=vY7MAy6O92bs-gRoZOO9jVPx23omr0jpSZucfjVkeOY,6170
71
+ code_puppy/command_line/mcp/catalog_server_installer.py,sha256=G9FvQPTBB4LeJ4cOR9DvIkp82mClKRKI35KELbFLCKU,6181
72
72
  code_puppy/command_line/mcp/custom_server_form.py,sha256=z0hsqXY1_ScJoacneyfrFHeVUlU3ZUHYt6CXV1wnJ0Y,23733
73
- code_puppy/command_line/mcp/custom_server_installer.py,sha256=sSln3Q0oMn5HNCCs-8ohP9V-CmGcKB-pPy5RRZpMAVg,5731
73
+ code_puppy/command_line/mcp/custom_server_installer.py,sha256=oLB5j07XKApdTrCDlY97GxE1NHrqupsX6DOCXzFj3TE,5744
74
74
  code_puppy/command_line/mcp/edit_command.py,sha256=_WxxpaTgxo9pbvMogG9yvh2mcLE5SYf0Qbi8a8IpZ0k,4603
75
75
  code_puppy/command_line/mcp/handler.py,sha256=jwhcLi28QQ6IuE6E5IsMbU67jMZChfZW96hG9ezYgN4,4547
76
76
  code_puppy/command_line/mcp/help_command.py,sha256=sTWecPqmmq6vmLVgZVNjBXKnziCLoJSKsugHkxiJlTI,5285
@@ -98,7 +98,7 @@ code_puppy/mcp_/config_wizard.py,sha256=JNNpgnSD6PFSyS3pTdEdD164oXd2VKp4VHLSz3To
98
98
  code_puppy/mcp_/dashboard.py,sha256=VtaFxLtPnbM_HL2TXRDAg6IqcM-EcFkoghGgkfhMrKI,9417
99
99
  code_puppy/mcp_/error_isolation.py,sha256=mpPBiH17zTXPsOEAn9WmkbwQwnt4gmgiaWv87JBJbUo,12426
100
100
  code_puppy/mcp_/health_monitor.py,sha256=n5R6EeYOYbUucUFe74qGWCU3g6Mep5UEQbLF0wbT0dU,19688
101
- code_puppy/mcp_/managed_server.py,sha256=HtjjHGYla77yrAdTNjMhUQHcEXr7_ON1oIPK-5xP73M,15241
101
+ code_puppy/mcp_/managed_server.py,sha256=e-DetKb3bVgdLYw7miTptLAbqhRgZ1cNoTGS0fh4Noo,15371
102
102
  code_puppy/mcp_/manager.py,sha256=_2ZRTLS8Sf3SMgEpAHl-wXBDYVqg2l-j9uI9EkfCNaE,31062
103
103
  code_puppy/mcp_/mcp_logs.py,sha256=o4pSHwELWIjEjqhfaMMEGrBvb159-VIgUp21E707BPo,6264
104
104
  code_puppy/mcp_/registry.py,sha256=U_t12WQ-En-KGyZoiTYdqlhp9NkDTWafu8g5InvF2NM,15774
@@ -111,7 +111,7 @@ code_puppy/messaging/__init__.py,sha256=THJQDdRub3jiWIRPqF34VggXem3Y2tuUFAJGdDAL
111
111
  code_puppy/messaging/bus.py,sha256=TbdltJ0D5tqnaE4irq1fcXllDYm-mQ_SiX1IFm-S4sw,21406
112
112
  code_puppy/messaging/commands.py,sha256=77CtKVNaF5KS3Xyzd0ccDAisZWQxL3weVEt3J-SfYxo,5464
113
113
  code_puppy/messaging/markdown_patches.py,sha256=dMIJozzJChuHa8QNMSEz_kC-dyt7kZiDLZ7rjthbcmg,1626
114
- code_puppy/messaging/message_queue.py,sha256=e-viZxacBoNSxRJnCJ4hU4vzsSI3oX_rN58RwhJKFfU,11825
114
+ code_puppy/messaging/message_queue.py,sha256=1-5NFWIes5kpecsKnhuQQJPeT0-X102Xi1-IwXUM5_Y,11430
115
115
  code_puppy/messaging/messages.py,sha256=F7RwMHeQrIk-8kuSSBU76wBq1NGuLb2H5cJrSMTC3XM,16464
116
116
  code_puppy/messaging/queue_console.py,sha256=T0U_V1tdN6hd9DLokp-HCk0mhu8Ivpfajha368CBZrU,9983
117
117
  code_puppy/messaging/renderers.py,sha256=GHVtMnxE1pJ-yrcRjacY81JcjlHRz3UVHzp-ohN-CGE,12058
@@ -158,7 +158,7 @@ code_puppy/plugins/shell_safety/command_cache.py,sha256=adYtSPNVOZfW_6dQdtEihO6E
158
158
  code_puppy/plugins/shell_safety/register_callbacks.py,sha256=W3v664RR48Fdbbbltf_NnX22_Ahw2AvAOtvXvWc7KxQ,7322
159
159
  code_puppy/prompts/codex_system_prompt.md,sha256=hEFTCziroLqZmqNle5kG34A8kvTteOWezCiVrAEKhE0,24400
160
160
  code_puppy/tools/__init__.py,sha256=BVTZ85jLHgDANwOnUSOz3UDlp8VQDq4DoGF23BRlyWw,6032
161
- code_puppy/tools/agent_tools.py,sha256=ZV2fdfBn02oj5jYA2Hg27lBAJqzwCCJXbglq20aBD60,19249
161
+ code_puppy/tools/agent_tools.py,sha256=snBI6FlFtR03CbYKXwu53R48c_fRSuDIwcNdVUruLcA,21020
162
162
  code_puppy/tools/command_runner.py,sha256=3qXVnVTaBPia6y2D29As47_TRKgpyCj82yMFK-8UUYc,44954
163
163
  code_puppy/tools/common.py,sha256=IYf-KOcP5eN2MwTlpULSXNATn7GzloAKl7_M1Uyfe4Y,40360
164
164
  code_puppy/tools/file_modifications.py,sha256=vz9n7R0AGDSdLUArZr_55yJLkyI30M8zreAppxIx02M,29380
@@ -173,11 +173,11 @@ code_puppy/tools/browser/browser_screenshot.py,sha256=7jeG5N4-OkpRPY3JMwOrsJjutY
173
173
  code_puppy/tools/browser/browser_scripts.py,sha256=sNb8eLEyzhasy5hV4B9OjM8yIVMLVEMRcQ4K77ABjRI,14564
174
174
  code_puppy/tools/browser/browser_workflows.py,sha256=nitW42vCf0ieTX1gLabozTugNQ8phtoFzZbiAhw1V90,6491
175
175
  code_puppy/tools/browser/camoufox_manager.py,sha256=RZjGOEftE5sI_tsercUyXFSZI2wpStXf-q0PdYh2G3I,8680
176
- code_puppy/tools/browser/vqa_agent.py,sha256=CkGtRUPOR35383J6nrKFgcTj86RZqUpPhOdZr5xYutA,2728
177
- code_puppy-0.0.344.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
178
- code_puppy-0.0.344.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
179
- code_puppy-0.0.344.dist-info/METADATA,sha256=5KBija1IWjLdX3ovpgxy3aSYjKfWuhT4fda7vDUFPuw,26014
180
- code_puppy-0.0.344.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
181
- code_puppy-0.0.344.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
182
- code_puppy-0.0.344.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
183
- code_puppy-0.0.344.dist-info/RECORD,,
176
+ code_puppy/tools/browser/vqa_agent.py,sha256=DBn9HKloILqJSTSdNZzH_PYWT0B2h9VwmY6akFQI_uU,2913
177
+ code_puppy-0.0.346.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
178
+ code_puppy-0.0.346.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
179
+ code_puppy-0.0.346.dist-info/METADATA,sha256=zaajsigLALDkNXHZYMXxEU3w19EWgW5p7ibXjkoOvbQ,27550
180
+ code_puppy-0.0.346.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
181
+ code_puppy-0.0.346.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
182
+ code_puppy-0.0.346.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
183
+ code_puppy-0.0.346.dist-info/RECORD,,