ripperdoc 0.2.6__py3-none-any.whl → 0.2.7__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.
@@ -0,0 +1,216 @@
1
+ """Message content formatting utilities.
2
+
3
+ This module provides functions for converting message content to plain text,
4
+ with support for detailed tool information extraction. Used primarily for
5
+ conversation summarization and compaction.
6
+ """
7
+
8
+ from typing import Any, List, Union
9
+
10
+ from ripperdoc.utils.messages import UserMessage, AssistantMessage, ProgressMessage
11
+
12
+ ConversationMessage = Union[UserMessage, AssistantMessage, ProgressMessage]
13
+
14
+
15
+ def stringify_message_content(
16
+ content: Any, *, include_tool_details: bool = False
17
+ ) -> str:
18
+ """Convert message content to plain string.
19
+
20
+ Args:
21
+ content: The message content to stringify.
22
+ include_tool_details: If True, include tool input/output details
23
+ instead of just placeholders. Useful for summarization.
24
+
25
+ Returns:
26
+ Plain text representation of the message content.
27
+ """
28
+ if content is None:
29
+ return ""
30
+ if isinstance(content, str):
31
+ return content
32
+ if isinstance(content, list):
33
+ parts: List[str] = []
34
+ for block in content:
35
+ block_type = getattr(block, "type", None)
36
+ if block_type == "text":
37
+ text = getattr(block, "text", None)
38
+ if text:
39
+ parts.append(str(text))
40
+ elif block_type == "tool_use":
41
+ name = getattr(block, "name", "tool")
42
+ if include_tool_details:
43
+ tool_input = getattr(block, "input", None)
44
+ parts.append(format_tool_use_detail(name, tool_input))
45
+ else:
46
+ parts.append(f"[Called {name}]")
47
+ elif block_type == "tool_result":
48
+ if include_tool_details:
49
+ result_text = getattr(block, "text", None) or ""
50
+ is_error = getattr(block, "is_error", False)
51
+ parts.append(format_tool_result_detail(result_text, is_error))
52
+ else:
53
+ parts.append("[Tool result]")
54
+ return "\n".join(parts)
55
+ return str(content)
56
+
57
+
58
+ def format_tool_use_detail(name: str, tool_input: Any) -> str:
59
+ """Format tool_use block with input details for summarization.
60
+
61
+ Args:
62
+ name: The tool name.
63
+ tool_input: The tool input dictionary.
64
+
65
+ Returns:
66
+ Formatted string like "[Called Bash(command=ls -la)]"
67
+ """
68
+ if not tool_input:
69
+ return f"[Called {name}]"
70
+
71
+ summary_parts: List[str] = []
72
+ if isinstance(tool_input, dict):
73
+ # Common patterns for different tools
74
+ if name == "Bash":
75
+ cmd = tool_input.get("command", "")
76
+ if cmd:
77
+ cmd_preview = cmd[:200] + "..." if len(cmd) > 200 else cmd
78
+ summary_parts.append(f"command={cmd_preview}")
79
+ elif name in ("Read", "Write", "Edit", "MultiEdit"):
80
+ path = tool_input.get("file_path", "")
81
+ if path:
82
+ summary_parts.append(f"file={path}")
83
+ elif name in ("Glob", "Grep"):
84
+ pattern = tool_input.get("pattern", "")
85
+ if pattern:
86
+ summary_parts.append(f"pattern={pattern}")
87
+ elif name == "Task":
88
+ desc = tool_input.get("description", "")
89
+ subagent = tool_input.get("subagent_type", "")
90
+ if subagent:
91
+ summary_parts.append(f"subagent={subagent}")
92
+ if desc:
93
+ summary_parts.append(f"desc={desc}")
94
+ else:
95
+ # Generic: show first few key-value pairs
96
+ for key, value in list(tool_input.items())[:3]:
97
+ val_str = str(value)
98
+ if len(val_str) > 50:
99
+ val_str = val_str[:47] + "..."
100
+ summary_parts.append(f"{key}={val_str}")
101
+
102
+ if summary_parts:
103
+ return f"[Called {name}({', '.join(summary_parts)})]"
104
+ return f"[Called {name}]"
105
+
106
+
107
+ def format_tool_result_detail(result_text: str, is_error: bool = False) -> str:
108
+ """Format tool_result block with output details for summarization.
109
+
110
+ Args:
111
+ result_text: The tool result text.
112
+ is_error: Whether this is an error result.
113
+
114
+ Returns:
115
+ Formatted string like "[Tool result]: file contents..."
116
+ """
117
+ prefix = "[Tool error]" if is_error else "[Tool result]"
118
+ if not result_text:
119
+ return prefix
120
+
121
+ # Truncate very long results but keep enough for context
122
+ max_len = 500
123
+ if len(result_text) > max_len:
124
+ result_preview = result_text[:max_len] + f"... (truncated, {len(result_text)} chars total)"
125
+ else:
126
+ result_preview = result_text
127
+
128
+ return f"{prefix}: {result_preview}"
129
+
130
+
131
+ def format_reasoning_preview(reasoning: Any) -> str:
132
+ """Return a short preview of reasoning/thinking content.
133
+
134
+ Args:
135
+ reasoning: The reasoning content (string, list, or other).
136
+
137
+ Returns:
138
+ A short preview string (max ~80 chars with ellipsis).
139
+ """
140
+ if reasoning is None:
141
+ return ""
142
+ if isinstance(reasoning, str):
143
+ text = reasoning
144
+ elif isinstance(reasoning, list):
145
+ parts = []
146
+ for block in reasoning:
147
+ if isinstance(block, dict):
148
+ parts.append(block.get("thinking") or block.get("summary") or "")
149
+ elif hasattr(block, "thinking"):
150
+ parts.append(getattr(block, "thinking", "") or "")
151
+ text = "\n".join(p for p in parts if p)
152
+ else:
153
+ text = str(reasoning)
154
+ lines = text.strip().splitlines()
155
+ if not lines:
156
+ return ""
157
+ preview = lines[0][:80]
158
+ if len(lines) > 1 or len(lines[0]) > 80:
159
+ preview += "..."
160
+ return preview
161
+
162
+
163
+ def render_transcript(
164
+ messages: List[ConversationMessage], *, include_tool_details: bool = True
165
+ ) -> str:
166
+ """Render conversation messages into a plain-text transcript.
167
+
168
+ Args:
169
+ messages: List of conversation messages to render.
170
+ include_tool_details: If True (default), include tool input/output
171
+ details for better summarization context.
172
+
173
+ Returns:
174
+ Plain text transcript of the conversation.
175
+ """
176
+ lines: List[str] = []
177
+ for msg in messages:
178
+ msg_type = getattr(msg, "type", "")
179
+ if msg_type == "progress":
180
+ continue
181
+ role = "User" if msg_type == "user" else "Assistant"
182
+ content = getattr(getattr(msg, "message", None), "content", None)
183
+ text = stringify_message_content(content, include_tool_details=include_tool_details)
184
+ if text.strip():
185
+ lines.append(f"{role}: {text}")
186
+ return "\n\n".join(lines)
187
+
188
+
189
+ def extract_assistant_text(assistant_message: Any) -> str:
190
+ """Extract plain text from an assistant response object.
191
+
192
+ Args:
193
+ assistant_message: An AssistantMessage or similar object.
194
+
195
+ Returns:
196
+ Plain text content from the message.
197
+ """
198
+ # AssistantMessage has .message.content structure
199
+ message = getattr(assistant_message, "message", None)
200
+ if message is not None:
201
+ content = getattr(message, "content", None)
202
+ else:
203
+ # Fallback: maybe it's a raw object with .content directly
204
+ content = getattr(assistant_message, "content", None)
205
+
206
+ if isinstance(content, str):
207
+ return content
208
+ if isinstance(content, list):
209
+ parts: List[str] = []
210
+ for block in content:
211
+ if getattr(block, "type", None) == "text":
212
+ text = getattr(block, "text", None)
213
+ if text:
214
+ parts.append(str(text))
215
+ return "\n".join(parts)
216
+ return ""
@@ -381,20 +381,30 @@ def normalize_messages_for_api(
381
381
  # Precompute tool_result positions so we can drop dangling tool_calls that
382
382
  # lack a following tool response (which OpenAI rejects).
383
383
  tool_result_positions: Dict[str, int] = {}
384
+ # Precompute tool_use positions so we can drop dangling tool_results that
385
+ # lack a preceding tool_call (which OpenAI also rejects).
386
+ tool_use_positions: Dict[str, int] = {}
384
387
  skipped_tool_uses_no_result = 0
385
388
  skipped_tool_uses_no_id = 0
389
+ skipped_tool_results_no_call = 0
386
390
  if protocol == "openai":
387
391
  for idx, msg in enumerate(messages):
388
- if _msg_type(msg) != "user":
389
- continue
392
+ msg_type = _msg_type(msg)
390
393
  content = _msg_content(msg)
391
394
  if not isinstance(content, list):
392
395
  continue
393
- for block in content:
394
- if getattr(block, "type", None) == "tool_result":
395
- tool_id = getattr(block, "tool_use_id", None) or getattr(block, "id", None)
396
- if tool_id and tool_id not in tool_result_positions:
397
- tool_result_positions[tool_id] = idx
396
+ if msg_type == "user":
397
+ for block in content:
398
+ if getattr(block, "type", None) == "tool_result":
399
+ tool_id = getattr(block, "tool_use_id", None) or getattr(block, "id", None)
400
+ if tool_id and tool_id not in tool_result_positions:
401
+ tool_result_positions[tool_id] = idx
402
+ elif msg_type == "assistant":
403
+ for block in content:
404
+ if getattr(block, "type", None) == "tool_use":
405
+ tool_id = getattr(block, "id", None) or getattr(block, "tool_use_id", None)
406
+ if tool_id and tool_id not in tool_use_positions:
407
+ tool_use_positions[tool_id] = idx
398
408
 
399
409
  for msg_index, msg in enumerate(messages):
400
410
  msg_type = _msg_type(msg)
@@ -412,8 +422,18 @@ def normalize_messages_for_api(
412
422
  # Map each block to an OpenAI-style message
413
423
  openai_msgs: List[Dict[str, Any]] = []
414
424
  for block in user_content:
415
- if getattr(block, "type", None) == "tool_result":
425
+ block_type = getattr(block, "type", None)
426
+ if block_type == "tool_result":
416
427
  tool_results_seen += 1
428
+ # Skip tool_result blocks that lack a preceding tool_use
429
+ tool_id = getattr(block, "tool_use_id", None) or getattr(block, "id", None)
430
+ if not tool_id:
431
+ skipped_tool_results_no_call += 1
432
+ continue
433
+ call_pos = tool_use_positions.get(tool_id)
434
+ if call_pos is None or call_pos >= msg_index:
435
+ skipped_tool_results_no_call += 1
436
+ continue
417
437
  mapped = _content_block_to_openai(block)
418
438
  if mapped:
419
439
  openai_msgs.append(mapped)
@@ -498,8 +518,10 @@ def normalize_messages_for_api(
498
518
  f"input_msgs={len(messages)} normalized={len(normalized)} "
499
519
  f"tool_results_seen={tool_results_seen} tool_uses_seen={tool_uses_seen} "
500
520
  f"tool_result_positions={len(tool_result_positions)} "
521
+ f"tool_use_positions={len(tool_use_positions)} "
501
522
  f"skipped_tool_uses_no_result={skipped_tool_uses_no_result} "
502
- f"skipped_tool_uses_no_id={skipped_tool_uses_no_id}"
523
+ f"skipped_tool_uses_no_id={skipped_tool_uses_no_id} "
524
+ f"skipped_tool_results_no_call={skipped_tool_results_no_call}"
503
525
  )
504
526
  return normalized
505
527
 
@@ -29,7 +29,7 @@ class SessionSummary:
29
29
  message_count: int
30
30
  created_at: datetime
31
31
  updated_at: datetime
32
- first_prompt: str
32
+ last_prompt: str
33
33
 
34
34
 
35
35
  def _sessions_root() -> Path:
@@ -132,7 +132,9 @@ class SessionHistory:
132
132
  }
133
133
  try:
134
134
  with self.path.open("a", encoding="utf-8") as fh:
135
- json.dump(entry, fh)
135
+ # ensure_ascii=False 避免中文等字符被转义为 \uXXXX
136
+ # separators 去掉多余空格,减小体积
137
+ json.dump(entry, fh, ensure_ascii=False, separators=(",", ":"))
136
138
  fh.write("\n")
137
139
  if isinstance(msg_uuid, str):
138
140
  self._seen_ids.add(msg_uuid)
@@ -198,10 +200,20 @@ def list_session_summaries(project_path: Path) -> List[SessionSummary]:
198
200
  if isinstance(updated_raw, str)
199
201
  else datetime.fromtimestamp(jsonl_path.stat().st_mtime)
200
202
  )
201
- first_prompt = ""
202
- for payload in conversation_payloads:
203
- first_prompt = _extract_prompt(payload)
204
- if first_prompt:
203
+ # Extract last user prompt with more than 10 characters
204
+ # If not found, fall back to any user prompt
205
+ last_prompt = ""
206
+ fallback_prompt = ""
207
+ for payload in reversed(conversation_payloads):
208
+ if payload.get("type") != "user":
209
+ continue
210
+ prompt = _extract_prompt(payload)
211
+ if not prompt:
212
+ continue
213
+ if not fallback_prompt:
214
+ fallback_prompt = prompt
215
+ if len(prompt) > 10:
216
+ last_prompt = prompt
205
217
  break
206
218
  summaries.append(
207
219
  SessionSummary(
@@ -210,7 +222,7 @@ def list_session_summaries(project_path: Path) -> List[SessionSummary]:
210
222
  message_count=len(conversation_payloads),
211
223
  created_at=created_at,
212
224
  updated_at=updated_at,
213
- first_prompt=first_prompt or "(no prompt)",
225
+ last_prompt=last_prompt or fallback_prompt or "(no prompt)",
214
226
  )
215
227
  )
216
228
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripperdoc
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: AI-powered terminal assistant for coding tasks
5
5
  Author: Ripperdoc Team
6
6
  License: Apache-2.0
@@ -1,45 +1,49 @@
1
- ripperdoc/__init__.py,sha256=9pRQSHIr5pIFaG1rbNYM4nNgdBNpy2bj8TXd_M7GItI,66
1
+ ripperdoc/__init__.py,sha256=2df2ZNxyMS77y0lZ3scL-6v6-11pxxJtRAwIs2hHfG8,66
2
2
  ripperdoc/__main__.py,sha256=1Avq2MceBfwUlNsfasC8n4dqVL_V56Bl3DRsnY4_Nxk,370
3
3
  ripperdoc/cli/__init__.py,sha256=03wf6gXBcEgXJrDJS-W_5BEG_DdJ_ep7CxQFPML-73g,35
4
4
  ripperdoc/cli/cli.py,sha256=ECbi5Rm8prKkNtiv4P191ftSMEMSwYIG2LKC5IJXyIk,14140
5
5
  ripperdoc/cli/commands/__init__.py,sha256=rSB7hmuMRBfqYCxuUMsdb3-8mloKQyfOTinXU3wm3Uo,2414
6
6
  ripperdoc/cli/commands/agents_cmd.py,sha256=YDE9oIXPmsPyvkHhq95aXxnneN3PqZ1ZOtcn26cXeO8,10438
7
7
  ripperdoc/cli/commands/base.py,sha256=4KUjxCM04MwbSMUKVNEBph_jeAKPI8b5MHsUFoz7l5g,386
8
- ripperdoc/cli/commands/clear_cmd.py,sha256=zSYT0Nn_htZzLWTTQ4E5KWHfRg0Q5CYvRO4e--7thBY,345
8
+ ripperdoc/cli/commands/clear_cmd.py,sha256=FDZ0W34VxGyLhLiU4TzukHCyElqsnLwkCmfKJqLfFAQ,366
9
9
  ripperdoc/cli/commands/compact_cmd.py,sha256=uR_nB1OX7cUL1TOJoefwdO31Qfyjd0nZSSttErqUxbA,473
10
10
  ripperdoc/cli/commands/config_cmd.py,sha256=ebIQk7zUFv353liWfbBSSfPiOaaCR7rQsd_eTw7nsvY,884
11
11
  ripperdoc/cli/commands/context_cmd.py,sha256=6Yrz3_Oa2NwEsZo4tLK_PFFYP0Vq-amQCMBomSVFmBo,5220
12
12
  ripperdoc/cli/commands/cost_cmd.py,sha256=yD9LSqgxVvYNTDPnEHxugjyLWcmbtH5dXim7DIW9zXc,2822
13
13
  ripperdoc/cli/commands/doctor_cmd.py,sha256=q_PO1mnknRysVG7uopiqDWvkIIcvRX2i-JWgfKN-0gQ,7052
14
- ripperdoc/cli/commands/exit_cmd.py,sha256=ASOoLeXarbWK2vdC0223s98qmAMMRh1lcJPX0Qe5o7c,327
14
+ ripperdoc/cli/commands/exit_cmd.py,sha256=B0CNKQos2eRC4LSjizLdKsFYzFfwRkrUur6Afu3Fh9M,334
15
15
  ripperdoc/cli/commands/help_cmd.py,sha256=iz1vR-rmWsvvfzdebLiIWEWrcMZo5_Eb55_wLr4Ufno,508
16
16
  ripperdoc/cli/commands/mcp_cmd.py,sha256=ZCnswx0TIiaiUUsIX7NpHaLZLZtvlUhBnN12s_ZtPCA,2424
17
17
  ripperdoc/cli/commands/memory_cmd.py,sha256=gDvRr_-U1gMrOdC3OvujYLL5_CUgyZpwaJdytRP5CBM,6549
18
18
  ripperdoc/cli/commands/models_cmd.py,sha256=p6IeV_K9BjOahmtqmI2Gu7xsqRagVsIPYy7FEeuKQWQ,16135
19
19
  ripperdoc/cli/commands/permissions_cmd.py,sha256=3ERLLYTLUFW7jV7uo29i6SmLxrizpK4LXayLrPZbB1U,10827
20
- ripperdoc/cli/commands/resume_cmd.py,sha256=to-904VX5TVT60-YF92WTUKF-IBRf30UN4dZnLvHKME,2997
20
+ ripperdoc/cli/commands/resume_cmd.py,sha256=Z1jptNhub2WepKL-XPmd8KfDEvU90qjdtpVUajuAZgQ,4122
21
21
  ripperdoc/cli/commands/status_cmd.py,sha256=yM_c_GgoAL7CMH_ucGSwUhlbHggxYuvCEb4AXtpN-8s,5534
22
22
  ripperdoc/cli/commands/tasks_cmd.py,sha256=QrRF9MKg6LIH9BQz5E39KKdrwMiI3HTvI-c14aM7BU0,8815
23
23
  ripperdoc/cli/commands/todos_cmd.py,sha256=7Q0B1NVqGtB3R29ndbn4m0VQQm-YQ7d4Wlk7vJ7dLQI,1848
24
24
  ripperdoc/cli/commands/tools_cmd.py,sha256=3cMi0vN4mAUhpKqJtRgNvZfcKzRPaMs_pkYYXlyvSSU,384
25
25
  ripperdoc/cli/ui/__init__.py,sha256=TxSzTYdITlrYmYVfins_w_jzPqqWRpqky5u1ikwvmtM,43
26
26
  ripperdoc/cli/ui/context_display.py,sha256=3ezdtHVwltkPQ5etYwfqUh-fjnpPu8B3P81UzrdHxZs,10020
27
- ripperdoc/cli/ui/helpers.py,sha256=TJCipP0neh-96ETQfGhusCJ4aWt5gLw1HZbI-3bWDpw,739
28
- ripperdoc/cli/ui/rich_ui.py,sha256=BXQAT1TAWd4p4uGiaNtYaV_qsrcbsoYIM1UcDd2KEvY,61380
27
+ ripperdoc/cli/ui/file_mention_completer.py,sha256=rFVNgTUulZYUZpBq9rnK7bOPp8_NnFokSm2zS3AmCTY,8951
28
+ ripperdoc/cli/ui/helpers.py,sha256=DmgMMouyQdesjQ5RsErwsRCKVdWiDJnpqJjv90a3neE,2545
29
+ ripperdoc/cli/ui/interrupt_handler.py,sha256=bHYBbReu8uzVkHj1kEnL09ILt6b1hdc4iK76ZVT4t84,5922
30
+ ripperdoc/cli/ui/message_display.py,sha256=vMOCX8KUPP5P1CSHyZgojj69AyIZAk-Czlcgx-gOSvc,10367
31
+ ripperdoc/cli/ui/panels.py,sha256=204DvcXwye75dkSiwpE0lGb7u0SnRLLXd0jY9kT84NU,1753
32
+ ripperdoc/cli/ui/rich_ui.py,sha256=yk0ZS7xGzw_NGDOEMvT99XSpHdwVWhprupkaEBxv7Q4,43941
29
33
  ripperdoc/cli/ui/spinner.py,sha256=XsPRwJ-70InLX9Qw50CEgSHn5oKA5PFIue8Un4edhUk,1449
30
34
  ripperdoc/cli/ui/thinking_spinner.py,sha256=9Et5EqPChfkmkiOO8w1OPs8t-sHaisgjn9A__kEYLyg,2824
31
- ripperdoc/cli/ui/tool_renderers.py,sha256=cG0aFa8alIYJryG9tV6a7arslMN-iN8bAF5Q2XNL5Dc,11195
35
+ ripperdoc/cli/ui/tool_renderers.py,sha256=gVuZM083Nys9KWYAFTdmr1vpJm7ardqNhyUZx7KkL6s,11170
32
36
  ripperdoc/core/__init__.py,sha256=UemJCA-Y8df1466AX-YbRFj071zKajmqO1mi40YVW2g,40
33
- ripperdoc/core/agents.py,sha256=2aOvGEdj-P7MVMPtcFT4mAFIWeaFLSd6aVTqXtNjew0,19930
37
+ ripperdoc/core/agents.py,sha256=3glBiVM8e9NruCQGYl4inQ6Lt7jWb7C4S44YQXRWWl8,19930
34
38
  ripperdoc/core/commands.py,sha256=NXCkljYbAP4dDoRy-_3semFNWxG4YAk9q82u8FTKH60,835
35
39
  ripperdoc/core/config.py,sha256=fuzXTSSpPFIkzgZJW-tOf18cNeemrot64ihO4cdM79g,20979
36
40
  ripperdoc/core/default_tools.py,sha256=fHmqIlPIE9qGwmgeYYw-QepKRoQLMhclnCv6Qahews0,3090
37
41
  ripperdoc/core/permissions.py,sha256=_WLWE7Kq-Z5j3zEDAPr8JqdT0fz2oFqNs18NL0qoeWQ,9768
38
42
  ripperdoc/core/query.py,sha256=wpeQEJGTNwqp5Vvu9KoF-y1sZ-TOx_-xGaNwcDJYhfc,38729
39
- ripperdoc/core/query_utils.py,sha256=PA2rmHBwT5dczF8YYfbrL3QWrfNxQOhCA57G1Z-XBXE,24776
43
+ ripperdoc/core/query_utils.py,sha256=-lBRL5oAV0p6p6LukpFfBZKEcRdztbzCNOt51pEsKBM,24776
40
44
  ripperdoc/core/skills.py,sha256=XkMt3WPT2_0xfx2qQhEnBbwJ0121aRFmuXLckw3MtVU,10251
41
45
  ripperdoc/core/system_prompt.py,sha256=smhuRfzbvhfzNsQ3D89Mucll16u_40VWpOzKS0kJPFQ,26724
42
- ripperdoc/core/tool.py,sha256=Kmc3D--fpaJuK2bSsNwRpoMoQEI3PDgKSKwyLYPOkyc,7818
46
+ ripperdoc/core/tool.py,sha256=Hnnt7FYBGlD6lyAr9XhDgp_LfcP7o3yapzd7t6FPlBE,7813
43
47
  ripperdoc/core/providers/__init__.py,sha256=yevsHF0AUI4b6Wiq_401NXewJ3dqe8LUUtQm0TLPPNQ,1911
44
48
  ripperdoc/core/providers/anthropic.py,sha256=XNv_LhQTBrsFN5dB5rdYrNikBQ6Qq4sEu_PxukJXuks,10257
45
49
  ripperdoc/core/providers/base.py,sha256=HNOa3_XWszu6DbI8BYixxV0hnZb9qZ_FU4uinFVRHjU,9271
@@ -51,19 +55,19 @@ ripperdoc/tools/__init__.py,sha256=RBFz0DDnztDXMqv_zRxFHVY-ez2HYcncx8zh_y-BX6w,4
51
55
  ripperdoc/tools/ask_user_question_tool.py,sha256=QgzmIDVR-wdlLf9fSiVPbRm_8tSaIlGJhuuRYOCGiUU,15446
52
56
  ripperdoc/tools/background_shell.py,sha256=HangGLwN4iy-udo02zUZF3QRPIqOa7sVDesbv2wL9Js,12854
53
57
  ripperdoc/tools/bash_output_tool.py,sha256=ljIOzTOnkbQfe3jExlhpUlMiLT6HpeD-1QI-D1CwHh8,3379
54
- ripperdoc/tools/bash_tool.py,sha256=qxIZyxygZVl1an0W9Tx_8lEfeKQ_6vg0V33yOVA458U,42669
58
+ ripperdoc/tools/bash_tool.py,sha256=eiPgyvY7m52xT6JxbFQ_4n3Qk6RZ0yQK5ZMgQMc8QpQ,42669
55
59
  ripperdoc/tools/dynamic_mcp_tool.py,sha256=GERh7qT1mPVivFUIhlFxPNRUwOGNw5CmCnymEwQ-7vk,15662
56
60
  ripperdoc/tools/enter_plan_mode_tool.py,sha256=FYjm_TmBL55pY4GdP7t0ISlqg-Qe3DwpIt-2weL1S4s,7976
57
61
  ripperdoc/tools/exit_plan_mode_tool.py,sha256=3smkwGLTITem5fgA8catSSRay_a1OGQrjs8JF1zDdUQ,5756
58
- ripperdoc/tools/file_edit_tool.py,sha256=RcaEowUeyHknQ9R8w7C7_BrxROa8--zO-iUc2KA8RG0,13768
59
- ripperdoc/tools/file_read_tool.py,sha256=smkqGtqE1ss6NYV6kjemcbBUS4vYVq4CKq32tGVyTDA,7413
62
+ ripperdoc/tools/file_edit_tool.py,sha256=pF4ZCBFq3vy2DukxGZjBGoyVJIRH-7UY4A-TpFegzdA,13768
63
+ ripperdoc/tools/file_read_tool.py,sha256=eGr1C5xPe22TXrJOU7zwXGDjBqAajtYKn6aiIG-KRBs,7413
60
64
  ripperdoc/tools/file_write_tool.py,sha256=SUsFvLVvCwegxEDhL8xpppNRlSl_Hcc1xwNR35FbMqU,7044
61
65
  ripperdoc/tools/glob_tool.py,sha256=oy1S-MrQl57X_wpNXcqXyE4oHI3kmpOQoTYavx3mzEg,5932
62
66
  ripperdoc/tools/grep_tool.py,sha256=n_YNKg8w63JgVJVpg7Qakj75JrymeKByNoapl8IS05U,14125
63
67
  ripperdoc/tools/kill_bash_tool.py,sha256=36F8w2Rm1IVQitwOAwS-D8NTnyQdWfKWIam44qlXErk,4625
64
68
  ripperdoc/tools/ls_tool.py,sha256=x0nw7F5cWjKK7Vb66nqdg9djgupPpCTwPAqd5rZlMUs,15367
65
69
  ripperdoc/tools/mcp_tools.py,sha256=P0wbk_WRUD_MVkLDpMjttjQ13nje8Zc-mR3ud94QqZ0,23018
66
- ripperdoc/tools/multi_edit_tool.py,sha256=da9WUvQRp8ZOm7G6OUUJhKIL4h0unjHSjg-oNco_NuE,17579
70
+ ripperdoc/tools/multi_edit_tool.py,sha256=ndCb-OlHQRqlrGElZ_q5QOTibZqW9uDp6e4ctOPNLbU,17579
67
71
  ripperdoc/tools/notebook_edit_tool.py,sha256=FOgx0RtZnD1zZCUv5vsXS531ep26ABXaNsRhh0ZU4Uc,14393
68
72
  ripperdoc/tools/skill_tool.py,sha256=vquTDL8ZihGvgP6U6EswOm5IjQYFHwxIiLzlzCEWrVw,7701
69
73
  ripperdoc/tools/task_tool.py,sha256=EcUAaWY3aTTZawE84wvk2LyHSNU7nXAbpeDoOjU4_K0,18325
@@ -74,6 +78,7 @@ ripperdoc/utils/bash_constants.py,sha256=KNn8bzB6nVU5jid9jvjiH4FAu8pP3DZONJ-OknJ
74
78
  ripperdoc/utils/bash_output_utils.py,sha256=3Cf5wKJzRbUesmCNy5HtXIBtv0Z2BxklTfFHJ9q1T3w,1210
75
79
  ripperdoc/utils/coerce.py,sha256=KOPb4KR4p32nwHWG_6GsGHeVZunJyYc2YhC5DLmEZO8,1015
76
80
  ripperdoc/utils/context_length_errors.py,sha256=oyDVr_ME_6j97TLwVZ8bDMb6ISGQx6wEHrY7ckc0GuA,7714
81
+ ripperdoc/utils/conversation_compaction.py,sha256=-yRGL2oVweYPNUZljMRH0xMeMeVN_R_vJ4G_wcEcpyg,18338
77
82
  ripperdoc/utils/exit_code_handlers.py,sha256=QtO1iDxVAb8Xp03D6_QixPoJC-RQlcp3ssIo_rm4two,7973
78
83
  ripperdoc/utils/file_watch.py,sha256=CoUIcLuS-VcfxotuxFkel5KpNluMmLGJKDzx26MG3yY,4039
79
84
  ripperdoc/utils/git_utils.py,sha256=Hq-Zx-KPyX4lp_i8ozhic15LyYdX_IfCRm-EyoFu59A,9047
@@ -81,15 +86,16 @@ ripperdoc/utils/json_utils.py,sha256=e12eMpWlLDniHZVg7zdOkXw5wBZhnjhVtDm8tpBEOjk
81
86
  ripperdoc/utils/log.py,sha256=mieFMPxiv-M87bB-dgiY8D5WMxQbjVKJdsrW8QvCui8,6138
82
87
  ripperdoc/utils/mcp.py,sha256=xszG3kDrlctVVZvXOHr7wgndthpu-AwMySSwpnaHFGc,19445
83
88
  ripperdoc/utils/memory.py,sha256=KNB8Eoobl0vgIEh6phXKtGmDUct7_DNQH-F6Il4KRDQ,8009
84
- ripperdoc/utils/message_compaction.py,sha256=9OX7eDYaUEbolGzqWk_4Uz-S1wrxahI2b6EfrOcS98Y,25063
85
- ripperdoc/utils/messages.py,sha256=b34bl7pvX3RSPKO0EbDQwve_ZEy88VkiSww5s3kB-Wk,20265
89
+ ripperdoc/utils/message_compaction.py,sha256=ydTtMqo09ds1qTneJrHaFroZ4UtmzzXlXHErPm7ZY5E,22370
90
+ ripperdoc/utils/message_formatting.py,sha256=M-2zEkjNEiLyda9x4S0-xyIBSsVOQS3eTzbIE3K7G_Q,7492
91
+ ripperdoc/utils/messages.py,sha256=OrUK758mmUx2_u_hE5RFLJMp8VeDy-crLGVADwgFO3c,21665
86
92
  ripperdoc/utils/output_utils.py,sha256=R3wqFh9Dko_GK00Exx7XI0DnnldRWMsxZypYX5y6SJo,7448
87
93
  ripperdoc/utils/path_ignore.py,sha256=2THb4h__cnFz_mZoR6gchDFiIirxSvsbcEKONJhiAQQ,17607
88
94
  ripperdoc/utils/path_utils.py,sha256=C45Q3OeXnj-0FVEtvf_tdG5922XB6HthUzlUCvfc17Y,1626
89
95
  ripperdoc/utils/prompt.py,sha256=zICNEsA_OtKx8t3zo9tHLXXu6G5K8rPO3jFLKz4j5tg,560
90
96
  ripperdoc/utils/safe_get_cwd.py,sha256=IvG8dIJd2tC5_glUsfeWXkpcF1EHzdkjFtuUGJd669w,815
91
97
  ripperdoc/utils/sandbox_utils.py,sha256=G91P8dw2VFcCiCpjXZ4LvzbAPiO8REqMhw39eI5Z4dU,1123
92
- ripperdoc/utils/session_history.py,sha256=M7TzmXibbWwh1xjHi_v3RfogaN6ZlKvfBYGRrsJp2hE,8978
98
+ ripperdoc/utils/session_history.py,sha256=SQ3HusavKtbVyx6V-KPCXbQtTm_rcVuL-qjwv_iNS7Y,9568
93
99
  ripperdoc/utils/session_usage.py,sha256=p8_s46zDTzV1qzP4HR4PuZmLeJvSfq9mG_Y5rCnRYyA,3213
94
100
  ripperdoc/utils/shell_token_utils.py,sha256=SduoSU-RERJdM_7gBn0urr5UXtl4XOpPgydBd2fwzWg,2500
95
101
  ripperdoc/utils/shell_utils.py,sha256=t-neFPy_VhEmWZ79J7hh1ULBEdX116Rb9_pnXvir1Jw,5235
@@ -99,9 +105,9 @@ ripperdoc/utils/permissions/__init__.py,sha256=33FfOaDLepxJSkp0RLvTdVu7qBXuEcnOo
99
105
  ripperdoc/utils/permissions/path_validation_utils.py,sha256=FWbb21Hwgb9gUPu_WtA14w99UUojVQLVz5HByormoUs,5760
100
106
  ripperdoc/utils/permissions/shell_command_validation.py,sha256=94ylqoDUiTF4v4wEiVS35jFJakyaSxdIFqYKumPTrGk,21205
101
107
  ripperdoc/utils/permissions/tool_permission_utils.py,sha256=6Fdu9-dMKhLsUExjEjoS0EUeRpEVN5UkqyseIC05YmM,9207
102
- ripperdoc-0.2.6.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
103
- ripperdoc-0.2.6.dist-info/METADATA,sha256=_G-UKolwTzfnVWiAc5Wf_5Zw7_Fss1XM7w71teW1z9o,6218
104
- ripperdoc-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
105
- ripperdoc-0.2.6.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
106
- ripperdoc-0.2.6.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
107
- ripperdoc-0.2.6.dist-info/RECORD,,
108
+ ripperdoc-0.2.7.dist-info/licenses/LICENSE,sha256=bRv9UhBor6GhnQDj12RciDcRfu0R7sB-lqCy1sWF75c,9242
109
+ ripperdoc-0.2.7.dist-info/METADATA,sha256=WRYXCFZZWO1HLnvzwoqeD1KdlhSZ0gNPE5rnkHDsaNc,6218
110
+ ripperdoc-0.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
111
+ ripperdoc-0.2.7.dist-info/entry_points.txt,sha256=79aohFxFPJmrQ3-Mhain04vb3EWpuc0EyzvDDUnwAu4,81
112
+ ripperdoc-0.2.7.dist-info/top_level.txt,sha256=u8LbdTr1a-laHgCO0Utl_R3QGFUhLxWelCDnP2ZgpCU,10
113
+ ripperdoc-0.2.7.dist-info/RECORD,,