Merge branch 'release/v0.2.8' into develop

* release/v0.2.8: (23 commits)
  [add] migration script
  fix(workflow and tool): Output processing modification of tool nodes and error modification for tool tests
  feat(workflow): add configurable workflow feature options
  fix(web): app features
  fix(web): app features
  fix(web): app bugfix
  fix(web): agent add tools bugfix
  fix(web): workflow node ports bugfix
  fix(web): my sharing app add empty
  fix(app): The bugs that were fixed in the previous version but were later rolled back.
  fix(web): app sharing bugfix
  fix(app): 1.The end users are still bound to the app. 2. Multi-modal file support includes xlsx, csv, and json. 3. The file routing protocol is consistent with the page routing.
  fix(web): audio recorder add max size check
  fix(web): max_file_count precision
  feat(workflow): expose workflow memory enable status in app share config API
  revert(web): file download
  fix(perceptual): resolve inconsistency between local filename and actual filename
  fix(multimodel): filter unsupported files during perception memory write
  fix(web): file download
  fix(web): file download
  ...
This commit is contained in:
Mark
2026-03-18 17:43:00 +08:00
57 changed files with 1764 additions and 1432 deletions

View File

@@ -195,6 +195,6 @@ class MCPToolManager:
except Exception as e:
return {
"success": False,
"error": str(e),
"message": "连接失败"
"error": "连接失败",
"message": str(e)
}

View File

@@ -5,7 +5,7 @@
import re
from typing import AsyncGenerator
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, PrivateAttr
from app.core.logging_config import get_logger
from app.core.workflow.engine.variable_pool import VariablePool
@@ -52,10 +52,11 @@ class OutputContent(BaseModel):
)
)
_SCOPE: str | None = None
_SCOPE: str | None = PrivateAttr(default=None)
def get_scope(self) -> str:
self._SCOPE = SCOPE_PATTERN.findall(self.literal)[0]
def get_scope(self) -> str | None:
matches = SCOPE_PATTERN.findall(self.literal)
self._SCOPE = matches[0] if matches else None
return self._SCOPE
def depends_on_scope(self, scope: str) -> bool:
@@ -68,6 +69,8 @@ class OutputContent(BaseModel):
Returns:
bool: True if this segment references the given scope.
"""
if not self.is_variable:
return False
if self._SCOPE:
return self._SCOPE == scope
return self.get_scope() == scope
@@ -152,7 +155,7 @@ class StreamOutputConfig(BaseModel):
"""
# Case 1: resolve control branch dependency
if scope in self.control_nodes.keys():
if scope in self.control_nodes:
if status is None:
raise RuntimeError("[Stream Output] Control node activation status not provided")
if status in self.control_nodes[scope]:

View File

@@ -27,7 +27,6 @@ class ToolNode(BaseNode):
def _output_types(self) -> dict[str, VariableType]:
return {
"data": VariableType.STRING,
"error_code": VariableType.STRING,
"execution_time": VariableType.NUMBER
}
@@ -48,10 +47,7 @@ class ToolNode(BaseNode):
if not tenant_id:
logger.error(f"节点 {self.node_id} 缺少租户ID")
return {
"success": False,
"data": "缺少租户ID"
}
raise ValueError("缺少租户ID")
# 渲染工具参数
rendered_parameters = {}
@@ -83,13 +79,8 @@ class ToolNode(BaseNode):
logger.info(f"节点 {self.node_id} 工具执行成功")
return {
"data": result.data if isinstance(result.data, str) else json.dumps(result.data, ensure_ascii=False),
"error_code": "",
"execution_time": result.execution_time
}
else:
logger.error(f"节点 {self.node_id} 工具执行失败: {result.error}")
return {
"data": result.error if isinstance(result.error, str) else json.dumps(result.error, ensure_ascii=False),
"error_code": result.error_code,
"execution_time": result.execution_time
}
raise ValueError(f"工具执行失败: {result.error if isinstance(result.error, str) else json.dumps(result.error, ensure_ascii=False)}")