zrb 1.16.3__py3-none-any.whl → 1.16.4__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.
@@ -19,6 +19,12 @@ from zrb.builtin.llm.tool.file import (
19
19
  write_many_files,
20
20
  write_to_file,
21
21
  )
22
+ from zrb.builtin.llm.tool.note import (
23
+ read_contextual_note,
24
+ read_long_term_note,
25
+ write_contextual_note,
26
+ write_long_term_note,
27
+ )
22
28
  from zrb.builtin.llm.tool.web import (
23
29
  create_search_internet_tool,
24
30
  open_web_page,
@@ -57,7 +63,12 @@ def _get_toolset(ctx: AnyContext) -> list["AbstractToolset[None] | str"]:
57
63
 
58
64
 
59
65
  def _get_tool(ctx: AnyContext) -> list["ToolOrCallable"]:
60
- tools = []
66
+ tools = [
67
+ read_long_term_note,
68
+ write_long_term_note,
69
+ read_contextual_note,
70
+ write_contextual_note,
71
+ ]
61
72
  if CFG.LLM_ALLOW_ANALYZE_REPO:
62
73
  tools.append(analyze_repo)
63
74
  if CFG.LLM_ALLOW_ANALYZE_FILE:
@@ -97,8 +108,8 @@ def _get_default_yolo_mode(ctx: AnyContext) -> str:
97
108
  def _render_yolo_mode_input(ctx: AnyContext) -> list[str] | bool | None:
98
109
  if ctx.input.yolo.strip() == "":
99
110
  return None
100
- elements = ctx.input.yolo.split(",")
101
- if len(elements) == 0:
111
+ elements = [element.strip() for element in ctx.input.yolo.split(",")]
112
+ if len(elements) == 1:
102
113
  try:
103
114
  return to_boolean(elements[0])
104
115
  except Exception:
@@ -104,8 +104,10 @@ def list_files(
104
104
 
105
105
  This is a fundamental tool for exploring the file system. Use it to
106
106
  discover the structure of a directory, find specific files, or get a
107
-
108
107
  general overview of the project layout before performing other operations.
108
+ **NOTE:**
109
+ User might provide inaccurate file path, so if you failed to read a file,
110
+ it is a good idea to list the files to get the accurate file path.
109
111
 
110
112
  Args:
111
113
  path (str, optional): The directory path to list. Defaults to the
@@ -228,9 +230,16 @@ def read_from_file(
228
230
  end line.
229
231
 
230
232
  This tool is essential for inspecting file contents. It can read both text
231
- and PDF files. The returned content is prefixed with line numbers, which is
232
- crucial for providing context when you need to modify the file later with
233
- the `apply_diff` tool.
233
+ and PDF files. The `content` field in the returned dictionary is **always**
234
+ prefixed with line numbers (`line_number | `) for each line. These prefixes
235
+ are added by the tool for internal processing and to provide contextual
236
+ information to the AI agent (e.g., for accurately applying diffs or for
237
+ referencing specific lines). They are **not** part of the actual file content.
238
+
239
+ When presenting the file content to a user, the AI agent **MUST** omit these
240
+ line number prefixes by default for better readability. They should only
241
+ be included if explicitly requested by the user or if the request inherently
242
+ requires line number context (e.g., 'show me lines 10-20').
234
243
 
235
244
  Use this tool to:
236
245
  - Examine the source code of a file.
@@ -246,18 +255,28 @@ def read_from_file(
246
255
 
247
256
  Returns:
248
257
  dict[str, Any]: A dictionary containing the file path, the requested content
249
- with line numbers, the start and end lines, and the total number
258
+ (always with line numbers), the start and end lines, and the total number
250
259
  of lines in the file.
251
260
  Example:
252
261
  ```
253
262
  {
254
263
  "path": "src/main.py",
255
- "content": "1| import os\n2|3| print(\"Hello, World!\")",
264
+ "content": "1| import os\n2|\n3| print(\"Hello, World!\")",
256
265
  "start_line": 1,
257
266
  "end_line": 3,
258
267
  "total_lines": 3
259
268
  }
260
269
  ```
270
+ If the file 'src/main.py' contains:
271
+ ```
272
+ import os
273
+
274
+ print("Hello, World!")
275
+ ```
276
+ The 'content' field in the returned dictionary will be:
277
+ `"1| import os\n2|\n3| print(\"Hello, World!\")"`
278
+ **IMPORTANT:** The "1|", "2|", "3|" prefixes are NOT part of the
279
+ original file. They are for internal agent use.
261
280
  Raises:
262
281
  FileNotFoundError: If the specified file does not exist.
263
282
  """
@@ -561,6 +580,17 @@ def read_many_files(paths: list[str]) -> dict[str, str]:
561
580
  project relate to each other, or when you need to inspect a set of related
562
581
  configuration or source code files.
563
582
 
583
+ The content for each file in the returned dictionary is **always** prefixed
584
+ with line numbers (`line_number | `) for each line. These prefixes are added
585
+ by the tool for internal processing and to provide contextual information to
586
+ the AI agent (e.g., for accurately applying diffs or for referencing specific
587
+ lines). They are **not** part of the actual file content.
588
+
589
+ When presenting the file content to a user, the AI agent **MUST** omit these
590
+ line number prefixes by default for better readability. They should only
591
+ be included if explicitly requested by the user or if the request inherently
592
+ requires line number context (e.g., 'show me lines 10-20').
593
+
564
594
  Args:
565
595
  paths (list[str]): A list of paths to the files you want to read. It is
566
596
  crucial to provide accurate paths. Use the `list_files` tool first
@@ -568,9 +598,25 @@ def read_many_files(paths: list[str]) -> dict[str, str]:
568
598
 
569
599
  Returns:
570
600
  dict[str, str]: a dictionary where keys are the file paths and values
571
- are their corresponding contents, prefixed with line numbers.
601
+ are their corresponding contents (always with line numbers).
572
602
  If a file cannot be read, its value will be an error message.
573
- Example: {"src/api.py": "1| import ...", "config.yaml": "1| key: value"}
603
+ Example:
604
+ ```
605
+ {
606
+ "src/api.py": "1| import os\n2|\n3| print("Hello, World!")",
607
+ "config.yaml": "1| key: value"
608
+ }
609
+ ```
610
+ If the file 'src/api.py' contains:
611
+ ```
612
+ import os
613
+
614
+ print("Hello, World!")
615
+ ```
616
+ The content for 'src/api.py' in the returned dictionary will be:
617
+ `"1| import os\n2|\n3| print("Hello, World!")"`
618
+ **IMPORTANT:** The "1|", "2|", "3|" prefixes are NOT part of the
619
+ original file. They are for internal agent use.
574
620
  """
575
621
  results = {}
576
622
  for path in paths:
@@ -0,0 +1,72 @@
1
+ import os
2
+
3
+ from zrb.config.llm_context.config import llm_context_config
4
+
5
+
6
+ def read_long_term_note() -> str:
7
+ """
8
+ Reads the global long-term note.
9
+ This note is shared across all projects and conversations.
10
+
11
+ Returns:
12
+ The content of the long-term note.
13
+ """
14
+ contexts = llm_context_config.get_notes()
15
+ return contexts.get("/", "")
16
+
17
+
18
+ def write_long_term_note(content: str) -> str:
19
+ """
20
+ Writes content to the global long-term note.
21
+ The content of this note is intended to be read by the AI Agent in the future.
22
+ It should contain global information/preference.
23
+
24
+ Args:
25
+ content: The content to write to the long-term note (overwriting the existing one).
26
+
27
+ Returns:
28
+ A confirmation message.
29
+ """
30
+ llm_context_config.write_note(content, "/")
31
+ return "Long term note saved"
32
+
33
+
34
+ def read_contextual_note(path: str | None = None) -> str:
35
+ """
36
+ Reads a contextual note for a specific path.
37
+ If no path is provided, it defaults to the current working directory.
38
+
39
+ Args:
40
+ path: The file or directory path to read the note for.
41
+ Defaults to the current working directory.
42
+
43
+ Returns:
44
+ The content of the contextual note.
45
+ """
46
+ if path is None:
47
+ path = os.getcwd()
48
+ abs_path = os.path.abspath(path)
49
+ contexts = llm_context_config.get_notes(cwd=abs_path)
50
+ return contexts.get(abs_path, "")
51
+
52
+
53
+ def write_contextual_note(content: str, path: str | None = None) -> str:
54
+ """
55
+ Writes a contextual note for a specific path.
56
+ The content of this note is intended to be read by the AI Agent in the future.
57
+ The content should contain contextual information.
58
+
59
+ If no path is provided, it defaults to the current working directory.
60
+
61
+ Args:
62
+ content: The new content of the note (overwriting the existing one).
63
+ path: The file or directory path to associate the note with.
64
+ Defaults to the current working directory.
65
+
66
+ Returns:
67
+ A confirmation message indicating where the note was saved.
68
+ """
69
+ if path is None:
70
+ path = os.getcwd()
71
+ llm_context_config.write_note(content, path)
72
+ return f"Contextual note saved to {path}"
@@ -1,7 +1,7 @@
1
1
  You are an expert interactive AI agent. You MUST follow this workflow for this interactive session. Respond in GitHub-flavored Markdown.
2
2
 
3
3
  # Core Principles
4
- - **Be Tool-Centric:** Do not describe what you are about to do. When a decision is made, call the tool directly. Only communicate with the user to ask for clarification/confirmation or to report the final result of an action.
4
+ - **Be Tool-Centric:** Describe what you are about to do. When a decision is made, call the tool directly. Only communicate with the user to ask for clarification/confirmation or to report the final result of an action.
5
5
  - **Efficiency:** Use your tools to get the job done with the minimum number of steps. Combine commands where possible.
6
6
  - **One Tool at a Time** Only call one tool at a time, wait for the result first before calling the next tool.
7
7
  - **Adhere to Conventions:** When modifying existing files or data, analyze the existing content to match its style and format.
@@ -31,4 +31,4 @@ You are an expert interactive AI agent. You MUST follow this workflow for this i
31
31
  * **CRITICAL:** Do not ask the user for help or report the failure until you have exhausted all reasonable attempts to fix it yourself.
32
32
 
33
33
  5. **Report Results:**
34
- * Provide a concise summary of the action taken and explicitly state how you verified it.
34
+ * Provide a concise summary of the action taken and explicitly state how you verified it.
@@ -6,11 +6,6 @@ Follow these instructions carefully:
6
6
  2. **Transcript:** Extract ONLY the last 4 (four) turns of the `Recent Conversation` to serve as the new transcript.
7
7
  * **Do not change or shorten the content of these turns, with one exception:** If a tool call returns a very long output, do not include the full output. Instead, briefly summarize the result of the tool call.
8
8
  * Ensure the timestamp format is `[YYYY-MM-DD HH:MM:SS UTC+Z] Role: Message/Tool name being called`.
9
- 3. **Notes:** Review the `Notes` and `Recent Conversation` to identify new or updated facts.
10
- * Update `long_term_note` with global facts about the user.
11
- * Update `contextual_note` with facts specific to the current project/directory.
12
- * **CRITICAL:** When updating `contextual_note`, you MUST determine the correct `context_path`. For example, if a fact was established when the working directory was `/app`, the `context_path` MUST be `/app`.
13
- * **CRITICAL:** Note content must be **brief**, raw, unformatted text, not a log of events. Only update notes if information has changed.
14
- 4. **Update Memory:** Call the `final_result` tool with all the information you consolidated.
9
+ 3. **Update Memory:** Call the `final_result` tool with all the information you consolidated.
15
10
 
16
11
  After you have called the tool, your task is complete.
@@ -54,7 +54,7 @@ class LLMContextConfig:
54
54
  f.write(new_file_content)
55
55
 
56
56
  def get_notes(self, cwd: str | None = None) -> dict[str, str]:
57
- """Gathers all relevant contexts for a given path."""
57
+ """Gathers all notes for a given path."""
58
58
  if cwd is None:
59
59
  cwd = os.getcwd()
60
60
  config_file = self._get_home_config_file()
zrb/group/any_group.py CHANGED
@@ -1,13 +1,9 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Generic, TypeVar
3
2
 
4
3
  from zrb.task.any_task import AnyTask
5
4
 
6
- GroupType = TypeVar("GroupType", bound="AnyGroup")
7
- TaskType = TypeVar("TaskType", bound=AnyTask)
8
5
 
9
-
10
- class AnyGroup(ABC, Generic[GroupType, TaskType]):
6
+ class AnyGroup(ABC):
11
7
  @property
12
8
  @abstractmethod
13
9
  def name(self) -> str:
@@ -39,11 +35,11 @@ class AnyGroup(ABC, Generic[GroupType, TaskType]):
39
35
  pass
40
36
 
41
37
  @abstractmethod
42
- def add_group(self, group: GroupType, alias: str | None) -> GroupType:
38
+ def add_group(self, group: "AnyGroup", alias: str | None) -> "AnyGroup":
43
39
  pass
44
40
 
45
41
  @abstractmethod
46
- def add_task(self, task: TaskType, alias: str | None = None) -> TaskType:
42
+ def add_task(self, task: "AnyTask", alias: str | None = None) -> "AnyTask":
47
43
  pass
48
44
 
49
45
  @abstractmethod
zrb/group/group.py CHANGED
@@ -1,10 +1,8 @@
1
- from typing import Generic, TypeVar
2
-
3
- from zrb.group.any_group import AnyGroup, GroupType, TaskType
1
+ from zrb.group.any_group import AnyGroup
4
2
  from zrb.task.any_task import AnyTask
5
3
 
6
4
 
7
- class Group(AnyGroup, Generic[GroupType, TaskType]):
5
+ class Group(AnyGroup):
8
6
  def __init__(
9
7
  self, name: str, description: str | None = None, banner: str | None = None
10
8
  ):
@@ -35,21 +33,21 @@ class Group(AnyGroup, Generic[GroupType, TaskType]):
35
33
  def subgroups(self) -> dict[str, AnyGroup]:
36
34
  names = list(self._groups.keys())
37
35
  names.sort()
38
- return {name: self._groups.get(name) for name in names}
36
+ return {name: self._groups[name] for name in names}
39
37
 
40
38
  @property
41
39
  def subtasks(self) -> dict[str, AnyTask]:
42
40
  alias = list(self._tasks.keys())
43
41
  alias.sort()
44
- return {name: self._tasks.get(name) for name in alias}
42
+ return {name: self._tasks[name] for name in alias}
45
43
 
46
- def add_group(self, group: GroupType, alias: str | None = None) -> GroupType:
47
- real_group: GroupType = Group(group) if isinstance(group, str) else group
44
+ def add_group(self, group: AnyGroup, alias: str | None = None) -> AnyGroup:
45
+ real_group = Group(group) if isinstance(group, str) else group
48
46
  alias = alias if alias is not None else real_group.name
49
47
  self._groups[alias] = real_group
50
48
  return real_group
51
49
 
52
- def add_task(self, task: TaskType, alias: str | None = None) -> TaskType:
50
+ def add_task(self, task: AnyTask, alias: str | None = None) -> AnyTask:
53
51
  alias = alias if alias is not None else task.name
54
52
  self._tasks[alias] = task
55
53
  return task
@@ -1,24 +1,64 @@
1
- When the user's request involves writing or modifying code, you MUST follow these domain-specific rules in addition to your core workflow.
1
+ When your primary task is to write, refactor, or fix code, you MUST follow this comprehensive software development workflow. This process is designed to ensure your contributions are safe, high-quality, and align with existing project conventions.
2
2
 
3
- ## 1. Critical Prohibitions
4
- - **NEVER Assume Dependencies:** You MUST NOT use a library, framework, or package unless you have first verified it is an existing project dependency (e.g., in `package.json`, `requirements.txt`, `pyproject.toml`, etc.).
5
- - **NEVER Commit Without Verification:** You MUST NOT use `git commit` until you have staged the changes and run the project's own verification steps (tests, linter, build).
3
+ # 1. Core Mandates & Prohibitions
6
4
 
7
- ## 2. Code Development Workflow
8
- This expands on your core "Execute and Verify" loop with steps specific to coding.
5
+ These are non-negotiable rules.
9
6
 
10
- 1. **CRITICAL: Gather Context First:** Before writing or modifying any code, you MUST gather context to ensure your changes are idiomatic and correct.
11
- * **Project Structure & Dependencies:** Check for `README.md`, `package.json`, etc., to understand the project's scripts (lint, test, build).
12
- * **Code Style & Conventions:** Look for `.eslintrc`, `.prettierrc`, `ruff.toml`, etc. Analyze surrounding source files to determine naming conventions, typing style, error handling, and architectural patterns.
13
- * **For new tests:** You MUST read the full source code of the module(s) you are testing.
14
- * **For new features:** You MUST look for existing tests and related modules to understand conventions.
7
+ - **Conventions First:** Rigorously adhere to existing project conventions. Your primary goal is to make it look like the existing codebase's author wrote the code.
8
+ - **Mimic Style & Structure:** Match the formatting, naming, structure, framework choices, typing, and architectural patterns of the code you're editing.
9
+ - **NEVER Assume Dependencies:** You MUST NOT use a library, framework, or package unless you have first verified it is an existing project dependency (e.g., by checking `package.json`, `requirements.txt`, `pyproject.toml`, etc.).
10
+ - **NEVER Commit Without Verification:** You MUST NOT use version control tools like `git commit` until you have staged the changes and successfully run the project's own verification steps (tests, linter, build).
15
11
 
16
- 2. **Implement Idiomatically:** Make the changes, strictly adhering to the patterns and conventions discovered in the context-gathering phase.
12
+ # 2. The Software Development Workflow
17
13
 
18
- 3. **CRITICAL: Design for Testability:** Your primary goal is to produce code that is easy to test automatically.
19
- * **Prefer `return` over `print`:** Core logic functions MUST `return` values. I/O operations like `print()` should be separated into different functions.
20
- * **Embrace Modularity:** Decompose complex tasks into smaller, single-responsibility functions or classes.
21
- * **Use Function Arguments:** Avoid relying on global state. Pass necessary data into functions as arguments.
14
+ Follow this sequence for all coding tasks.
22
15
 
23
- 4. **Verify with Project Tooling:** After implementation, run all relevant project-specific commands (e.g., `npm run test`, `pytest`, `npm run lint`). This is the verification step for code.
24
- * **CRITICAL:** If any verification step fails, you MUST enter your standard Debugging Loop. You are responsible for fixing the code until all project-specific verifications pass. Do not stop until the code is working correctly.
16
+ ### Step 1: Understand & Strategize
17
+
18
+ Before writing a single line of code, you MUST gather context.
19
+
20
+ - **Analyze the Request:** Break down the user's request into concrete requirements.
21
+ - **Explore the Codebase:** Use file search and read tools extensively to build a mental model.
22
+ - **Identify Relevant Files:** Locate the primary files that need modification.
23
+ - **Discover Conventions:** Look for configuration files (`.eslintrc`, `.prettierrc`, `ruff.toml`, etc.). Analyze surrounding source files to determine naming conventions, typing style, error handling patterns, and architecture.
24
+ - **Understand the Safety Net:** Find existing tests. Understanding how the project tests its code is critical for your implementation and verification steps.
25
+ - **Check Dependencies:** Verify that any libraries you plan to use are already part of the project.
26
+
27
+ ### Step 2: Plan
28
+
29
+ Based on your understanding, create and communicate a clear plan.
30
+
31
+ - **Formulate a Plan:** Build a coherent, step-by-step plan to implement the required changes.
32
+ - **Share the Plan:** Present a concise summary of your plan to the user before you begin writing code. This ensures alignment and allows for course correction.
33
+
34
+ ### Step 3: Implement Idiomatically
35
+
36
+ Execute your plan, adhering strictly to the patterns discovered in the "Understand" phase.
37
+
38
+ - **Write/Modify Code:** Make the necessary changes.
39
+ - **CRITICAL: Design for Testability:** Produce code that is easy to test automatically. This is a core principle of good software engineering.
40
+ - **Prefer `return` over `print`:** Core logic functions MUST `return` values. I/O operations like `print()` should be separated into different, testable functions.
41
+ - **Embrace Modularity:** Decompose complex tasks into smaller, single-responsibility functions or classes that can be tested independently.
42
+ - **Use Function Arguments:** Avoid relying on global state. Pass necessary data into functions as arguments to make them pure and predictable.
43
+
44
+ ### Step 4: Verify with Tests
45
+
46
+ This is the first of two critical verification phases. You are responsible for ensuring your changes work as expected.
47
+
48
+ - **Run Existing Tests:** Execute the project's full test suite (e.g., `npm run test`, `pytest`).
49
+ - **Add New Tests:** For new features or bug fixes, add corresponding unit or integration tests to prove your code works and prevent future regressions.
50
+ - **Debug and Fix:** If any tests fail, you MUST enter a debugging loop. Analyze the errors, fix the code, and re-run the tests until they all pass.
51
+
52
+ ### Step 5: Verify with Standards
53
+
54
+ This second verification phase ensures your code meets the project's quality standards.
55
+
56
+ - **Run Linters & Formatters:** Use the project's linting and formatting commands (e.g., `npm run lint`, `ruff check .`).
57
+ - **Run Build Processes:** If applicable, run the build command (e.g., `npm run build`, `tsc`) to check for compilation or type-checking errors.
58
+ - **Debug and Fix:** Just as with testing, if any of these checks fail, you MUST debug and fix the issues until all standards are met.
59
+
60
+ ### Step 6: Finalize
61
+
62
+ Only after all verification steps have passed is the task complete.
63
+
64
+ - **Await Instruction:** Do not remove any created files (like tests). Await the user's next instruction. If they are satisfied, you can suggest committing the changes.
@@ -124,23 +124,6 @@ async def summarize_history(
124
124
  json.dumps(truncate_str(conversation_history.history, 1000)),
125
125
  as_code=True,
126
126
  ),
127
- make_prompt_section(
128
- "Notes",
129
- "\n".join(
130
- [
131
- make_prompt_section(
132
- "Long Term",
133
- conversation_history.long_term_note,
134
- as_code=True,
135
- ),
136
- make_prompt_section(
137
- "Contextual",
138
- conversation_history.contextual_note,
139
- as_code=True,
140
- ),
141
- ]
142
- ),
143
- ),
144
127
  ]
145
128
  )
146
129
  summarize = create_history_summarization_tool(conversation_history)
@@ -7,32 +7,20 @@ from zrb.task.llm.conversation_history_model import ConversationHistory
7
7
 
8
8
  def create_history_summarization_tool(
9
9
  conversation_history: ConversationHistory,
10
- ) -> Callable[[str, str, str | None, str | None, str | None], str]:
10
+ ) -> Callable[[str, str], str]:
11
11
  def update_conversation_memory(
12
12
  past_conversation_summary: str,
13
13
  past_conversation_transcript: str,
14
- long_term_note: str | None = None,
15
- contextual_note: str | None = None,
16
- context_path: str | None = None,
17
14
  ) -> str:
18
15
  """
19
- Update the conversation memory including summary, transcript, and notes.
16
+ Update the conversation memory including summary and transcript.
20
17
  - past_conversation_summary: A concise narrative that integrates the
21
18
  previous summary with the recent conversation.
22
19
  - past_conversation_transcript: MUST be ONLY the last 4 (four) turns
23
20
  of the conversation.
24
- - long_term_note: Global facts about the user or their preferences.
25
- - contextual_note: Facts specific to the current project or directory.
26
- - context_path: The directory path for the contextual note.
27
21
  """
28
22
  conversation_history.past_conversation_summary = past_conversation_summary
29
23
  conversation_history.past_conversation_transcript = past_conversation_transcript
30
- if long_term_note is not None:
31
- llm_context_config.write_note(long_term_note, context_path="/")
32
- if contextual_note is not None:
33
- if context_path is None:
34
- context_path = conversation_history.project_path
35
- llm_context_config.write_note(contextual_note, context_path=context_path)
36
24
  return "Conversation memory updated"
37
25
 
38
26
  return update_conversation_memory
@@ -111,16 +111,24 @@ def _create_wrapper(
111
111
  # somehow provided it.
112
112
  kwargs[any_context_param_name] = ctx
113
113
  try:
114
+ has_ever_edited = False
114
115
  if not ctx.is_web_mode and ctx.is_tty:
115
116
  if (
116
117
  isinstance(yolo_mode, list) and func.__name__ not in yolo_mode
117
118
  ) or not yolo_mode:
118
- approval, reason = await _handle_user_response(
119
+ approval, reason, has_ever_edited = await _handle_user_response(
119
120
  ctx, func, args, kwargs
120
121
  )
121
122
  if not approval:
122
123
  raise ToolExecutionCancelled(f"User disapproving: {reason}")
123
- return await run_async(func(*args, **kwargs))
124
+ result = await run_async(func(*args, **kwargs))
125
+ if has_ever_edited:
126
+ return {
127
+ "tool_call_result": result,
128
+ "new_tool_parameters": kwargs,
129
+ "message": "User has intercepted tool call and updated the parameters",
130
+ }
131
+ return result
124
132
  except KeyboardInterrupt as e:
125
133
  raise e
126
134
  except Exception as e:
@@ -140,7 +148,8 @@ async def _handle_user_response(
140
148
  func: Callable,
141
149
  args: list[Any] | tuple[Any],
142
150
  kwargs: dict[str, Any],
143
- ) -> tuple[bool, str]:
151
+ ) -> tuple[bool, str, bool]:
152
+ has_ever_edited = False
144
153
  while True:
145
154
  func_call_str = _get_func_call_str(func, args, kwargs)
146
155
  complete_confirmation_message = "\n".join(
@@ -156,13 +165,15 @@ async def _handle_user_response(
156
165
  new_kwargs, is_edited = _get_edited_kwargs(ctx, user_response, kwargs)
157
166
  if is_edited:
158
167
  kwargs = new_kwargs
168
+ has_ever_edited = True
159
169
  continue
160
170
  approval_and_reason = _get_user_approval_and_reason(
161
171
  ctx, user_response, func_call_str
162
172
  )
163
173
  if approval_and_reason is None:
164
174
  continue
165
- return approval_and_reason
175
+ approval, reason = approval_and_reason
176
+ return approval, reason, has_ever_edited
166
177
 
167
178
 
168
179
  def _get_edited_kwargs(
zrb/task/llm_task.py CHANGED
@@ -205,8 +205,6 @@ class LLMTask(BaseTask):
205
205
  self.append_toolset(*toolset)
206
206
 
207
207
  def append_toolset(self, *toolset: "AbstractToolset[None] | str"):
208
- from pydantic_ai.mcp import load_mcp_servers
209
-
210
208
  for single_toolset in toolset:
211
209
  self._additional_toolsets.append(single_toolset)
212
210
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: zrb
3
- Version: 1.16.3
3
+ Version: 1.16.4
4
4
  Summary: Your Automation Powerhouse
5
5
  License: AGPL-3.0-or-later
6
6
  Keywords: Automation,Task Runner,Code Generator,Monorepo,Low Code
@@ -13,13 +13,14 @@ zrb/builtin/llm/chat_session.py,sha256=6q40xQdv56OtaTZCVSS16WDchn4l0sagZI2BGX_Jy
13
13
  zrb/builtin/llm/chat_trigger.py,sha256=xaJmzrvBGz6LFPOpYnG9bMeT1dY6XqZPXamtr9e72-w,2427
14
14
  zrb/builtin/llm/history.py,sha256=LDOrL0p7r_AHLa5L8Dp7bHNsOALugmJd7OguXRWGnm4,3087
15
15
  zrb/builtin/llm/input.py,sha256=Nw-26uTWp2QhUgKJcP_IMHmtk-b542CCSQ_vCOjhvhM,877
16
- zrb/builtin/llm/llm_ask.py,sha256=8V5YAShUPes5qnlxLaS9Ks4KOs7oCQFGrpfkj-6rLMU,7546
16
+ zrb/builtin/llm/llm_ask.py,sha256=Va2uSfzKUWrifn4yYdw-yndy38-s1Z_pjO6YLzIwrF0,7850
17
17
  zrb/builtin/llm/previous-session.js,sha256=xMKZvJoAbrwiyHS0OoPrWuaKxWYLoyR5sguePIoCjTY,816
18
18
  zrb/builtin/llm/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  zrb/builtin/llm/tool/api.py,sha256=p55Fs6QTmsM2u-vVcBJb9xYxGAkiHIKX2wYKhsGlWFE,2417
20
20
  zrb/builtin/llm/tool/cli.py,sha256=sm_maE1WBB051odh1xXr8QQOWln_ewAU_7OScKAneT4,1244
21
21
  zrb/builtin/llm/tool/code.py,sha256=-MKUpXX4jkWm4rCqrUmTTzsYhjfzKle9_XsNPtq8PNM,8952
22
- zrb/builtin/llm/tool/file.py,sha256=fDgt31CWwZtOYOk6lfBSBxX85NARLr-ZzXPvG1JP8C0,23589
22
+ zrb/builtin/llm/tool/file.py,sha256=9pwE4dme95EEuH_W-Cau3h-i2dAF8FZbXxRI27NhAzA,25902
23
+ zrb/builtin/llm/tool/note.py,sha256=DZChwYiKqxBvPsEQOMnk4HFOXYCPjtxCmK1nRQsbLvI,2203
23
24
  zrb/builtin/llm/tool/rag.py,sha256=aN8D8ZqzGXWCP_1F1LbN0QgfyzaK9CKrjfTPorDIYjw,9824
24
25
  zrb/builtin/llm/tool/sub_agent.py,sha256=nYluPfc8FlSobpP_4vnBIqkPARrDHq_SwKkmlh_ATUI,5067
25
26
  zrb/builtin/llm/tool/web.py,sha256=zDgxYRIQRj9A8QXb80-ZSPmGCvIOWy8bpJMGSAuTL8Y,7491
@@ -220,14 +221,14 @@ zrb/cmd/cmd_result.py,sha256=L8bQJzWCpcYexIxHBNsXj2pT3BtLmWex0iJSMkvimOA,597
220
221
  zrb/cmd/cmd_val.py,sha256=7Doowyg6BK3ISSGBLt-PmlhzaEkBjWWm51cED6fAUOQ,1014
221
222
  zrb/config/config.py,sha256=afsH-a7yTPT5hhvnmxGarAiU5yRUcUbua3Y_hRgltEg,14326
222
223
  zrb/config/default_prompt/file_extractor_system_prompt.md,sha256=dNBWy4O4mfCqGkqaRQHDo18hDCengU0IZ0vQMSDwbyY,3821
223
- zrb/config/default_prompt/interactive_system_prompt.md,sha256=EZmBOwFTzpTkIgQlY-Zfp3oUjvM4ojQjQFIIa-V6wHA,2710
224
+ zrb/config/default_prompt/interactive_system_prompt.md,sha256=5wE_E1WiMwbXY_jaRoQzDM9esnOxmlsD3BShGx8HyEc,2702
224
225
  zrb/config/default_prompt/persona.md,sha256=GfUJ4-Mlf_Bm1YTzxFNkPkdVbAi06ZDVYh-iIma3NOs,253
225
226
  zrb/config/default_prompt/repo_extractor_system_prompt.md,sha256=jkmpjKfWKKo3a5xzRi4CjFuRXhWwNmVGbR_Os-M8WZg,3822
226
227
  zrb/config/default_prompt/repo_summarizer_system_prompt.md,sha256=nEDVbhVwTUKJ9W3AoV_h-7hcsT9VjfjE62F3FeWA1a4,1993
227
- zrb/config/default_prompt/summarization_prompt.md,sha256=LKE0t4sioWWMmZgFf2fiYknMFZ7m-QxS5DSMnjGFPrU,1548
228
+ zrb/config/default_prompt/summarization_prompt.md,sha256=1dY5dbAUgTdJHystOVSFOT0kUDnrfbFfiwIFjXq0OYI,951
228
229
  zrb/config/default_prompt/system_prompt.md,sha256=SP49gcvIQB7mxWqfQAAQUwXwYemQkgWAXpRHiuXjCy0,2368
229
230
  zrb/config/llm_config.py,sha256=9I_msAibvTY-ADG6lXCBXQ0EshaA-GQzJym9EZKsetw,8901
230
- zrb/config/llm_context/config.py,sha256=yHMElDqOwTduTYCfTDZJ6bOlgcApc203-nZQzxae7N8,6845
231
+ zrb/config/llm_context/config.py,sha256=NXS1KNok-82VArstHmTVgrelPHSlKOWYJ6lytEyCQao,6833
231
232
  zrb/config/llm_context/config_parser.py,sha256=XrvkpbmzrrAuDhLCugriBHf2t9fSFuYxwzKehvTl9x4,1467
232
233
  zrb/config/llm_rate_limitter.py,sha256=_iQRv3d6kUPeRvmUYZX_iwCE7iDSEK1oKa4bQ9GROho,5261
233
234
  zrb/config/web_auth_config.py,sha256=_PXatQTYh2mX9H3HSYSQKp13zm1RlLyVIoeIr6KYMQ8,6279
@@ -247,8 +248,8 @@ zrb/env/env.py,sha256=zT-xj0l5G_lp_ginV_InuBr5qTqZMcGaMKE_p5FoqlU,1061
247
248
  zrb/env/env_file.py,sha256=dccsR_cjXp1Q_qz692YPaZSny3Xa049V5NAZ-WLDDEQ,746
248
249
  zrb/env/env_map.py,sha256=CKMmXUbuvf6h2rgFWj-WvW_xLZNgG3TtWdfrqHDe4qk,1253
249
250
  zrb/group/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
250
- zrb/group/any_group.py,sha256=BstkxtRQTGjMKdr1ErEHhB0UopXtYvwbCEOJnX5LjXA,1420
251
- zrb/group/group.py,sha256=JBiigohvtLdR1t3irXJzfUPeYqiNgqUWhlKaB79stpE,4409
251
+ zrb/group/any_group.py,sha256=2ViaIcGKkKavZSlTahjUtAKA06W7BV2GGfCr1tOWSyI,1260
252
+ zrb/group/group.py,sha256=jt6GMxe3_9j49s3TfEDqA-EwzXKhVaW_npQ1gmyHre8,4298
252
253
  zrb/input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
253
254
  zrb/input/any_input.py,sha256=3EQgg2Qk_2t2Ege_4pHV2G95tV3XAIThLLuFZ6uV6oM,1051
254
255
  zrb/input/base_input.py,sha256=HCTxBZwnK-gP-F7XQGDbyyZHvwYR0fwNICzqVUw5Cg8,4169
@@ -351,17 +352,17 @@ zrb/task/llm/agent.py,sha256=mAiDc5YhQ87dJX9Lfv_Q8bTyY2laZDCaAcpHs2yxvtc,11724
351
352
  zrb/task/llm/config.py,sha256=2usr_FjylQrrRjjPDOIBhb7pIwVQkicA0hYGeMy9qYg,4076
352
353
  zrb/task/llm/conversation_history.py,sha256=_ThBOCv4vs3V6B3P_s-Aad2sH0RqE46KzLqgwdwHMC0,6758
353
354
  zrb/task/llm/conversation_history_model.py,sha256=kk-7niTl29Rm2EUIhTHzPXgZ5tp4IThMnIB3dS-1OdU,3062
354
- zrb/task/llm/default_workflow/coding.md,sha256=2uythvPsnBpYfIhiIH1cCinQXX0i0yUqsL474Zpemw0,2484
355
+ zrb/task/llm/default_workflow/coding.md,sha256=u2JMCNHjmRYQUzzgTGb7Mp6K7g9Tv2tqGPnyf4SbCLs,4487
355
356
  zrb/task/llm/default_workflow/copywriting.md,sha256=xSO7GeDolwGxiuz6kXsK2GKGpwp8UgtG0yRqTmill_s,1999
356
357
  zrb/task/llm/default_workflow/researching.md,sha256=KD-aYHFHir6Ti-4FsBBtGwiI0seSVgleYbKJZi_POXA,2139
357
358
  zrb/task/llm/error.py,sha256=QR-nIohS6pBpC_16cWR-fw7Mevo1sNYAiXMBsh_CJDE,4157
358
- zrb/task/llm/history_summarization.py,sha256=blTKfaSpgaqvORWzGL3BKrRWAsfNdZB03oJTEOS-j5s,8098
359
- zrb/task/llm/history_summarization_tool.py,sha256=5KQUoP75udhtVRbGVxJgnQ36WtVoX--aMvAfGIMBJmw,1760
359
+ zrb/task/llm/history_summarization.py,sha256=Ntk0mCr4OKeFqjgyfI2UGifNLmAZi4UbSivphXfBwMc,7493
360
+ zrb/task/llm/history_summarization_tool.py,sha256=KjL2RPThg6zDAk9eATuXmQvyrzd1t0scCgo2Cn4-hpY,1022
360
361
  zrb/task/llm/print_node.py,sha256=Nnf4F6eDJR4PFcOqQ1jLWBTFnzNGl1Stux2DZ3SMhsY,8062
361
362
  zrb/task/llm/prompt.py,sha256=Mb3hz-KAp2bL9m6ZL0gcfLveRfBDRZvDVN_HHPGuEPM,12028
362
- zrb/task/llm/tool_wrapper.py,sha256=9HlfsOi73eGVC43iGZ1BasohT9clFD4IX9xSxu4nGU4,10520
363
+ zrb/task/llm/tool_wrapper.py,sha256=Bvfds8WE0ewuXwJepvHJ5w547ImU5KaLaJf46Sxlth4,10997
363
364
  zrb/task/llm/typing.py,sha256=c8VAuPBw_4A3DxfYdydkgedaP-LU61W9_wj3m3CAX1E,58
364
- zrb/task/llm_task.py,sha256=uzHNAPziTeIiBr08-tg5_C3MOqJMhfbKmvkWEeS0GZM,15022
365
+ zrb/task/llm_task.py,sha256=iG2urOrhlvKerzagej7CheIEs2XNXzm-ByVqR9oFfeQ,14968
365
366
  zrb/task/make_task.py,sha256=PD3b_aYazthS8LHeJsLAhwKDEgdurQZpymJDKeN60u0,2265
366
367
  zrb/task/rsync_task.py,sha256=WfqNSaicJgYWpunNU34eYxXDqHDHOftuDHyWJKjqwg0,6365
367
368
  zrb/task/scaffolder.py,sha256=rME18w1HJUHXgi9eTYXx_T2G4JdqDYzBoNOkdOOo5-o,6806
@@ -411,7 +412,7 @@ zrb/util/todo_model.py,sha256=hhzAX-uFl5rsg7iVX1ULlJOfBtblwQ_ieNUxBWfc-Os,1670
411
412
  zrb/util/truncate.py,sha256=eSzmjBpc1Qod3lM3M73snNbDOcARHukW_tq36dWdPvc,921
412
413
  zrb/xcom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
413
414
  zrb/xcom/xcom.py,sha256=o79rxR9wphnShrcIushA0Qt71d_p3ZTxjNf7x9hJB78,1571
414
- zrb-1.16.3.dist-info/METADATA,sha256=XONEHMCkv5j0YPf35oaP5MYliI1CYXoPXUv-8FNKFI4,9935
415
- zrb-1.16.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
416
- zrb-1.16.3.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
417
- zrb-1.16.3.dist-info/RECORD,,
415
+ zrb-1.16.4.dist-info/METADATA,sha256=lf7QW0PTsKVMmB9WYWYY8LTRnIrp5Zg_Vqq2YSY4ZhE,9935
416
+ zrb-1.16.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
417
+ zrb-1.16.4.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
418
+ zrb-1.16.4.dist-info/RECORD,,
File without changes