Custom EnvPlugin DevelopmentΒΆ
Create plugins that extend OWA with your own functionality.
OWA's Env: MCP for Desktop Agents
Just as Model Context Protocol (MCP) is the "USB-C of LLMs", OWA's Env is the "USB-C of desktop agents" - a universal interface for native desktop automation.
Plugin DiscoveryΒΆ
How It WorksΒΆ
OWA automatically discovers plugins using Python's Entry Points system:
flowchart TD
A[Your Plugin Package] --> B[pyproject.toml]
B --> C[Entry Point Declaration]
C --> E[Plugin Specification]
E --> F[Component Definitions]
F --> G[Callables]
F --> H[Listeners]
F --> I[Runnables]
J[OWA Core Registry] -.->|Plugin Discovery| C
G -.-> J
H -.-> J
I -.-> J
J --> K[Available to Users]
K --> L["CALLABLES['namespace/name']"]
K --> M["LISTENERS['namespace/name']"]
K --> N["RUNNABLES['namespace/name']"]
style A fill:#e1f5fe
style J fill:#f3e5f5
style K fill:#e8f5e8
Discovery ProcessΒΆ
- Entry Point Scanning - OWA scans all installed packages for
"owa.env.plugins"
entry points - Plugin Spec Loading - Loads and validates each
PluginSpec
object - Lazy Registration - Registers component metadata without importing actual code
- On-Demand Loading - Components are imported only when first accessed
Quick Start: Your First Plugin in 5 MinutesΒΆ
Step 1: Copy the TemplateΒΆ
# Copy the example plugin as your starting point
cp -r projects/owa-env-example my-first-plugin
cd my-first-plugin
Step 2: Make It YoursΒΆ
Edit pyproject.toml
to change the plugin name:
[project.entry-points."owa.env.plugins"]
myfirst = "owa.env.plugins.myfirst:plugin_spec" # Changed from 'example'
Edit owa/env/plugins/example.py
and change the namespace:
plugin_spec = PluginSpec(
namespace="myfirst", # Changed from 'example'
version="0.1.0",
description="My first OWA plugin",
# ... rest stays the same
)
Step 3: Install and TestΒΆ
# Install your plugin
pip install -e .
# Verify OWA discovered it
owl env list myfirst
# Test a component
python -c "from owa.core import CALLABLES; print(CALLABLES['myfirst/add'](2, 3))"
π Congratulations!
You just created your first OWA plugin. Your components are now available to any OWA user or application.
Component TypesΒΆ
CallablesΒΆ
Functions for immediate results:
def get_weather(city: str) -> dict:
return {"city": city, "temp": 25, "condition": "sunny"}
# Usage: CALLABLES["myplugin/weather"]("New York")
ListenersΒΆ
Event monitoring with callbacks (inherits from Runnable):
from owa.core import Listener
class FileWatcher(Listener):
def on_configure(self, callback, watch_folder, **kwargs):
self.callback = callback
self.watch_folder = watch_folder
def start(self):
# Monitor folder, call self.callback(event) on changes
pass
def stop(self):
# Stop and cleanup
pass
RunnablesΒΆ
Background processes with start/stop control:
from owa.core import Runnable
class DataCollector(Runnable):
def on_configure(self, output_file, interval=60, **kwargs):
self.output_file = output_file
self.interval = interval
def loop(self, *, stop_event):
while not stop_event.is_set():
# Do work
stop_event.wait(self.interval)
Plugin SpecificationΒΆ
StructureΒΆ
from owa.core.plugin_spec import PluginSpec
plugin_spec = PluginSpec(
namespace="myplugin",
version="0.1.0",
description="My custom plugin",
components={
"callables": {
"weather": "owa.env.myplugin.api:get_weather",
"calculate": "owa.env.myplugin.math:add_numbers",
},
"listeners": {
"file_watcher": "owa.env.myplugin.watchers:FileWatcher",
},
"runnables": {
"data_collector": "owa.env.myplugin.workers:DataCollector",
}
}
)
Key ElementsΒΆ
- namespace: Unique identifier for your plugin
- components: Maps component names to import paths
- Import format:
"module.path:object_name"
Reference ImplementationΒΆ
Study the working example: projects/owa-env-example
Getting StartedΒΆ
cd projects/owa-env-example
pip install -e .
owl env list example
# Test it works
python -c "
from owa.core import CALLABLES
result = CALLABLES['example/add'](2, 3)
print(f'2 + 3 = {result}')
"
What It ShowsΒΆ
- File organization and project structure
- All three component types with working examples
- Proper entry point configuration
- Complete test suite
Project Structure OptionsΒΆ
Choose based on your needs
Best PracticesΒΆ
Naming ConventionsΒΆ
- Namespace:
desktop
,my_company
(lowercase, underscores) - Components:
mouse.click
,file.read
(dots for hierarchy) - Package:
owa-env-yourplugin
Error HandlingΒΆ
def safe_function(param: str) -> dict:
try:
result = do_work(param)
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}
Resource ManagementΒΆ
class MyRunnable(Runnable):
def loop(self, *, stop_event):
try:
resource = acquire_resource()
while not stop_event.is_set():
use_resource(resource)
stop_event.wait(0.1) # Small delay
finally:
release_resource(resource)
TroubleshootingΒΆ
Common Issues and Solutions
Symptoms: owl env list
doesn't show your plugin
Debug steps:
-
Verify installation:
-
Check entry points:
-
Test plugin spec import:
Common causes:
- Plugin not installed (
pip install -e .
) - Entry point name conflicts with existing plugin
- Incorrect entry point path in
pyproject.toml
Symptoms: Validation fails with import errors
Debug command:
Common causes & solutions:
Problem | Solution |
---|---|
Missing dependencies | Add them to pyproject.toml dependencies |
Wrong import paths | Check module.path:object_name format |
Circular imports | Keep plugin spec separate from implementation |
Module not found | Ensure module is importable after installation |
"Component not callable" errors:
# β Wrong - points to module
"callables": {
"bad": "mymodule.utils"
}
# β
Correct - points to function
"callables": {
"good": "mymodule.utils:my_function"
}
Listener/Runnable doesn't work:
# β
Correct structure
from owa.core import Listener
class MyListener(Listener): # Must inherit
def on_configure(self, callback, **kwargs): # Must implement
self.callback = callback
# Your setup code
Common issues:
- Not inheriting from
owa.core.Listener
orowa.core.Runnable
- Missing
on_configure()
method - Not calling
super().__init__()
in custom__init__
Run these commands to diagnose issues:
# Check if OWA can see your plugin
owl env list yourplugin
# Validate plugin specification
owl env docs --validate yourplugin --strict
# Check for namespace conflicts
owl env stats --namespaces
# Test component loading manually
python -c "
from owa.core import CALLABLES, LISTENERS, RUNNABLES
print('Available namespaces:')
namespaces = set()
for key in list(CALLABLES.keys()) + list(LISTENERS.keys()) + list(RUNNABLES.keys()):
namespaces.add(key.split('/')[0])
for ns in sorted(namespaces):
print(f' - {ns}')
"
Still having issues?
- Check the OWA GitHub Issues
- Look at working examples in
projects/owa-env-*
- Ensure you're using compatible versions of dependencies
PublishingΒΆ
PyPI (Recommended):
GitHub/Git:
Local Development:
Next StepsΒΆ
Topic | Description |
---|---|
Environment Guide | Complete system overview and advanced usage patterns |
Built-in Plugins | Learn from standard, desktop, and GStreamer implementations |
CLI Tools | Plugin management and exploration commands |