Add a new MCP tool that bridges the gap between fully automatic
evolution (inside execute_task) and manual repair (fix_skill).
scan_evolution_opportunities lets host agents proactively inspect
which skills could benefit from evolution by:
1. Loading execution analyses marked as evolution candidates
2. Enriching each with SkillRecord usage statistics (completion
rate, fallback rate, effective rate)
3. Returning a structured JSON report with prioritized suggestions
This enables an "agent-in-the-loop" evolution pattern for all host
agents (Claude Code, Codex, nanobot, Cursor, etc.): periodic scan,
review report, selectively apply fixes.
Closes#24
Made-with: Cursor
The original change removed atomacos from dependencies but left it
bundled in the same try/except as AppKit. Without atomacos installed,
the ImportError made MACOS_LIBS_AVAILABLE=False, silently disabling
every macOS feature (screenshots, window control, etc.) even though
they never use atomacos.
- Split import into independent try/except blocks
- Guard get_accessibility_tree() with ATOMACOS_AVAILABLE
- Condense redundant comments in requirements files
- Tighten README note into a blockquote
Error handlers in execute_task, fix_skill, and upload_skill returned
traceback.format_exc() to MCP clients, exposing internal file paths,
code structure, and potentially sensitive details. The full traceback
is already logged server-side via logger.error(exc_info=True).
Remove the traceback field from client-facing error responses and
clean up the unused traceback import.
Closes#19
Made-with: Cursor
When the LLM returns a short tool name that doesn't match the deduped
key in tool_map, the fallback scan correctly resolves tool_obj via
schema.name. However the execution branch still checked
`tool_name not in tool_map` and passed `tool_map[tool_name]`, so
fallback-resolved tools were never executed.
Change the condition to check `tool_obj is None` and pass `tool_obj`
directly to _execute_tool_call.
Closes#15
Made-with: Cursor
__main__.py calls Logger.set_level(args.log_level) when --log-level
is passed, but the method did not exist on Logger, causing an
AttributeError. Add set_level(level: str) that resolves the name
to a logging constant and reconfigures via configure(force=True).
Closes#13
Made-with: Cursor
ErrorCode is a str Enum whose members are not callable. Calling
`raise ErrorCode.SESSION_NOT_FOUND(name)` produces a TypeError
instead of the intended session-not-found error. Replace all 5
occurrences with `raise GroundingError(..., code=ErrorCode.SESSION_NOT_FOUND)`.
Closes#11
Made-with: Cursor
- Forward resolved max_iterations to grounding agent in no-skill execution path
- Use hash-based workflow ID to prevent collision across roots and separator ambiguity
Co-authored-by: wul48527-code <wul48527-code@users.noreply.github.com>
The previous __-joined scheme was not injective: a directory named
a__b and a nested path a/b both mapped to the same ID. Use a sha256
hash suffix of the resolved path instead, which is collision-free
and keeps the dir name as a human-readable prefix.
Added regression test for separator collision case.
workflow_dir.name was used as the discovery key and API ID, so two
different WORKFLOW_ROOTS containing a leaf directory with the same
name would silently drop one. Use root name + relative path joined
with __ as a stable unique ID instead.
Co-authored-by: wul48527-code <wul48527-code@users.noreply.github.com>
Without this, the no-skill path ignores the resolved max_iterations
and uses whatever default the agent has, instead of the configured
grounding_max_iterations value.
Co-authored-by: wul48527-code <wul48527-code@users.noreply.github.com>
- Add resolve() + is_relative_to() check in _extract_zip() to block
nested traversal entries like nested/../../escape.txt
- Sanitize server-provided skill name in import_skill() to prevent
directory escape via malicious record metadata
- Add 6 regression tests covering both attack vectors
Closes#17
Co-authored-by: LeftX <xzq-xu@users.noreply.github.com>
Versions 1.82.7 and 1.82.8 of litellm were published on March 24, 2026
and contained malicious code that exfiltrated credentials (SSH keys,
cloud credentials, .env files, API keys) to an attacker-controlled domain.
Pin the dependency to >=1.70.0,<1.82.7 in both pyproject.toml and
requirements.txt as a stopgap until litellm can be replaced with direct
provider SDK calls.
See: https://github.com/HKUDS/OpenSpace/issues/31
Ref: PYSEC-2026-2, BerriAI/litellm#24521
Fixes#29. When a skill is already registered, register_skill_dir()
returned None, which caused fix_skill() to incorrectly report a failure.
Now returns the existing SkillMeta instead of None when the skill_id
is already present in the registry, making register_skill_dir() truly
idempotent as its callers (fix_skill, _auto_register_skill_dirs) expect.