mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-10 11:50:45 +08:00
重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
This commit is contained in:
parent
ba3836f00f
commit
4a23b715a2
498 changed files with 2760 additions and 2200 deletions
|
|
@ -1,211 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _load_module(module_name: str, path: Path):
|
||||
spec = importlib.util.spec_from_file_location(module_name, str(path))
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"cannot load module: {module_name} from {path}")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def _mount_webhooks_package():
|
||||
root = Path(__file__).resolve().parents[1] / "extensions" / "webhooks"
|
||||
pkg_root = types.ModuleType("ocx")
|
||||
pkg_root.__path__ = [str(root.parent)]
|
||||
sys.modules["ocx"] = pkg_root
|
||||
pkg = types.ModuleType("ocx.webhooks")
|
||||
pkg.__path__ = [str(root)]
|
||||
sys.modules["ocx.webhooks"] = pkg
|
||||
|
||||
runtime_api = _load_module("ocx.webhooks.runtime_api", root / "runtime_api.py")
|
||||
config = _load_module("ocx.webhooks.config", root / "config.py")
|
||||
http = _load_module("ocx.webhooks.http", root / "http.py")
|
||||
return runtime_api, config, http
|
||||
|
||||
|
||||
def test_webhooks_config_resolve_and_conflict() -> None:
|
||||
_, config, _ = _mount_webhooks_package()
|
||||
rows = config.resolve_webhooks_plugin_config(
|
||||
plugin_config={
|
||||
"routes": {
|
||||
"r1": {
|
||||
"sessionKey": "s1",
|
||||
"secret": "abc",
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
assert len(rows) == 1
|
||||
assert rows[0].path == "/plugins/webhooks/r1"
|
||||
assert rows[0].controller_id == "webhooks/r1"
|
||||
|
||||
try:
|
||||
config.resolve_webhooks_plugin_config(
|
||||
plugin_config={
|
||||
"routes": {
|
||||
"a": {"path": "/x", "sessionKey": "s1", "secret": "k1"},
|
||||
"b": {"path": "/x", "sessionKey": "s2", "secret": "k2"},
|
||||
}
|
||||
}
|
||||
)
|
||||
except ValueError as exc:
|
||||
assert "conflicts" in str(exc)
|
||||
else:
|
||||
raise AssertionError("expected conflict error")
|
||||
|
||||
|
||||
class _TaskFlow:
|
||||
def create_managed(self, **kwargs):
|
||||
return {"flowId": "f1", "goal": kwargs.get("goal"), "revision": 0, "status": "queued", "createdAt": 1, "updatedAt": 1}
|
||||
|
||||
def get(self, flow_id: str):
|
||||
return {"flowId": flow_id, "revision": 1, "status": "running", "createdAt": 1, "updatedAt": 2}
|
||||
|
||||
def list(self):
|
||||
return [self.get("f1")]
|
||||
|
||||
def request_cancel(self, **kwargs):
|
||||
if kwargs.get("flow_id") == "missing":
|
||||
return {"applied": False, "code": "not_found"}
|
||||
return {"applied": True, "flow": self.get(kwargs.get("flow_id", "f1"))}
|
||||
|
||||
def resume(self, **kwargs):
|
||||
flow_id = kwargs.get("flow_id")
|
||||
if flow_id == "conflict":
|
||||
return {"applied": False, "code": "revision_conflict", "current": self.get("f1")}
|
||||
return {"applied": True, "flow": self.get(flow_id or "f1")}
|
||||
|
||||
def finish(self, **kwargs):
|
||||
return {"applied": True, "flow": self.get(kwargs.get("flow_id", "f1"))}
|
||||
|
||||
def fail(self, **kwargs):
|
||||
return {"applied": True, "flow": self.get(kwargs.get("flow_id", "f1"))}
|
||||
|
||||
def set_waiting(self, **kwargs):
|
||||
return {"applied": True, "flow": self.get(kwargs.get("flow_id", "f1"))}
|
||||
|
||||
def cancel(self, **kwargs):
|
||||
flow_id = kwargs.get("flow_id")
|
||||
if flow_id == "pending":
|
||||
return {"found": True, "cancelled": False, "reason": "One or more child tasks are still active."}
|
||||
return {"found": True, "cancelled": True, "flow": self.get(flow_id or "f1"), "tasks": []}
|
||||
|
||||
def run_task(self, **kwargs):
|
||||
flow_id = kwargs.get("flow_id")
|
||||
if flow_id == "missing":
|
||||
return {"created": False, "found": False, "reason": "not found"}
|
||||
if flow_id == "terminal":
|
||||
return {"created": False, "found": True, "reason": "Flow is already completed."}
|
||||
return {
|
||||
"created": True,
|
||||
"flow": self.get(flow_id or "f1"),
|
||||
"task": {
|
||||
"taskId": "t1",
|
||||
"runtime": kwargs.get("runtime", "subagent"),
|
||||
"scopeKind": "flow",
|
||||
"task": kwargs.get("task", ""),
|
||||
"status": "queued",
|
||||
"deliveryStatus": "queued",
|
||||
"notifyPolicy": "done_only",
|
||||
"createdAt": 1,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_webhooks_handler_auth_and_actions() -> None:
|
||||
_, _, http = _mount_webhooks_package()
|
||||
target = http.TaskFlowWebhookTarget(
|
||||
route_id="r1",
|
||||
path="/hooks/a",
|
||||
secret_input="sekret",
|
||||
secret_config_path="plugins.entries.webhooks.routes.r1.secret",
|
||||
default_controller_id="webhooks/r1",
|
||||
task_flow=_TaskFlow(),
|
||||
)
|
||||
handler = http.create_taskflow_webhook_request_handler(cfg={}, targets_by_path={"/hooks/a": [target]})
|
||||
|
||||
unauthorized = handler({"path": "/hooks/a", "headers": {}, "json": {"action": "list_flows"}})
|
||||
assert unauthorized["ok"] is False
|
||||
assert unauthorized["code"] == "unauthorized"
|
||||
|
||||
created = handler(
|
||||
{
|
||||
"path": "/hooks/a",
|
||||
"headers": {"authorization": "Bearer sekret"},
|
||||
"json": {"action": "create_flow", "goal": "ship it"},
|
||||
}
|
||||
)
|
||||
assert created["ok"] is True
|
||||
assert created["statusCode"] == 200
|
||||
assert created["result"]["flow"]["flowId"] == "f1"
|
||||
|
||||
missing = handler(
|
||||
{
|
||||
"path": "/hooks/a",
|
||||
"headers": {"x-openclaw-webhook-secret": "sekret"},
|
||||
"json": {"action": "request_cancel", "flowId": "missing", "expectedRevision": 1},
|
||||
}
|
||||
)
|
||||
assert missing["ok"] is False
|
||||
assert missing["statusCode"] == 404
|
||||
assert missing["code"] == "not_found"
|
||||
|
||||
|
||||
def test_webhooks_handler_action_status_mappings() -> None:
|
||||
_, _, http = _mount_webhooks_package()
|
||||
target = http.TaskFlowWebhookTarget(
|
||||
route_id="r2",
|
||||
path="/hooks/b",
|
||||
secret_input="sekret2",
|
||||
secret_config_path="plugins.entries.webhooks.routes.r2.secret",
|
||||
default_controller_id="webhooks/r2",
|
||||
task_flow=_TaskFlow(),
|
||||
)
|
||||
handler = http.create_taskflow_webhook_request_handler(cfg={}, targets_by_path={"/hooks/b": [target]})
|
||||
|
||||
def _call(payload: dict):
|
||||
return handler(
|
||||
{
|
||||
"path": "/hooks/b",
|
||||
"headers": {"authorization": "Bearer sekret2"},
|
||||
"json": payload,
|
||||
}
|
||||
)
|
||||
|
||||
resume_conflict = _call({"action": "resume_flow", "flowId": "conflict", "expectedRevision": 1})
|
||||
assert resume_conflict["ok"] is False
|
||||
assert resume_conflict["statusCode"] == 409
|
||||
assert resume_conflict["code"] == "revision_conflict"
|
||||
|
||||
cancel_pending = _call({"action": "cancel_flow", "flowId": "pending"})
|
||||
assert cancel_pending["ok"] is True
|
||||
assert cancel_pending["statusCode"] == 202
|
||||
assert cancel_pending["code"] == "cancel_pending"
|
||||
|
||||
run_task_missing = _call(
|
||||
{"action": "run_task", "flowId": "missing", "runtime": "subagent", "task": "ping"}
|
||||
)
|
||||
assert run_task_missing["ok"] is False
|
||||
assert run_task_missing["statusCode"] == 404
|
||||
assert run_task_missing["code"] == "not_found"
|
||||
|
||||
run_task_terminal = _call(
|
||||
{"action": "run_task", "flowId": "terminal", "runtime": "subagent", "task": "ping"}
|
||||
)
|
||||
assert run_task_terminal["ok"] is False
|
||||
assert run_task_terminal["statusCode"] == 409
|
||||
assert run_task_terminal["code"] == "terminal"
|
||||
|
||||
run_task_ok = _call({"action": "run_task", "flowId": "f1", "runtime": "subagent", "task": "ping"})
|
||||
assert run_task_ok["ok"] is True
|
||||
assert run_task_ok["statusCode"] == 200
|
||||
assert run_task_ok["result"]["created"] is True
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue