code-puppy 0.0.164__tar.gz → 0.0.166__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 (111) hide show
  1. {code_puppy-0.0.164 → code_puppy-0.0.166}/PKG-INFO +1 -1
  2. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/message_history_processor.py +75 -0
  3. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tools/file_operations.py +42 -7
  4. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/custom_widgets.py +7 -2
  5. {code_puppy-0.0.164 → code_puppy-0.0.166}/pyproject.toml +1 -1
  6. {code_puppy-0.0.164 → code_puppy-0.0.166}/.gitignore +0 -0
  7. {code_puppy-0.0.164 → code_puppy-0.0.166}/LICENSE +0 -0
  8. {code_puppy-0.0.164 → code_puppy-0.0.166}/README.md +0 -0
  9. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/__init__.py +0 -0
  10. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/__main__.py +0 -0
  11. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agent.py +0 -0
  12. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/__init__.py +0 -0
  13. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/agent_code_puppy.py +0 -0
  14. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/agent_creator_agent.py +0 -0
  15. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/agent_manager.py +0 -0
  16. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/agent_orchestrator.json +0 -0
  17. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/base_agent.py +0 -0
  18. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/json_agent.py +0 -0
  19. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/agents/runtime_manager.py +0 -0
  20. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/callbacks.py +0 -0
  21. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/__init__.py +0 -0
  22. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/command_handler.py +0 -0
  23. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/file_path_completion.py +0 -0
  24. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/load_context_completion.py +0 -0
  25. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/__init__.py +0 -0
  26. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/add_command.py +0 -0
  27. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/base.py +0 -0
  28. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/handler.py +0 -0
  29. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/help_command.py +0 -0
  30. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/install_command.py +0 -0
  31. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/list_command.py +0 -0
  32. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/logs_command.py +0 -0
  33. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/remove_command.py +0 -0
  34. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/restart_command.py +0 -0
  35. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/search_command.py +0 -0
  36. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  37. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/start_command.py +0 -0
  38. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/status_command.py +0 -0
  39. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  40. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/stop_command.py +0 -0
  41. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/test_command.py +0 -0
  42. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/utils.py +0 -0
  43. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  44. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/meta_command_handler.py +0 -0
  45. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/model_picker_completion.py +0 -0
  46. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/motd.py +0 -0
  47. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  48. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/command_line/utils.py +0 -0
  49. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/config.py +0 -0
  50. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/http_utils.py +0 -0
  51. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/main.py +0 -0
  52. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/__init__.py +0 -0
  53. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/async_lifecycle.py +0 -0
  54. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/blocking_startup.py +0 -0
  55. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/captured_stdio_server.py +0 -0
  56. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/circuit_breaker.py +0 -0
  57. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/config_wizard.py +0 -0
  58. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/dashboard.py +0 -0
  59. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/error_isolation.py +0 -0
  60. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/examples/retry_example.py +0 -0
  61. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/health_monitor.py +0 -0
  62. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/managed_server.py +0 -0
  63. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/manager.py +0 -0
  64. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/registry.py +0 -0
  65. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/retry_manager.py +0 -0
  66. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/server_registry_catalog.py +0 -0
  67. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/status_tracker.py +0 -0
  68. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/mcp/system_tools.py +0 -0
  69. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/__init__.py +0 -0
  70. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/message_queue.py +0 -0
  71. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/queue_console.py +0 -0
  72. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/renderers.py +0 -0
  73. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/spinner/__init__.py +0 -0
  74. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  75. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  76. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
  77. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/model_factory.py +0 -0
  78. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/models.json +0 -0
  79. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/plugins/__init__.py +0 -0
  80. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/reopenable_async_client.py +0 -0
  81. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/round_robin_model.py +0 -0
  82. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/state_management.py +0 -0
  83. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/status_display.py +0 -0
  84. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/summarization_agent.py +0 -0
  85. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tools/__init__.py +0 -0
  86. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tools/agent_tools.py +0 -0
  87. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tools/command_runner.py +0 -0
  88. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tools/common.py +0 -0
  89. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tools/file_modifications.py +0 -0
  90. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tools/tools_content.py +0 -0
  91. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/__init__.py +0 -0
  92. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/app.py +0 -0
  93. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/__init__.py +0 -0
  94. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/chat_view.py +0 -0
  95. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/command_history_modal.py +0 -0
  96. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/copy_button.py +0 -0
  97. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/human_input_modal.py +0 -0
  98. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/input_area.py +0 -0
  99. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/sidebar.py +0 -0
  100. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/components/status_bar.py +0 -0
  101. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/messages.py +0 -0
  102. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/models/__init__.py +0 -0
  103. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/models/chat_message.py +0 -0
  104. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/models/command_history.py +0 -0
  105. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/models/enums.py +0 -0
  106. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/screens/__init__.py +0 -0
  107. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/screens/help.py +0 -0
  108. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
  109. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/screens/settings.py +0 -0
  110. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/tui/screens/tools.py +0 -0
  111. {code_puppy-0.0.164 → code_puppy-0.0.166}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.164
3
+ Version: 0.0.166
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
@@ -148,6 +148,81 @@ def split_messages_for_protected_summarization(
148
148
  return messages_to_summarize, protected_messages
149
149
 
150
150
 
151
+ def deduplicate_tool_returns(messages: List[ModelMessage]) -> List[ModelMessage]:
152
+ """
153
+ Remove duplicate tool returns while preserving the first occurrence for each tool_call_id.
154
+
155
+ This function identifies tool-return parts that share the same tool_call_id and
156
+ removes duplicates, keeping only the first return for each id. This prevents
157
+ conversation corruption from duplicate tool_result blocks.
158
+ """
159
+ if not messages:
160
+ return messages
161
+
162
+ seen_tool_returns: Set[str] = set()
163
+ deduplicated: List[ModelMessage] = []
164
+ removed_count = 0
165
+
166
+ for msg in messages:
167
+ # Check if this message has any parts we need to filter
168
+ if not hasattr(msg, "parts") or not msg.parts:
169
+ deduplicated.append(msg)
170
+ continue
171
+
172
+ # Filter parts within this message
173
+ filtered_parts = []
174
+ msg_had_duplicates = False
175
+
176
+ for part in msg.parts:
177
+ tool_call_id = getattr(part, "tool_call_id", None)
178
+ part_kind = getattr(part, "part_kind", None)
179
+
180
+ # Check if this is a tool-return part
181
+ if tool_call_id and part_kind in {
182
+ "tool-return",
183
+ "tool-result",
184
+ "tool_result",
185
+ }:
186
+ if tool_call_id in seen_tool_returns:
187
+ # This is a duplicate return, skip it
188
+ msg_had_duplicates = True
189
+ removed_count += 1
190
+ continue
191
+ else:
192
+ # First occurrence of this return, keep it
193
+ seen_tool_returns.add(tool_call_id)
194
+ filtered_parts.append(part)
195
+ else:
196
+ # Not a tool return, always keep
197
+ filtered_parts.append(part)
198
+
199
+ # If we filtered out parts, create a new message with filtered parts
200
+ if msg_had_duplicates and filtered_parts:
201
+ # Create a new message with the same attributes but filtered parts
202
+ new_msg = type(msg)(parts=filtered_parts)
203
+ # Copy over other attributes if they exist
204
+ for attr_name in dir(msg):
205
+ if (
206
+ not attr_name.startswith("_")
207
+ and attr_name != "parts"
208
+ and hasattr(msg, attr_name)
209
+ ):
210
+ try:
211
+ setattr(new_msg, attr_name, getattr(msg, attr_name))
212
+ except (AttributeError, TypeError):
213
+ # Skip attributes that can't be set
214
+ pass
215
+ deduplicated.append(new_msg)
216
+ elif filtered_parts: # No duplicates but has parts
217
+ deduplicated.append(msg)
218
+ # If no parts remain after filtering, drop the entire message
219
+
220
+ if removed_count > 0:
221
+ emit_warning(f"Removed {removed_count} duplicate tool-return part(s)")
222
+
223
+ return deduplicated
224
+
225
+
151
226
  def summarize_messages(
152
227
  messages: List[ModelMessage], with_protection=True
153
228
  ) -> Tuple[List[ModelMessage], List[ModelMessage]]:
@@ -208,17 +208,22 @@ def _list_files(
208
208
  files = result.stdout.strip().split("\n") if result.stdout.strip() else []
209
209
 
210
210
  # Create ListedFile objects with metadata
211
- for file_path in files:
212
- if not file_path: # Skip empty lines
211
+ for full_path in files:
212
+ if not full_path: # Skip empty lines
213
213
  continue
214
214
 
215
- full_path = os.path.join(directory, file_path)
216
-
217
215
  # Skip if file doesn't exist (though it should)
218
216
  if not os.path.exists(full_path):
219
217
  continue
220
218
 
219
+ # Extract relative path from the full path
220
+ if full_path.startswith(directory):
221
+ file_path = full_path[len(directory):].lstrip(os.sep)
222
+ else:
223
+ file_path = full_path
224
+
221
225
  # For non-recursive mode, skip files in subdirectories
226
+ # Only check the relative path, not the full path
222
227
  if not recursive and os.sep in file_path:
223
228
  continue
224
229
 
@@ -242,7 +247,7 @@ def _list_files(
242
247
  if entry_type == "file":
243
248
  size = actual_size
244
249
 
245
- # Calculate depth
250
+ # Calculate depth based on the relative path
246
251
  depth = file_path.count(os.sep)
247
252
 
248
253
  # Add directory entries if needed for files
@@ -281,6 +286,33 @@ def _list_files(
281
286
  except (FileNotFoundError, PermissionError, OSError):
282
287
  # Skip files we can't access
283
288
  continue
289
+
290
+ # In non-recursive mode, we also need to explicitly list directories in the target directory
291
+ # ripgrep's --files option only returns files, not directories
292
+ if not recursive:
293
+ try:
294
+ entries = os.listdir(directory)
295
+ for entry in entries:
296
+ full_entry_path = os.path.join(directory, entry)
297
+ # Skip if it doesn't exist or if it's a file (since files are already listed by ripgrep)
298
+ if not os.path.exists(full_entry_path) or os.path.isfile(full_entry_path):
299
+ continue
300
+
301
+ # For non-recursive mode, only include directories that are directly in the target directory
302
+ if os.path.isdir(full_entry_path):
303
+ # Create a ListedFile for the directory
304
+ results.append(
305
+ ListedFile(
306
+ path=entry,
307
+ type="directory",
308
+ size=0,
309
+ full_path=full_entry_path,
310
+ depth=0,
311
+ )
312
+ )
313
+ except (FileNotFoundError, PermissionError, OSError):
314
+ # Skip directories we can't access
315
+ pass
284
316
  except subprocess.TimeoutExpired:
285
317
  error_msg = (
286
318
  "[red bold]Error:[/red bold] List files command timed out after 30 seconds"
@@ -337,9 +369,12 @@ def _list_files(
337
369
  else:
338
370
  return "\U0001f4c4"
339
371
 
372
+ # Count items in results
340
373
  dir_count = sum(1 for item in results if item.type == "directory")
341
374
  file_count = sum(1 for item in results if item.type == "file")
342
375
  total_size = sum(item.size for item in results if item.type == "file")
376
+
377
+
343
378
 
344
379
  # Build the directory header section
345
380
  dir_name = os.path.basename(directory) or directory
@@ -393,8 +428,8 @@ def _list_files(
393
428
  final_divider = "[dim]" + "─" * 100 + "\n" + "[/dim]"
394
429
  output_lines.append(final_divider)
395
430
 
396
- # Return both the content string and the list of ListedFile objects
397
- return ListFileOutput(content="\n".join(output_lines), files=results)
431
+ # Return the content string
432
+ return ListFileOutput(content="\n".join(output_lines))
398
433
 
399
434
 
400
435
  def _read_file(
@@ -21,8 +21,13 @@ class CustomTextArea(TextArea):
21
21
 
22
22
  def on_key(self, event):
23
23
  """Handle key events before they reach the internal _on_key handler."""
24
- # Explicitly handle escape+enter/alt+enter sequences
25
- if event.key == "escape+enter" or event.key == "alt+enter":
24
+ # Let the binding system handle alt+enter
25
+ if event.key == "alt+enter":
26
+ # Don't prevent default - let the binding system handle it
27
+ return
28
+
29
+ # Handle escape+enter manually
30
+ if event.key == "escape+enter":
26
31
  self.action_insert_newline()
27
32
  event.prevent_default()
28
33
  event.stop()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.164"
7
+ version = "0.0.166"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes