pygpt-net 2.6.62__py3-none-any.whl → 2.6.64__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.
Files changed (74) hide show
  1. pygpt_net/CHANGELOG.txt +11 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/attachment/attachment.py +17 -8
  4. pygpt_net/controller/camera/camera.py +4 -4
  5. pygpt_net/controller/lang/custom.py +2 -2
  6. pygpt_net/controller/presets/editor.py +65 -1
  7. pygpt_net/controller/ui/mode.py +18 -3
  8. pygpt_net/core/agents/custom/llama_index/runner.py +15 -52
  9. pygpt_net/core/agents/custom/runner.py +194 -76
  10. pygpt_net/core/agents/runners/llama_workflow.py +60 -10
  11. pygpt_net/core/render/web/renderer.py +11 -0
  12. pygpt_net/data/config/config.json +3 -3
  13. pygpt_net/data/config/models.json +3 -3
  14. pygpt_net/data/config/presets/agent_openai_b2b.json +1 -15
  15. pygpt_net/data/config/presets/agent_openai_coder.json +0 -0
  16. pygpt_net/data/config/presets/agent_openai_evolve.json +1 -23
  17. pygpt_net/data/config/presets/agent_openai_planner.json +1 -21
  18. pygpt_net/data/config/presets/agent_openai_researcher.json +1 -21
  19. pygpt_net/data/config/presets/agent_openai_supervisor.json +1 -13
  20. pygpt_net/data/config/presets/agent_openai_writer.json +1 -15
  21. pygpt_net/data/config/presets/agent_supervisor.json +1 -11
  22. pygpt_net/data/js/app/runtime.js +10 -0
  23. pygpt_net/data/js/app/scroll.js +14 -0
  24. pygpt_net/data/js/app.min.js +6 -4
  25. pygpt_net/data/locale/locale.de.ini +32 -0
  26. pygpt_net/data/locale/locale.en.ini +37 -0
  27. pygpt_net/data/locale/locale.es.ini +32 -0
  28. pygpt_net/data/locale/locale.fr.ini +32 -0
  29. pygpt_net/data/locale/locale.it.ini +32 -0
  30. pygpt_net/data/locale/locale.pl.ini +34 -2
  31. pygpt_net/data/locale/locale.uk.ini +32 -0
  32. pygpt_net/data/locale/locale.zh.ini +32 -0
  33. pygpt_net/js_rc.py +7571 -7499
  34. pygpt_net/provider/agents/base.py +0 -0
  35. pygpt_net/provider/agents/llama_index/flow_from_schema.py +0 -0
  36. pygpt_net/provider/agents/llama_index/planner_workflow.py +15 -3
  37. pygpt_net/provider/agents/llama_index/workflow/codeact.py +0 -0
  38. pygpt_net/provider/agents/llama_index/workflow/planner.py +272 -44
  39. pygpt_net/provider/agents/llama_index/workflow/supervisor.py +0 -0
  40. pygpt_net/provider/agents/openai/agent.py +0 -0
  41. pygpt_net/provider/agents/openai/agent_b2b.py +4 -4
  42. pygpt_net/provider/agents/openai/agent_planner.py +631 -254
  43. pygpt_net/provider/agents/openai/agent_with_experts.py +0 -0
  44. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -4
  45. pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -4
  46. pygpt_net/provider/agents/openai/evolve.py +6 -9
  47. pygpt_net/provider/agents/openai/flow_from_schema.py +0 -0
  48. pygpt_net/provider/agents/openai/supervisor.py +290 -37
  49. pygpt_net/provider/api/google/__init__.py +9 -3
  50. pygpt_net/provider/api/google/image.py +11 -1
  51. pygpt_net/provider/api/google/music.py +375 -0
  52. pygpt_net/provider/api/x_ai/__init__.py +0 -0
  53. pygpt_net/provider/core/agent/__init__.py +0 -0
  54. pygpt_net/provider/core/agent/base.py +0 -0
  55. pygpt_net/provider/core/agent/json_file.py +0 -0
  56. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -0
  57. pygpt_net/provider/llms/base.py +0 -0
  58. pygpt_net/provider/llms/deepseek_api.py +0 -0
  59. pygpt_net/provider/llms/google.py +0 -0
  60. pygpt_net/provider/llms/hugging_face_api.py +0 -0
  61. pygpt_net/provider/llms/hugging_face_router.py +0 -0
  62. pygpt_net/provider/llms/mistral.py +0 -0
  63. pygpt_net/provider/llms/perplexity.py +0 -0
  64. pygpt_net/provider/llms/x_ai.py +0 -0
  65. pygpt_net/ui/widget/dialog/confirm.py +34 -8
  66. pygpt_net/ui/widget/option/combo.py +149 -11
  67. pygpt_net/ui/widget/textarea/input.py +1 -1
  68. pygpt_net/ui/widget/textarea/web.py +1 -1
  69. pygpt_net/ui/widget/vision/camera.py +135 -12
  70. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/METADATA +13 -2
  71. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/RECORD +53 -52
  72. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/LICENSE +0 -0
  73. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/WHEEL +0 -0
  74. {pygpt_net-2.6.62.dist-info → pygpt_net-2.6.64.dist-info}/entry_points.txt +0 -0
File without changes
File without changes
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.26 01:00:00 #
9
+ # Updated Date: 2025.09.27 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Dict, Any, List
@@ -56,12 +56,16 @@ class PlannerAgent(BaseAgent):
56
56
  prompt_step = self.get_option(preset, "step", "prompt")
57
57
  prompt_plan_initial = self.get_option(preset, "plan", "prompt")
58
58
  prompt_plan_refine = self.get_option(preset, "plan_refine", "prompt")
59
+ prompt_plan_refine_each_step = self.get_option(preset, "plan_refine", "after_each_subtask")
59
60
  if not prompt_step:
60
61
  prompt_step = DEFAULT_EXECUTE_PROMPT
61
62
  if not prompt_plan_initial:
62
63
  prompt_plan_initial = DEFAULT_INITIAL_PLAN_PROMPT
63
64
  if not prompt_plan_refine:
64
65
  prompt_plan_refine = DEFAULT_PLAN_REFINE_PROMPT
66
+ if prompt_plan_refine_each_step is None:
67
+ prompt_plan_refine_each_step = True
68
+
65
69
 
66
70
  return PlannerWorkflow(
67
71
  tools=tools,
@@ -69,8 +73,9 @@ class PlannerAgent(BaseAgent):
69
73
  verbose=verbose,
70
74
  max_steps=max_steps,
71
75
  system_prompt=prompt_step,
72
- initial_plan_prompt= prompt_plan_initial,
73
- plan_refine_prompt= prompt_plan_refine,
76
+ initial_plan_prompt=prompt_plan_initial,
77
+ plan_refine_prompt=prompt_plan_refine,
78
+ refine_after_each_subtask=prompt_plan_refine_each_step,
74
79
  )
75
80
 
76
81
  def get_options(self) -> Dict[str, Any]:
@@ -79,6 +84,7 @@ class PlannerAgent(BaseAgent):
79
84
 
80
85
  :return: dict of options
81
86
  """
87
+ # step model -> from globals
82
88
  return {
83
89
  "step": {
84
90
  "label": trans("agent.planner.step.label"),
@@ -111,6 +117,12 @@ class PlannerAgent(BaseAgent):
111
117
  "description": trans("agent.planner.refine.prompt.desc"),
112
118
  "default": DEFAULT_PLAN_REFINE_PROMPT,
113
119
  },
120
+ "after_each_subtask": {
121
+ "type": "bool",
122
+ "label": trans("agent.option.refine.after_each"),
123
+ "description": trans("agent.option.refine.after_each.desc"),
124
+ "default": True,
125
+ },
114
126
  }
115
127
  },
116
128
  }
File without changes
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.26 15:20:00 #
9
+ # Updated Date: 2025.09.27 10:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import List, Optional, Callable
@@ -22,6 +22,7 @@ from llama_index.core.workflow import (
22
22
  step,
23
23
  )
24
24
  from llama_index.core.llms.llm import LLM
25
+ # noqa
25
26
  from llama_index.core.prompts import PromptTemplate
26
27
  from llama_index.core.tools.types import BaseTool
27
28
 
@@ -43,6 +44,10 @@ except Exception:
43
44
  except Exception:
44
45
  ChatMemoryBuffer = None
45
46
 
47
+ # Translation utility
48
+ from pygpt_net.utils import trans
49
+
50
+
46
51
  class SubTask(BaseModel):
47
52
  name: str = Field(..., description="The name of the sub-task.")
48
53
  input: str = Field(..., description="The input prompt for the sub-task.")
@@ -56,6 +61,16 @@ class Plan(BaseModel):
56
61
  sub_tasks: List[SubTask] = Field(..., description="The sub-tasks in the plan.")
57
62
 
58
63
 
64
+ # Structured refinement output to emulate the legacy Planner's refine behavior.
65
+ class PlanRefinement(BaseModel):
66
+ is_done: bool = Field(..., description="Whether the overall task is already satisfied.")
67
+ reason: Optional[str] = Field(None, description="Short justification why the plan is complete or needs update.")
68
+ plan: Optional[Plan] = Field(
69
+ default=None,
70
+ description="An updated plan that replaces the remaining sub-tasks. Omit if is_done=True or no update is needed.",
71
+ )
72
+
73
+
59
74
  DEFAULT_INITIAL_PLAN_PROMPT = """\
60
75
  You have the following prior context/memory (may be empty):
61
76
  {memory_context}
@@ -70,14 +85,29 @@ The tools available are:
70
85
  Overall Task: {task}
71
86
  """
72
87
 
88
+ # Refinement prompt tuned to prevent premature completion and enforce "final deliverable present" rule.
73
89
  DEFAULT_PLAN_REFINE_PROMPT = """\
74
90
  You have the following prior context/memory (may be empty):
75
91
  {memory_context}
76
92
 
77
- Think step-by-step. Given an overall task, a set of tools, and completed sub-tasks, update (if needed) the remaining sub-tasks so that the overall task can still be completed.
78
- The plan should end with a sub-task that can achieve and satisfy the overall task.
79
- If you do update the plan, only create new sub-tasks that will replace the remaining sub-tasks, do NOT repeat tasks that are already completed.
80
- If the remaining sub-tasks are enough to achieve the overall task, it is ok to skip this step, and instead explain why the plan is complete.
93
+ Think step-by-step. Given an overall task, a set of tools, and completed sub-tasks, decide whether the overall task is already satisfied.
94
+ If not, update the remaining sub-tasks so that the overall task can still be completed.
95
+
96
+ Completion criteria (ALL must be true to set is_done=true):
97
+ - A final, user-facing answer that directly satisfies "Overall Task" already exists within "Completed Sub-Tasks + Outputs".
98
+ - The final answer matches any explicit format and language requested in "Overall Task".
99
+ - No critical transformation/summarization/finalization step remains among "Remaining Sub-Tasks" (e.g., steps like: provide/present/report/answer/summarize/finalize/deliver the result).
100
+ - The final answer does not rely on placeholders such as "will be provided later" or "see plan above".
101
+
102
+ If ANY of the above is false, set is_done=false.
103
+
104
+ Update policy:
105
+ - If the remaining sub-tasks are already reasonable and correctly ordered, do not propose changes: set is_done=false and omit "plan".
106
+ - Only propose a new "plan" if you need to REPLACE the "Remaining Sub-Tasks" (e.g., wrong order, missing critical steps, or new info from completed outputs).
107
+ - Do NOT repeat any completed sub-task. New sub-tasks must replace only the "Remaining Sub-Tasks".
108
+
109
+ Output schema:
110
+ - Return a JSON object matching the schema with fields: is_done (bool), reason (string), and optional plan (Plan).
81
111
 
82
112
  The tools available are:
83
113
  {tools_str}
@@ -122,6 +152,7 @@ class PlannerWorkflow(Workflow):
122
152
  clear_executor_memory_between_subtasks: bool = False,
123
153
  executor_memory_factory: Optional[Callable[[], object]] = None,
124
154
  on_stop: Optional[Callable] = None,
155
+ refine_after_each_subtask: bool = True,
125
156
  ):
126
157
  super().__init__(timeout=None, verbose=verbose)
127
158
  self._planner_llm = llm
@@ -135,8 +166,8 @@ class PlannerWorkflow(Workflow):
135
166
  self._on_stop = on_stop
136
167
 
137
168
  # Human-friendly display names propagated to UI via workflow events.
138
- self._display_planner_name: str = "PlannerWorkflow"
139
- self._display_executor_name: str = "FunctionAgent"
169
+ self._display_planner_name: str = trans("agent.planner.display.planner") # UI label
170
+ self._display_executor_name: str = trans("agent.planner.display.executor_agent") # UI label
140
171
 
141
172
  self._executor = FunctionAgent(
142
173
  name="PlannerExecutor",
@@ -158,6 +189,9 @@ class PlannerWorkflow(Workflow):
158
189
 
159
190
  self._clear_exec_mem_between_subtasks = clear_executor_memory_between_subtasks
160
191
 
192
+ # Controls whether the legacy-style refine happens after every sub-task execution.
193
+ self._refine_after_each_subtask = refine_after_each_subtask
194
+
161
195
  def _stopped(self) -> bool:
162
196
  """
163
197
  Check if the workflow has been stopped.
@@ -171,6 +205,32 @@ class PlannerWorkflow(Workflow):
171
205
  return False
172
206
  return False
173
207
 
208
+ # Build human-friendly, step-scoped labels to display in the UI instead of agent names.
209
+ def _agent_label(
210
+ self,
211
+ step: str,
212
+ index: Optional[int] = None,
213
+ total: Optional[int] = None,
214
+ subtask_name: Optional[str] = None,
215
+ ) -> str:
216
+ if step == "subtask":
217
+ if index and total:
218
+ base = trans("agent.planner.label.subtask.index_total").format(index=index, total=total)
219
+ elif index:
220
+ base = trans("agent.planner.label.subtask.index").format(index=index)
221
+ else:
222
+ base = trans("agent.planner.label.subtask")
223
+ return trans("agent.planner.label.with_name").format(base=base, name=subtask_name) if subtask_name else base
224
+ if step == "refine":
225
+ if index and total:
226
+ return trans("agent.planner.label.refine.index_total").format(index=index, total=total)
227
+ return trans("agent.planner.label.refine.index").format(index=index) if index else trans("agent.planner.label.refine")
228
+ if step in {"make_plan", "plan"}:
229
+ return trans("agent.planner.label.plan")
230
+ if step in {"execute", "execute_plan"}:
231
+ return trans("agent.planner.label.execute")
232
+ return trans("agent.planner.label.step")
233
+
174
234
  def _emit_step_event(
175
235
  self,
176
236
  ctx: Context,
@@ -188,9 +248,15 @@ class PlannerWorkflow(Workflow):
188
248
  :param total: The total number of steps (optional).
189
249
  :param meta: Additional metadata for the step (optional).
190
250
  """
191
- # Always pass a friendly agent name; for 'subtask' we signal the executor agent to the UI.
251
+ # Always pass a friendly per-step label as "agent_name".
192
252
  m = dict(meta or {})
193
- m.setdefault("agent_name", self._display_executor_name if name == "subtask" else self._display_planner_name)
253
+ label = self._agent_label(
254
+ step=name,
255
+ index=index,
256
+ total=total,
257
+ subtask_name=m.get("name"),
258
+ )
259
+ m["agent_name"] = label
194
260
 
195
261
  try:
196
262
  ctx.write_event_to_stream(
@@ -203,7 +269,7 @@ class PlannerWorkflow(Workflow):
203
269
  AgentStream(
204
270
  delta="",
205
271
  response="",
206
- current_agent_name=m.get("agent_name", self._display_planner_name),
272
+ current_agent_name=label,
207
273
  tool_calls=[],
208
274
  raw={"StepEvent": {"name": name, "index": index, "total": total, "meta": m}}
209
275
  )
@@ -307,22 +373,23 @@ class PlannerWorkflow(Workflow):
307
373
  self,
308
374
  ctx: Context,
309
375
  text: str,
310
- agent_name: str = "PlannerWorkflow"
376
+ agent_name: Optional[str] = None
311
377
  ):
312
378
  """
313
379
  Emit a text message to the context stream.
314
380
 
315
- :param ctx: The context to write the event to
381
+ :param ctx: The context to write the event to.
316
382
  :param text: The text message to emit.
317
- :param agent_name: The name of the agent emitting the text (default: "PlannerWorkflow").
383
+ :param agent_name: The name/label to display in UI (we pass per-step labels here).
318
384
  """
385
+ label = agent_name or self._display_planner_name
319
386
  # Always try to include agent name; fall back to minimal event for older validators.
320
387
  try:
321
388
  ctx.write_event_to_stream(
322
389
  AgentStream(
323
390
  delta=text,
324
391
  response=text,
325
- current_agent_name=agent_name,
392
+ current_agent_name=label,
326
393
  tool_calls=[],
327
394
  raw={},
328
395
  )
@@ -406,12 +473,13 @@ class PlannerWorkflow(Workflow):
406
473
  ctx_text = "Completed sub-tasks context:\n" + "\n".join(parts)
407
474
  return self._truncate(ctx_text, char_limit or 8000)
408
475
 
409
- async def _run_subtask(self, ctx: Context, prompt: str) -> str:
476
+ async def _run_subtask(self, ctx: Context, prompt: str, agent_label: Optional[str] = None) -> str:
410
477
  """
411
478
  Run a sub-task using the executor agent.
412
479
 
413
480
  :param ctx: The context in which the sub-task is executed.
414
481
  :param prompt: The prompt for the sub-task.
482
+ :param agent_label: Per-step UI label (e.g., 'Sub-task 1/7: ...') used instead of agent name.
415
483
  """
416
484
  if self._clear_exec_mem_between_subtasks:
417
485
  self._reset_executor_memory()
@@ -452,15 +520,15 @@ class PlannerWorkflow(Workflow):
452
520
  if delta:
453
521
  has_stream = True
454
522
  stream_buf.append(str(delta))
455
- # Always enforce a stable display name for executor events.
523
+ # Force the per-step label for executor events.
456
524
  try:
457
- e.current_agent_name = self._display_executor_name
525
+ e.current_agent_name = agent_label or self._display_executor_name
458
526
  except Exception:
459
527
  try:
460
528
  e = AgentStream(
461
529
  delta=getattr(e, "delta", ""),
462
530
  response=getattr(e, "response", ""),
463
- current_agent_name=self._display_executor_name,
531
+ current_agent_name=agent_label or self._display_executor_name,
464
532
  tool_calls=getattr(e, "tool_calls", []),
465
533
  raw=getattr(e, "raw", {}),
466
534
  )
@@ -478,7 +546,7 @@ class PlannerWorkflow(Workflow):
478
546
  AgentStream(
479
547
  delta=last_answer,
480
548
  response=last_answer,
481
- current_agent_name=self._display_executor_name,
549
+ current_agent_name=agent_label or self._display_executor_name,
482
550
  tool_calls=e.tool_calls,
483
551
  raw=e.raw,
484
552
  )
@@ -502,9 +570,73 @@ class PlannerWorkflow(Workflow):
502
570
  try:
503
571
  return await _stream()
504
572
  except Exception as ex:
505
- await self._emit_text(ctx, f"\n`Sub-task failed: {ex}`", agent_name=self._display_executor_name)
573
+ await self._emit_text(
574
+ ctx,
575
+ f"\n`{trans('agent.planner.ui.subtask_failed').format(error=ex)}`",
576
+ agent_name=agent_label or self._display_executor_name,
577
+ )
506
578
  return last_answer or ("".join(stream_buf).strip() if stream_buf else "")
507
579
 
580
+ # Helper to render sub-tasks into a readable string for prompts and UI.
581
+ def _format_subtasks(self, sub_tasks: List[SubTask]) -> str:
582
+ parts = []
583
+ for i, st in enumerate(sub_tasks, 1):
584
+ parts.append(
585
+ f"[{i}] name={st.name}\n"
586
+ f" input={st.input}\n"
587
+ f" expected_output={st.expected_output}\n"
588
+ f" dependencies={st.dependencies}"
589
+ )
590
+ return "\n".join(parts) if parts else "(none)"
591
+
592
+ # Helper to render completed outputs for refinement prompt.
593
+ def _format_completed(self, completed: list[tuple[str, str]]) -> str:
594
+ if not completed:
595
+ return "(none)"
596
+ parts = []
597
+ for i, (name, out) in enumerate(completed, 1):
598
+ parts.append(f"[{i}] {name} -> {self._truncate((out or '').strip(), 2000)}")
599
+ joined = "\n".join(parts)
600
+ return self._truncate(joined, self._memory_char_limit or 8000)
601
+
602
+ async def _refine_plan(
603
+ self,
604
+ ctx: Context,
605
+ task: str,
606
+ tools_str: str,
607
+ completed: list[tuple[str, str]],
608
+ remaining: List[SubTask],
609
+ memory_context: str,
610
+ agent_label: Optional[str] = None,
611
+ ) -> Optional[PlanRefinement]:
612
+ """
613
+ Ask the planner LLM to refine the plan. Returns a PlanRefinement or None on failure.
614
+ """
615
+ completed_text = self._format_completed(completed)
616
+ remaining_text = self._format_subtasks(remaining)
617
+
618
+ # Emit a lightweight status line to the UI.
619
+ await self._emit_text(
620
+ ctx,
621
+ f"\n`{trans('agent.planner.ui.refining_remaining_plan')}`",
622
+ agent_name=agent_label or trans("agent.planner.label.refine"),
623
+ )
624
+
625
+ try:
626
+ refinement = await self._planner_llm.astructured_predict(
627
+ PlanRefinement,
628
+ self._plan_refine_prompt,
629
+ tools_str=tools_str,
630
+ task=task,
631
+ completed_outputs=completed_text,
632
+ remaining_sub_tasks=remaining_text,
633
+ memory_context=memory_context,
634
+ )
635
+ return refinement
636
+ except (ValueError, ValidationError):
637
+ # Graceful fallback if the model fails to conform to schema.
638
+ return None
639
+
508
640
  @step
509
641
  async def make_plan(self, ctx: Context, ev: QueryEvent) -> PlanReady:
510
642
  """
@@ -531,14 +663,16 @@ class PlannerWorkflow(Workflow):
531
663
  except (ValueError, ValidationError):
532
664
  plan = Plan(sub_tasks=[SubTask(name="default", input=ev.query, expected_output="", dependencies=[])])
533
665
 
534
- lines = ["`Current plan:`"]
666
+ lines = [f"`{trans('agent.planner.ui.current_plan')}`"]
535
667
  for i, st in enumerate(plan.sub_tasks, 1):
668
+ header = trans("agent.planner.ui.subtask_header.one").format(index=i, name=st.name)
536
669
  lines.append(
537
- f"\n**===== Sub Task {i}: {st.name} =====**\n"
538
- f"Expected output: {st.expected_output}\n"
539
- f"Dependencies: {st.dependencies}\n\n"
670
+ f"\n{header}\n"
671
+ f"{trans('agent.planner.ui.expected_output')} {st.expected_output}\n"
672
+ f"{trans('agent.planner.ui.dependencies')} {st.dependencies}\n\n"
540
673
  )
541
- await self._emit_text(ctx, "\n".join(lines), agent_name=self._display_planner_name)
674
+ # Use a per-step label for plan creation
675
+ await self._emit_text(ctx, "\n".join(lines), agent_name=self._agent_label("make_plan"))
542
676
  return PlanReady(plan=plan, query=ev.query)
543
677
 
544
678
  @step
@@ -555,36 +689,54 @@ class PlannerWorkflow(Workflow):
555
689
  last_answer = ""
556
690
  completed: list[tuple[str, str]] = [] # (name, output)
557
691
 
558
- await self._emit_text(ctx, "\n\n`Executing plan...`", agent_name=self._display_planner_name)
692
+ # Start executing with a per-step label
693
+ execute_label = self._agent_label("execute")
694
+ await self._emit_text(ctx, f"\n\n`{trans('agent.planner.ui.executing_plan')}`", agent_name=execute_label)
695
+
696
+ # Prepare static prompt parts for refinement.
697
+ tools_str = ""
698
+ for t in self._tools:
699
+ tools_str += f"{(t.metadata.name or '').strip()}: {(t.metadata.description or '').strip()}\n"
700
+ memory_context = self._memory_to_text(self._memory)
701
+
702
+ i = 0 # manual index to allow in-place plan updates during refinement
703
+ while i < len(plan_sub_tasks):
704
+ st = plan_sub_tasks[i]
705
+ total = len(plan_sub_tasks)
706
+
707
+ # Compute label for this sub-task
708
+ subtask_label = self._agent_label("subtask", index=i + 1, total=total, subtask_name=st.name)
559
709
 
560
- for i, st in enumerate(plan_sub_tasks, 1):
561
710
  self._emit_step_event(
562
711
  ctx,
563
712
  name="subtask",
564
- index=i,
713
+ index=i + 1,
565
714
  total=total,
566
715
  meta={
567
716
  "name": st.name,
568
717
  "expected_output": st.expected_output,
569
718
  "dependencies": st.dependencies,
570
719
  "input": st.input,
571
- # Signal that the next stream will come from the executor.
572
- "agent_name": self._display_executor_name,
720
+ # UI label for this sub-task step
721
+ "agent_name": subtask_label,
573
722
  },
574
723
  )
575
724
 
576
- header = (
577
- f"\n\n**===== Sub Task {i}/{total}: {st.name} =====**\n"
578
- f"Expected output: {st.expected_output}\n"
579
- f"Dependencies: {st.dependencies}\n\n"
725
+ header = trans("agent.planner.ui.subtask_header.progress").format(
726
+ index=i + 1, total=total, name=st.name
727
+ )
728
+ header_block = (
729
+ f"\n\n{header}\n"
730
+ f"{trans('agent.planner.ui.expected_output')} {st.expected_output}\n"
731
+ f"{trans('agent.planner.ui.dependencies')} {st.dependencies}\n\n"
580
732
  )
581
733
 
582
734
  # stop callback
583
735
  if self._stopped():
584
- await self._emit_text(ctx, "\n`Plan execution stopped.`", agent_name=self._display_planner_name)
585
- return FinalEvent(result=last_answer or "Plan execution stopped.")
736
+ await self._emit_text(ctx, f"\n`{trans('agent.planner.ui.execution_stopped')}`", agent_name=execute_label)
737
+ return FinalEvent(result=last_answer or trans("agent.planner.ui.execution_stopped"))
586
738
 
587
- await self._emit_text(ctx, header, agent_name=self._display_planner_name)
739
+ await self._emit_text(ctx, header_block, agent_name=subtask_label)
588
740
 
589
741
  # build context for sub-task
590
742
  ctx_text = self._build_context_for_subtask(
@@ -593,7 +745,7 @@ class PlannerWorkflow(Workflow):
593
745
  char_limit=self._memory_char_limit,
594
746
  )
595
747
 
596
- # make composed prompt for sub-task
748
+ # make composed prompt for sub-task (internal; do not translate)
597
749
  if ctx_text:
598
750
  composed_prompt = (
599
751
  f"{ctx_text}\n\n"
@@ -604,18 +756,94 @@ class PlannerWorkflow(Workflow):
604
756
  else:
605
757
  composed_prompt = st.input
606
758
 
607
- # run the sub-task
608
- sub_answer = await self._run_subtask(ctx, composed_prompt)
759
+ # run the sub-task with the per-step label
760
+ sub_answer = await self._run_subtask(ctx, composed_prompt, agent_label=subtask_label)
609
761
  sub_answer = (sub_answer or "").strip()
610
762
 
611
- await self._emit_text(ctx, f"\n\n`Finished Sub Task {i}/{total}: {st.name}`", agent_name=self._display_planner_name)
763
+ await self._emit_text(
764
+ ctx,
765
+ f"\n\n`{trans('agent.planner.ui.subtask_finished').format(index=i + 1, total=total, name=st.name)}`",
766
+ agent_name=subtask_label,
767
+ )
612
768
 
613
769
  # save completed sub-task
614
770
  completed.append((st.name, sub_answer))
615
771
  if sub_answer:
616
772
  last_answer = sub_answer
617
773
 
618
- # TODO: refine plan if needed
774
+ # Early stop check (external cancel)
775
+ if self._stopped():
776
+ await self._emit_text(ctx, f"\n`{trans('agent.planner.ui.execution_stopped')}`", agent_name=execute_label)
777
+ return FinalEvent(result=last_answer or trans("agent.planner.ui.execution_stopped"))
778
+
779
+ # Optional legacy-style refine after each sub-task
780
+ i += 1 # move pointer to the next item before potential replacement of tail
781
+ if self._refine_after_each_subtask and i < len(plan_sub_tasks):
782
+ remaining = plan_sub_tasks[i:]
783
+ # Label for refine step
784
+ refine_label = self._agent_label("refine", index=i, total=len(plan_sub_tasks))
785
+
786
+ # Emit a step event for refine to keep UI parity with the legacy Planner.
787
+ self._emit_step_event(
788
+ ctx,
789
+ name="refine",
790
+ index=i,
791
+ total=len(plan_sub_tasks),
792
+ meta={"agent_name": refine_label},
793
+ )
794
+
795
+ refinement = await self._refine_plan(
796
+ ctx=ctx,
797
+ task=ev.query,
798
+ tools_str=tools_str,
799
+ completed=completed,
800
+ remaining=remaining,
801
+ memory_context=memory_context,
802
+ agent_label=refine_label,
803
+ )
804
+
805
+ # If refinement failed to parse, skip gracefully.
806
+ if refinement is None:
807
+ continue
808
+
809
+ # If the planner states the task is complete, stop early.
810
+ if getattr(refinement, "is_done", False):
811
+ reason = getattr(refinement, "reason", "") or "Planner judged the task as satisfied."
812
+ await self._emit_text(
813
+ ctx,
814
+ f"\n`{trans('agent.planner.ui.plan_marked_complete').format(reason=reason)}`",
815
+ agent_name=refine_label,
816
+ )
817
+ await self._emit_text(
818
+ ctx,
819
+ f"\n\n`{trans('agent.planner.ui.plan_execution_finished')}`",
820
+ agent_name=execute_label,
821
+ )
822
+ return FinalEvent(result=last_answer or trans("agent.planner.ui.plan_finished"))
823
+
824
+ # If an updated plan was provided, replace the remaining sub-tasks.
825
+ if refinement.plan and refinement.plan.sub_tasks is not None:
826
+ # Filter out any sub-tasks that repeat completed names to avoid loops.
827
+ completed_names = {n for (n, _) in completed}
828
+ new_remaining = [st for st in refinement.plan.sub_tasks if st.name not in completed_names]
829
+
830
+ # If nothing changes, continue.
831
+ current_remaining_repr = self._format_subtasks(remaining)
832
+ new_remaining_repr = self._format_subtasks(new_remaining)
833
+ if new_remaining_repr.strip() != current_remaining_repr.strip():
834
+ plan_sub_tasks = plan_sub_tasks[:i] + new_remaining
835
+ # Present the updated tail of the plan to the UI.
836
+ lines = [f"`{trans('agent.planner.ui.updated_remaining_plan')}`"]
837
+ for k, st_upd in enumerate(new_remaining, i + 1):
838
+ upd_header = trans("agent.planner.ui.subtask_header.progress").format(
839
+ index=k, total=len(plan_sub_tasks), name=st_upd.name
840
+ )
841
+ lines.append(
842
+ f"\n{upd_header}\n"
843
+ f"{trans('agent.planner.ui.expected_output')} {st_upd.expected_output}\n"
844
+ f"{trans('agent.planner.ui.dependencies')} {st_upd.dependencies}\n\n"
845
+ )
846
+ await self._emit_text(ctx, "\n".join(lines), agent_name=refine_label)
619
847
 
620
- await self._emit_text(ctx, "\n\n`Plan execution finished.`", agent_name=self._display_planner_name)
621
- return FinalEvent(result=last_answer or "Plan finished.")
848
+ await self._emit_text(ctx, f"\n\n`{trans('agent.planner.ui.plan_execution_finished')}`", agent_name=execute_label)
849
+ return FinalEvent(result=last_answer or trans("agent.planner.ui.plan_finished"))
File without changes
@@ -449,13 +449,13 @@ class Agent(BaseAgent):
449
449
  "type": "bool",
450
450
  "label": trans("agent.option.tools.local"),
451
451
  "description": trans("agent.option.tools.local.desc"),
452
- "default": False,
452
+ "default": True,
453
453
  },
454
454
  "allow_remote_tools": {
455
455
  "type": "bool",
456
456
  "label": trans("agent.option.tools.remote"),
457
457
  "description": trans("agent.option.tools.remote.desc"),
458
- "default": False,
458
+ "default": True,
459
459
  },
460
460
  }
461
461
  },
@@ -483,13 +483,13 @@ class Agent(BaseAgent):
483
483
  "type": "bool",
484
484
  "label": trans("agent.option.tools.local"),
485
485
  "description": trans("agent.option.tools.local.desc"),
486
- "default": False,
486
+ "default": True,
487
487
  },
488
488
  "allow_remote_tools": {
489
489
  "type": "bool",
490
490
  "label": trans("agent.option.tools.remote"),
491
491
  "description": trans("agent.option.tools.remote.desc"),
492
- "default": False,
492
+ "default": True,
493
493
  },
494
494
  }
495
495
  },