mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-22 12:42:22 +02:00
130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
"""WorkflowExportable: shares a scheduled-task/workflow recipe (steps, schedule
|
|
shape, actions, model). The workflow store lives on the eric/workflow branch and
|
|
is NOT on eric/dev yet, so every store touch is lazy: on a build without it,
|
|
export finds nothing and import fails with a clear message, and the module still
|
|
imports cleanly. It lights up the moment the workflow forward-port lands.
|
|
|
|
Safety: an imported workflow must never silently start running on someone else's
|
|
machine, so the schedule is forced off on import (the importer re-arms it). The
|
|
sharer's phone numbers (text/call escalation) are stripped as PII, and run
|
|
history / session + dashboard linkage are dropped."""
|
|
from __future__ import annotations
|
|
|
|
from backend.apps.swarm.exportable import DepRef, ExportContext, RemapTable
|
|
from backend.apps.swarm.models import EntityType, Requirement, RequirementKind
|
|
|
|
P_BUILTIN_MODES = {"agent", "ask", "plan", "view-builder", "skill-builder"}
|
|
|
|
# Run-state, machine-linkage, and identifiers that must not ride along.
|
|
P_DROP_FIELDS = {
|
|
"id", "source_session_id", "dashboard_id", "edit_agent_session_id",
|
|
"last_run_at", "last_run_status", "last_run_id", "next_run_at",
|
|
"created_at", "updated_at", "cost_cap_usd_monthly",
|
|
}
|
|
|
|
|
|
def sanitize_workflow(data: dict) -> dict:
|
|
out = {k: v for k, v in data.items() if k not in P_DROP_FIELDS}
|
|
sched = dict(out.get("schedule") or {})
|
|
if sched:
|
|
sched["enabled"] = False
|
|
sched["runs_count"] = 0
|
|
sched["next_run_at"] = None
|
|
sched["ends_at"] = None
|
|
out["schedule"] = sched
|
|
perms = []
|
|
for tier in out.get("permissions") or []:
|
|
t = dict(tier)
|
|
t["phone"] = None # the sharer's number; the importer sets their own
|
|
perms.append(t)
|
|
if perms:
|
|
out["permissions"] = perms
|
|
return out
|
|
|
|
|
|
class WorkflowExportable:
|
|
type = EntityType.workflow
|
|
|
|
def __init__(self, local_id: str, name: str, data: dict):
|
|
self.local_id = local_id
|
|
self.name = name
|
|
self.p_data = data
|
|
|
|
@classmethod
|
|
def load(cls, local_id: str) -> "WorkflowExportable | None":
|
|
store = p_store()
|
|
if store is None:
|
|
return None
|
|
wf = store.get_workflow(local_id)
|
|
if wf is None:
|
|
return None
|
|
data = wf.model_dump(mode="json")
|
|
return cls(local_id, data.get("title") or "Untitled workflow", data)
|
|
|
|
def serialize(self, ctx: ExportContext) -> dict:
|
|
return sanitize_workflow(self.p_data)
|
|
|
|
def files(self) -> dict[str, bytes]:
|
|
return {}
|
|
|
|
def dependencies(self) -> list[DepRef]:
|
|
return []
|
|
|
|
def requirements(self) -> list[Requirement]:
|
|
reqs: list[Requirement] = []
|
|
for name in (self.p_data.get("actions") or {}).get("configured_sets") or []:
|
|
reqs.append(Requirement(
|
|
kind=RequirementKind.mcp_action, key=name, label=name,
|
|
detail="This workflow uses this action.",
|
|
))
|
|
mode = self.p_data.get("mode") or "agent"
|
|
if mode in P_BUILTIN_MODES and mode != "agent":
|
|
reqs.append(Requirement(
|
|
kind=RequirementKind.builtin_mode, key=mode, label=f"{mode} mode",
|
|
detail="A built-in mode this workflow runs in.",
|
|
))
|
|
provider = self.p_data.get("provider") or "anthropic"
|
|
reqs.append(Requirement(
|
|
kind=RequirementKind.api_key, key=provider, label=f"A {provider} model",
|
|
detail="Set up this provider to run the workflow.",
|
|
))
|
|
return reqs
|
|
|
|
@classmethod
|
|
def import_(cls, payload: dict, files: dict[str, bytes], remap: RemapTable) -> str:
|
|
store = p_store()
|
|
model = p_model()
|
|
if store is None or model is None:
|
|
from backend.apps.swarm.ziputil import BundleError
|
|
raise BundleError("this build doesn't support workflows yet; please update OpenSwarm")
|
|
clean = sanitize_workflow(payload)
|
|
clean.pop("id", None) # fresh id via the model's default_factory
|
|
wf = model(**clean)
|
|
store.save_workflow(wf)
|
|
return wf.id
|
|
|
|
@classmethod
|
|
def rollback(cls, local_id: str) -> None:
|
|
store = p_store()
|
|
if store is not None:
|
|
try:
|
|
store.delete_workflow(local_id)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def p_store():
|
|
try:
|
|
from backend.apps.workflows import storage
|
|
return storage
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def p_model():
|
|
try:
|
|
from backend.apps.workflows.models import Workflow
|
|
return Workflow
|
|
except Exception:
|
|
return None
|