Conversation
- Detect x-openclaw flag in OpenAPI schema and init dedicated config - Implement multimodal input/output (image download, compress, base64) - Add OpenClaw connection test and status validation in tool service - Fix auth_config token check to support both api_key and bearer_token - Inject runtime context (user_id, conversation_id, files) in chat services
…ibility - Use safe .get() for server URL to avoid KeyError - Support both api_key and token in connection test auth - Add OpenAI/Volcano image format (image_url) support - Add aiohttp import in _test_openclaw_connection
Create OpenClawTool class inheriting BuiltinTool with dedicated config Remove all x-openclaw special handling from CustomTool (~270 lines) Add multi-operation support (print_task, device_query, image_understand, general) Change ensure_builtin_tools_initialized to incremental mode for auto-provisioning Fix OperationTool and LangchainAdapter to support OpenClaw operation routing
Reviewer's Guide实现了一个新的多模态 OpenClaw 内置工具,将其接入到工具发现/初始化流程和 LangChain 操作处理当中,为支持该能力的工具添加了运行时上下文注入机制,并增强了自定义/内置工具基础设施(增量式内置工具初始化和更健壮的 schema 解析)。 使用 OpenClawTool 和运行时上下文注入的聊天流程时序图sequenceDiagram
actor User
participant AppChatService
participant MultimodalService
participant Agent
participant OperationTool
participant OpenClawTool
participant OpenClawAgentHTTP
User->>AppChatService: send message, files, selected tools
alt has_files
AppChatService->>MultimodalService: process_files(files)
MultimodalService-->>AppChatService: processed_files
else no_files
AppChatService-->>AppChatService: processed_files = []
end
loop for each tool in tools
AppChatService->>OpenClawTool: set_runtime_context(user_id, conversation_id, uploaded_files)
note over AppChatService,OpenClawTool: Only called for tools with set_runtime_context
end
AppChatService->>Agent: chat(message, tools, processed_files)
Agent->>OperationTool: execute(operation, message, image_url)
OperationTool->>OpenClawTool: execute(operation, message, image_url)
OpenClawTool->>OpenClawTool: _extract_image_from_uploads()
OpenClawTool->>OpenClawTool: _build_input(message, image_url)
OpenClawTool->>OpenClawAgentHTTP: POST /v1/responses
OpenClawAgentHTTP-->>OpenClawTool: JSON response
OpenClawTool->>OpenClawTool: _extract_response(response_data)
OpenClawTool-->>OperationTool: ToolResult.success_result(data)
OperationTool-->>Agent: ToolResult
Agent-->>AppChatService: chat result
AppChatService-->>User: final answer with OpenClaw result
OpenClawTool 及相关工具基础设施的类图classDiagram
class BuiltinTool {
}
class OpenClawTool {
- string _server_url
- string _api_key
- string _agent_id
- string _model
- string _session_strategy
- int _timeout
- string _user_id
- string _conversation_id
- list _uploaded_files
+ OpenClawTool(tool_id, config)
+ name str
+ description str
+ parameters List~ToolParameter~
+ set_runtime_context(user_id, conversation_id, uploaded_files) void
+ test_connection() Dict~str, Any~
+ execute(**kwargs) ToolResult
- _extract_image_from_uploads() str
- _download_and_encode_image(image_url) str
- _build_input(message, image_url) Any
- _extract_response(response_data) str
- _format_result(text) str
}
class OperationTool {
- BaseTool base_tool
- string operation
+ parameters List~ToolParameter~
- _get_openclaw_params() List~ToolParameter~
+ execute(**kwargs) ToolResult
}
class ToolService {
+ ensure_builtin_tools_initialized(tenant_id) void
+ _get_operation_specific_params(tool_instance, operation) List~Dict[str, Any]~
- _get_openclaw_tool_params(operation) List~Dict[str, Any]~
}
class BuiltinToolConfig {
+ uuid id
+ string tool_class
+ string display_name
+ bool requires_config
}
class ToolConfig {
+ uuid id
+ string name
+ string tool_type
+ uuid tenant_id
}
class BuiltinToolRepository {
+ get_existing_tool_classes(db, tenant_id) set
}
class CustomToolBase {
- Any schema_content
- dict _parsed_operations
+ __init__(tool_id, config)
+ _parse_openapi_schema() Any
}
class LangchainAdapter {
+ convert_tool(tool, operation) LangchainTool
- _tool_supports_operations(tool) bool
}
class ToolParameter {
+ string name
+ ParameterType type
+ string description
+ bool required
+ list enum
}
class ToolResult {
+ Any data
+ float execution_time
+ string error
+ string error_code
+ success_result(data, execution_time) ToolResult
+ error_result(error, error_code, execution_time) ToolResult
}
BuiltinTool <|-- OpenClawTool
BaseTool <|-- BuiltinTool
OperationTool --> BaseTool
OperationTool --> ToolParameter
OperationTool --> ToolResult
ToolService --> BuiltinToolRepository
ToolService --> ToolConfig
ToolService --> BuiltinToolConfig
ToolService --> OpenClawTool
ToolService --> ToolParameter
BuiltinToolRepository --> BuiltinToolConfig
BuiltinToolRepository --> ToolConfig
CustomToolBase --> ToolConfig
LangchainAdapter --> BaseTool
LangchainAdapter --> OperationTool
LangchainAdapter --> OpenClawTool
文件级变更
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 来:
Getting HelpOriginal review guide in EnglishReviewer's GuideImplements a new multimodal OpenClaw built‑in tool, wires it into tool discovery/initialization and LangChain operation handling, adds runtime context injection for tools that support it, and hardens custom/builtin tool infrastructure (incremental builtin init and robust schema parsing). Sequence diagram for chat flow with OpenClawTool and runtime context injectionsequenceDiagram
actor User
participant AppChatService
participant MultimodalService
participant Agent
participant OperationTool
participant OpenClawTool
participant OpenClawAgentHTTP
User->>AppChatService: send message, files, selected tools
alt has_files
AppChatService->>MultimodalService: process_files(files)
MultimodalService-->>AppChatService: processed_files
else no_files
AppChatService-->>AppChatService: processed_files = []
end
loop for each tool in tools
AppChatService->>OpenClawTool: set_runtime_context(user_id, conversation_id, uploaded_files)
note over AppChatService,OpenClawTool: Only called for tools with set_runtime_context
end
AppChatService->>Agent: chat(message, tools, processed_files)
Agent->>OperationTool: execute(operation, message, image_url)
OperationTool->>OpenClawTool: execute(operation, message, image_url)
OpenClawTool->>OpenClawTool: _extract_image_from_uploads()
OpenClawTool->>OpenClawTool: _build_input(message, image_url)
OpenClawTool->>OpenClawAgentHTTP: POST /v1/responses
OpenClawAgentHTTP-->>OpenClawTool: JSON response
OpenClawTool->>OpenClawTool: _extract_response(response_data)
OpenClawTool-->>OperationTool: ToolResult.success_result(data)
OperationTool-->>Agent: ToolResult
Agent-->>AppChatService: chat result
AppChatService-->>User: final answer with OpenClaw result
Class diagram for OpenClawTool and related tool infrastructureclassDiagram
class BuiltinTool {
}
class OpenClawTool {
- string _server_url
- string _api_key
- string _agent_id
- string _model
- string _session_strategy
- int _timeout
- string _user_id
- string _conversation_id
- list _uploaded_files
+ OpenClawTool(tool_id, config)
+ name str
+ description str
+ parameters List~ToolParameter~
+ set_runtime_context(user_id, conversation_id, uploaded_files) void
+ test_connection() Dict~str, Any~
+ execute(**kwargs) ToolResult
- _extract_image_from_uploads() str
- _download_and_encode_image(image_url) str
- _build_input(message, image_url) Any
- _extract_response(response_data) str
- _format_result(text) str
}
class OperationTool {
- BaseTool base_tool
- string operation
+ parameters List~ToolParameter~
- _get_openclaw_params() List~ToolParameter~
+ execute(**kwargs) ToolResult
}
class ToolService {
+ ensure_builtin_tools_initialized(tenant_id) void
+ _get_operation_specific_params(tool_instance, operation) List~Dict[str, Any]~
- _get_openclaw_tool_params(operation) List~Dict[str, Any]~
}
class BuiltinToolConfig {
+ uuid id
+ string tool_class
+ string display_name
+ bool requires_config
}
class ToolConfig {
+ uuid id
+ string name
+ string tool_type
+ uuid tenant_id
}
class BuiltinToolRepository {
+ get_existing_tool_classes(db, tenant_id) set
}
class CustomToolBase {
- Any schema_content
- dict _parsed_operations
+ __init__(tool_id, config)
+ _parse_openapi_schema() Any
}
class LangchainAdapter {
+ convert_tool(tool, operation) LangchainTool
- _tool_supports_operations(tool) bool
}
class ToolParameter {
+ string name
+ ParameterType type
+ string description
+ bool required
+ list enum
}
class ToolResult {
+ Any data
+ float execution_time
+ string error
+ string error_code
+ success_result(data, execution_time) ToolResult
+ error_result(error, error_code, execution_time) ToolResult
}
BuiltinTool <|-- OpenClawTool
BaseTool <|-- BuiltinTool
OperationTool --> BaseTool
OperationTool --> ToolParameter
OperationTool --> ToolResult
ToolService --> BuiltinToolRepository
ToolService --> ToolConfig
ToolService --> BuiltinToolConfig
ToolService --> OpenClawTool
ToolService --> ToolParameter
BuiltinToolRepository --> BuiltinToolConfig
BuiltinToolRepository --> ToolConfig
CustomToolBase --> ToolConfig
LangchainAdapter --> BaseTool
LangchainAdapter --> OperationTool
LangchainAdapter --> OpenClawTool
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 5 个问题,并留下了一些高层次的反馈:
- 在
app_chat_service和draft_run_service中的运行时上下文注入循环里,当没有传入文件时,processed_files可能在赋值前被引用;请在if files/if file_ids分支之前先初始化processed_files = [],以避免出现NameError。 - 在
CustomToolBase.__init__中,当json.loads失败时,你将schema设为{},但仍然将self.schema_content保持为原始(无效)字符串;请在成功和异常分支中都将self.schema_content = schema,以确保_parse_openapi_schema始终接收到解析后的对象或空字典。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- 在 `app_chat_service` 和 `draft_run_service` 中的运行时上下文注入循环里,当没有传入文件时,`processed_files` 可能在赋值前被引用;请在 `if files`/`if file_ids` 分支之前先初始化 `processed_files = []`,以避免出现 `NameError`。
- 在 `CustomToolBase.__init__` 中,当 `json.loads` 失败时,你将 `schema` 设为 `{}`,但仍然将 `self.schema_content` 保持为原始(无效)字符串;请在成功和异常分支中都将 `self.schema_content = schema`,以确保 `_parse_openapi_schema` 始终接收到解析后的对象或空字典。
## Individual Comments
### Comment 1
<location path="api/app/services/app_chat_service.py" line_range="168" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** 当没有提供文件时,processed_files 可能在赋值前被引用
由于 `processed_files` 只在 `if files:` 块内部定义,而后续的 `processed_files or []` 用法在 `files` 为 falsy 时会抛出 `UnboundLocalError`。请在 `if` 之前初始化 `processed_files`(例如设为 `None` 或 `[]`),以确保它始终已定义。
</issue_to_address>
### Comment 2
<location path="api/app/services/app_chat_service.py" line_range="171" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** 在流式聊天路径中也存在同样的 processed_files 未初始化问题
这里同样,`processed_files` 只在 `if files:` 中定义,但在 `uploaded_files = processed_files or []` 中被无条件使用,这会在 `files` 为 falsy 时抛出异常。请和非流式路径保持一致,在条件判断前初始化 `processed_files`。
</issue_to_address>
### Comment 3
<location path="api/app/services/draft_run_service.py" line_range="643" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** draft_run 非流式路径也有在赋值前使用 processed_files 的风险
`processed_files` 只会在 `files` 为 truthy 时设置,但在上下文注入循环中会始终被使用。请在 `if files:` 块之前初始化 `processed_files`,以避免在未提供文件时出现 `UnboundLocalError`。
</issue_to_address>
### Comment 4
<location path="api/app/services/draft_run_service.py" line_range="643" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** 在 run_stream 中也存在相同的 processed_files 初始化问题
当 `files` 为 falsy 时,`processed_files` 没有被初始化,但在注入运行时上下文时仍然会被使用。请将 `processed_files` 的初始化放在 `if files:` 块之外(如其它路径一样),以确保它始终已定义。
</issue_to_address>
### Comment 5
<location path="api/app/core/tools/custom/base.py" line_range="35-40" />
<code_context>
- # 解析schema
+
+ # 解析 schema
+ schema = self.schema_content
+ if isinstance(schema, str):
+ try:
+ schema = json.loads(schema)
+ self.schema_content = schema
+ except json.JSONDecodeError:
+ schema = {}
self._parsed_operations = self._parse_openapi_schema()
</code_context>
<issue_to_address>
**issue (bug_risk):** 在 JSON 解码失败时,schema_content 保留了无效的字符串,而没有使用回退字典
如果 `schema_content` 是字符串并且 `json.loads` 失败,`schema` 会被设为 `{}`,但 `self.schema_content` 仍然是无效字符串,因此 `_parse_openapi_schema()` 依然会收到错误的值。请在 `except` 块中同样赋值 `self.schema_content = {}`(或 `schema`),以确保下游代码始终看到的是一个字典。
</issue_to_address>帮我变得更有用!请在每条评论上点选 👍 或 👎,你的反馈会用于改进后续的评审。
Original comment in English
Hey - I've found 5 issues, and left some high level feedback:
- In the runtime context injection loops in
app_chat_serviceanddraft_run_service,processed_filescan be referenced before assignment when no files are provided; initializeprocessed_files = []before theif files/if file_idsbranches to avoid aNameError. - In
CustomToolBase.__init__, whenjson.loadsfails you setschema = {}but leaveself.schema_contentas the original (invalid) string; assignself.schema_content = schemain both the success and exception paths so_parse_openapi_schemaalways receives a parsed object or empty dict.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the runtime context injection loops in `app_chat_service` and `draft_run_service`, `processed_files` can be referenced before assignment when no files are provided; initialize `processed_files = []` before the `if files`/`if file_ids` branches to avoid a `NameError`.
- In `CustomToolBase.__init__`, when `json.loads` fails you set `schema = {}` but leave `self.schema_content` as the original (invalid) string; assign `self.schema_content = schema` in both the success and exception paths so `_parse_openapi_schema` always receives a parsed object or empty dict.
## Individual Comments
### Comment 1
<location path="api/app/services/app_chat_service.py" line_range="168" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** processed_files may be referenced before assignment when no files are provided
Because `processed_files` is only defined inside the `if files:` block, the later `processed_files or []` usage will raise `UnboundLocalError` when `files` is falsy. Initialize `processed_files` (e.g. to `None` or `[]`) before the `if` so it’s always defined.
</issue_to_address>
### Comment 2
<location path="api/app/services/app_chat_service.py" line_range="171" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** Same uninitialized processed_files problem in the streaming chat path
Here too, `processed_files` is only defined inside `if files:` but then used unconditionally in `uploaded_files = processed_files or []`, which will raise when `files` is falsy. Mirror the non-streaming path by initializing `processed_files` before the conditional.
</issue_to_address>
### Comment 3
<location path="api/app/services/draft_run_service.py" line_range="643" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** draft_run non-stream path also risks using processed_files before assignment
`processed_files` is only set when `files` is truthy but is always used in the context-injection loop. Please initialize `processed_files` before the `if files:` block to avoid an `UnboundLocalError` when no files are supplied.
</issue_to_address>
### Comment 4
<location path="api/app/services/draft_run_service.py" line_range="643" />
<code_context>
if files:
</code_context>
<issue_to_address>
**issue (bug_risk):** Same processed_files initialization issue in run_stream
When `files` is falsy, `processed_files` isn’t initialized but is still used when injecting runtime context. Please initialize `processed_files` outside the `if files:` block (as in the other path) so it’s always defined.
</issue_to_address>
### Comment 5
<location path="api/app/core/tools/custom/base.py" line_range="35-40" />
<code_context>
- # 解析schema
+
+ # 解析 schema
+ schema = self.schema_content
+ if isinstance(schema, str):
+ try:
+ schema = json.loads(schema)
+ self.schema_content = schema
+ except json.JSONDecodeError:
+ schema = {}
self._parsed_operations = self._parse_openapi_schema()
</code_context>
<issue_to_address>
**issue (bug_risk):** On JSON decode failure, schema_content keeps the invalid string instead of the fallback dict
If `schema_content` is a string and `json.loads` fails, `schema` is set to `{}`, but `self.schema_content` remains the invalid string, so `_parse_openapi_schema()` still receives the bad value. In the `except` block, also assign `self.schema_content = {}` (or `schema`) so downstream code always sees a dict.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
This PR adds a new built-in multimodal tool (OpenClawTool) that calls a remote OpenClaw Agent service, and extends the tool system to support operation-specific parameters and runtime context injection (user/conversation/uploaded files). It also adjusts tenant built-in tool initialization to support incremental additions, and improves robustness of custom tool schema parsing.
Changes:
- Introduce
OpenClawTool(text + image), register it as a built-in tool, and add corresponding built-in configs. - Extend operation-tool plumbing (ToolService method discovery + LangChain adapter + OperationTool) to treat
openclaw_toolas a multi-operation tool with per-operation parameter sets. - Inject runtime context into tools during chat/draft runs and make built-in tool initialization incremental.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| api/app/services/tool_service.py | Registers OpenClawTool, supports incremental built-in init, and adds OpenClaw operation-specific parameter metadata. |
| api/app/services/draft_run_service.py | Injects runtime context (user/conversation/uploaded files) into tools before agent execution. |
| api/app/services/app_chat_service.py | Injects runtime context into tools for both non-streaming and streaming chat paths. |
| api/app/repositories/tool_repository.py | Adds query to fetch existing built-in tool classes for incremental initialization. |
| api/app/core/tools/langchain_adapter.py | Treats openclaw_tool as an operation-capable built-in tool. |
| api/app/core/tools/custom/base.py | Attempts to parse stringified OpenAPI schema content during custom tool initialization. |
| api/app/core/tools/configs/builtin/openclaw_tool.json | Adds per-tool built-in config file for OpenClaw. |
| api/app/core/tools/configs/builtin_tools.json | Adds OpenClaw entry to system-level built-in tool discovery/config schema. |
| api/app/core/tools/builtin/operation_tool.py | Adds OpenClaw operation-specific parameter sets for operation-wrapped execution. |
| api/app/core/tools/builtin/openclaw_tool.py | Implements the new OpenClaw built-in tool (HTTP execution, multimodal input building, image handling, response extraction). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| elif operation == "image_understand": | ||
| return [ | ||
| { | ||
| "name": "message", | ||
| "type": "string", | ||
| "description": "发送给 OpenClaw 的图片理解任务,应描述需要对图片做什么(如描述内容、提取文字、分析信息)", | ||
| "required": True | ||
| }, | ||
| { | ||
| "name": "image_url", | ||
| "type": "string", | ||
| "description": "必须提供,要分析的图片 URL 或 base64 data URI", | ||
| "required": True | ||
| } | ||
| ] |
There was a problem hiding this comment.
_get_openclaw_tool_params() 对 image_understand 也把 image_url 标记为必填,但 OpenClawTool 实现支持从 uploaded_files 提取图片;两者会造成能力/参数声明不一致(尤其是方法发现/前端表单/模型工具选择阶段会认为必须显式传 image_url)。建议把必填改为“有上传图片时可省略”,并在执行阶段做兜底校验与报错。
There was a problem hiding this comment.
同“image_understand 操作这里把 image_url 设为必填会与 OpenClawTool 的“优先从 uploaded_files 提取图片”的实现冲突:如果用户上传了图片但模型未显式传 image_url,参数校验会在执行前失败。建议将 image_url 设为非必填,并在 OpenClawTool.execute 中在“上传文件/参数均无图片”时再返回明确错误。”问题
…schema parsing The _parse_openapi_schema() method already handles string-to-dict conversion internally, so the duplicate json.loads in __init__ was unnecessary.
…_tool OperationTool wraps builtin tools for multi-operation support but did not forward set_runtime_context, causing OpenClawTool to miss uploaded_files and conversation_id when used with operation routing.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| elif operation == "image_understand": | ||
| return [ | ||
| { | ||
| "name": "message", | ||
| "type": "string", | ||
| "description": "发送给 OpenClaw 的图片理解任务,应描述需要对图片做什么(如描述内容、提取文字、分析信息)", | ||
| "required": True | ||
| }, | ||
| { | ||
| "name": "image_url", | ||
| "type": "string", | ||
| "description": "必须提供,要分析的图片 URL 或 base64 data URI", | ||
| "required": True | ||
| } | ||
| ] |
There was a problem hiding this comment.
同“image_understand 操作这里把 image_url 设为必填会与 OpenClawTool 的“优先从 uploaded_files 提取图片”的实现冲突:如果用户上传了图片但模型未显式传 image_url,参数校验会在执行前失败。建议将 image_url 设为非必填,并在 OpenClawTool.execute 中在“上传文件/参数均无图片”时再返回明确错误。”问题
| "Authorization": f"Bearer {self._api_key}", | ||
| "Content-Type": "application/json", | ||
| "x-openclaw-agent-id": self._agent_id | ||
| } |
…d operation variable Change image_url from required to optional in both operation_tool.py and tool_service.py for image_understand operation, avoiding parameter validation conflict with uploaded_files priority logic. Remove unused operation variable from OpenClawTool.execute().
This pull request introduces the new
OpenClawTool, integrating a remote OpenClaw Agent service as a built-in tool with multimodal (text and image) capabilities. It adds configuration, parameter handling, and runtime context injection for OpenClaw, and ensures the tool is properly registered and initialized for tenants. Several core services are updated to support runtime context injection for tools that require it, and the overall tool configuration and discovery mechanisms are extended for OpenClaw.The most important changes are:
OpenClawTool Implementation and Configuration
OpenClawToolinopenclaw_tool.py, supporting multimodal (text/image) input, runtime context injection, connection testing, and robust execution with error handling. The tool can process user-uploaded images and interact with the OpenClaw Agent via HTTP.OpenClawToolin the tool service import map and created its configuration file (openclaw_tool.json), as well as its entry inbuiltin_tools.jsonfor system-level discovery and initialization.Parameter Handling and Operation Support
print_task,device_query,image_understand).openclaw_toolas a multi-operation tool.Runtime Context Injection
set_runtime_context, ensuring OpenClaw and similar tools receive necessary data during execution.Tool Initialization and Discovery
Custom Tool Schema Parsing
Summary by Sourcery
将 OpenClaw 远程 Agent 集成为内置多模态工具,并改进在聊天和草稿服务中的工具处理、初始化和上下文注入。
新功能:
OpenClawTool,并在工具服务和内置配置中注册该工具。operation_tool包装器中引入 OpenClaw 专用的操作参数处理逻辑,并在 Langchain 适配器中将其识别为多操作工具。缺陷修复:
增强优化:
Original summary in English
Summary by Sourcery
Integrate the OpenClaw remote Agent as a built-in multimodal tool and enhance tool handling, initialization, and context injection across chat and draft services.
New Features:
Bug Fixes:
Enhancements: