cognis-executor 0.1.0__py3-none-any.whl
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.
- cognis/__init__.py +5 -0
- cognis/api/__init__.py +3 -0
- cognis/api/app.py +609 -0
- cognis/api/common.py +149 -0
- cognis/api/error_sanitizer.py +38 -0
- cognis/api/executor_runtime.py +390 -0
- cognis/api/executor_ws.py +258 -0
- cognis/api/middleware.py +230 -0
- cognis/api/models.py +1039 -0
- cognis/api/routes/__init__.py +3 -0
- cognis/api/routes/agents.py +620 -0
- cognis/api/routes/artifacts.py +142 -0
- cognis/api/routes/auth.py +283 -0
- cognis/api/routes/channels.py +706 -0
- cognis/api/routes/conversations.py +711 -0
- cognis/api/routes/escalations.py +81 -0
- cognis/api/routes/executors.py +182 -0
- cognis/api/routes/images.py +264 -0
- cognis/api/routes/notifications.py +130 -0
- cognis/api/routes/schedules.py +398 -0
- cognis/api/routes/secrets.py +53 -0
- cognis/api/routes/sessions.py +110 -0
- cognis/api/routes/settings.py +438 -0
- cognis/api/routes/skills.py +356 -0
- cognis/api/routes/system.py +147 -0
- cognis/api/routes/tasks.py +585 -0
- cognis/api/routes/tools.py +831 -0
- cognis/api/routes/users.py +180 -0
- cognis/api/routes/workflows.py +208 -0
- cognis/api/runtime_support.py +1086 -0
- cognis/api/serializers.py +351 -0
- cognis/api/sse.py +244 -0
- cognis/api/tool_inventory.py +72 -0
- cognis/api/websocket.py +1734 -0
- cognis/artifacts/__init__.py +3 -0
- cognis/artifacts/store.py +570 -0
- cognis/bootstrap.py +547 -0
- cognis/channels/__init__.py +17 -0
- cognis/channels/adapters/__init__.py +1 -0
- cognis/channels/adapters/bluebubbles.py +430 -0
- cognis/channels/adapters/discord.py +369 -0
- cognis/channels/adapters/google_chat.py +208 -0
- cognis/channels/adapters/irc.py +242 -0
- cognis/channels/adapters/matrix.py +358 -0
- cognis/channels/adapters/signal.py +1078 -0
- cognis/channels/adapters/signal_cli_runtime.py +427 -0
- cognis/channels/adapters/slack.py +328 -0
- cognis/channels/adapters/telegram.py +363 -0
- cognis/channels/adapters/whatsapp.py +305 -0
- cognis/channels/delivery.py +666 -0
- cognis/channels/factory.py +52 -0
- cognis/channels/formatting.py +143 -0
- cognis/channels/inbound.py +986 -0
- cognis/channels/manager.py +664 -0
- cognis/channels/pairing.py +300 -0
- cognis/channels/protocol.py +475 -0
- cognis/channels/registry.py +497 -0
- cognis/channels/remote.py +279 -0
- cognis/channels/signal_formatting.py +523 -0
- cognis/cli/__init__.py +3 -0
- cognis/cli/admin.py +268 -0
- cognis/cli/executor.py +104 -0
- cognis/cli/serve.py +20 -0
- cognis/config.py +253 -0
- cognis/core/__init__.py +3 -0
- cognis/core/agent_loop.py +3939 -0
- cognis/core/agent_registry.py +627 -0
- cognis/core/artifact_maintenance.py +86 -0
- cognis/core/attachment_utils.py +23 -0
- cognis/core/commands.py +694 -0
- cognis/core/compaction.py +280 -0
- cognis/core/context.py +1425 -0
- cognis/core/decision.py +337 -0
- cognis/core/events.py +165 -0
- cognis/core/executor_policy.py +79 -0
- cognis/core/executor_resolution.py +147 -0
- cognis/core/json_utils.py +370 -0
- cognis/core/notifications.py +603 -0
- cognis/core/prompts.py +218 -0
- cognis/core/pruning.py +142 -0
- cognis/core/remember_queue.py +99 -0
- cognis/core/runtime.py +129 -0
- cognis/core/scheduler.py +463 -0
- cognis/core/session.py +917 -0
- cognis/core/session_cache.py +638 -0
- cognis/core/step_evaluator.py +259 -0
- cognis/core/task_queue.py +877 -0
- cognis/core/tool_exposure.py +241 -0
- cognis/core/tool_output_store.py +498 -0
- cognis/core/tool_router.py +804 -0
- cognis/core/truncation.py +52 -0
- cognis/core/turn_scheduler.py +1565 -0
- cognis/core/workflow_engine.py +1864 -0
- cognis/core/workflow_registry.py +328 -0
- cognis/executor/__init__.py +3 -0
- cognis/executor/__main__.py +99 -0
- cognis/executor/channel_handler.py +260 -0
- cognis/executor/inference.py +303 -0
- cognis/executor/runner.py +1025 -0
- cognis/logging.py +208 -0
- cognis/main.py +41 -0
- cognis/models/__init__.py +3 -0
- cognis/models/agent.py +137 -0
- cognis/models/artifact.py +56 -0
- cognis/models/channel.py +275 -0
- cognis/models/config.py +135 -0
- cognis/models/delegation.py +19 -0
- cognis/models/schedule.py +195 -0
- cognis/models/session.py +134 -0
- cognis/models/skill.py +219 -0
- cognis/models/task.py +90 -0
- cognis/models/tool.py +224 -0
- cognis/models/workflow.py +295 -0
- cognis/providers/__init__.py +3 -0
- cognis/providers/auth/__init__.py +3 -0
- cognis/providers/auth/jwt.py +140 -0
- cognis/providers/auth/protocol.py +5 -0
- cognis/providers/base.py +265 -0
- cognis/providers/circuit_breaker.py +69 -0
- cognis/providers/executor/__init__.py +3 -0
- cognis/providers/executor/composite.py +160 -0
- cognis/providers/executor/in_process.py +515 -0
- cognis/providers/executor/protocol.py +21 -0
- cognis/providers/executor/subprocess.py +158 -0
- cognis/providers/executor/websocket.py +666 -0
- cognis/providers/guardrails/__init__.py +3 -0
- cognis/providers/guardrails/intaris.py +604 -0
- cognis/providers/guardrails/protocol.py +5 -0
- cognis/providers/llm/__init__.py +3 -0
- cognis/providers/llm/inference_router.py +195 -0
- cognis/providers/llm/litellm.py +1715 -0
- cognis/providers/llm/protocol.py +5 -0
- cognis/providers/llm/responses_bridge.py +616 -0
- cognis/providers/llm/retry.py +171 -0
- cognis/providers/memory/__init__.py +3 -0
- cognis/providers/memory/mnemory.py +448 -0
- cognis/providers/memory/protocol.py +5 -0
- cognis/providers/registry.py +89 -0
- cognis/providers/retry.py +159 -0
- cognis/providers/secrets/__init__.py +3 -0
- cognis/providers/secrets/encrypted_db.py +135 -0
- cognis/providers/secrets/protocol.py +5 -0
- cognis/runtime_context.py +27 -0
- cognis/security.py +139 -0
- cognis/settings_schema.py +67 -0
- cognis/store/__init__.py +3 -0
- cognis/store/database.py +90 -0
- cognis/store/migrations/alembic.ini +36 -0
- cognis/store/migrations/env.py +78 -0
- cognis/store/migrations/script.py.mako +17 -0
- cognis/store/migrations/versions/001_initial.py +154 -0
- cognis/store/migrations/versions/002_session_lifecycle_fields.py +41 -0
- cognis/store/migrations/versions/003_tasks_workflows_schedules.py +140 -0
- cognis/store/migrations/versions/004_api_key_last_used_at.py +19 -0
- cognis/store/migrations/versions/005_agent_sync_metadata.py +27 -0
- cognis/store/migrations/versions/006_provider_is_default.py +27 -0
- cognis/store/migrations/versions/007_skills_table.py +53 -0
- cognis/store/migrations/versions/008_executors_table.py +53 -0
- cognis/store/migrations/versions/009_session_compaction_fields.py +32 -0
- cognis/store/migrations/versions/010_task_expected_output.py +28 -0
- cognis/store/migrations/versions/011_rename_root_to_active_session_id.py +28 -0
- cognis/store/migrations/versions/012_step_run_conversation_id.py +29 -0
- cognis/store/migrations/versions/013_user_management_fields.py +42 -0
- cognis/store/migrations/versions/014_notifications_table.py +51 -0
- cognis/store/migrations/versions/015_conversation_last_read_at.py +22 -0
- cognis/store/migrations/versions/016_channel_accounts_contacts.py +101 -0
- cognis/store/migrations/versions/017_channel_pairing_requests.py +83 -0
- cognis/store/migrations/versions/018_channel_adapter_location.py +31 -0
- cognis/store/migrations/versions/019_channel_executor_fk.py +34 -0
- cognis/store/migrations/versions/020_agent_avatar_image.py +24 -0
- cognis/store/migrations/versions/021_artifact_records.py +46 -0
- cognis/store/migrations/versions/022_mcp_servers_table.py +40 -0
- cognis/store/migrations/versions/023_executor_runtime_state.py +39 -0
- cognis/store/migrations/versions/024_skill_versions.py +96 -0
- cognis/store/migrations/versions/025_channel_delivery_outbox.py +74 -0
- cognis/store/migrations/versions/026_executor_runtime_metadata.py +24 -0
- cognis/store/migrations/versions/027_extend_schedules.py +109 -0
- cognis/store/models.py +801 -0
- cognis/store/queries.py +3185 -0
- cognis/tools/__init__.py +3 -0
- cognis/tools/argument_normalization.py +52 -0
- cognis/tools/builtin/__init__.py +3 -0
- cognis/tools/builtin/datetime_tools.py +322 -0
- cognis/tools/builtin/image.py +335 -0
- cognis/tools/builtin/memory.py +675 -0
- cognis/tools/builtin/orchestration.py +626 -0
- cognis/tools/builtin/schedule.py +522 -0
- cognis/tools/builtin/skill_management.py +666 -0
- cognis/tools/builtin/system.py +78 -0
- cognis/tools/builtin/tool_output.py +178 -0
- cognis/tools/builtin/tool_search.py +85 -0
- cognis/tools/builtin/workflow.py +131 -0
- cognis/tools/executor/__init__.py +11 -0
- cognis/tools/executor/definitions.py +511 -0
- cognis/tools/executor/document.py +528 -0
- cognis/tools/executor/filesystem.py +467 -0
- cognis/tools/executor/lsp/__init__.py +26 -0
- cognis/tools/executor/lsp/client.py +633 -0
- cognis/tools/executor/lsp/diagnostics.py +140 -0
- cognis/tools/executor/lsp/install.py +538 -0
- cognis/tools/executor/lsp/manager.py +678 -0
- cognis/tools/executor/lsp/servers.py +449 -0
- cognis/tools/executor/lsp/types.py +52 -0
- cognis/tools/executor/paths.py +16 -0
- cognis/tools/executor/search.py +196 -0
- cognis/tools/executor/shell.py +73 -0
- cognis/tools/executor/web/__init__.py +24 -0
- cognis/tools/executor/web/backends/__init__.py +97 -0
- cognis/tools/executor/web/backends/brave.py +153 -0
- cognis/tools/executor/web/backends/direct.py +151 -0
- cognis/tools/executor/web/backends/protocol.py +51 -0
- cognis/tools/executor/web/backends/tavily.py +308 -0
- cognis/tools/executor/web/definitions.py +337 -0
- cognis/tools/executor/web/handlers.py +211 -0
- cognis/tools/executor/web/headers.py +204 -0
- cognis/tools/mcp.py +354 -0
- cognis/tools/registry.py +124 -0
- cognis/tools/skill_import.py +205 -0
- cognis/tools/skill_parser.py +332 -0
- cognis/tools/skills.py +341 -0
- cognis/ui_assets.py +125 -0
- cognis_executor-0.1.0.dist-info/METADATA +44 -0
- cognis_executor-0.1.0.dist-info/RECORD +225 -0
- cognis_executor-0.1.0.dist-info/WHEEL +4 -0
- cognis_executor-0.1.0.dist-info/entry_points.txt +2 -0
cognis/__init__.py
ADDED
cognis/api/__init__.py
ADDED
cognis/api/app.py
ADDED
|
@@ -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
|