zrb 1.21.31__py3-none-any.whl → 1.21.43__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.

Potentially problematic release.


This version of zrb might be problematic. Click here for more details.

Files changed (46) hide show
  1. zrb/builtin/llm/chat_completion.py +94 -84
  2. zrb/builtin/llm/chat_session.py +90 -30
  3. zrb/builtin/llm/chat_session_cmd.py +115 -22
  4. zrb/builtin/llm/chat_trigger.py +92 -5
  5. zrb/builtin/llm/history.py +14 -7
  6. zrb/builtin/llm/llm_ask.py +16 -7
  7. zrb/builtin/llm/tool/cli.py +34 -15
  8. zrb/builtin/llm/tool/file.py +14 -2
  9. zrb/builtin/llm/tool/search/brave.py +8 -2
  10. zrb/builtin/llm/tool/search/searxng.py +8 -2
  11. zrb/builtin/llm/tool/search/serpapi.py +8 -2
  12. zrb/builtin/llm/tool/sub_agent.py +4 -1
  13. zrb/builtin/llm/tool/web.py +5 -0
  14. zrb/builtin/llm/xcom_names.py +3 -0
  15. zrb/callback/callback.py +8 -1
  16. zrb/cmd/cmd_result.py +2 -1
  17. zrb/config/config.py +6 -2
  18. zrb/config/default_prompt/interactive_system_prompt.md +15 -12
  19. zrb/config/default_prompt/system_prompt.md +16 -18
  20. zrb/config/llm_rate_limitter.py +36 -13
  21. zrb/context/context.py +11 -0
  22. zrb/input/option_input.py +30 -2
  23. zrb/task/base/context.py +25 -13
  24. zrb/task/base/execution.py +52 -47
  25. zrb/task/base/lifecycle.py +1 -1
  26. zrb/task/base_task.py +31 -45
  27. zrb/task/base_trigger.py +0 -1
  28. zrb/task/cmd_task.py +3 -0
  29. zrb/task/llm/agent.py +39 -31
  30. zrb/task/llm/agent_runner.py +65 -3
  31. zrb/task/llm/default_workflow/researching/workflow.md +2 -0
  32. zrb/task/llm/history_list.py +13 -0
  33. zrb/task/llm/history_processor.py +4 -13
  34. zrb/task/llm/print_node.py +79 -25
  35. zrb/task/llm/prompt.py +70 -40
  36. zrb/task/llm/tool_wrapper.py +4 -1
  37. zrb/task/llm/workflow.py +54 -15
  38. zrb/task/llm_task.py +87 -33
  39. zrb/task/rsync_task.py +2 -0
  40. zrb/util/cmd/command.py +33 -10
  41. zrb/util/match.py +71 -0
  42. zrb/util/run.py +3 -3
  43. {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/METADATA +1 -1
  44. {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/RECORD +46 -43
  45. {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/WHEEL +0 -0
  46. {zrb-1.21.31.dist-info → zrb-1.21.43.dist-info}/entry_points.txt +0 -0
@@ -11,16 +11,24 @@ from zrb.builtin.llm.chat_session_cmd import (
11
11
  CLEAR_SUB_CMD,
12
12
  HELP_CMD,
13
13
  HELP_CMD_DESC,
14
+ LOAD_CMD,
15
+ LOAD_CMD_DESC,
14
16
  MULTILINE_END_CMD,
15
17
  MULTILINE_END_CMD_DESC,
16
18
  MULTILINE_START_CMD,
17
19
  MULTILINE_START_CMD_DESC,
18
20
  QUIT_CMD,
19
21
  QUIT_CMD_DESC,
22
+ RESPONSE_CMD,
23
+ RESPONSE_CMD_DESC,
24
+ RESPONSE_SAVE_SUB_CMD_DESC,
20
25
  RUN_CLI_CMD,
21
26
  RUN_CLI_CMD_DESC,
22
27
  SAVE_CMD,
23
28
  SAVE_CMD_DESC,
29
+ SAVE_SUB_CMD,
30
+ SESSION_CMD,
31
+ SESSION_CMD_DESC,
24
32
  SET_SUB_CMD,
25
33
  WORKFLOW_ADD_SUB_CMD_DESC,
26
34
  WORKFLOW_CLEAR_SUB_CMD_DESC,
@@ -33,6 +41,8 @@ from zrb.builtin.llm.chat_session_cmd import (
33
41
  YOLO_SET_FALSE_CMD_DESC,
34
42
  YOLO_SET_TRUE_CMD_DESC,
35
43
  )
44
+ from zrb.config.config import CFG
45
+ from zrb.util.match import fuzzy_match
36
46
 
37
47
  if TYPE_CHECKING:
38
48
  from prompt_toolkit.completion import Completer
@@ -51,10 +61,39 @@ def get_chat_completer() -> "Completer":
51
61
  yield completion
52
62
  for completion in self._complete_slash_file_command(document):
53
63
  yield completion
64
+ for completion in self._complete_slash_session_load_command(document):
65
+ yield completion
54
66
  # Appendix
55
67
  for completion in self._complete_appendix(document):
56
68
  yield completion
57
69
 
70
+ def _complete_slash_session_load_command(self, document: Document):
71
+ text = document.text_before_cursor
72
+ prefixes = []
73
+ for cmd in LOAD_CMD:
74
+ prefixes.append(f"{cmd} ")
75
+
76
+ for prefix in prefixes:
77
+ if text.startswith(prefix):
78
+ pattern = text[len(prefix) :]
79
+ # Use fuzzy_path_search but in LLM_HISTORY_DIR/save-point
80
+ save_point_dir = os.path.join(CFG.LLM_HISTORY_DIR, "save-point")
81
+ if not os.path.isdir(save_point_dir):
82
+ return
83
+
84
+ potential_options = self._fuzzy_path_search(
85
+ pattern, root=save_point_dir, dirs=False, files=True
86
+ )
87
+ for option in potential_options:
88
+ if option.startswith(save_point_dir):
89
+ rel_option = os.path.relpath(option, save_point_dir)
90
+ else:
91
+ rel_option = option
92
+ yield Completion(
93
+ f"{prefix}{rel_option}",
94
+ start_position=-len(text),
95
+ )
96
+
58
97
  def _complete_slash_file_command(self, document: Document):
59
98
  text = document.text_before_cursor
60
99
  prefixes = []
@@ -64,7 +103,7 @@ def get_chat_completer() -> "Completer":
64
103
  for prefix in prefixes:
65
104
  if text.startswith(prefix):
66
105
  pattern = text[len(prefix) :]
67
- potential_options = self._fuzzy_path_search(pattern, dirs=False)
106
+ potential_options = self._fuzzy_path_search(pattern, dirs=True)
68
107
  for prefixed_option in [
69
108
  f"{prefix}{option}" for option in potential_options
70
109
  ]:
@@ -91,7 +130,7 @@ def get_chat_completer() -> "Completer":
91
130
  if not token.startswith(prefix):
92
131
  return
93
132
  pattern = token[len(prefix) :]
94
- potential_options = self._fuzzy_path_search(pattern, dirs=False)
133
+ potential_options = self._fuzzy_path_search(pattern, dirs=True)
95
134
  for prefixed_option in [
96
135
  f"{prefix}{option}" for option in potential_options
97
136
  ]:
@@ -117,8 +156,16 @@ def get_chat_completer() -> "Completer":
117
156
  cmd_options[f"{cmd} {subcmd}"] = WORKFLOW_CLEAR_SUB_CMD_DESC
118
157
  for subcmd in SET_SUB_CMD:
119
158
  cmd_options[f"{cmd} {subcmd}"] = WORKFLOW_SET_SUB_CMD_DESC
159
+ for cmd in SESSION_CMD:
160
+ cmd_options[cmd] = SESSION_CMD_DESC
120
161
  for cmd in SAVE_CMD:
121
162
  cmd_options[cmd] = SAVE_CMD_DESC
163
+ for cmd in LOAD_CMD:
164
+ cmd_options[cmd] = LOAD_CMD_DESC
165
+ for cmd in RESPONSE_CMD:
166
+ cmd_options[cmd] = RESPONSE_CMD_DESC
167
+ for subcmd in SAVE_SUB_CMD:
168
+ cmd_options[f"{cmd} {subcmd}"] = RESPONSE_SAVE_SUB_CMD_DESC
122
169
  for cmd in ATTACHMENT_CMD:
123
170
  cmd_options[cmd] = ATTACHMENT_CMD_DESC
124
171
  for subcmd in ADD_SUB_CMD:
@@ -156,47 +203,14 @@ def get_chat_completer() -> "Completer":
156
203
  - dirs/files booleans let you restrict results
157
204
  - returns list of relative paths (from root), sorted best-first
158
205
  """
159
- search_pattern = pattern
160
- if root is None:
161
- # Determine root and adjust pattern if necessary
162
- expanded_pattern = os.path.expanduser(pattern)
163
- if os.path.isabs(expanded_pattern) or pattern.startswith("~"):
164
- # For absolute paths, find the deepest existing directory
165
- if os.path.isdir(expanded_pattern):
166
- root = expanded_pattern
167
- search_pattern = ""
168
- else:
169
- root = os.path.dirname(expanded_pattern)
170
- while root and not os.path.isdir(root) and len(root) > 1:
171
- root = os.path.dirname(root)
172
- if not os.path.isdir(root):
173
- root = "." # Fallback
174
- search_pattern = pattern
175
- else:
176
- try:
177
- search_pattern = os.path.relpath(expanded_pattern, root)
178
- if search_pattern == ".":
179
- search_pattern = ""
180
- except ValueError:
181
- search_pattern = os.path.basename(pattern)
182
- else:
183
- root = "."
184
- search_pattern = pattern
185
- # Normalize pattern -> tokens split on path separators or whitespace
186
- search_pattern = search_pattern.strip()
187
- if search_pattern:
188
- raw_tokens = [t for t in search_pattern.split(os.path.sep) if t]
189
- else:
190
- raw_tokens = []
191
- # prepare tokens (case)
192
- if not case_sensitive:
193
- tokens = [t.lower() for t in raw_tokens]
194
- else:
195
- tokens = raw_tokens
206
+ root, search_pattern = self._get_root_and_search_pattern(pattern, root)
196
207
  # specific ignore list
197
208
  try:
198
- is_recursive = os.path.abspath(os.path.expanduser(root)).startswith(
199
- os.path.abspath(os.getcwd())
209
+ abs_root = os.path.abspath(os.path.expanduser(root))
210
+ abs_cwd = os.path.abspath(os.getcwd())
211
+ # Check if root is a subdirectory of cwd or is cwd itself
212
+ is_recursive = (
213
+ abs_root.startswith(abs_cwd + os.sep) or abs_root == abs_cwd
200
214
  )
201
215
  except Exception:
202
216
  is_recursive = False
@@ -230,29 +244,8 @@ def get_chat_completer() -> "Completer":
230
244
  ):
231
245
  continue
232
246
  cand = display_path.replace(os.sep, "/") # unify separator
233
- cand_cmp = cand if case_sensitive else cand.lower()
234
- last_pos = 0
235
- score = 0.0
236
- matched_all = True
237
- for token in tokens:
238
- # try contiguous substring search first
239
- idx = cand_cmp.find(token, last_pos)
240
- if idx != -1:
241
- # good match: reward contiguous early matches
242
- score += idx # smaller idx preferred
243
- last_pos = idx + len(token)
244
- else:
245
- # fallback to subsequence matching
246
- pos = self._find_subsequence_pos(cand_cmp, token, last_pos)
247
- if pos is None:
248
- matched_all = False
249
- break
250
- # subsequence match is less preferred than contiguous substring
251
- score += pos + 0.5 * len(token)
252
- last_pos = pos + len(token)
253
- if matched_all:
254
- # prefer shorter paths when score ties, so include length as tiebreaker
255
- score += 0.01 * len(cand)
247
+ matched, score = fuzzy_match(cand, search_pattern)
248
+ if matched:
256
249
  out = (
257
250
  cand
258
251
  if os.path.abspath(cand) == cand
@@ -263,25 +256,42 @@ def get_chat_completer() -> "Completer":
263
256
  candidates.sort(key=lambda x: (x[0], x[1]))
264
257
  return [p for _, p in candidates[:max_results]]
265
258
 
266
- def _find_subsequence_pos(
267
- self, hay: str, needle: str, start: int = 0
268
- ) -> int | None:
269
- """
270
- Try to locate needle in hay as a subsequence starting at `start`.
271
- Returns the index of the first matched character of the subsequence or None if not
272
- match.
273
- """
274
- if not needle:
275
- return start
276
- i = start
277
- j = 0
278
- first_pos = None
279
- while i < len(hay) and j < len(needle):
280
- if hay[i] == needle[j]:
281
- if first_pos is None:
282
- first_pos = i
283
- j += 1
284
- i += 1
285
- return first_pos if j == len(needle) else None
259
+ def _get_root_and_search_pattern(
260
+ self,
261
+ pattern: str,
262
+ root: str | None = None,
263
+ ) -> tuple[str, str]:
264
+ search_pattern = pattern
265
+ if root is None:
266
+ # Determine root and adjust pattern if necessary
267
+ expanded_pattern = os.path.expanduser(pattern)
268
+ if os.path.isabs(expanded_pattern) or pattern.startswith("~"):
269
+ # For absolute paths, find the deepest existing directory
270
+ if os.path.isdir(expanded_pattern):
271
+ root = expanded_pattern
272
+ return (root, "")
273
+ root = os.path.dirname(expanded_pattern)
274
+ while root and not os.path.isdir(root) and len(root) > 1:
275
+ root = os.path.dirname(root)
276
+ if not os.path.isdir(root):
277
+ return (".", pattern) # Fallback
278
+ try:
279
+ search_pattern = os.path.relpath(expanded_pattern, root)
280
+ if search_pattern == ".":
281
+ return (root, "")
282
+ except ValueError:
283
+ return (root, os.path.basename(pattern))
284
+ # Handle redundant current directory prefixes (e.g., ./ or .\)
285
+ if pattern.startswith(f".{os.sep}"):
286
+ return (".", pattern[len(f".{os.sep}") :])
287
+ if os.sep != "/" and pattern.startswith("./"):
288
+ return (".", pattern[2:])
289
+ return (".", pattern)
290
+
291
+ if pattern.startswith(f".{os.sep}"):
292
+ pattern = pattern[len(f".{os.sep}") :]
293
+ elif os.sep != "/" and pattern.startswith("./"):
294
+ pattern = pattern[2:]
295
+ return (root, pattern)
286
296
 
287
297
  return ChatCompleter()
@@ -5,27 +5,39 @@ from typing import TYPE_CHECKING, Any
5
5
  from zrb.builtin.llm.chat_session_cmd import (
6
6
  ATTACHMENT_CMD,
7
7
  HELP_CMD,
8
+ LOAD_CMD,
8
9
  MULTILINE_END_CMD,
9
10
  MULTILINE_START_CMD,
10
11
  QUIT_CMD,
12
+ RESPONSE_CMD,
11
13
  RUN_CLI_CMD,
12
14
  SAVE_CMD,
15
+ SESSION_CMD,
13
16
  WORKFLOW_CMD,
14
17
  YOLO_CMD,
15
18
  get_new_attachments,
16
19
  get_new_workflows,
17
20
  get_new_yolo_mode,
21
+ handle_response_cmd,
22
+ handle_session,
18
23
  is_command_match,
19
24
  print_commands,
20
25
  print_current_attachments,
21
26
  print_current_workflows,
22
27
  print_current_yolo_mode,
23
28
  run_cli_command,
24
- save_final_result,
25
29
  )
26
30
  from zrb.builtin.llm.chat_trigger import llm_chat_trigger
31
+ from zrb.builtin.llm.history import get_last_session_name
32
+ from zrb.builtin.llm.xcom_names import (
33
+ LLM_ASK_ERROR_XCOM_NAME,
34
+ LLM_ASK_RESULT_XCOM_NAME,
35
+ LLM_ASK_SESSION_XCOM_NAME,
36
+ )
27
37
  from zrb.config.llm_config import llm_config
28
38
  from zrb.context.any_context import AnyContext
39
+ from zrb.context.any_shared_context import AnySharedContext
40
+ from zrb.task.llm.workflow import get_llm_loaded_workflow_xcom
29
41
  from zrb.util.cli.markdown import render_markdown
30
42
 
31
43
  if TYPE_CHECKING:
@@ -44,30 +56,39 @@ async def read_user_prompt(ctx: AnyContext) -> str:
44
56
  reader: PromptSession[Any] | StreamReader = await _setup_input_reader(is_tty)
45
57
  multiline_mode = False
46
58
  is_first_time = True
47
- current_workflows: str = ctx.input.workflows
59
+ current_workflows: str = ctx.input.workflow
48
60
  current_yolo_mode: bool | str = ctx.input.yolo
49
61
  current_attachments: str = ctx.input.attach
50
62
  user_inputs: list[str] = []
51
63
  final_result: str = ""
52
64
  should_end = False
65
+ start_new: bool = ctx.input.start_new
66
+ if not start_new and ctx.input.previous_session == "":
67
+ session = ctx.session
68
+ if session is not None:
69
+ # Automatically inject last session name as previous session
70
+ last_session_name = get_last_session_name()
71
+ session.shared_ctx.input["previous_session"] = last_session_name
72
+ session.shared_ctx.input["previous-session"] = last_session_name
73
+ current_session_name: str | None = ctx.input.previous_session
53
74
  while not should_end:
54
- await asyncio.sleep(0.01)
55
- previous_session_name: str | None = (
56
- ctx.input.previous_session if is_first_time else ""
57
- )
58
- start_new: bool = ctx.input.start_new if is_first_time else False
75
+ await asyncio.sleep(0)
59
76
  if is_first_time and ctx.input.message.strip() != "":
60
77
  user_input = ctx.input.message
61
78
  else:
62
79
  # Get user input based on mode
63
80
  if not multiline_mode:
64
81
  ctx.print("💬 >>", plain=True)
65
- user_input = await llm_chat_trigger.wait(reader, ctx)
82
+ user_input = await llm_chat_trigger.wait(
83
+ ctx, reader, current_session_name, is_first_time
84
+ )
66
85
  if not multiline_mode:
67
86
  ctx.print("", plain=True)
68
87
  # At this point, is_first_time has to be False
69
88
  if is_first_time:
70
89
  is_first_time = False
90
+ # Add additional workflows activated by LLM in the previous session
91
+ current_workflows = _get_new_workflows_from_xcom(ctx, current_workflows)
71
92
  # Handle user input (including slash commands)
72
93
  if multiline_mode:
73
94
  if is_command_match(user_input, MULTILINE_END_CMD):
@@ -87,8 +108,8 @@ async def read_user_prompt(ctx: AnyContext) -> str:
87
108
  current_workflows = get_new_workflows(current_workflows, user_input)
88
109
  print_current_workflows(ctx, current_workflows)
89
110
  continue
90
- elif is_command_match(user_input, SAVE_CMD):
91
- save_final_result(ctx, user_input, final_result)
111
+ elif is_command_match(user_input, RESPONSE_CMD):
112
+ handle_response_cmd(ctx, user_input, final_result)
92
113
  continue
93
114
  elif is_command_match(user_input, ATTACHMENT_CMD):
94
115
  current_attachments = get_new_attachments(
@@ -101,25 +122,35 @@ async def read_user_prompt(ctx: AnyContext) -> str:
101
122
  print_current_yolo_mode(ctx, current_yolo_mode)
102
123
  continue
103
124
  elif is_command_match(user_input, RUN_CLI_CMD):
104
- run_cli_command(ctx, user_input)
125
+ await run_cli_command(ctx, user_input)
105
126
  continue
106
127
  elif is_command_match(user_input, HELP_CMD):
107
128
  print_commands(ctx)
108
129
  continue
130
+ elif (
131
+ is_command_match(user_input, SESSION_CMD)
132
+ or is_command_match(user_input, SAVE_CMD)
133
+ or is_command_match(user_input, LOAD_CMD)
134
+ ):
135
+ current_session_name, start_new = handle_session(
136
+ ctx, current_session_name, start_new, user_input
137
+ )
109
138
  else:
110
139
  user_inputs.append(user_input)
111
140
  # Trigger LLM
112
141
  user_prompt = "\n".join(user_inputs)
113
142
  user_inputs = []
114
- result = await _trigger_ask_and_wait_for_result(
143
+ result, current_session_name = await _trigger_ask_and_wait_for_result(
115
144
  ctx=ctx,
116
145
  user_prompt=user_prompt,
117
146
  attach=current_attachments,
118
147
  workflows=current_workflows,
119
148
  yolo_mode=current_yolo_mode,
120
- previous_session_name=previous_session_name,
149
+ previous_session_name=current_session_name,
121
150
  start_new=start_new,
122
151
  )
152
+ # After the first trigger, we no longer need to force start_new
153
+ start_new = False
123
154
  current_attachments = ""
124
155
  final_result = final_result if result is None else result
125
156
  if ctx.is_web_mode or not is_tty:
@@ -127,6 +158,23 @@ async def read_user_prompt(ctx: AnyContext) -> str:
127
158
  return final_result
128
159
 
129
160
 
161
+ def _get_new_workflows_from_xcom(ctx: AnyContext, current_workflows: str):
162
+ llm_loaded_workflow_xcom = get_llm_loaded_workflow_xcom(ctx)
163
+ new_workflow_names = [
164
+ workflow_name.strip()
165
+ for workflow_name in current_workflows.split(",")
166
+ if workflow_name.strip() != ""
167
+ ]
168
+ while len(llm_loaded_workflow_xcom) > 0:
169
+ additional_workflow_names = [
170
+ workflow_name
171
+ for workflow_name in llm_loaded_workflow_xcom.pop()
172
+ if workflow_name not in new_workflow_names
173
+ ]
174
+ new_workflow_names += additional_workflow_names
175
+ return ",".join(new_workflow_names)
176
+
177
+
130
178
  async def _setup_input_reader(
131
179
  is_interactive: bool,
132
180
  ) -> "PromptSession[Any] | StreamReader":
@@ -151,7 +199,7 @@ async def _trigger_ask_and_wait_for_result(
151
199
  yolo_mode: bool | str,
152
200
  previous_session_name: str | None = None,
153
201
  start_new: bool = False,
154
- ) -> str | None:
202
+ ) -> tuple[str | None, str | None]:
155
203
  """
156
204
  Triggers the LLM ask task and waits for the result via XCom.
157
205
 
@@ -162,22 +210,27 @@ async def _trigger_ask_and_wait_for_result(
162
210
  start_new: Whether to start a new conversation (optional).
163
211
 
164
212
  Returns:
165
- The result from the LLM task, or None if the user prompt is empty.
213
+ The result from the LLM task and the session name.
166
214
  """
167
215
  if user_prompt.strip() == "":
168
- return None
216
+ return None, previous_session_name
169
217
  await _trigger_ask(
170
218
  ctx, user_prompt, attach, workflows, yolo_mode, previous_session_name, start_new
171
219
  )
172
220
  result = await _wait_ask_result(ctx)
221
+
222
+ resolved_session_name = previous_session_name
223
+ if result is not None:
224
+ resolved_session_name = await _wait_ask_session_name(ctx)
225
+
173
226
  md_result = render_markdown(result) if result is not None else ""
174
227
  ctx.print("\n🤖 >>", plain=True)
175
228
  ctx.print(md_result, plain=True)
176
229
  ctx.print("", plain=True)
177
- return result
230
+ return result, resolved_session_name
178
231
 
179
232
 
180
- def get_llm_ask_input_mapping(callback_ctx: AnyContext):
233
+ def get_llm_ask_input_mapping(callback_ctx: AnyContext | AnySharedContext):
181
234
  """
182
235
  Generates the input mapping for the LLM ask task from the callback context.
183
236
 
@@ -200,7 +253,7 @@ def get_llm_ask_input_mapping(callback_ctx: AnyContext):
200
253
  "previous-session": data.get("previous_session_name"),
201
254
  "message": data.get("message"),
202
255
  "attach": data.get("attach"),
203
- "workflows": data.get("workflows"),
256
+ "workflow": data.get("workflow"),
204
257
  "yolo": data.get("yolo"),
205
258
  }
206
259
 
@@ -223,15 +276,13 @@ async def _trigger_ask(
223
276
  previous_session_name: The name of the previous chat session (optional).
224
277
  start_new: Whether to start a new conversation (optional).
225
278
  """
226
- if previous_session_name is None:
227
- previous_session_name = await _wait_ask_session_name(ctx)
228
279
  ctx.xcom["ask_trigger"].push(
229
280
  {
230
281
  "previous_session_name": previous_session_name,
231
282
  "start_new": start_new,
232
283
  "message": user_prompt,
233
284
  "attach": attach,
234
- "workflows": workflows,
285
+ "workflow": workflows,
235
286
  "yolo": yolo_mode,
236
287
  }
237
288
  )
@@ -247,12 +298,18 @@ async def _wait_ask_result(ctx: AnyContext) -> str | None:
247
298
  Returns:
248
299
  The result string from the LLM task.
249
300
  """
250
- while "ask_result" not in ctx.xcom or len(ctx.xcom.ask_result) == 0:
251
- await asyncio.sleep(0.1)
252
- if "ask_error" in ctx.xcom and len(ctx.xcom.ask_error) > 0:
253
- ctx.xcom.ask_error.pop()
301
+ while (
302
+ LLM_ASK_RESULT_XCOM_NAME not in ctx.xcom
303
+ or len(ctx.xcom[LLM_ASK_RESULT_XCOM_NAME]) == 0
304
+ ):
305
+ await asyncio.sleep(0)
306
+ if (
307
+ LLM_ASK_ERROR_XCOM_NAME in ctx.xcom
308
+ and len(ctx.xcom[LLM_ASK_ERROR_XCOM_NAME]) > 0
309
+ ):
310
+ ctx.xcom[LLM_ASK_ERROR_XCOM_NAME].pop()
254
311
  return None
255
- return ctx.xcom.ask_result.pop()
312
+ return ctx.xcom[LLM_ASK_RESULT_XCOM_NAME].pop()
256
313
 
257
314
 
258
315
  async def _wait_ask_session_name(ctx: AnyContext) -> str:
@@ -265,6 +322,9 @@ async def _wait_ask_session_name(ctx: AnyContext) -> str:
265
322
  Returns:
266
323
  The session name string.
267
324
  """
268
- while "ask_session_name" not in ctx.xcom or len(ctx.xcom.ask_session_name) == 0:
269
- await asyncio.sleep(0.1)
270
- return ctx.xcom.ask_session_name.pop()
325
+ while (
326
+ LLM_ASK_SESSION_XCOM_NAME not in ctx.xcom
327
+ or len(ctx.xcom[LLM_ASK_SESSION_XCOM_NAME]) == 0
328
+ ):
329
+ await asyncio.sleep(0)
330
+ return ctx.xcom[LLM_ASK_SESSION_XCOM_NAME].pop()