-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: Proactive Agents and Subagents Orchestrator #4697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - 我发现了 3 个问题,并给出了一些整体性的反馈:
dashboard/src/main.ts里的全局window.fetch包装器在第一个参数是Request对象时会丢失method、body和其他Request属性;建议检测参数是否为Request,如果是则原样转发(只克隆/增强 headers),以避免一些隐蔽的破坏性行为。- 在
SubAgentOrchestrator.reload_from_config中,通过func_list和remove_func手动移除/添加工具绕过了FunctionToolManager的正常注册不变式;更安全的做法是暴露并使用专门的 API 来注册HandoffTool实例,从而保证它们仍然可被发现,并与其它工具保持一致。 - 在
SubAgentRoute.update_config中保存子代理配置时,目前没有对 agent 名称做校验(例如非空、唯一性、允许字符集),这可能导致transfer_to_*工具名冲突或生成非法标识符;添加基本的服务端校验会让编排逻辑更健壮。
给 AI 代理的 Prompt
Please address the comments from this code review:
## Overall Comments
- `dashboard/src/main.ts` 里的全局 `window.fetch` 包装器在第一个参数是 `Request` 对象时会丢失 `method`、`body` 和其他 `Request` 属性;建议检测参数是否为 `Request`,如果是则原样转发(只克隆/增强 headers),以避免一些隐蔽的破坏性行为。
- 在 `SubAgentOrchestrator.reload_from_config` 中,通过 `func_list` 和 `remove_func` 手动移除/添加工具绕过了 `FunctionToolManager` 的正常注册不变式;更安全的做法是暴露并使用专门的 API 来注册 `HandoffTool` 实例,从而保证它们仍然可被发现,并与其它工具保持一致。
- 在 `SubAgentRoute.update_config` 中保存子代理配置时,目前没有对 agent 名称做校验(例如非空、唯一性、允许字符集),这可能导致 `transfer_to_*` 工具名冲突或生成非法标识符;添加基本的服务端校验会让编排逻辑更健壮。
## Individual Comments
### Comment 1
<location> `astrbot/dashboard/server.py:135-136` </location>
<code_context>
r.status_code = 401
return r
- token = token.removeprefix("Bearer ")
+ # Be tolerant of different header casing / formatting.
+ token = token.strip().removeprefix("Bearer ").strip()
try:
payload = jwt.decode(token, self._jwt_secret, algorithms=["HS256"])
</code_context>
<issue_to_address>
**suggestion:** 尽管注释里说明要兼容不同的 header 大小写,但当前 JWT 提取对 `Bearer` 前缀仍然是大小写敏感的。
代码只会移除精确匹配的 "Bearer " 前缀,因此像 `authorization: bearer <token>` 或 `Authorization: BEARER <token>` 这样的 header 会失败,这与注释不符。建议在去前缀前先做归一化,例如:
```py
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
这样与注释中宣称的“对 header 大小写/格式更宽容”是一致的,也能避免来自非标准客户端的意外认证失败。
```suggestion
# Be tolerant of different header casing / formatting.
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
</issue_to_address>
### Comment 2
<location> `astrbot/dashboard/routes/subagent.py:93-105` </location>
<code_context>
+ UI can use this to build a multi-select list for subagent tool assignment.
+ """
+ try:
+ tool_mgr = self.core_lifecycle.provider_manager.llm_tools
+ tools_dict = []
+ for tool in tool_mgr.func_list:
+ tools_dict.append(
+ {
+ "name": tool.name,
+ "description": tool.description,
+ "parameters": tool.parameters,
+ "active": tool.active,
+ "handler_module_path": tool.handler_module_path,
+ }
+ )
</code_context>
<issue_to_address>
**suggestion (bug_risk):** available-tools 接口会暴露 handoff 工具,使得子代理可以选择 `transfer_to_*` 工具,从而创建递归路由。
由于该接口返回 `func_list` 中的所有条目,它也会暴露 `HandoffTool` 实例(即 `transfer_to_*` 工具)。这让 UI 能够把 handoff 工具分配为子代理工具,从而可能形成令人困惑或递归的委托循环。为避免这种情况,请过滤掉这些工具——例如跳过 `handler_module_path == "core.subagent_orchestrator"` 的工具,或者如果可以的话,排除 `isinstance(tool, HandoffTool)` 的工具。
```suggestion
try:
tool_mgr = self.core_lifecycle.provider_manager.llm_tools
tools_dict = []
for tool in tool_mgr.func_list:
# Skip handoff tools (e.g., transfer_to_*), which live in the subagent orchestrator.
# These should not be assignable as subagent tools to avoid recursive delegation.
if getattr(tool, "handler_module_path", None) == "core.subagent_orchestrator":
continue
tools_dict.append(
{
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters,
"active": tool.active,
"handler_module_path": tool.handler_module_path,
}
)
```
</issue_to_address>
### Comment 3
<location> `astrbot/core/subagent_orchestrator.py:37` </location>
<code_context>
+ self._tool_mgr = tool_mgr
+ self._registered_handoff_names: set[str] = set()
+
+ def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
+ cfg = provider_settings.get("subagent_orchestrator", {})
+ enabled = bool(cfg.get("main_enable", False))
</code_context>
<issue_to_address>
**issue (complexity):** 建议重构 orchestrator,将工具注册/移除的职责委托给 `FunctionToolManager`,并通过 `SubAgentConfig` 统一子代理配置的解析逻辑,从而让 `reload_from_config` 更加简洁、聚焦。
可以通过加强与 `FunctionToolManager` 的交互,并真正把 `SubAgentConfig` 作为标准化后的配置类型来使用,从而显著简化当前实现。
### 1) 去掉 add/remove/append 的“舞蹈”
与其这样:
```python
self._tool_mgr.add_func(
name=handoff.name,
func_args=[...],
desc=handoff.description,
handler=handoff.handler,
)
self._tool_mgr.remove_func(handoff.name)
self._tool_mgr.func_list.append(handoff)
```
不如在 `FunctionToolManager` 中增加一个接受预构造工具并且负责替换的轻量 API:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def add_tool(self, tool: FunctionTool) -> None:
"""Register a pre-constructed tool, replacing by name if it exists."""
# remove any existing tool with same name
self.func_list = [t for t in self.func_list if t.name != tool.name]
self.func_list.append(tool)
```
这样你的 orchestrator 就变成单一、清晰的注册路径:
```python
# in SubAgentOrchestrator.reload_from_config
handoff = HandoffTool(agent=agent, description=public_description or None)
handoff.provider_id = provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
self._registered_handoff_names.add(handoff.name)
```
既保留了原有行为,又避免了对 `func_list` 的直接操作,以及“先 `add_func` 再立刻撤销”的模式。
### 2) 尽量避免手动状态跟踪
如果可以在工具对象中编码来源信息(你已经设置了 `handler_module_path`),就可以把移除逻辑集中到 manager 里,从而去掉 `_registered_handoff_names` 这套手动跟踪:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def remove_by_module(self, module_path: str) -> None:
self.func_list = [
t for t in self.func_list
if getattr(t, "handler_module_path", None) != module_path
]
```
然后在 orchestrator 中:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
# clean up previously registered tools from this orchestrator
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
...
self._tool_mgr.add_tool(handoff)
```
如果采用这种方式,就可以完全删除 `_registered_handoff_names`。
### 3) 使用 `SubAgentConfig` 简化解析逻辑
当前的 `reload_from_config` 混合了配置解析和编排逻辑。你已经有 `SubAgentConfig`,如果用它来承载标准化后的配置,方法会更短、更易读。
示例重构:
```python
@dataclass(frozen=True)
class SubAgentConfig:
name: str
instructions: str
public_description: str
tools: list[str]
provider_id: str | None = None
enabled: bool = True
```
解析辅助函数:
```python
def _parse_agent_config(self, item: dict[str, Any]) -> SubAgentConfig | None:
if not isinstance(item, dict):
return None
if not item.get("enabled", True):
return None
name = str(item.get("name", "")).strip()
if not name:
return None
instructions = str(item.get("system_prompt", "")).strip()
public_description = str(item.get("public_description", "")).strip()
provider_id = item.get("provider_id")
if provider_id is not None:
provider_id = str(provider_id).strip() or None
tools = item.get("tools", [])
if not isinstance(tools, list):
tools = []
tools = [str(t).strip() for t in tools if str(t).strip()]
return SubAgentConfig(
name=name,
instructions=instructions,
public_description=public_description,
tools=tools,
provider_id=provider_id,
enabled=True,
)
```
然后 `reload_from_config` 就可以聚焦在编排本身:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
agents = cfg.get("agents", [])
if not isinstance(agents, list):
logger.warning("subagent_orchestrator.agents must be a list")
return
for item in agents:
cfg_item = self._parse_agent_config(item)
if cfg_item is None:
continue
agent = Agent[AstrAgentContext](
name=cfg_item.name,
instructions=cfg_item.instructions,
tools=cfg_item.tools,
)
handoff = HandoffTool(
agent=agent,
description=cfg_item.public_description or None,
)
handoff.provider_id = cfg_item.provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
logger.info(f"Registered subagent handoff tool: {handoff.name}")
```
这样可以让 `SubAgentConfig` 成为真正的抽象(而不是闲置代码),在保持行为不变的前提下,降低 `reload_from_config` 的认知负担。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进评审质量。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- The global
window.fetchwrapper indashboard/src/main.tslosesmethod,body, and otherRequestproperties when the first argument is aRequestobject; consider detectingRequestand forwarding it unchanged (only cloning/augmenting headers) to avoid subtle breakage. - In
SubAgentOrchestrator.reload_from_config, manually removing/adding tools viafunc_listandremove_funcbypassesFunctionToolManager's normal registration invariants; it would be safer to expose and use a dedicated API for registeringHandoffToolinstances so they remain discoverable and consistent with other tools. - When saving subagent config in
SubAgentRoute.update_config, there is currently no validation of agent names (e.g., non-empty, uniqueness, allowed characters), which could lead to conflictingtransfer_to_*tool names or invalid identifiers; adding basic server-side validation would make the orchestration more robust.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The global `window.fetch` wrapper in `dashboard/src/main.ts` loses `method`, `body`, and other `Request` properties when the first argument is a `Request` object; consider detecting `Request` and forwarding it unchanged (only cloning/augmenting headers) to avoid subtle breakage.
- In `SubAgentOrchestrator.reload_from_config`, manually removing/adding tools via `func_list` and `remove_func` bypasses `FunctionToolManager`'s normal registration invariants; it would be safer to expose and use a dedicated API for registering `HandoffTool` instances so they remain discoverable and consistent with other tools.
- When saving subagent config in `SubAgentRoute.update_config`, there is currently no validation of agent names (e.g., non-empty, uniqueness, allowed characters), which could lead to conflicting `transfer_to_*` tool names or invalid identifiers; adding basic server-side validation would make the orchestration more robust.
## Individual Comments
### Comment 1
<location> `astrbot/dashboard/server.py:135-136` </location>
<code_context>
r.status_code = 401
return r
- token = token.removeprefix("Bearer ")
+ # Be tolerant of different header casing / formatting.
+ token = token.strip().removeprefix("Bearer ").strip()
try:
payload = jwt.decode(token, self._jwt_secret, algorithms=["HS256"])
</code_context>
<issue_to_address>
**suggestion:** JWT extraction is still case-sensitive for the 'Bearer' prefix despite the comment about header casing.
The code only removes an exact "Bearer " prefix, so headers like `authorization: bearer <token>` or `Authorization: BEARER <token>` will fail despite the comment. Consider normalizing before stripping, e.g.:
```py
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
This matches the stated tolerance for header casing/formatting and avoids unexpected auth failures from non-standard clients.
```suggestion
# Be tolerant of different header casing / formatting.
raw = token.strip()
if raw.lower().startswith("bearer "):
raw = raw[7:]
token = raw.strip()
```
</issue_to_address>
### Comment 2
<location> `astrbot/dashboard/routes/subagent.py:93-105` </location>
<code_context>
+ UI can use this to build a multi-select list for subagent tool assignment.
+ """
+ try:
+ tool_mgr = self.core_lifecycle.provider_manager.llm_tools
+ tools_dict = []
+ for tool in tool_mgr.func_list:
+ tools_dict.append(
+ {
+ "name": tool.name,
+ "description": tool.description,
+ "parameters": tool.parameters,
+ "active": tool.active,
+ "handler_module_path": tool.handler_module_path,
+ }
+ )
</code_context>
<issue_to_address>
**suggestion (bug_risk):** available-tools endpoint exposes handoff tools, allowing subagents to select transfer_to_* tools and create recursive routing.
Because this endpoint returns all entries in `func_list`, it also exposes `HandoffTool` instances (the `transfer_to_*` tools). That lets the UI assign handoff tools as subagent tools, enabling confusing or recursive delegation loops. To avoid this, filter these out—for example, skip tools with `handler_module_path == "core.subagent_orchestrator"` or, if possible, exclude `isinstance(tool, HandoffTool)`.
```suggestion
try:
tool_mgr = self.core_lifecycle.provider_manager.llm_tools
tools_dict = []
for tool in tool_mgr.func_list:
# Skip handoff tools (e.g., transfer_to_*), which live in the subagent orchestrator.
# These should not be assignable as subagent tools to avoid recursive delegation.
if getattr(tool, "handler_module_path", None) == "core.subagent_orchestrator":
continue
tools_dict.append(
{
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters,
"active": tool.active,
"handler_module_path": tool.handler_module_path,
}
)
```
</issue_to_address>
### Comment 3
<location> `astrbot/core/subagent_orchestrator.py:37` </location>
<code_context>
+ self._tool_mgr = tool_mgr
+ self._registered_handoff_names: set[str] = set()
+
+ def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
+ cfg = provider_settings.get("subagent_orchestrator", {})
+ enabled = bool(cfg.get("main_enable", False))
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the orchestrator to delegate tool registration/removal to `FunctionToolManager` and normalize subagent parsing through `SubAgentConfig` for a cleaner, more focused `reload_from_config`.
You can simplify this substantially by tightening the interaction with `FunctionToolManager` and actually using `SubAgentConfig` as your normalized config type.
### 1) Remove the add/remove/append dance
Instead of:
```python
self._tool_mgr.add_func(
name=handoff.name,
func_args=[...],
desc=handoff.description,
handler=handoff.handler,
)
self._tool_mgr.remove_func(handoff.name)
self._tool_mgr.func_list.append(handoff)
```
add a small API to `FunctionToolManager` that accepts a pre‑built tool and handles replacement:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def add_tool(self, tool: FunctionTool) -> None:
"""Register a pre-constructed tool, replacing by name if it exists."""
# remove any existing tool with same name
self.func_list = [t for t in self.func_list if t.name != tool.name]
self.func_list.append(tool)
```
Then your orchestrator becomes a single, clear registration path:
```python
# in SubAgentOrchestrator.reload_from_config
handoff = HandoffTool(agent=agent, description=public_description or None)
handoff.provider_id = provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
self._registered_handoff_names.add(handoff.name)
```
This keeps all behavior but avoids reaching into `func_list` and calling `add_func` only to immediately undo it.
### 2) Avoid manual tracking where possible
If you can encode the origin in the tools (you already set `handler_module_path`), you can centralize removal in the manager and drop the `_registered_handoff_names` set:
```python
# in func_tool_manager.py
class FunctionToolManager:
...
def remove_by_module(self, module_path: str) -> None:
self.func_list = [
t for t in self.func_list
if getattr(t, "handler_module_path", None) != module_path
]
```
Then in the orchestrator:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
# clean up previously registered tools from this orchestrator
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
...
self._tool_mgr.add_tool(handoff)
```
If you adopt this, you can remove `_registered_handoff_names` completely.
### 3) Use `SubAgentConfig` to simplify parsing
Right now `reload_from_config` mixes config parsing with orchestration. You already have `SubAgentConfig`; using it makes the method shorter and easier to read.
Example refactor:
```python
@dataclass(frozen=True)
class SubAgentConfig:
name: str
instructions: str
public_description: str
tools: list[str]
provider_id: str | None = None
enabled: bool = True
```
Parsing helper:
```python
def _parse_agent_config(self, item: dict[str, Any]) -> SubAgentConfig | None:
if not isinstance(item, dict):
return None
if not item.get("enabled", True):
return None
name = str(item.get("name", "")).strip()
if not name:
return None
instructions = str(item.get("system_prompt", "")).strip()
public_description = str(item.get("public_description", "")).strip()
provider_id = item.get("provider_id")
if provider_id is not None:
provider_id = str(provider_id).strip() or None
tools = item.get("tools", [])
if not isinstance(tools, list):
tools = []
tools = [str(t).strip() for t in tools if str(t).strip()]
return SubAgentConfig(
name=name,
instructions=instructions,
public_description=public_description,
tools=tools,
provider_id=provider_id,
enabled=True,
)
```
Then `reload_from_config` focuses on orchestration:
```python
def reload_from_config(self, provider_settings: dict[str, Any]) -> None:
cfg = provider_settings.get("subagent_orchestrator", {})
enabled = bool(cfg.get("main_enable", False))
self._tool_mgr.remove_by_module("core.subagent_orchestrator")
if not enabled:
return
agents = cfg.get("agents", [])
if not isinstance(agents, list):
logger.warning("subagent_orchestrator.agents must be a list")
return
for item in agents:
cfg_item = self._parse_agent_config(item)
if cfg_item is None:
continue
agent = Agent[AstrAgentContext](
name=cfg_item.name,
instructions=cfg_item.instructions,
tools=cfg_item.tools,
)
handoff = HandoffTool(
agent=agent,
description=cfg_item.public_description or None,
)
handoff.provider_id = cfg_item.provider_id
handoff.handler_module_path = "core.subagent_orchestrator"
self._tool_mgr.add_tool(handoff)
logger.info(f"Registered subagent handoff tool: {handoff.name}")
```
This makes `SubAgentConfig` a real abstraction (not dead code), keeps behavior intact, and reduces the cognitive load in `reload_from_config`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Soulter
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just made some quick reviews
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感觉相关逻辑可以直接放到 tool_manager 里面,用一个方法代替。
- Implemented proactive cron job tools in InternalAgentSubStage for scheduling tasks. - Created SendMessageToUserTool for sending messages to users based on cron job triggers. - Added CreateActiveCronTool, DeleteCronJobTool, and ListCronJobsTool for cron job management. - Introduced CronRoute for handling cron job API requests in the dashboard. - Developed CronJobPage.vue for managing cron jobs in the dashboard UI. - Updated SubAgentPage.vue to include persona selection for subagents.
- Updated FunctionToolExecutor to improve background task handling and integrate new system prompts for proactive agents. - Enhanced MainAgentBuildConfig with additional configuration options for tool management and context handling. - Introduced new system prompts for proactive agents triggered by cron jobs and background tasks to improve user interaction. - Refactored cron job management to utilize ProviderRequest for better context management and tool integration. - Renamed cron job tools for clarity, changing "create_cron_job" to "create_future_task" and similar adjustments for consistency. - Improved error handling and logging for cron job execution and agent responses. - Added support for image captioning and persona management in agent requests.
… SendMessageToUserTool
…e cron job permission handling
… download tool with user notification option
…hanced UI for task management
…ng files if necessary
|
Related Documentation No published documentation to review for changes on this repository. |
|
已为该 PR 生成文档更新 PR(待人工审核): AI 改动摘要: Updated the documentation to include the new SubAgent Orchestration and Proactive Agent features introduced in PR #4697. Documentation Changes
Sidebar Updates
|
AstrBot 现在拥有完善的 Proactive Agent(主动 Agent)系统和简单的 SubAgent 编排功能,并且具备更好的将多媒体文件发送给用户的能力!
动机
改动点
Motivations
SubAgent
In the current architecture, all tools are mounted on the main agent. The main agent is responsible for user interaction and for organizing and invoking a large number of tools. As the number of tools grows, this easily leads to prompt bloat and higher LLM failure rates.
By allowing users to create SubAgents, each SubAgent can be responsible for a specific subset of tools. The main agent only needs to handle conversation and task delegation, while actual tool execution is handled by SubAgents. This design greatly reduces the prompt length of the main agent and significantly lowers the probability of execution errors.
FutureTask
The existing scheduling mechanism is very limited and cannot meet the requirements of a true proactive agent.
Modifications
SubAgent Support
When SubAgent mode is enabled, the main agent is responsible only for conversation and delegation. It can see and use only delegation tools such as
transfer_to_*.When a task requires tool usage, the main agent delegates it to the appropriate SubAgent. The SubAgent performs the actual tool calls, organizes the results, and returns them to the main agent.
Each registered SubAgent is automatically exposed as a tool named
transfer_to_<subagent_name>, reusing the framework’s existing handoff mechanism. The main agent can delegate tasks simply by callingtransfer_to_<agent_name>.FutureTask Support
The main agent can now manage a global Cron Job List, allowing it to assign tasks to its future self. AstrBot will automatically wake up at the scheduled time, execute the task, and report the results back to the task creator.
Currently supported platforms include Telegram, OneBot, Slack, Feishu (Lark), Discord, Misskey, and Satori.
ChatUI support is planned for the future.
Multimedia Message Delivery Tool
By default, the main agent is provided with a
send_message_to_usertool, which allows it to conveniently send images, files, audio, and other multimedia content directly to users (supported on the platforms listed above).Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations inrequirements.txtandpyproject.toml.Summary by Sourcery
引入一种可配置的子代理(subagent)编排模式,在该模式下主 LLM 通过动态交接(handoff)工具进行委派,而不是直接挂载所有工具。
New Features:
transfer_to_*交接工具,支持可选的按子代理级别的 provider 覆盖。Enhancements:
fetch的仪表盘请求自动包含Authorization头。Build:
monaco-editor添加为仪表盘依赖,以在 UI 中提供更强大的编辑器能力。Original summary in English
Summary by Sourcery
Introduce a configurable subagent orchestration mode where the main LLM delegates via dynamic handoff tools instead of mounting all tools directly.
New Features:
Enhancements:
Build: