cognis-executor 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. cognis_executor-0.1.0/PKG-INFO +44 -0
  2. cognis_executor-0.1.0/README.md +6 -0
  3. cognis_executor-0.1.0/cognis/__init__.py +5 -0
  4. cognis_executor-0.1.0/cognis/api/__init__.py +3 -0
  5. cognis_executor-0.1.0/cognis/api/app.py +609 -0
  6. cognis_executor-0.1.0/cognis/api/common.py +149 -0
  7. cognis_executor-0.1.0/cognis/api/error_sanitizer.py +38 -0
  8. cognis_executor-0.1.0/cognis/api/executor_runtime.py +390 -0
  9. cognis_executor-0.1.0/cognis/api/executor_ws.py +258 -0
  10. cognis_executor-0.1.0/cognis/api/middleware.py +230 -0
  11. cognis_executor-0.1.0/cognis/api/models.py +1039 -0
  12. cognis_executor-0.1.0/cognis/api/routes/__init__.py +3 -0
  13. cognis_executor-0.1.0/cognis/api/routes/agents.py +620 -0
  14. cognis_executor-0.1.0/cognis/api/routes/artifacts.py +142 -0
  15. cognis_executor-0.1.0/cognis/api/routes/auth.py +283 -0
  16. cognis_executor-0.1.0/cognis/api/routes/channels.py +706 -0
  17. cognis_executor-0.1.0/cognis/api/routes/conversations.py +711 -0
  18. cognis_executor-0.1.0/cognis/api/routes/escalations.py +81 -0
  19. cognis_executor-0.1.0/cognis/api/routes/executors.py +182 -0
  20. cognis_executor-0.1.0/cognis/api/routes/images.py +264 -0
  21. cognis_executor-0.1.0/cognis/api/routes/notifications.py +130 -0
  22. cognis_executor-0.1.0/cognis/api/routes/schedules.py +398 -0
  23. cognis_executor-0.1.0/cognis/api/routes/secrets.py +53 -0
  24. cognis_executor-0.1.0/cognis/api/routes/sessions.py +110 -0
  25. cognis_executor-0.1.0/cognis/api/routes/settings.py +438 -0
  26. cognis_executor-0.1.0/cognis/api/routes/skills.py +356 -0
  27. cognis_executor-0.1.0/cognis/api/routes/system.py +147 -0
  28. cognis_executor-0.1.0/cognis/api/routes/tasks.py +585 -0
  29. cognis_executor-0.1.0/cognis/api/routes/tools.py +831 -0
  30. cognis_executor-0.1.0/cognis/api/routes/users.py +180 -0
  31. cognis_executor-0.1.0/cognis/api/routes/workflows.py +208 -0
  32. cognis_executor-0.1.0/cognis/api/runtime_support.py +1086 -0
  33. cognis_executor-0.1.0/cognis/api/serializers.py +351 -0
  34. cognis_executor-0.1.0/cognis/api/sse.py +244 -0
  35. cognis_executor-0.1.0/cognis/api/tool_inventory.py +72 -0
  36. cognis_executor-0.1.0/cognis/api/websocket.py +1734 -0
  37. cognis_executor-0.1.0/cognis/artifacts/__init__.py +3 -0
  38. cognis_executor-0.1.0/cognis/artifacts/store.py +570 -0
  39. cognis_executor-0.1.0/cognis/bootstrap.py +547 -0
  40. cognis_executor-0.1.0/cognis/channels/__init__.py +17 -0
  41. cognis_executor-0.1.0/cognis/channels/adapters/__init__.py +1 -0
  42. cognis_executor-0.1.0/cognis/channels/adapters/bluebubbles.py +430 -0
  43. cognis_executor-0.1.0/cognis/channels/adapters/discord.py +369 -0
  44. cognis_executor-0.1.0/cognis/channels/adapters/google_chat.py +208 -0
  45. cognis_executor-0.1.0/cognis/channels/adapters/irc.py +242 -0
  46. cognis_executor-0.1.0/cognis/channels/adapters/matrix.py +358 -0
  47. cognis_executor-0.1.0/cognis/channels/adapters/signal.py +1078 -0
  48. cognis_executor-0.1.0/cognis/channels/adapters/signal_cli_runtime.py +427 -0
  49. cognis_executor-0.1.0/cognis/channels/adapters/slack.py +328 -0
  50. cognis_executor-0.1.0/cognis/channels/adapters/telegram.py +363 -0
  51. cognis_executor-0.1.0/cognis/channels/adapters/whatsapp.py +305 -0
  52. cognis_executor-0.1.0/cognis/channels/delivery.py +666 -0
  53. cognis_executor-0.1.0/cognis/channels/factory.py +52 -0
  54. cognis_executor-0.1.0/cognis/channels/formatting.py +143 -0
  55. cognis_executor-0.1.0/cognis/channels/inbound.py +986 -0
  56. cognis_executor-0.1.0/cognis/channels/manager.py +664 -0
  57. cognis_executor-0.1.0/cognis/channels/pairing.py +300 -0
  58. cognis_executor-0.1.0/cognis/channels/protocol.py +475 -0
  59. cognis_executor-0.1.0/cognis/channels/registry.py +497 -0
  60. cognis_executor-0.1.0/cognis/channels/remote.py +279 -0
  61. cognis_executor-0.1.0/cognis/channels/signal_formatting.py +523 -0
  62. cognis_executor-0.1.0/cognis/cli/__init__.py +3 -0
  63. cognis_executor-0.1.0/cognis/cli/admin.py +268 -0
  64. cognis_executor-0.1.0/cognis/cli/executor.py +104 -0
  65. cognis_executor-0.1.0/cognis/cli/serve.py +20 -0
  66. cognis_executor-0.1.0/cognis/config.py +253 -0
  67. cognis_executor-0.1.0/cognis/core/__init__.py +3 -0
  68. cognis_executor-0.1.0/cognis/core/agent_loop.py +3939 -0
  69. cognis_executor-0.1.0/cognis/core/agent_registry.py +627 -0
  70. cognis_executor-0.1.0/cognis/core/artifact_maintenance.py +86 -0
  71. cognis_executor-0.1.0/cognis/core/attachment_utils.py +23 -0
  72. cognis_executor-0.1.0/cognis/core/commands.py +694 -0
  73. cognis_executor-0.1.0/cognis/core/compaction.py +280 -0
  74. cognis_executor-0.1.0/cognis/core/context.py +1425 -0
  75. cognis_executor-0.1.0/cognis/core/decision.py +337 -0
  76. cognis_executor-0.1.0/cognis/core/events.py +165 -0
  77. cognis_executor-0.1.0/cognis/core/executor_policy.py +79 -0
  78. cognis_executor-0.1.0/cognis/core/executor_resolution.py +147 -0
  79. cognis_executor-0.1.0/cognis/core/json_utils.py +370 -0
  80. cognis_executor-0.1.0/cognis/core/notifications.py +603 -0
  81. cognis_executor-0.1.0/cognis/core/prompts.py +218 -0
  82. cognis_executor-0.1.0/cognis/core/pruning.py +142 -0
  83. cognis_executor-0.1.0/cognis/core/remember_queue.py +99 -0
  84. cognis_executor-0.1.0/cognis/core/runtime.py +129 -0
  85. cognis_executor-0.1.0/cognis/core/scheduler.py +463 -0
  86. cognis_executor-0.1.0/cognis/core/session.py +917 -0
  87. cognis_executor-0.1.0/cognis/core/session_cache.py +638 -0
  88. cognis_executor-0.1.0/cognis/core/step_evaluator.py +259 -0
  89. cognis_executor-0.1.0/cognis/core/task_queue.py +877 -0
  90. cognis_executor-0.1.0/cognis/core/tool_exposure.py +241 -0
  91. cognis_executor-0.1.0/cognis/core/tool_output_store.py +498 -0
  92. cognis_executor-0.1.0/cognis/core/tool_router.py +804 -0
  93. cognis_executor-0.1.0/cognis/core/truncation.py +52 -0
  94. cognis_executor-0.1.0/cognis/core/turn_scheduler.py +1565 -0
  95. cognis_executor-0.1.0/cognis/core/workflow_engine.py +1864 -0
  96. cognis_executor-0.1.0/cognis/core/workflow_registry.py +328 -0
  97. cognis_executor-0.1.0/cognis/executor/__init__.py +3 -0
  98. cognis_executor-0.1.0/cognis/executor/__main__.py +99 -0
  99. cognis_executor-0.1.0/cognis/executor/channel_handler.py +260 -0
  100. cognis_executor-0.1.0/cognis/executor/inference.py +303 -0
  101. cognis_executor-0.1.0/cognis/executor/runner.py +1025 -0
  102. cognis_executor-0.1.0/cognis/logging.py +208 -0
  103. cognis_executor-0.1.0/cognis/main.py +41 -0
  104. cognis_executor-0.1.0/cognis/models/__init__.py +3 -0
  105. cognis_executor-0.1.0/cognis/models/agent.py +137 -0
  106. cognis_executor-0.1.0/cognis/models/artifact.py +56 -0
  107. cognis_executor-0.1.0/cognis/models/channel.py +275 -0
  108. cognis_executor-0.1.0/cognis/models/config.py +135 -0
  109. cognis_executor-0.1.0/cognis/models/delegation.py +19 -0
  110. cognis_executor-0.1.0/cognis/models/schedule.py +195 -0
  111. cognis_executor-0.1.0/cognis/models/session.py +134 -0
  112. cognis_executor-0.1.0/cognis/models/skill.py +219 -0
  113. cognis_executor-0.1.0/cognis/models/task.py +90 -0
  114. cognis_executor-0.1.0/cognis/models/tool.py +224 -0
  115. cognis_executor-0.1.0/cognis/models/workflow.py +295 -0
  116. cognis_executor-0.1.0/cognis/providers/__init__.py +3 -0
  117. cognis_executor-0.1.0/cognis/providers/auth/__init__.py +3 -0
  118. cognis_executor-0.1.0/cognis/providers/auth/jwt.py +140 -0
  119. cognis_executor-0.1.0/cognis/providers/auth/protocol.py +5 -0
  120. cognis_executor-0.1.0/cognis/providers/base.py +265 -0
  121. cognis_executor-0.1.0/cognis/providers/circuit_breaker.py +69 -0
  122. cognis_executor-0.1.0/cognis/providers/executor/__init__.py +3 -0
  123. cognis_executor-0.1.0/cognis/providers/executor/composite.py +160 -0
  124. cognis_executor-0.1.0/cognis/providers/executor/in_process.py +515 -0
  125. cognis_executor-0.1.0/cognis/providers/executor/protocol.py +21 -0
  126. cognis_executor-0.1.0/cognis/providers/executor/subprocess.py +158 -0
  127. cognis_executor-0.1.0/cognis/providers/executor/websocket.py +666 -0
  128. cognis_executor-0.1.0/cognis/providers/guardrails/__init__.py +3 -0
  129. cognis_executor-0.1.0/cognis/providers/guardrails/intaris.py +604 -0
  130. cognis_executor-0.1.0/cognis/providers/guardrails/protocol.py +5 -0
  131. cognis_executor-0.1.0/cognis/providers/llm/__init__.py +3 -0
  132. cognis_executor-0.1.0/cognis/providers/llm/inference_router.py +195 -0
  133. cognis_executor-0.1.0/cognis/providers/llm/litellm.py +1715 -0
  134. cognis_executor-0.1.0/cognis/providers/llm/protocol.py +5 -0
  135. cognis_executor-0.1.0/cognis/providers/llm/responses_bridge.py +616 -0
  136. cognis_executor-0.1.0/cognis/providers/llm/retry.py +171 -0
  137. cognis_executor-0.1.0/cognis/providers/memory/__init__.py +3 -0
  138. cognis_executor-0.1.0/cognis/providers/memory/mnemory.py +448 -0
  139. cognis_executor-0.1.0/cognis/providers/memory/protocol.py +5 -0
  140. cognis_executor-0.1.0/cognis/providers/registry.py +89 -0
  141. cognis_executor-0.1.0/cognis/providers/retry.py +159 -0
  142. cognis_executor-0.1.0/cognis/providers/secrets/__init__.py +3 -0
  143. cognis_executor-0.1.0/cognis/providers/secrets/encrypted_db.py +135 -0
  144. cognis_executor-0.1.0/cognis/providers/secrets/protocol.py +5 -0
  145. cognis_executor-0.1.0/cognis/runtime_context.py +27 -0
  146. cognis_executor-0.1.0/cognis/security.py +139 -0
  147. cognis_executor-0.1.0/cognis/settings_schema.py +67 -0
  148. cognis_executor-0.1.0/cognis/store/__init__.py +3 -0
  149. cognis_executor-0.1.0/cognis/store/database.py +90 -0
  150. cognis_executor-0.1.0/cognis/store/migrations/alembic.ini +36 -0
  151. cognis_executor-0.1.0/cognis/store/migrations/env.py +78 -0
  152. cognis_executor-0.1.0/cognis/store/migrations/script.py.mako +17 -0
  153. cognis_executor-0.1.0/cognis/store/migrations/versions/001_initial.py +154 -0
  154. cognis_executor-0.1.0/cognis/store/migrations/versions/002_session_lifecycle_fields.py +41 -0
  155. cognis_executor-0.1.0/cognis/store/migrations/versions/003_tasks_workflows_schedules.py +140 -0
  156. cognis_executor-0.1.0/cognis/store/migrations/versions/004_api_key_last_used_at.py +19 -0
  157. cognis_executor-0.1.0/cognis/store/migrations/versions/005_agent_sync_metadata.py +27 -0
  158. cognis_executor-0.1.0/cognis/store/migrations/versions/006_provider_is_default.py +27 -0
  159. cognis_executor-0.1.0/cognis/store/migrations/versions/007_skills_table.py +53 -0
  160. cognis_executor-0.1.0/cognis/store/migrations/versions/008_executors_table.py +53 -0
  161. cognis_executor-0.1.0/cognis/store/migrations/versions/009_session_compaction_fields.py +32 -0
  162. cognis_executor-0.1.0/cognis/store/migrations/versions/010_task_expected_output.py +28 -0
  163. cognis_executor-0.1.0/cognis/store/migrations/versions/011_rename_root_to_active_session_id.py +28 -0
  164. cognis_executor-0.1.0/cognis/store/migrations/versions/012_step_run_conversation_id.py +29 -0
  165. cognis_executor-0.1.0/cognis/store/migrations/versions/013_user_management_fields.py +42 -0
  166. cognis_executor-0.1.0/cognis/store/migrations/versions/014_notifications_table.py +51 -0
  167. cognis_executor-0.1.0/cognis/store/migrations/versions/015_conversation_last_read_at.py +22 -0
  168. cognis_executor-0.1.0/cognis/store/migrations/versions/016_channel_accounts_contacts.py +101 -0
  169. cognis_executor-0.1.0/cognis/store/migrations/versions/017_channel_pairing_requests.py +83 -0
  170. cognis_executor-0.1.0/cognis/store/migrations/versions/018_channel_adapter_location.py +31 -0
  171. cognis_executor-0.1.0/cognis/store/migrations/versions/019_channel_executor_fk.py +34 -0
  172. cognis_executor-0.1.0/cognis/store/migrations/versions/020_agent_avatar_image.py +24 -0
  173. cognis_executor-0.1.0/cognis/store/migrations/versions/021_artifact_records.py +46 -0
  174. cognis_executor-0.1.0/cognis/store/migrations/versions/022_mcp_servers_table.py +40 -0
  175. cognis_executor-0.1.0/cognis/store/migrations/versions/023_executor_runtime_state.py +39 -0
  176. cognis_executor-0.1.0/cognis/store/migrations/versions/024_skill_versions.py +96 -0
  177. cognis_executor-0.1.0/cognis/store/migrations/versions/025_channel_delivery_outbox.py +74 -0
  178. cognis_executor-0.1.0/cognis/store/migrations/versions/026_executor_runtime_metadata.py +24 -0
  179. cognis_executor-0.1.0/cognis/store/migrations/versions/027_extend_schedules.py +109 -0
  180. cognis_executor-0.1.0/cognis/store/models.py +801 -0
  181. cognis_executor-0.1.0/cognis/store/queries.py +3185 -0
  182. cognis_executor-0.1.0/cognis/tools/__init__.py +3 -0
  183. cognis_executor-0.1.0/cognis/tools/argument_normalization.py +52 -0
  184. cognis_executor-0.1.0/cognis/tools/builtin/__init__.py +3 -0
  185. cognis_executor-0.1.0/cognis/tools/builtin/datetime_tools.py +322 -0
  186. cognis_executor-0.1.0/cognis/tools/builtin/image.py +335 -0
  187. cognis_executor-0.1.0/cognis/tools/builtin/memory.py +675 -0
  188. cognis_executor-0.1.0/cognis/tools/builtin/orchestration.py +626 -0
  189. cognis_executor-0.1.0/cognis/tools/builtin/schedule.py +522 -0
  190. cognis_executor-0.1.0/cognis/tools/builtin/skill_management.py +666 -0
  191. cognis_executor-0.1.0/cognis/tools/builtin/system.py +78 -0
  192. cognis_executor-0.1.0/cognis/tools/builtin/tool_output.py +178 -0
  193. cognis_executor-0.1.0/cognis/tools/builtin/tool_search.py +85 -0
  194. cognis_executor-0.1.0/cognis/tools/builtin/workflow.py +131 -0
  195. cognis_executor-0.1.0/cognis/tools/executor/__init__.py +11 -0
  196. cognis_executor-0.1.0/cognis/tools/executor/definitions.py +511 -0
  197. cognis_executor-0.1.0/cognis/tools/executor/document.py +528 -0
  198. cognis_executor-0.1.0/cognis/tools/executor/filesystem.py +467 -0
  199. cognis_executor-0.1.0/cognis/tools/executor/lsp/__init__.py +26 -0
  200. cognis_executor-0.1.0/cognis/tools/executor/lsp/client.py +633 -0
  201. cognis_executor-0.1.0/cognis/tools/executor/lsp/diagnostics.py +140 -0
  202. cognis_executor-0.1.0/cognis/tools/executor/lsp/install.py +538 -0
  203. cognis_executor-0.1.0/cognis/tools/executor/lsp/manager.py +678 -0
  204. cognis_executor-0.1.0/cognis/tools/executor/lsp/servers.py +449 -0
  205. cognis_executor-0.1.0/cognis/tools/executor/lsp/types.py +52 -0
  206. cognis_executor-0.1.0/cognis/tools/executor/paths.py +16 -0
  207. cognis_executor-0.1.0/cognis/tools/executor/search.py +196 -0
  208. cognis_executor-0.1.0/cognis/tools/executor/shell.py +73 -0
  209. cognis_executor-0.1.0/cognis/tools/executor/web/__init__.py +24 -0
  210. cognis_executor-0.1.0/cognis/tools/executor/web/backends/__init__.py +97 -0
  211. cognis_executor-0.1.0/cognis/tools/executor/web/backends/brave.py +153 -0
  212. cognis_executor-0.1.0/cognis/tools/executor/web/backends/direct.py +151 -0
  213. cognis_executor-0.1.0/cognis/tools/executor/web/backends/protocol.py +51 -0
  214. cognis_executor-0.1.0/cognis/tools/executor/web/backends/tavily.py +308 -0
  215. cognis_executor-0.1.0/cognis/tools/executor/web/definitions.py +337 -0
  216. cognis_executor-0.1.0/cognis/tools/executor/web/handlers.py +211 -0
  217. cognis_executor-0.1.0/cognis/tools/executor/web/headers.py +204 -0
  218. cognis_executor-0.1.0/cognis/tools/mcp.py +354 -0
  219. cognis_executor-0.1.0/cognis/tools/registry.py +124 -0
  220. cognis_executor-0.1.0/cognis/tools/skill_import.py +205 -0
  221. cognis_executor-0.1.0/cognis/tools/skill_parser.py +332 -0
  222. cognis_executor-0.1.0/cognis/tools/skills.py +341 -0
  223. cognis_executor-0.1.0/cognis/ui_assets.py +125 -0
  224. cognis_executor-0.1.0/pyproject.toml +53 -0
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: cognis-executor
3
+ Version: 0.1.0
4
+ Summary: Standalone remote executor for Cognis
5
+ Author-email: Filip Pytloun <filip@pytloun.cz>
6
+ License: TBD
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Framework :: FastAPI
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
11
+ Requires-Python: >=3.12
12
+ Requires-Dist: aiosqlite>=0.20.0
13
+ Requires-Dist: alembic>=1.13.0
14
+ Requires-Dist: argon2-cffi>=23.1.0
15
+ Requires-Dist: croniter>=2.0.0
16
+ Requires-Dist: cryptography>=42.0.0
17
+ Requires-Dist: ddgs>=9.0.0
18
+ Requires-Dist: email-validator>=2.0.0
19
+ Requires-Dist: fastapi>=0.115.0
20
+ Requires-Dist: httpx>=0.27.0
21
+ Requires-Dist: litellm>=1.40.0
22
+ Requires-Dist: markdown>=3.7
23
+ Requires-Dist: markdownify>=0.14.0
24
+ Requires-Dist: mcp>=1.6.0
25
+ Requires-Dist: prometheus-client>=0.20.0
26
+ Requires-Dist: pydantic-settings>=2.0.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: pypdf>=5.4.0
29
+ Requires-Dist: python-dateutil>=2.9.0
30
+ Requires-Dist: python-jose[cryptography]>=3.3.0
31
+ Requires-Dist: python-multipart>=0.0.9
32
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0
33
+ Requires-Dist: typer>=0.12.0
34
+ Requires-Dist: uvicorn[standard]>=0.30.0
35
+ Requires-Dist: weasyprint>=63.0
36
+ Requires-Dist: websockets>=14.0
37
+ Description-Content-Type: text/markdown
38
+
39
+ # cognis-executor
40
+
41
+ Standalone remote executor for Cognis.
42
+
43
+ This package provides the `cognis-executor` command used to connect a
44
+ remote executor process to a Cognis controller over WebSocket.
@@ -0,0 +1,6 @@
1
+ # cognis-executor
2
+
3
+ Standalone remote executor for Cognis.
4
+
5
+ This package provides the `cognis-executor` command used to connect a
6
+ remote executor process to a Cognis controller over WebSocket.
@@ -0,0 +1,5 @@
1
+ """Cognis — Decoupled control plane for AI agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ """API Gateway — FastAPI routes, WebSocket, auth middleware."""
2
+
3
+ from __future__ import annotations
@@ -0,0 +1,609 @@
1
+ """FastAPI application factory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import hashlib
7
+ import secrets
8
+ import sys
9
+ from collections.abc import AsyncIterator
10
+ from contextlib import asynccontextmanager
11
+ from pathlib import Path
12
+
13
+ from fastapi import FastAPI, Request, WebSocket
14
+ from fastapi.exceptions import RequestValidationError
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.responses import JSONResponse
17
+ from starlette.exceptions import HTTPException as StarletteHTTPException
18
+
19
+ from cognis.api.common import error_response
20
+ from cognis.api.middleware import AuthenticationMiddleware
21
+ from cognis.api.routes.agents import router as agents_router
22
+ from cognis.api.routes.artifacts import router as artifacts_router
23
+ from cognis.api.routes.auth import router as auth_router
24
+ from cognis.api.routes.channels import router as channels_router
25
+ from cognis.api.routes.conversations import router as conversations_router
26
+ from cognis.api.routes.escalations import router as escalations_router
27
+ from cognis.api.routes.executors import router as executors_router
28
+ from cognis.api.routes.images import router as images_router
29
+ from cognis.api.routes.notifications import router as notifications_router
30
+ from cognis.api.routes.schedules import router as schedules_router
31
+ from cognis.api.routes.secrets import router as secrets_router
32
+ from cognis.api.routes.sessions import router as sessions_router
33
+ from cognis.api.routes.settings import router as settings_router
34
+ from cognis.api.routes.skills import router as skills_router
35
+ from cognis.api.routes.system import router as system_router
36
+ from cognis.api.routes.tasks import router as tasks_router
37
+ from cognis.api.routes.tools import router as tools_router
38
+ from cognis.api.routes.users import router as users_router
39
+ from cognis.api.routes.workflows import router as workflows_router
40
+ from cognis.api.runtime_support import build_shared_runtime, build_step_runtime_factory
41
+ from cognis.api.websocket import handle_websocket
42
+ from cognis.bootstrap import bootstrap_runtime
43
+ from cognis.config import load_config
44
+ from cognis.core.agent_loop import AgentLoop, PauseWaiter, SessionLock
45
+ from cognis.core.compaction import CompactionStrategy
46
+ from cognis.core.context import ContextAssembler
47
+ from cognis.core.decision import DecisionEngine
48
+ from cognis.core.events import EventBus
49
+ from cognis.core.remember_queue import RememberRetryQueue
50
+ from cognis.core.scheduler import Scheduler
51
+ from cognis.core.session import SessionManager
52
+ from cognis.core.session_cache import SessionCache
53
+ from cognis.core.step_evaluator import StepEvaluator
54
+ from cognis.core.task_queue import TaskQueue
55
+ from cognis.core.tool_output_store import ToolOutputStore
56
+ from cognis.core.tool_router import ToolRouter
57
+ from cognis.core.workflow_engine import WorkflowEngine
58
+ from cognis.core.workflow_registry import WorkflowRegistry
59
+ from cognis.logging import get_logger, setup_logging
60
+ from cognis.providers.auth.jwt import JWTAuthProvider
61
+ from cognis.providers.registry import build_provider_registry
62
+ from cognis.security import LoginRateLimiter, RequestRateLimiter, create_password_hasher
63
+ from cognis.ui_assets import SPAMiddleware, resolve_ui_build_dir
64
+
65
+
66
+ def _as_int(value: object, default: int) -> int:
67
+ return value if isinstance(value, int) else default
68
+
69
+
70
+ def _as_user_facing_host(host: str) -> str:
71
+ return "localhost" if host in {"0.0.0.0", "::"} else host
72
+
73
+
74
+ def _build_user_facing_url(config: object) -> str:
75
+ explicit = getattr(config, "public_base_url", "")
76
+ if isinstance(explicit, str) and explicit:
77
+ return explicit.rstrip("/")
78
+ return f"http://{_as_user_facing_host(config.host)}:{config.port}" # type: ignore[attr-defined]
79
+
80
+
81
+ def _ensure_artifact_signing_secret(config: object) -> str:
82
+ key_path = Path(config.data_dir) / "artifact-signing.key" # type: ignore[attr-defined]
83
+ key_path.parent.mkdir(parents=True, exist_ok=True)
84
+ if not key_path.exists():
85
+ key_path.write_text(secrets.token_urlsafe(32), encoding="utf-8")
86
+ return key_path.read_text(encoding="utf-8").strip()
87
+
88
+
89
+ def _key_fingerprint(path: Path) -> str | None:
90
+ if not path.exists():
91
+ return None
92
+ return hashlib.sha256(path.read_bytes()).hexdigest()[:16]
93
+
94
+
95
+ async def _print_startup_status(
96
+ config: object, providers: object, ui_build_dir: Path | None
97
+ ) -> None:
98
+ base_url = _build_user_facing_url(config)
99
+ memory_health, guardrails_health = await asyncio.gather(
100
+ providers.memory.health(), # type: ignore[attr-defined]
101
+ providers.guardrails.health(), # type: ignore[attr-defined]
102
+ )
103
+
104
+ if getattr(config, "serve_ui", False) and ui_build_dir is not None:
105
+ sys.stdout.write(f"\nWeb UI: {base_url}\n")
106
+ elif getattr(config, "serve_ui", False):
107
+ sys.stdout.write(
108
+ "\nWeb UI assets not found — build the UI in ui/ or set COGNIS_SERVE_UI=false.\n"
109
+ )
110
+ else:
111
+ sys.stdout.write("\nWeb UI: disabled (COGNIS_SERVE_UI=false)\n")
112
+
113
+ if memory_health.status == "healthy":
114
+ sys.stdout.write(f"Mnemory: reachable at {config.mnemory_url}\n") # type: ignore[attr-defined]
115
+ else:
116
+ sys.stdout.write(
117
+ f"Mnemory: NOT reachable at {config.mnemory_url} — memory features will be unavailable\n" # type: ignore[attr-defined]
118
+ )
119
+
120
+ if guardrails_health.status == "healthy":
121
+ sys.stdout.write(f"Intaris: reachable at {config.intaris_url}\n") # type: ignore[attr-defined]
122
+ else:
123
+ sys.stdout.write(
124
+ f"Intaris: NOT reachable at {config.intaris_url} — guardrail features will be unavailable\n" # type: ignore[attr-defined]
125
+ )
126
+ sys.stdout.flush()
127
+
128
+
129
+ logger = get_logger(__name__)
130
+
131
+
132
+ def create_app() -> FastAPI:
133
+ config = load_config()
134
+ setup_logging(config.log_level, config.log_format)
135
+ ui_build_dir = resolve_ui_build_dir() if config.serve_ui else None
136
+
137
+ @asynccontextmanager
138
+ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
139
+ password_hasher = create_password_hasher()
140
+ config_runtime, engine, session_factory, setup_token_manager = await bootstrap_runtime(
141
+ config, password_hasher
142
+ )
143
+ auth_provider = JWTAuthProvider(
144
+ config_runtime.jwt_private_key_path, config_runtime.jwt_public_key_path
145
+ )
146
+ providers = build_provider_registry(config_runtime, session_factory, auth_provider)
147
+ remember_queue = RememberRetryQueue(providers.memory)
148
+ await remember_queue.start()
149
+ await _print_startup_status(config_runtime, providers, ui_build_dir)
150
+
151
+ async with session_factory() as session:
152
+ from cognis.store.queries import count_users, ensure_default_executor, get_setting_value
153
+
154
+ allow_in_process = bool(
155
+ await get_setting_value(session, "executors.allow_in_process", True)
156
+ )
157
+ if allow_in_process:
158
+ await ensure_default_executor(session)
159
+ await session.commit()
160
+
161
+ auth_provider.token_ttl_seconds = _as_int(
162
+ await get_setting_value(session, "security.token_ttl_seconds", 3600), 3600
163
+ )
164
+ app.state.ws_auth_timeout_seconds = _as_int(
165
+ await get_setting_value(session, "security.ws_auth_timeout_seconds", 10), 10
166
+ )
167
+ api_read_requests_per_minute = _as_int(
168
+ await get_setting_value(session, "security.api_read_requests_per_minute", 600), 600
169
+ )
170
+ api_write_requests_per_minute = _as_int(
171
+ await get_setting_value(session, "security.api_write_requests_per_minute", 200), 200
172
+ )
173
+ cache_max_entries = _as_int(
174
+ await get_setting_value(session, "session.cache_max_entries", 200), 200
175
+ )
176
+
177
+ if await count_users(session) == 0:
178
+ token = setup_token_manager.issue()
179
+ if config_runtime.serve_ui and ui_build_dir is not None:
180
+ sys.stdout.write(
181
+ f"\nNo users found. Complete setup at:\n {_build_user_facing_url(config_runtime)}/setup?token={token}\nThis link expires in 15 minutes.\n\n"
182
+ )
183
+ else:
184
+ sys.stdout.write(
185
+ "\nNo users found. Web UI setup is unavailable, so create the first admin with:\n"
186
+ ' cognis admin create-user admin@example.com --name "Admin"\n\n'
187
+ )
188
+ sys.stdout.flush()
189
+
190
+ event_bus = EventBus()
191
+ session_cache = SessionCache(
192
+ providers.guardrails,
193
+ max_entries=cache_max_entries,
194
+ redis_url=config_runtime.redis_url,
195
+ )
196
+ session_manager = SessionManager(
197
+ session_factory, providers, session_cache, event_bus=event_bus
198
+ )
199
+ context_assembler = await ContextAssembler.from_session_factory(
200
+ session_factory=session_factory,
201
+ memory=providers.memory,
202
+ guardrails=providers.guardrails,
203
+ llm=providers.llm,
204
+ session_cache=session_cache,
205
+ session_manager=session_manager,
206
+ )
207
+ compaction_strategy = await CompactionStrategy.from_session_factory(
208
+ session_factory=session_factory,
209
+ guardrails=providers.guardrails,
210
+ llm=providers.llm,
211
+ session_cache=session_cache,
212
+ )
213
+ decision_engine = await DecisionEngine.from_session_factory(
214
+ session_factory=session_factory,
215
+ llm=providers.llm,
216
+ )
217
+ pause_waiter = PauseWaiter()
218
+ session_lock = SessionLock()
219
+ from cognis.core.tool_output_store import (
220
+ FilesystemToolOutputBackend,
221
+ S3ToolOutputBackend,
222
+ )
223
+
224
+ if config_runtime.tool_output_backend == "s3":
225
+ tool_output_backend = S3ToolOutputBackend(
226
+ endpoint=config_runtime.tool_output_s3_endpoint,
227
+ access_key=config_runtime.tool_output_s3_access_key,
228
+ secret_key=config_runtime.tool_output_s3_secret_key,
229
+ bucket=config_runtime.tool_output_s3_bucket,
230
+ region=config_runtime.tool_output_s3_region,
231
+ )
232
+ else:
233
+ tool_output_backend = FilesystemToolOutputBackend(Path(config_runtime.data_dir))
234
+
235
+ tool_output_store = ToolOutputStore(
236
+ tool_output_backend,
237
+ ttl_hours=config_runtime.tool_output_ttl_hours,
238
+ max_size_mb=config_runtime.tool_output_max_size_mb,
239
+ )
240
+ await tool_output_store.cleanup_expired()
241
+
242
+ # Artifact store for images and other binary content
243
+ from cognis.artifacts.store import ArtifactStore, ArtifactStoreConfig
244
+
245
+ artifact_store = ArtifactStore(
246
+ ArtifactStoreConfig(
247
+ backend=config_runtime.artifact_backend,
248
+ path=str(config_runtime.artifact_path),
249
+ s3_endpoint=config_runtime.artifact_s3_endpoint,
250
+ s3_access_key=config_runtime.artifact_s3_access_key,
251
+ s3_secret_key=config_runtime.artifact_s3_secret_key,
252
+ s3_bucket=config_runtime.artifact_s3_bucket,
253
+ s3_region=config_runtime.artifact_s3_region,
254
+ max_size_bytes=config_runtime.artifact_max_size_bytes,
255
+ base_url=_build_user_facing_url(config_runtime),
256
+ signing_secret=(
257
+ config_runtime.artifact_signing_secret
258
+ or _ensure_artifact_signing_secret(config_runtime)
259
+ ),
260
+ signed_url_ttl_seconds=config_runtime.artifact_signed_url_ttl_seconds,
261
+ )
262
+ )
263
+
264
+ from cognis.core.artifact_maintenance import ArtifactMaintenanceService
265
+
266
+ artifact_maintenance = ArtifactMaintenanceService(
267
+ session_factory=session_factory,
268
+ artifact_store=artifact_store,
269
+ )
270
+ await artifact_maintenance.start()
271
+
272
+ tool_router = await ToolRouter.from_session_factory(
273
+ providers.guardrails,
274
+ session_factory,
275
+ memory=providers.memory,
276
+ tool_output_store=tool_output_store,
277
+ image_generation_provider=providers.image_generation,
278
+ artifact_store=artifact_store,
279
+ )
280
+ workflow_registry = WorkflowRegistry(session_factory)
281
+ step_evaluator = await StepEvaluator.from_session_factory(
282
+ session_factory=session_factory,
283
+ llm=providers.llm,
284
+ )
285
+ shared_runtime = await build_shared_runtime(providers)
286
+ step_runtime_factory = build_step_runtime_factory(
287
+ providers=providers,
288
+ shared_registry=shared_runtime.tool_registry,
289
+ shared_connection=shared_runtime.executor_connection,
290
+ session_factory=session_factory,
291
+ )
292
+ agent_loop = AgentLoop(
293
+ providers=providers,
294
+ session_manager=session_manager,
295
+ session_cache=session_cache,
296
+ context_assembler=context_assembler,
297
+ compaction_strategy=compaction_strategy,
298
+ tool_router=tool_router,
299
+ remember_queue=remember_queue,
300
+ event_bus=event_bus,
301
+ session_lock=session_lock,
302
+ pause_waiter=pause_waiter,
303
+ tool_output_store=tool_output_store,
304
+ step_runtime_factory=step_runtime_factory,
305
+ )
306
+ workflow_engine = WorkflowEngine(
307
+ session_factory=session_factory,
308
+ providers=providers,
309
+ agent_loop=agent_loop,
310
+ step_evaluator=step_evaluator,
311
+ workflow_registry=workflow_registry,
312
+ session_manager=session_manager,
313
+ event_bus=event_bus,
314
+ pause_waiter=pause_waiter,
315
+ step_runtime_factory=step_runtime_factory,
316
+ shared_tool_registry=shared_runtime.tool_registry,
317
+ shared_executor_connection=shared_runtime.executor_connection,
318
+ session_cache=session_cache,
319
+ )
320
+ task_queue = await TaskQueue.from_session_factory(
321
+ session_factory=session_factory,
322
+ workflow_engine=workflow_engine,
323
+ workflow_registry=workflow_registry,
324
+ event_bus=event_bus,
325
+ llm_provider=providers.llm,
326
+ )
327
+ agent_loop.set_task_queue(task_queue)
328
+ # Unified notification service — created early so recovery code
329
+ # can use it. Must be before recover_paused_tasks().
330
+ from cognis.core.notifications import NotificationService
331
+
332
+ notification_service = NotificationService(
333
+ session_factory=session_factory,
334
+ pause_waiter=pause_waiter,
335
+ event_bus=event_bus,
336
+ providers=providers,
337
+ )
338
+ agent_loop.notification_service = notification_service
339
+ workflow_engine._notification_service = notification_service # noqa: SLF001
340
+
341
+ # Reconcile pending notifications from before restart (re-registers
342
+ # PauseWaiters from DB so gates/escalations/step-questions survive).
343
+ await notification_service.reconcile_pending()
344
+
345
+ # TurnScheduler — core-layer turn orchestration, no WebSocket dependency.
346
+ # Must be registered BEFORE task_queue.start() so recovered tasks
347
+ # that complete during startup have a handler for their follow-up.
348
+ from cognis.core.turn_scheduler import TurnScheduler
349
+
350
+ turn_scheduler = TurnScheduler(
351
+ session_factory=session_factory,
352
+ workflow_engine=workflow_engine,
353
+ decision_engine=decision_engine,
354
+ task_queue=task_queue,
355
+ session_manager=session_manager,
356
+ session_cache=session_cache,
357
+ compaction_strategy=compaction_strategy,
358
+ agent_loop=agent_loop,
359
+ pause_waiter=pause_waiter,
360
+ notification_service=notification_service,
361
+ providers=providers,
362
+ artifact_store=artifact_store,
363
+ workflow_registry=workflow_registry,
364
+ event_bus=event_bus,
365
+ )
366
+
367
+ # CommandDispatcher — transport-agnostic slash command handling.
368
+ from cognis.core.commands import CommandDispatcher
369
+
370
+ command_dispatcher = CommandDispatcher(
371
+ session_factory=session_factory,
372
+ session_manager=session_manager,
373
+ session_cache=session_cache,
374
+ compaction_strategy=compaction_strategy,
375
+ providers=providers,
376
+ pause_waiter=pause_waiter,
377
+ notification_service=notification_service,
378
+ turn_scheduler=turn_scheduler,
379
+ )
380
+
381
+ recovered_sessions = await session_manager.recover_stale_sessions()
382
+ recovered_tasks = await task_queue.recover_stale_tasks()
383
+ recovered_paused_tasks = await task_queue.recover_paused_tasks()
384
+ await task_queue.start()
385
+
386
+ # Scheduler — evaluates cron/interval/one-shot schedules and
387
+ # creates Tasks via task_queue.submit() when they become due.
388
+ scheduler = Scheduler(
389
+ session_factory=session_factory,
390
+ task_queue=task_queue,
391
+ event_bus=event_bus,
392
+ )
393
+ await scheduler.start()
394
+ tool_router._scheduler = scheduler
395
+
396
+ app.state.config = config_runtime
397
+ app.state.engine = engine
398
+ app.state.session_factory = session_factory
399
+ app.state.setup_token_manager = setup_token_manager
400
+ app.state.password_hasher = password_hasher
401
+ app.state.auth_provider = auth_provider
402
+ app.state.providers = providers
403
+ app.state.login_rate_limiter = LoginRateLimiter()
404
+ app.state.api_rate_limiter = RequestRateLimiter(
405
+ read_requests_per_minute=api_read_requests_per_minute,
406
+ write_requests_per_minute=api_write_requests_per_minute,
407
+ )
408
+ app.state.provider_test_results = {}
409
+ app.state.provider_test_cooldowns = {}
410
+ app.state.remember_queue = remember_queue
411
+ app.state.artifact_store = artifact_store
412
+ app.state.artifact_maintenance = artifact_maintenance
413
+ app.state.serve_ui = config_runtime.serve_ui
414
+ app.state.ui_build_dir = str(ui_build_dir) if ui_build_dir is not None else None
415
+ app.state.user_facing_url = _build_user_facing_url(config_runtime)
416
+ app.state.jwt_public_key_fingerprint = _key_fingerprint(config_runtime.jwt_public_key_path)
417
+ app.state.session_cache = session_cache
418
+ app.state.session_manager = session_manager
419
+ app.state.context_assembler = context_assembler
420
+ app.state.compaction_strategy = compaction_strategy
421
+ app.state.decision_engine = decision_engine
422
+ app.state.event_bus = event_bus
423
+ app.state.pause_waiter = pause_waiter
424
+ app.state.session_lock = session_lock
425
+ app.state.tool_router = tool_router
426
+ app.state.workflow_registry = workflow_registry
427
+ app.state.step_evaluator = step_evaluator
428
+ app.state.agent_loop = agent_loop
429
+ app.state.workflow_engine = workflow_engine
430
+ app.state.task_queue = task_queue
431
+ app.state.scheduler = scheduler
432
+ app.state.tool_registry = shared_runtime.tool_registry
433
+ app.state.executor_connection = shared_runtime.executor_connection
434
+ # Store as frozensets for O(1) lookup; these are written once at
435
+ # startup and never grow.
436
+ app.state.recovered_session_ids = frozenset(recovered_sessions)
437
+ app.state.recovered_task_ids = frozenset(recovered_tasks)
438
+ app.state.recovered_paused_task_ids = frozenset(recovered_paused_tasks)
439
+
440
+ app.state.notification_service = notification_service
441
+ app.state.turn_scheduler = turn_scheduler
442
+ app.state.command_dispatcher = command_dispatcher
443
+
444
+ # Channel manager — lifecycle orchestration for channel adapters.
445
+ from cognis.channels.delivery import ChannelDeliveryService
446
+ from cognis.channels.inbound import InboundPipeline
447
+ from cognis.channels.manager import ChannelManager
448
+ from cognis.channels.pairing import PairingService
449
+
450
+ # Use a lazy ref to avoid circular dependency
451
+ _channel_manager_holder: list[ChannelManager | None] = [None]
452
+
453
+ def _get_channel_manager() -> ChannelManager | None:
454
+ return _channel_manager_holder[0]
455
+
456
+ pairing_service = PairingService(
457
+ session_factory=session_factory,
458
+ channel_manager_ref=_get_channel_manager,
459
+ )
460
+
461
+ inbound_pipeline = InboundPipeline(
462
+ session_factory=session_factory,
463
+ turn_scheduler=turn_scheduler,
464
+ llm_provider=providers.llm,
465
+ session_manager=session_manager,
466
+ pairing_service=pairing_service,
467
+ channel_manager_ref=_get_channel_manager,
468
+ command_dispatcher=command_dispatcher,
469
+ notification_service=notification_service,
470
+ )
471
+ channel_manager = ChannelManager(
472
+ session_factory=session_factory,
473
+ inbound_pipeline=inbound_pipeline,
474
+ secrets_provider=providers.secrets,
475
+ artifact_store=artifact_store,
476
+ event_bus=event_bus,
477
+ ws_provider=providers.executor.websocket
478
+ if hasattr(providers.executor, "websocket")
479
+ else None,
480
+ )
481
+ _channel_manager_holder[0] = channel_manager
482
+
483
+ channel_delivery = ChannelDeliveryService(
484
+ session_factory=session_factory,
485
+ event_bus=event_bus,
486
+ channel_manager_ref=_get_channel_manager,
487
+ turn_scheduler=turn_scheduler,
488
+ )
489
+
490
+ app.state.channel_manager = channel_manager
491
+ app.state.channel_delivery = channel_delivery
492
+ app.state.pairing_service = pairing_service
493
+
494
+ # Start channel adapters (non-blocking — failures are logged)
495
+ try:
496
+ await channel_manager.start_all()
497
+ except Exception:
498
+ logger.exception("Failed to start channel adapters")
499
+
500
+ try:
501
+ await channel_delivery.recover_pending_deliveries()
502
+ except Exception:
503
+ logger.exception("Failed to recover pending channel deliveries")
504
+
505
+ await channel_delivery.start()
506
+
507
+ yield
508
+
509
+ await artifact_maintenance.stop()
510
+ await channel_delivery.stop()
511
+ await channel_manager.stop_all()
512
+ await scheduler.stop()
513
+ await task_queue.stop()
514
+ await shared_runtime.cleanup()
515
+ await remember_queue.stop()
516
+ await providers.executor.cleanup()
517
+ await session_cache.aclose()
518
+ await providers.memory.client.aclose()
519
+ await providers.guardrails.client.aclose()
520
+ await engine.dispose()
521
+
522
+ app = FastAPI(title="Cognis", version="0.1.0", lifespan=lifespan)
523
+
524
+ # Middleware stack (execution order is bottom-to-top):
525
+ # 1. SPA middleware — serves UI static files for non-API paths
526
+ # 2. Auth middleware — authenticates /api/* routes
527
+ # 3. CORS middleware — handles CORS preflight and headers
528
+ if config.serve_ui and ui_build_dir is not None:
529
+ app.add_middleware(SPAMiddleware, directory=ui_build_dir)
530
+ app.add_middleware(
531
+ CORSMiddleware,
532
+ allow_origins=config.cors_origins,
533
+ allow_credentials=True,
534
+ allow_methods=["*"],
535
+ allow_headers=["*"],
536
+ )
537
+ app.add_middleware(AuthenticationMiddleware)
538
+ app.include_router(auth_router)
539
+ app.include_router(system_router)
540
+ app.include_router(artifacts_router)
541
+ app.include_router(channels_router)
542
+ app.include_router(conversations_router)
543
+ app.include_router(agents_router)
544
+ app.include_router(images_router)
545
+ app.include_router(sessions_router)
546
+ app.include_router(settings_router)
547
+ app.include_router(tasks_router)
548
+ app.include_router(schedules_router)
549
+ app.include_router(workflows_router)
550
+ app.include_router(secrets_router)
551
+ app.include_router(tools_router)
552
+ app.include_router(skills_router)
553
+ app.include_router(executors_router)
554
+ app.include_router(escalations_router)
555
+ app.include_router(notifications_router)
556
+ app.include_router(users_router)
557
+
558
+ @app.exception_handler(StarletteHTTPException)
559
+ async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
560
+ if isinstance(exc.detail, dict) and exc.detail.get("code"):
561
+ return error_response(
562
+ exc.status_code,
563
+ str(exc.detail.get("code")),
564
+ str(exc.detail.get("message", "Request failed")),
565
+ details=exc.detail.get("details"),
566
+ )
567
+ return error_response(exc.status_code, "request_error", str(exc.detail))
568
+
569
+ @app.exception_handler(RequestValidationError)
570
+ async def request_validation_exception_handler(
571
+ request: Request, exc: RequestValidationError
572
+ ) -> JSONResponse:
573
+ return error_response(
574
+ 422,
575
+ "validation_error",
576
+ "Request validation failed",
577
+ details={"errors": exc.errors()},
578
+ )
579
+
580
+ @app.exception_handler(ValueError)
581
+ async def value_error_handler(request: Request, exc: ValueError) -> JSONResponse:
582
+ return error_response(400, "validation_error", str(exc))
583
+
584
+ @app.exception_handler(Exception)
585
+ async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse:
586
+ logger.exception("Unhandled API exception")
587
+ return error_response(500, "internal_error", "Internal server error")
588
+
589
+ @app.websocket("/api/ws")
590
+ async def websocket_endpoint(websocket: WebSocket) -> None:
591
+ await handle_websocket(websocket)
592
+
593
+ @app.websocket("/api/executor/ws")
594
+ async def executor_websocket_endpoint(websocket: WebSocket) -> None:
595
+ from cognis.api.executor_ws import handle_executor_websocket
596
+
597
+ ws_provider = app.state.providers.executor.websocket
598
+ await handle_executor_websocket(
599
+ websocket,
600
+ ws_provider,
601
+ app.state.providers,
602
+ app.state.session_factory,
603
+ )
604
+
605
+ # NOTE: SPA serving moved to SPAMiddleware (added above) which runs
606
+ # before the FastAPI router, avoiding the 404 exception handler
607
+ # intercepting requests meant for the UI.
608
+
609
+ return app