77 branches : [main]
88
99jobs :
10+ # ── Detect changes ──────────────────────────────────────────
11+ changes :
12+ runs-on : ubuntu-latest
13+ outputs :
14+ openclaw : ${{ steps.filter.outputs.openclaw }}
15+ hermes : ${{ steps.filter.outputs.hermes }}
16+ steps :
17+ - uses : actions/checkout@v4
18+ - uses : dorny/paths-filter@v3
19+ id : filter
20+ with :
21+ filters : |
22+ openclaw:
23+ - 'openclaw-plugin/**'
24+ hermes:
25+ - 'hermes-plugin/**'
26+
27+ # ── TypeScript (openclaw-plugin) ──────────────────────────────
1028 typecheck :
1129 runs-on : ubuntu-latest
30+ needs : changes
31+ if : needs.changes.outputs.openclaw == 'true' || github.event_name == 'push'
1232 steps :
1333 - uses : actions/checkout@v4
1434
2949 build :
3050 runs-on : ubuntu-latest
3151 needs : typecheck
52+ if : needs.typecheck.result == 'success'
3253 steps :
3354 - uses : actions/checkout@v4
3455
5374 lint :
5475 runs-on : ubuntu-latest
5576 needs : typecheck
77+ if : needs.typecheck.result == 'success'
5678 steps :
5779 - uses : actions/checkout@v4
5880
@@ -69,3 +91,109 @@ jobs:
6991 - name : Lint
7092 working-directory : openclaw-plugin
7193 run : npx tsc --noEmit --pretty 2>&1 | grep "error TS" && exit 1 || echo "No errors"
94+
95+ # ── Python (hermes-plugin) ────────────────────────────────────
96+ python-test :
97+ runs-on : ubuntu-latest
98+ needs : changes
99+ if : needs.changes.outputs.hermes == 'true' || github.event_name == 'push'
100+ defaults :
101+ run :
102+ working-directory : hermes-plugin
103+ steps :
104+ - uses : actions/checkout@v4
105+
106+ - name : Set up Python
107+ uses : actions/setup-python@v5
108+ with :
109+ python-version : " 3.11"
110+
111+ - name : Syntax check
112+ run : |
113+ python -m py_compile services/workflow_service.py
114+ python -m py_compile services/scheduler.py
115+ python -m py_compile store/sqlite_store.py
116+ python -m py_compile store/migrations.py
117+ python -m py_compile core/dag.py
118+ python -m py_compile core/fsm.py
119+ python -m py_compile memory/working_memory.py
120+ python -m py_compile memory/episodic_memory.py
121+ python -m py_compile memory/semantic_memory.py
122+ python -m py_compile models.py
123+ python -m py_compile config.py
124+
125+ - name : Import check
126+ run : |
127+ python -c "
128+ from store.sqlite_store import SQLiteStore
129+ from services.workflow_service import WorkflowService
130+ from services.scheduler import Scheduler
131+ from core.dag import build_dag, get_ready_steps
132+ from core.fsm import transition, can_transition
133+ from memory.working_memory import WorkingMemory
134+ from memory.episodic_memory import EpisodicMemory
135+ from memory.semantic_memory import SemanticMemory
136+ from models import WorkflowState, StepState
137+ print('All imports OK')
138+ "
139+
140+ - name : Run integration tests
141+ run : |
142+ cat > /tmp/test_soloflow.py << 'TESTEOF'
143+ import sys, asyncio, tempfile
144+ sys.path.insert(0, ".")
145+ from pathlib import Path
146+ from store.sqlite_store import SQLiteStore
147+ from services.workflow_service import WorkflowService
148+ from services.scheduler import Scheduler
149+
150+ passed = failed = 0
151+ def check(name, cond, detail=""):
152+ global passed, failed
153+ if cond: passed += 1; print(f" ✅ {name}")
154+ else: failed += 1; print(f" ❌ {name} {detail}")
155+
156+ async def main():
157+ tmpdir = tempfile.mkdtemp()
158+ store = SQLiteStore(Path(tmpdir) / "test.db")
159+ store.initialize()
160+ ws = WorkflowService(store)
161+ ws.set_scheduler(Scheduler(store, ws))
162+
163+ steps = [
164+ {"id": "a", "name": "A", "description": "", "discipline": "quick", "prompt": "Do A"},
165+ {"id": "b", "name": "B", "description": "", "discipline": "quick", "prompt": "Do B"},
166+ {"id": "c", "name": "C", "description": "", "discipline": "quick", "prompt": "Do C"},
167+ ]
168+ wf = await ws.create_workflow("test", "Test", steps, [("a","b"),("b","c")])
169+ check("created", wf["state"] == "draft")
170+ check("3 steps", len(wf["steps"]) == 3)
171+
172+ await ws.start_workflow(wf["id"])
173+ ready = await ws.get_ready_steps(wf["id"])
174+ check("a ready", "a" in ready)
175+
176+ await ws.advance_step(wf["id"], "a", result="A done")
177+ await ws.advance_step(wf["id"], "b", result="B done")
178+ await ws.advance_step(wf["id"], "c", result="C done")
179+ status = await ws.get_workflow_status(wf["id"])
180+ check("completed", status["state"] == "completed")
181+ check("progress 3/3", status["progress"]["completed"] == 3)
182+
183+ wf2 = await ws.create_workflow("test2", "Test2",
184+ [{"id": "x", "name": "X", "description": "", "discipline": "quick", "prompt": "X"}], [])
185+ await ws.start_workflow(wf2["id"])
186+ await ws.cancel_workflow(wf2["id"])
187+ status2 = await ws.get_workflow_status(wf2["id"])
188+ check("cancelled", status2["state"] == "cancelled")
189+
190+ all_wfs = await ws.list_workflows()
191+ check("2 workflows", len(all_wfs) == 2)
192+ store.close()
193+
194+ print(f"\nResults: {passed} passed, {failed} failed")
195+ return failed
196+
197+ sys.exit(asyncio.run(main()))
198+ TESTEOF
199+ python /tmp/test_soloflow.py
0 commit comments