ltcai 5.6.0 → 6.0.0
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.
- package/README.md +42 -25
- package/docs/CHANGELOG.md +38 -0
- package/frontend/openapi.json +39 -0
- package/frontend/src/api/client.ts +104 -23
- package/frontend/src/api/openapi.ts +48 -0
- package/frontend/src/components/FirstRunGuide.tsx +3 -3
- package/frontend/src/features/review/ReviewCard.tsx +91 -0
- package/frontend/src/features/review/ReviewInbox.tsx +112 -0
- package/frontend/src/features/review/reviewHelpers.ts +69 -0
- package/frontend/src/i18n.ts +8 -8
- package/frontend/src/pages/Act.tsx +5 -177
- package/frontend/src/routes.ts +1 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/review_queue.py +7 -3
- package/latticeai/app_factory.py +224 -473
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/runtime/app_context_runtime.py +13 -0
- package/latticeai/runtime/automation_runtime.py +64 -0
- package/latticeai/runtime/bootstrap.py +48 -0
- package/latticeai/runtime/context_runtime.py +43 -0
- package/latticeai/runtime/hooks_runtime.py +77 -0
- package/latticeai/runtime/lifespan_runtime.py +138 -0
- package/latticeai/runtime/persistence_runtime.py +87 -0
- package/latticeai/runtime/platform_services_runtime.py +39 -0
- package/latticeai/runtime/router_registration.py +570 -0
- package/latticeai/runtime/web_runtime.py +65 -0
- package/latticeai/services/review_queue.py +20 -4
- package/package.json +1 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +3 -3
- package/static/app/assets/index-D2zafMYb.js +16 -0
- package/static/app/assets/index-D2zafMYb.js.map +1 -0
- package/static/app/index.html +1 -1
- package/static/app/assets/index-xMFu94cX.js +0 -16
- package/static/app/assets/index-xMFu94cX.js.map +0 -1
package/latticeai/app_factory.py
CHANGED
|
@@ -17,9 +17,34 @@ from __future__ import annotations
|
|
|
17
17
|
import threading
|
|
18
18
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
19
19
|
|
|
20
|
+
from latticeai.runtime.automation_runtime import build_automation_runtime
|
|
21
|
+
from latticeai.runtime.app_context_runtime import build_app_context
|
|
22
|
+
from latticeai.runtime.bootstrap import build_session_runtime
|
|
20
23
|
from latticeai.runtime.brain_runtime import build_brain_runtime
|
|
21
24
|
from latticeai.runtime.config_runtime import build_config_runtime
|
|
25
|
+
from latticeai.runtime.context_runtime import build_context_runtime
|
|
26
|
+
from latticeai.runtime.hooks_runtime import (
|
|
27
|
+
bind_builtin_hook_runners,
|
|
28
|
+
bind_trigger_hook_runner,
|
|
29
|
+
build_hooks_runtime,
|
|
30
|
+
)
|
|
31
|
+
from latticeai.runtime.lifespan_runtime import build_lifespan_runtime
|
|
32
|
+
from latticeai.runtime.platform_services_runtime import (
|
|
33
|
+
build_brain_network,
|
|
34
|
+
build_model_service,
|
|
35
|
+
)
|
|
36
|
+
from latticeai.runtime.persistence_runtime import build_persistence_runtime
|
|
37
|
+
from latticeai.runtime.router_registration import (
|
|
38
|
+
build_auth_admin_security_router_bundle,
|
|
39
|
+
build_static_routes_bundle,
|
|
40
|
+
register_health_and_model_routers,
|
|
41
|
+
register_foundation_routers,
|
|
42
|
+
register_interaction_routers,
|
|
43
|
+
register_platform_feature_routers,
|
|
44
|
+
register_review_and_brain_tail_routers,
|
|
45
|
+
)
|
|
22
46
|
from latticeai.runtime.security_runtime import build_security_runtime
|
|
47
|
+
from latticeai.runtime.web_runtime import build_web_runtime
|
|
23
48
|
|
|
24
49
|
if TYPE_CHECKING: # imports for annotations only — keep module import light
|
|
25
50
|
from fastapi import FastAPI
|
|
@@ -34,7 +59,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
34
59
|
deliberately *inside* this function so that importing the module performs
|
|
35
60
|
no GPU init, no singleton construction, and no filesystem writes.
|
|
36
61
|
"""
|
|
37
|
-
import asyncio
|
|
38
62
|
import hashlib
|
|
39
63
|
import json
|
|
40
64
|
import logging
|
|
@@ -45,7 +69,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
45
69
|
import subprocess
|
|
46
70
|
import sys
|
|
47
71
|
import time
|
|
48
|
-
from contextlib import asynccontextmanager
|
|
49
72
|
from pathlib import Path
|
|
50
73
|
|
|
51
74
|
try:
|
|
@@ -56,14 +79,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
56
79
|
print(f"⚠️ MLX Metal context unavailable: {e}")
|
|
57
80
|
mx = None
|
|
58
81
|
import uvicorn
|
|
59
|
-
from fastapi import
|
|
60
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
61
|
-
from fastapi.staticfiles import StaticFiles
|
|
82
|
+
from fastapi import HTTPException, Request
|
|
62
83
|
from pydantic import BaseModel
|
|
63
84
|
|
|
64
85
|
from latticeai.models.router import LLMRouter, normalize_branding
|
|
65
86
|
from lattice_brain._kg_common import set_llm_router
|
|
66
|
-
from local_knowledge_api import LocalKnowledgeWatcher
|
|
67
87
|
from latticeai.core.security import (
|
|
68
88
|
hash_password,
|
|
69
89
|
verify_password,
|
|
@@ -74,7 +94,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
74
94
|
check_ip_rate_limit as _check_ip_rate_limit,
|
|
75
95
|
enforce_rate_limit as _enforce_rate_limit,
|
|
76
96
|
)
|
|
77
|
-
from latticeai.core.sessions import SessionStore as _SessionStore
|
|
78
97
|
from latticeai.core.audit import (
|
|
79
98
|
get_audit_log as _get_audit_log,
|
|
80
99
|
append_audit_event as _append_audit_event,
|
|
@@ -88,13 +107,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
88
107
|
from latticeai.core.model_compat import list_cached_profiles as _list_compat_profiles
|
|
89
108
|
from latticeai.core.workspace_os import (
|
|
90
109
|
WORKSPACE_OS_VERSION,
|
|
91
|
-
WorkspaceOSStore,
|
|
92
110
|
remove_skill_directory,
|
|
93
111
|
)
|
|
94
112
|
from latticeai.core.enterprise import (
|
|
95
113
|
capability_registry,
|
|
96
114
|
)
|
|
97
|
-
from latticeai.core.invitations import InvitationStore
|
|
98
115
|
from latticeai.core.policy import normalize_role, policy_matrix, require_capability
|
|
99
116
|
from latticeai.core.users import (
|
|
100
117
|
ensure_user_identity,
|
|
@@ -104,13 +121,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
104
121
|
save_users_file,
|
|
105
122
|
user_id_for_email as _user_id_for_email,
|
|
106
123
|
)
|
|
107
|
-
from latticeai.services.app_context import AppContext
|
|
108
|
-
from latticeai.services.workspace_service import WorkspaceService
|
|
109
|
-
from latticeai.services.model_service import ModelService
|
|
110
124
|
from latticeai.services.chat_service import ChatService
|
|
111
|
-
from latticeai.services.search_service import SearchService
|
|
112
125
|
from latticeai.core.embedding_providers import resolve_embedder, resolve_embedding_profile
|
|
113
|
-
from lattice_brain.runtime.agent_runtime import AgentRuntime
|
|
114
126
|
from latticeai.services.model_runtime import (
|
|
115
127
|
CLOUD_VERIFY_TTL_SECONDS,
|
|
116
128
|
ENGINE_MODEL_CATALOG,
|
|
@@ -133,11 +145,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
133
145
|
from latticeai.api.workspace import create_workspace_router, _workspace_scope_from_request
|
|
134
146
|
from latticeai.api.health import create_health_router
|
|
135
147
|
# ── v2 Agentic Workspace Platform layers ─────────────────────────────────────
|
|
136
|
-
from latticeai.core.plugins import PluginRegistry
|
|
137
|
-
from latticeai.core.realtime import RealtimeBus
|
|
138
|
-
from latticeai.core.marketplace import TemplateCatalog
|
|
139
148
|
from latticeai.services.platform_runtime import PlatformRuntime
|
|
140
|
-
from latticeai.services.run_executor import RunExecutor
|
|
141
149
|
from latticeai.api.plugins import create_plugins_router
|
|
142
150
|
from latticeai.api.workflow_designer import create_workflow_designer_router
|
|
143
151
|
from latticeai.api.agents import create_agents_router
|
|
@@ -152,23 +160,14 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
152
160
|
from latticeai.api.garden import create_garden_router
|
|
153
161
|
from latticeai.api.setup import create_setup_router
|
|
154
162
|
from latticeai.api.hooks import create_hooks_router
|
|
155
|
-
from lattice_brain.runtime.hooks import HooksRegistry
|
|
156
|
-
from latticeai.core.builtin_hooks import register_builtin_hook_runners
|
|
157
163
|
from latticeai.core.product_hardening import build_product_hardening_status
|
|
158
164
|
from latticeai.api.agent_registry import create_agent_registry_router
|
|
159
|
-
from latticeai.core.agent_registry import AgentRegistry
|
|
160
165
|
from latticeai.api.memory import create_memory_router
|
|
161
166
|
from latticeai.api.browser import create_browser_router
|
|
162
167
|
from latticeai.api.portability import create_portability_router
|
|
163
|
-
from
|
|
164
|
-
from lattice_brain.ingestion import IngestionItem, IngestionPipeline
|
|
168
|
+
from lattice_brain.ingestion import IngestionItem
|
|
165
169
|
from lattice_brain.storage import storage_from_env
|
|
166
|
-
from lattice_brain.context import ContextAssembler
|
|
167
|
-
from lattice_brain.memory import BrainMemory
|
|
168
|
-
from lattice_brain.identity import DeviceIdentity
|
|
169
|
-
from lattice_brain.network import BrainNetwork
|
|
170
170
|
from latticeai.api.network import create_network_router
|
|
171
|
-
from lattice_brain.portability import KGPortabilityService
|
|
172
171
|
# The aliased names below look unused but are part of the legacy
|
|
173
172
|
# ``server_app`` attribute surface: every local is exported via
|
|
174
173
|
# ``dict(locals())`` and reached through ``server_app.__getattr__``
|
|
@@ -281,10 +280,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
281
280
|
return True
|
|
282
281
|
return False
|
|
283
282
|
|
|
284
|
-
# ── Session store — delegated to latticeai.
|
|
285
|
-
_SESSION_TTL = 60 * 60 * 24
|
|
286
|
-
_session_store = _SessionStore()
|
|
287
|
-
|
|
283
|
+
# ── Session store — delegated to latticeai.runtime.bootstrap ──────────────────
|
|
288
284
|
def _check_rate_limit(ip: str, action: str, max_calls: int, window_secs: float) -> None:
|
|
289
285
|
_check_ip_rate_limit(ip, action, max_calls=max_calls, window_secs=window_secs)
|
|
290
286
|
|
|
@@ -294,17 +290,15 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
294
290
|
def user_id_for_email(email: Optional[str]) -> Optional[str]:
|
|
295
291
|
return _user_id_for_email(load_users(), email)
|
|
296
292
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def invalidate_session(token: str) -> None:
|
|
307
|
-
_session_store.invalidate(token)
|
|
293
|
+
# Session token lifecycle (store + create/get/invalidate closures) lives in
|
|
294
|
+
# the bootstrap seam; user_id_for_email is injected as the subject resolver.
|
|
295
|
+
_session_runtime = build_session_runtime(user_id_resolver=user_id_for_email)
|
|
296
|
+
_SESSION_TTL = _session_runtime["_SESSION_TTL"]
|
|
297
|
+
_session_store = _session_runtime["_session_store"]
|
|
298
|
+
create_session = _session_runtime["create_session"]
|
|
299
|
+
get_session_email = _session_runtime["get_session_email"]
|
|
300
|
+
get_session_user_id = _session_runtime["get_session_user_id"]
|
|
301
|
+
invalidate_session = _session_runtime["invalidate_session"]
|
|
308
302
|
|
|
309
303
|
# ── User Management Logic ──────────────────────────────────────────────────
|
|
310
304
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
@@ -360,54 +354,40 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
360
354
|
# file is left untouched on disk as the import source.
|
|
361
355
|
CONVERSATIONS = _brain_runtime["CONVERSATIONS"]
|
|
362
356
|
# Hooks registry is constructed here (ahead of the watcher) so folder-watch
|
|
363
|
-
# reindexes can fire the pre_index/post_index lifecycle hooks.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
# ── v2 Realtime bus: constructed first so the store can fan every timeline
|
|
367
|
-
# event into the realtime feed via a single additive sink (no per-call wiring).
|
|
368
|
-
REALTIME_BUS = RealtimeBus()
|
|
369
|
-
WORKSPACE_OS = WorkspaceOSStore(DATA_DIR, event_sink=REALTIME_BUS)
|
|
370
|
-
# Service layer (latticeai.services) wraps the store with scope/permission
|
|
371
|
-
# guardrails; routers and the app assembly share this single instance.
|
|
372
|
-
WORKSPACE_SERVICE = WorkspaceService(WORKSPACE_OS, resolve_user_id=user_id_for_email)
|
|
373
|
-
INVITATION_STORE = InvitationStore(DATA_DIR / "invitations.json")
|
|
374
|
-
# ── v2 Plugin SDK registry (extends skills; discovers plugins/<id>/plugin.json)
|
|
375
|
-
PLUGINS_DIR = Path(os.getenv("LATTICEAI_PLUGINS_DIR") or (BASE_DIR / "plugins"))
|
|
376
|
-
PLUGIN_REGISTRY = PluginRegistry(PLUGINS_DIR, store=WORKSPACE_OS)
|
|
377
|
-
TEMPLATE_CATALOG = TemplateCatalog()
|
|
378
|
-
# ── v3.2 platform registries: lifecycle hooks + agent registry, persisted under
|
|
379
|
-
# DATA_DIR so the /app Hooks and Agent Registry views read/write real state.
|
|
380
|
-
# (HOOKS_REGISTRY is constructed earlier, before the local-knowledge watcher.)
|
|
381
|
-
AGENT_REGISTRY = AgentRegistry(DATA_DIR / "agent_registry.json")
|
|
382
|
-
# Unified long-term memory platform fronting workspace memories, agent
|
|
383
|
-
# snapshots, conversation history, and the KG graph/vector index.
|
|
384
|
-
MEMORY_SERVICE = MemoryService(
|
|
385
|
-
store=WORKSPACE_OS,
|
|
357
|
+
# reindexes can fire the pre_index/post_index lifecycle hooks. The registry
|
|
358
|
+
# + watcher pair is assembled behind the hooks_runtime seam.
|
|
359
|
+
_hooks_runtime = build_hooks_runtime(
|
|
386
360
|
data_dir=DATA_DIR,
|
|
387
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
388
361
|
enable_graph=ENABLE_GRAPH,
|
|
389
|
-
|
|
390
|
-
conversation_store=CONVERSATIONS,
|
|
391
|
-
)
|
|
392
|
-
# ── v3.6.0 unified ingestion pipeline: the single write-side seam into the
|
|
393
|
-
# Knowledge Graph. Every new source (web URL, browser tab, …) flows through this
|
|
394
|
-
# so pre_tool/post_tool hooks fire on ingestion and provenance is captured
|
|
395
|
-
# uniformly. Existing direct ingest callers keep working; new paths converge here.
|
|
396
|
-
INGESTION_PIPELINE = IngestionPipeline(
|
|
397
|
-
KNOWLEDGE_GRAPH,
|
|
398
|
-
hooks=HOOKS_REGISTRY,
|
|
399
|
-
enable_graph=ENABLE_GRAPH,
|
|
400
|
-
audit=lambda action, detail, user: append_audit_event(action, user_email=user, **detail),
|
|
362
|
+
knowledge_graph_getter=lambda: KNOWLEDGE_GRAPH,
|
|
401
363
|
)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
364
|
+
HOOKS_REGISTRY = _hooks_runtime["HOOKS_REGISTRY"]
|
|
365
|
+
LOCAL_KG_WATCHER = _hooks_runtime["LOCAL_KG_WATCHER"]
|
|
366
|
+
# ── Persistence/service graph: workspace store, realtime feed, plugin/memory
|
|
367
|
+
# registries, ingestion pipeline, device identity, and portability services.
|
|
368
|
+
_persistence_runtime = build_persistence_runtime(
|
|
407
369
|
data_dir=DATA_DIR,
|
|
370
|
+
base_dir=BASE_DIR,
|
|
408
371
|
enable_graph=ENABLE_GRAPH,
|
|
409
|
-
|
|
372
|
+
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
373
|
+
hooks_registry=HOOKS_REGISTRY,
|
|
374
|
+
history_file=HISTORY_FILE,
|
|
375
|
+
conversations=CONVERSATIONS,
|
|
376
|
+
user_id_for_email=user_id_for_email,
|
|
377
|
+
audit=lambda action, detail, user: append_audit_event(action, user_email=user, **detail),
|
|
410
378
|
)
|
|
379
|
+
REALTIME_BUS = _persistence_runtime["REALTIME_BUS"]
|
|
380
|
+
WORKSPACE_OS = _persistence_runtime["WORKSPACE_OS"]
|
|
381
|
+
WORKSPACE_SERVICE = _persistence_runtime["WORKSPACE_SERVICE"]
|
|
382
|
+
INVITATION_STORE = _persistence_runtime["INVITATION_STORE"]
|
|
383
|
+
PLUGINS_DIR = _persistence_runtime["PLUGINS_DIR"]
|
|
384
|
+
PLUGIN_REGISTRY = _persistence_runtime["PLUGIN_REGISTRY"]
|
|
385
|
+
TEMPLATE_CATALOG = _persistence_runtime["TEMPLATE_CATALOG"]
|
|
386
|
+
AGENT_REGISTRY = _persistence_runtime["AGENT_REGISTRY"]
|
|
387
|
+
MEMORY_SERVICE = _persistence_runtime["MEMORY_SERVICE"]
|
|
388
|
+
INGESTION_PIPELINE = _persistence_runtime["INGESTION_PIPELINE"]
|
|
389
|
+
DEVICE_IDENTITY = _persistence_runtime["DEVICE_IDENTITY"]
|
|
390
|
+
KG_PORTABILITY = _persistence_runtime["KG_PORTABILITY"]
|
|
411
391
|
|
|
412
392
|
def _require_graph():
|
|
413
393
|
if not ENABLE_GRAPH or KNOWLEDGE_GRAPH is None:
|
|
@@ -1071,134 +1051,38 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1071
1051
|
except Exception as exc:
|
|
1072
1052
|
logging.warning("garden vault import skipped: %s", exc)
|
|
1073
1053
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
required_env = env_by_provider.get(provider)
|
|
1090
|
-
if required_env and not os.getenv(required_env) and provider != "ollama":
|
|
1091
|
-
print(f"🌐 Public mode ready. Set {required_env} to autoload {model_id}.")
|
|
1092
|
-
return
|
|
1093
|
-
print(f"🌐 Public mode autoload: {model_id}")
|
|
1094
|
-
try:
|
|
1095
|
-
msg = await router.load_model(model_id)
|
|
1096
|
-
print(f"✅ {msg}")
|
|
1097
|
-
except Exception as e:
|
|
1098
|
-
print(f"⚠️ Public model autoload failed: {e}")
|
|
1099
|
-
return
|
|
1100
|
-
|
|
1101
|
-
if not ALLOW_LOCAL_MODELS:
|
|
1102
|
-
print("⏭️ Local model autoload skipped because LATTICEAI_ALLOW_LOCAL_MODELS=false.")
|
|
1103
|
-
return
|
|
1104
|
-
|
|
1105
|
-
print("⏳ Auto-loading local model stack:")
|
|
1106
|
-
print(f" - Target: {LOCAL_MODEL}")
|
|
1107
|
-
if LOCAL_DRAFT_MODEL:
|
|
1108
|
-
print(f" - Draft: {LOCAL_DRAFT_MODEL}")
|
|
1109
|
-
else:
|
|
1110
|
-
print(" - Draft: disabled (set LATTICEAI_LOCAL_DRAFT_MODEL to enable)")
|
|
1111
|
-
try:
|
|
1112
|
-
await router.load_model(LOCAL_MODEL, draft_model_id=LOCAL_DRAFT_MODEL or None)
|
|
1113
|
-
except Exception as e:
|
|
1114
|
-
print(f"⚠️ Local model autoload failed: {e}")
|
|
1115
|
-
|
|
1116
|
-
async def unload_idle_models_loop() -> None:
|
|
1117
|
-
if MODEL_IDLE_UNLOAD_SECONDS <= 0:
|
|
1118
|
-
print("⏭️ Model idle unload disabled.")
|
|
1119
|
-
return
|
|
1120
|
-
while True:
|
|
1121
|
-
await asyncio.sleep(min(60, MODEL_IDLE_UNLOAD_SECONDS))
|
|
1122
|
-
try:
|
|
1123
|
-
unloaded = router.unload_idle_models(MODEL_IDLE_UNLOAD_SECONDS)
|
|
1124
|
-
if unloaded:
|
|
1125
|
-
print(f"🧹 Idle model unload: {', '.join(unloaded)}")
|
|
1126
|
-
except Exception as e:
|
|
1127
|
-
logging.warning("Idle model unload failed: %s", e)
|
|
1128
|
-
|
|
1129
|
-
def _spawn(coro, *, name: str):
|
|
1130
|
-
"""Fire-and-forget asyncio task that logs exceptions instead of swallowing them."""
|
|
1131
|
-
task = asyncio.create_task(coro, name=name)
|
|
1132
|
-
def _on_done(t: asyncio.Task) -> None:
|
|
1133
|
-
if t.cancelled():
|
|
1134
|
-
return
|
|
1135
|
-
exc = t.exception()
|
|
1136
|
-
if exc is not None:
|
|
1137
|
-
logging.warning("background task '%s' failed: %s", name, exc)
|
|
1138
|
-
task.add_done_callback(_on_done)
|
|
1139
|
-
return task
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
@asynccontextmanager
|
|
1143
|
-
async def lifespan(app: FastAPI):
|
|
1144
|
-
try:
|
|
1145
|
-
print(f"🧭 Lattice AI mode: {APP_MODE}")
|
|
1146
|
-
if ENABLE_TELEGRAM:
|
|
1147
|
-
from telegram_bot import run_bot
|
|
1148
|
-
_spawn(run_bot(), name="telegram_bot")
|
|
1149
|
-
print("🚀 Telegram Bot Bridge activated!")
|
|
1150
|
-
else:
|
|
1151
|
-
print("⏭️ Telegram Bot Bridge disabled for this mode.")
|
|
1152
|
-
_spawn(unload_idle_models_loop(), name="unload_idle_models")
|
|
1153
|
-
_spawn(autoload_default_model(), name="autoload_default_model")
|
|
1154
|
-
if LOCAL_KG_WATCHER:
|
|
1155
|
-
restored = LOCAL_KG_WATCHER.restore_enabled_sources()
|
|
1156
|
-
if restored.get("restored"):
|
|
1157
|
-
print(f"🕸️ Local knowledge watchers restored: {restored['restored']}")
|
|
1158
|
-
except Exception as e:
|
|
1159
|
-
print(f"⚠️ Startup sequence failed: {e}")
|
|
1160
|
-
try:
|
|
1161
|
-
yield
|
|
1162
|
-
finally:
|
|
1163
|
-
if LOCAL_KG_WATCHER:
|
|
1164
|
-
LOCAL_KG_WATCHER.stop_all()
|
|
1165
|
-
router.unload_all()
|
|
1166
|
-
for proc in LOCAL_SERVER_PROCESSES.values():
|
|
1167
|
-
try:
|
|
1168
|
-
if proc.poll() is None:
|
|
1169
|
-
proc.terminate()
|
|
1170
|
-
proc.wait(timeout=5)
|
|
1171
|
-
except Exception:
|
|
1172
|
-
pass
|
|
1173
|
-
|
|
1174
|
-
app = FastAPI(title=f"Lattice AI Server ({APP_MODE})", version=APP_VERSION, lifespan=lifespan)
|
|
1175
|
-
|
|
1176
|
-
CORS_ALLOWED_ORIGINS = [
|
|
1177
|
-
f"http://localhost:{DEFAULT_PORT}",
|
|
1178
|
-
f"http://127.0.0.1:{DEFAULT_PORT}",
|
|
1179
|
-
*CORS_EXTRA_ORIGINS,
|
|
1180
|
-
]
|
|
1181
|
-
if CORS_ALLOW_NETWORK:
|
|
1182
|
-
CORS_ALLOWED_ORIGINS = CORS_ALLOWED_ORIGINS + [
|
|
1183
|
-
f"http://{DEFAULT_HOST}:{DEFAULT_PORT}",
|
|
1184
|
-
f"https://{DEFAULT_HOST}:{DEFAULT_PORT}",
|
|
1185
|
-
]
|
|
1186
|
-
|
|
1187
|
-
app.add_middleware(
|
|
1188
|
-
CORSMiddleware,
|
|
1189
|
-
allow_origins=CORS_ALLOWED_ORIGINS,
|
|
1190
|
-
allow_methods=["*"],
|
|
1191
|
-
allow_headers=["*"],
|
|
1192
|
-
allow_credentials=True,
|
|
1054
|
+
_lifespan_runtime = build_lifespan_runtime(
|
|
1055
|
+
app_mode=APP_MODE,
|
|
1056
|
+
enable_telegram=ENABLE_TELEGRAM,
|
|
1057
|
+
autoload_models=AUTOLOAD_MODELS,
|
|
1058
|
+
is_public_mode=IS_PUBLIC_MODE,
|
|
1059
|
+
public_model=PUBLIC_MODEL,
|
|
1060
|
+
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
1061
|
+
local_model=LOCAL_MODEL,
|
|
1062
|
+
local_draft_model=LOCAL_DRAFT_MODEL,
|
|
1063
|
+
model_idle_unload_seconds=MODEL_IDLE_UNLOAD_SECONDS,
|
|
1064
|
+
model_router=router,
|
|
1065
|
+
local_kg_watcher=LOCAL_KG_WATCHER,
|
|
1066
|
+
local_server_processes=LOCAL_SERVER_PROCESSES,
|
|
1067
|
+
logger=logging,
|
|
1193
1068
|
)
|
|
1069
|
+
autoload_default_model = _lifespan_runtime["autoload_default_model"]
|
|
1070
|
+
unload_idle_models_loop = _lifespan_runtime["unload_idle_models_loop"]
|
|
1071
|
+
_spawn = _lifespan_runtime["_spawn"]
|
|
1072
|
+
lifespan = _lifespan_runtime["lifespan"]
|
|
1194
1073
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1074
|
+
_web_runtime = build_web_runtime(
|
|
1075
|
+
app_mode=APP_MODE,
|
|
1076
|
+
app_version=APP_VERSION,
|
|
1077
|
+
lifespan=lifespan,
|
|
1078
|
+
default_host=DEFAULT_HOST,
|
|
1079
|
+
default_port=DEFAULT_PORT,
|
|
1080
|
+
cors_extra_origins=CORS_EXTRA_ORIGINS,
|
|
1081
|
+
cors_allow_network=CORS_ALLOW_NETWORK,
|
|
1082
|
+
static_dir=STATIC_DIR,
|
|
1083
|
+
)
|
|
1084
|
+
app = _web_runtime["app"]
|
|
1085
|
+
CORS_ALLOWED_ORIGINS = _web_runtime["CORS_ALLOWED_ORIGINS"]
|
|
1202
1086
|
ensure_agent_root()
|
|
1203
1087
|
|
|
1204
1088
|
OPEN_REGISTRATION = CONFIG.open_registration
|
|
@@ -1227,7 +1111,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1227
1111
|
get_current_user=get_current_user,
|
|
1228
1112
|
get_user_api_key=get_user_api_key,
|
|
1229
1113
|
)
|
|
1230
|
-
|
|
1114
|
+
_static_routes_bundle = build_static_routes_bundle(
|
|
1115
|
+
create_static_routes_router=create_static_routes_router,
|
|
1231
1116
|
static_dir=STATIC_DIR,
|
|
1232
1117
|
invite_gate_enabled=INVITE_GATE_ENABLED,
|
|
1233
1118
|
invite_code=INVITE_CODE,
|
|
@@ -1235,102 +1120,70 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1235
1120
|
model_router=router,
|
|
1236
1121
|
require_user=require_user,
|
|
1237
1122
|
)
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1123
|
+
STATIC_ROUTES = _static_routes_bundle["STATIC_ROUTES"]
|
|
1124
|
+
ui_file_response = _static_routes_bundle["ui_file_response"]
|
|
1125
|
+
local_sysinfo = _static_routes_bundle["local_sysinfo"]
|
|
1241
1126
|
|
|
1242
1127
|
# ── Auth & Admin routers (latticeai.api) ─────────────────────────────────────
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1128
|
+
_foundation_router_bundle = build_auth_admin_security_router_bundle(
|
|
1129
|
+
create_auth_router=create_auth_router,
|
|
1130
|
+
load_users=load_users,
|
|
1131
|
+
save_users=save_users,
|
|
1132
|
+
hash_password=hash_password,
|
|
1133
|
+
verify_and_migrate_password=verify_and_migrate_password,
|
|
1134
|
+
create_session=create_session,
|
|
1135
|
+
get_session_email=get_session_email,
|
|
1136
|
+
invalidate_session=invalidate_session,
|
|
1137
|
+
extract_bearer_token=_extract_bearer_token,
|
|
1138
|
+
get_user_role=get_user_role,
|
|
1139
|
+
require_user=require_user,
|
|
1140
|
+
check_ip_rate_limit=_check_rate_limit,
|
|
1141
|
+
client_ip=_client_ip,
|
|
1142
|
+
get_sso_settings=get_sso_settings,
|
|
1143
|
+
get_sso_discovery=_get_sso_discovery,
|
|
1251
1144
|
public_sso_config=public_sso_config,
|
|
1252
|
-
open_registration=OPEN_REGISTRATION,
|
|
1145
|
+
open_registration=OPEN_REGISTRATION,
|
|
1146
|
+
session_ttl=_SESSION_TTL,
|
|
1253
1147
|
require_auth=REQUIRE_AUTH,
|
|
1254
1148
|
ensure_identity=ensure_user_identity,
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
def _product_hardening_status():
|
|
1264
|
-
return build_product_hardening_status(
|
|
1265
|
-
config=CONFIG,
|
|
1266
|
-
portability=KG_PORTABILITY,
|
|
1267
|
-
device_identity=DEVICE_IDENTITY,
|
|
1268
|
-
)
|
|
1269
|
-
|
|
1270
|
-
app.include_router(create_admin_router(
|
|
1271
|
-
require_admin=require_admin, require_user=require_user,
|
|
1272
|
-
load_users=load_users, save_users=save_users,
|
|
1273
|
-
get_user_role=get_user_role, get_history=get_history,
|
|
1274
|
-
get_audit_log=get_audit_log,
|
|
1275
|
-
public_user=public_user, load_vpc_config=load_vpc_config,
|
|
1149
|
+
create_admin_router=create_admin_router,
|
|
1150
|
+
require_admin=require_admin,
|
|
1151
|
+
get_history=get_history,
|
|
1152
|
+
get_audit_log=_get_audit_log,
|
|
1153
|
+
audit_file=AUDIT_FILE,
|
|
1154
|
+
public_user=public_user,
|
|
1155
|
+
load_vpc_config=load_vpc_config,
|
|
1276
1156
|
save_vpc_config=save_vpc_config,
|
|
1277
1157
|
build_admin_audit_report=build_admin_audit_report,
|
|
1278
1158
|
build_sensitivity_report=build_sensitivity_report,
|
|
1279
1159
|
append_audit_event=append_audit_event,
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1160
|
+
save_sso_config=save_sso_config,
|
|
1161
|
+
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
1162
|
+
enable_graph=ENABLE_GRAPH,
|
|
1163
|
+
logger=logging,
|
|
1164
|
+
invite_code=INVITE_CODE,
|
|
1165
|
+
invite_gate_enabled=INVITE_GATE_ENABLED,
|
|
1283
1166
|
default_port=DEFAULT_PORT,
|
|
1284
1167
|
policy_matrix=policy_matrix,
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1168
|
+
build_product_hardening_status=build_product_hardening_status,
|
|
1169
|
+
config=CONFIG,
|
|
1170
|
+
kg_portability=KG_PORTABILITY,
|
|
1171
|
+
device_identity=DEVICE_IDENTITY,
|
|
1172
|
+
create_invitations_router=create_invitations_router,
|
|
1289
1173
|
invitation_store=INVITATION_STORE,
|
|
1290
1174
|
workspace_service=WORKSPACE_SERVICE,
|
|
1291
|
-
require_admin=require_admin,
|
|
1292
|
-
require_user=require_user,
|
|
1293
1175
|
user_id_for_email=user_id_for_email,
|
|
1294
|
-
|
|
1295
|
-
))
|
|
1296
|
-
|
|
1297
|
-
# ── Security & Audit Command Center (피드백 #5) ──────────────────────────────
|
|
1298
|
-
def _security_audit_events_safe() -> List[Dict]:
|
|
1299
|
-
try:
|
|
1300
|
-
return _get_audit_log(AUDIT_FILE)
|
|
1301
|
-
except Exception as e:
|
|
1302
|
-
logging.warning("security audit events load failed: %s", e)
|
|
1303
|
-
return []
|
|
1304
|
-
|
|
1305
|
-
def _security_list_uploaded_files() -> List[Dict]:
|
|
1306
|
-
"""Audit log에서 document_upload 이벤트를 가공해서 file 목록으로 노출."""
|
|
1307
|
-
files: List[Dict] = []
|
|
1308
|
-
for idx, e in enumerate(_security_audit_events_safe()):
|
|
1309
|
-
if e.get("event_type") != "document_upload":
|
|
1310
|
-
continue
|
|
1311
|
-
files.append({
|
|
1312
|
-
"file_id": str(e.get("filename") or idx),
|
|
1313
|
-
"filename": e.get("filename"),
|
|
1314
|
-
"user_email": e.get("user_email"),
|
|
1315
|
-
"user_nickname": e.get("user_nickname"),
|
|
1316
|
-
"uploaded_at": e.get("timestamp"),
|
|
1317
|
-
"ext": e.get("ext"),
|
|
1318
|
-
"bytes": e.get("bytes"),
|
|
1319
|
-
"sensitivity": e.get("sensitivity") or "none",
|
|
1320
|
-
"sensitive_labels": e.get("sensitive_labels") or [],
|
|
1321
|
-
"content_preview": e.get("content_preview"),
|
|
1322
|
-
})
|
|
1323
|
-
return files
|
|
1324
|
-
|
|
1325
|
-
app.include_router(_create_security_router(
|
|
1326
|
-
require_admin=require_admin,
|
|
1327
|
-
get_history=get_history,
|
|
1328
|
-
get_audit_events=_security_audit_events_safe,
|
|
1176
|
+
create_security_router=_create_security_router,
|
|
1329
1177
|
classify_sensitive_message=classify_sensitive_message,
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1178
|
+
)
|
|
1179
|
+
auth_router = _foundation_router_bundle["auth_router"]
|
|
1180
|
+
admin_router = _foundation_router_bundle["admin_router"]
|
|
1181
|
+
invitations_router = _foundation_router_bundle["invitations_router"]
|
|
1182
|
+
security_router = _foundation_router_bundle["security_router"]
|
|
1183
|
+
_graph_stats_safe = _foundation_router_bundle["_graph_stats_safe"]
|
|
1184
|
+
_product_hardening_status = _foundation_router_bundle["_product_hardening_status"]
|
|
1185
|
+
_security_audit_events_safe = _foundation_router_bundle["_security_audit_events_safe"]
|
|
1186
|
+
_security_list_uploaded_files = _foundation_router_bundle["_security_list_uploaded_files"]
|
|
1334
1187
|
|
|
1335
1188
|
# ── Static UI/status routes moved to latticeai.api.static_routes ──
|
|
1336
1189
|
|
|
@@ -1365,22 +1218,18 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1365
1218
|
return KNOWLEDGE_GRAPH if (ENABLE_GRAPH and KNOWLEDGE_GRAPH) else None
|
|
1366
1219
|
|
|
1367
1220
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if REQUIRE_AUTH and user_email:
|
|
1376
|
-
allowed = PLATFORM.allowed_scopes(user_email)
|
|
1377
|
-
return SEARCH_SERVICE.hybrid_search(q, allowed_workspaces=allowed, **kw)
|
|
1378
|
-
|
|
1379
|
-
CONTEXT_ASSEMBLER = ContextAssembler(
|
|
1380
|
-
memory_recall=MEMORY_SERVICE.recall,
|
|
1381
|
-
hybrid_search=_scoped_hybrid_search,
|
|
1382
|
-
notes_context=gardener.get_relevant_context,
|
|
1221
|
+
_context_runtime = build_context_runtime(
|
|
1222
|
+
graph_store=_workspace_graph(),
|
|
1223
|
+
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1224
|
+
memory_service=MEMORY_SERVICE,
|
|
1225
|
+
gardener=gardener,
|
|
1226
|
+
require_auth=REQUIRE_AUTH,
|
|
1227
|
+
allowed_scopes_for_user=lambda user_email: PLATFORM.allowed_scopes(user_email),
|
|
1383
1228
|
)
|
|
1229
|
+
SEARCH_SERVICE = _context_runtime["SEARCH_SERVICE"]
|
|
1230
|
+
BRAIN_MEMORY = _context_runtime["BRAIN_MEMORY"]
|
|
1231
|
+
CONTEXT_ASSEMBLER = _context_runtime["CONTEXT_ASSEMBLER"]
|
|
1232
|
+
_scoped_hybrid_search = _context_runtime["_scoped_hybrid_search"]
|
|
1384
1233
|
|
|
1385
1234
|
|
|
1386
1235
|
# ── Telegram chat mirror: registered only when ENABLE_TELEGRAM is truthy.
|
|
@@ -1395,7 +1244,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1395
1244
|
|
|
1396
1245
|
# ── Typed dependency context (latticeai.services.app_context) ────────────────
|
|
1397
1246
|
# One context object replaces the historical 25-30-kwarg router wiring.
|
|
1398
|
-
context =
|
|
1247
|
+
context = build_app_context(
|
|
1399
1248
|
config=CONFIG,
|
|
1400
1249
|
data_dir=DATA_DIR,
|
|
1401
1250
|
static_dir=STATIC_DIR,
|
|
@@ -1449,8 +1298,16 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1449
1298
|
)
|
|
1450
1299
|
app.state.context = context
|
|
1451
1300
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1301
|
+
register_foundation_routers(
|
|
1302
|
+
app,
|
|
1303
|
+
static_router=STATIC_ROUTES.router,
|
|
1304
|
+
auth_router=auth_router,
|
|
1305
|
+
admin_router=admin_router,
|
|
1306
|
+
invitations_router=invitations_router,
|
|
1307
|
+
security_router=security_router,
|
|
1308
|
+
create_workspace_router=create_workspace_router,
|
|
1309
|
+
context=context,
|
|
1310
|
+
)
|
|
1454
1311
|
|
|
1455
1312
|
|
|
1456
1313
|
# ── v2 Agentic Workspace Platform: cross-system wiring ───────────────────────
|
|
@@ -1480,130 +1337,55 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1480
1337
|
agent_registry=AGENT_REGISTRY,
|
|
1481
1338
|
)
|
|
1482
1339
|
|
|
1483
|
-
|
|
1484
|
-
from latticeai.services.review_queue import ReviewQueueService
|
|
1485
|
-
|
|
1486
|
-
REVIEW_QUEUE = ReviewQueueService(store=WORKSPACE_OS)
|
|
1487
|
-
|
|
1488
|
-
# ── v4 Trigger system (T7d): interval + brain-event workflow triggers.
|
|
1489
|
-
from latticeai.services.triggers import TRIGGER_HOOK_NAME, TriggerService
|
|
1490
|
-
|
|
1491
|
-
TRIGGER_SERVICE = TriggerService(
|
|
1340
|
+
_automation_runtime = build_automation_runtime(
|
|
1492
1341
|
store=WORKSPACE_OS,
|
|
1493
|
-
|
|
1494
|
-
wf_id, None, None, with_agent=False, inputs=inputs,
|
|
1495
|
-
),
|
|
1342
|
+
platform=PLATFORM,
|
|
1496
1343
|
data_dir=DATA_DIR,
|
|
1497
|
-
review_sink=REVIEW_QUEUE,
|
|
1498
|
-
)
|
|
1499
|
-
# Idempotent hook registration: ingestion post_tool events fan into triggers.
|
|
1500
|
-
_trigger_hook_id = next(
|
|
1501
|
-
(h.get("id") for h in HOOKS_REGISTRY._state.get("custom", [])
|
|
1502
|
-
if h.get("name") == TRIGGER_HOOK_NAME),
|
|
1503
|
-
None,
|
|
1504
|
-
)
|
|
1505
|
-
if _trigger_hook_id is None:
|
|
1506
|
-
_trigger_hook_id = HOOKS_REGISTRY.register(
|
|
1507
|
-
name=TRIGGER_HOOK_NAME,
|
|
1508
|
-
kind="post_tool",
|
|
1509
|
-
description="Fires brain_event workflow triggers when knowledge enters the brain.",
|
|
1510
|
-
)["id"]
|
|
1511
|
-
HOOKS_REGISTRY.register_hook(_trigger_hook_id, TRIGGER_SERVICE.hook_runner())
|
|
1512
|
-
|
|
1513
|
-
# Single AgentRuntime boundary over the orchestrator + run store.
|
|
1514
|
-
# (lattice_brain/runtime.agent_runtime.AgentRuntime — see runtime/__init__.py for full dep graph + entry mapping)
|
|
1515
|
-
AGENT_RUNTIME = AgentRuntime(
|
|
1516
|
-
store=WORKSPACE_OS,
|
|
1517
|
-
orchestrator_factory=PLATFORM.build_orchestrator,
|
|
1518
1344
|
workspace_graph=_workspace_graph,
|
|
1519
1345
|
append_audit_event=append_audit_event,
|
|
1520
1346
|
hooks=HOOKS_REGISTRY,
|
|
1521
1347
|
)
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
append_audit_event=append_audit_event,
|
|
1528
|
-
hooks=HOOKS_REGISTRY,
|
|
1529
|
-
review_sink=REVIEW_QUEUE,
|
|
1530
|
-
)
|
|
1531
|
-
AGENT_RUNTIME.attach_executor(RUN_EXECUTOR)
|
|
1348
|
+
REVIEW_QUEUE = _automation_runtime["REVIEW_QUEUE"]
|
|
1349
|
+
TRIGGER_SERVICE = _automation_runtime["TRIGGER_SERVICE"]
|
|
1350
|
+
AGENT_RUNTIME = _automation_runtime["AGENT_RUNTIME"]
|
|
1351
|
+
RUN_EXECUTOR = _automation_runtime["RUN_EXECUTOR"]
|
|
1352
|
+
bind_trigger_hook_runner(registry=HOOKS_REGISTRY, trigger_service=TRIGGER_SERVICE)
|
|
1532
1353
|
app.state.run_executor = RUN_EXECUTOR
|
|
1533
1354
|
app.state.run_reconciliation = RUN_EXECUTOR.reconcile_startup()
|
|
1534
1355
|
TRIGGER_SERVICE.start()
|
|
1535
1356
|
|
|
1536
1357
|
# ── Hooks dispatch: bind real built-in runners ───────────────────────────────
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
# HookContext and may mutate its payload, return a status dict, or block.
|
|
1540
|
-
# Bind a real runner to every built-in hook so none is a silent no-op.
|
|
1541
|
-
register_builtin_hook_runners(
|
|
1542
|
-
HOOKS_REGISTRY,
|
|
1358
|
+
bind_builtin_hook_runners(
|
|
1359
|
+
registry=HOOKS_REGISTRY,
|
|
1543
1360
|
append_audit_event=append_audit_event,
|
|
1544
1361
|
get_tool_permission=get_tool_permission,
|
|
1545
1362
|
classify_sensitive_message=classify_sensitive_message,
|
|
1546
1363
|
)
|
|
1547
1364
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1365
|
+
register_platform_feature_routers(
|
|
1366
|
+
app,
|
|
1367
|
+
create_plugins_router=create_plugins_router,
|
|
1368
|
+
plugin_registry=PLUGIN_REGISTRY,
|
|
1550
1369
|
require_user=require_user,
|
|
1551
1370
|
require_admin=require_admin,
|
|
1552
1371
|
append_audit_event=append_audit_event,
|
|
1553
|
-
|
|
1554
|
-
plugin_runners_factory=lambda: PLATFORM.plugin_capability_runners(None, None),
|
|
1372
|
+
platform=PLATFORM,
|
|
1555
1373
|
ui_file_response=ui_file_response,
|
|
1556
1374
|
static_dir=STATIC_DIR,
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
app.include_router(create_workflow_designer_router(
|
|
1375
|
+
create_workflow_designer_router=create_workflow_designer_router,
|
|
1560
1376
|
store=WORKSPACE_OS,
|
|
1561
|
-
require_user=require_user,
|
|
1562
1377
|
get_current_user=get_current_user,
|
|
1563
|
-
gate_read=PLATFORM.gate_read,
|
|
1564
|
-
gate_write=PLATFORM.gate_write,
|
|
1565
1378
|
workspace_graph=_workspace_graph,
|
|
1566
|
-
build_runners=PLATFORM.build_workflow_runners,
|
|
1567
|
-
append_audit_event=append_audit_event,
|
|
1568
|
-
ui_file_response=ui_file_response,
|
|
1569
|
-
static_dir=STATIC_DIR,
|
|
1570
1379
|
hooks=HOOKS_REGISTRY,
|
|
1571
1380
|
run_executor=RUN_EXECUTOR,
|
|
1572
1381
|
trigger_service=TRIGGER_SERVICE,
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
app.include_router(create_agents_router(
|
|
1576
|
-
store=WORKSPACE_OS,
|
|
1577
|
-
orchestrator_factory=PLATFORM.build_orchestrator,
|
|
1578
|
-
require_user=require_user,
|
|
1579
|
-
get_current_user=get_current_user,
|
|
1580
|
-
gate_read=PLATFORM.gate_read,
|
|
1581
|
-
gate_write=PLATFORM.gate_write,
|
|
1582
|
-
workspace_graph=_workspace_graph,
|
|
1583
|
-
append_audit_event=append_audit_event,
|
|
1584
|
-
ui_file_response=ui_file_response,
|
|
1585
|
-
static_dir=STATIC_DIR,
|
|
1382
|
+
create_agents_router=create_agents_router,
|
|
1586
1383
|
agent_runtime=AGENT_RUNTIME,
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
catalog=TEMPLATE_CATALOG,
|
|
1593
|
-
require_user=require_user,
|
|
1594
|
-
gate_read=PLATFORM.gate_read,
|
|
1595
|
-
gate_write=PLATFORM.gate_write,
|
|
1596
|
-
workspace_graph=_workspace_graph,
|
|
1597
|
-
))
|
|
1598
|
-
|
|
1599
|
-
app.include_router(create_realtime_router(
|
|
1600
|
-
bus=REALTIME_BUS,
|
|
1601
|
-
require_user=require_user,
|
|
1602
|
-
get_current_user=get_current_user,
|
|
1603
|
-
allowed_scopes=PLATFORM.allowed_scopes,
|
|
1604
|
-
ui_file_response=ui_file_response,
|
|
1605
|
-
static_dir=STATIC_DIR,
|
|
1606
|
-
))
|
|
1384
|
+
create_marketplace_router=create_marketplace_router,
|
|
1385
|
+
template_catalog=TEMPLATE_CATALOG,
|
|
1386
|
+
create_realtime_router=create_realtime_router,
|
|
1387
|
+
realtime_bus=REALTIME_BUS,
|
|
1388
|
+
)
|
|
1607
1389
|
|
|
1608
1390
|
|
|
1609
1391
|
# ── Health & Info ──────────────────────────────────────────────────────────────
|
|
@@ -1612,26 +1394,23 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1612
1394
|
# ── Health / status / engine-summary router (latticeai.api.health, v1.2.0) ───
|
|
1613
1395
|
# /health, /mode, /runtime_features, /engines(GET) now live in the health router.
|
|
1614
1396
|
# Heavier engine mutation endpoints remain below in server_app.
|
|
1615
|
-
MODEL_SERVICE =
|
|
1397
|
+
MODEL_SERVICE = build_model_service(
|
|
1616
1398
|
model_router=router,
|
|
1617
1399
|
runtime_features=runtime_features,
|
|
1618
1400
|
is_public=IS_PUBLIC_MODE,
|
|
1619
1401
|
)
|
|
1620
|
-
|
|
1402
|
+
register_health_and_model_routers(
|
|
1403
|
+
app,
|
|
1404
|
+
create_health_router=create_health_router,
|
|
1621
1405
|
model_service=MODEL_SERVICE,
|
|
1622
1406
|
engine_status=engine_status,
|
|
1623
1407
|
get_current_user=get_current_user,
|
|
1624
1408
|
require_auth=REQUIRE_AUTH,
|
|
1625
1409
|
app_version=APP_VERSION,
|
|
1626
1410
|
app_mode=APP_MODE,
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
# ── Model / Engine router (latticeai.api.models, v1.3.0) ─────────────────────
|
|
1631
|
-
app.include_router(create_models_router(
|
|
1411
|
+
create_models_router=create_models_router,
|
|
1632
1412
|
model_router=router,
|
|
1633
1413
|
require_user=require_user,
|
|
1634
|
-
get_current_user=get_current_user,
|
|
1635
1414
|
load_users=load_users,
|
|
1636
1415
|
get_user_role=get_user_role,
|
|
1637
1416
|
install_engine=install_engine,
|
|
@@ -1643,7 +1422,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1643
1422
|
sse_event=sse_event,
|
|
1644
1423
|
ensure_ollama_server=ensure_ollama_server,
|
|
1645
1424
|
local_binary=local_binary,
|
|
1646
|
-
engine_status=engine_status,
|
|
1647
1425
|
filter_lower_family_versions=filter_lower_family_versions,
|
|
1648
1426
|
list_compat_profiles=_list_compat_profiles,
|
|
1649
1427
|
set_user_api_key=set_user_api_key,
|
|
@@ -1652,14 +1430,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1652
1430
|
cloud_verify_ttl_seconds=CLOUD_VERIFY_TTL_SECONDS,
|
|
1653
1431
|
is_public_mode=IS_PUBLIC_MODE,
|
|
1654
1432
|
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
1655
|
-
|
|
1656
|
-
))
|
|
1433
|
+
)
|
|
1657
1434
|
|
|
1658
1435
|
|
|
1659
1436
|
# ── Chat / Completion ──────────────────────────────────────────────────────────
|
|
1660
1437
|
|
|
1661
|
-
app.include_router(create_chat_router(context))
|
|
1662
|
-
|
|
1663
1438
|
def _embedding_info() -> dict:
|
|
1664
1439
|
from latticeai.core.embedding_providers import PROVIDER_TYPES, embedding_provider_profiles
|
|
1665
1440
|
info = EMBEDDER.as_dict()
|
|
@@ -1676,20 +1451,21 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1676
1451
|
return None
|
|
1677
1452
|
return PLATFORM.allowed_scopes(user)
|
|
1678
1453
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1454
|
+
register_interaction_routers(
|
|
1455
|
+
app,
|
|
1456
|
+
create_chat_router=create_chat_router,
|
|
1457
|
+
context=context,
|
|
1458
|
+
create_search_router=create_search_router,
|
|
1459
|
+
search_service=SEARCH_SERVICE,
|
|
1681
1460
|
allowed_workspaces_for=_allowed_workspaces_for,
|
|
1682
1461
|
require_user=require_user,
|
|
1683
1462
|
embedding_info=_embedding_info,
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
app.include_router(create_tools_router(
|
|
1463
|
+
create_tools_router=create_tools_router,
|
|
1687
1464
|
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1688
1465
|
config=CONFIG,
|
|
1689
1466
|
data_dir=DATA_DIR,
|
|
1690
1467
|
static_dir=STATIC_DIR,
|
|
1691
1468
|
model_router=router,
|
|
1692
|
-
require_user=require_user,
|
|
1693
1469
|
require_admin=require_admin,
|
|
1694
1470
|
get_current_user=get_current_user,
|
|
1695
1471
|
clear_history=clear_history,
|
|
@@ -1707,28 +1483,13 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1707
1483
|
install_mcp=install_mcp,
|
|
1708
1484
|
mcp_public_item=mcp_public_item,
|
|
1709
1485
|
hooks=HOOKS_REGISTRY,
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
)
|
|
1717
|
-
|
|
1718
|
-
app.include_router(create_agent_registry_router(
|
|
1719
|
-
registry=AGENT_REGISTRY,
|
|
1720
|
-
require_user=require_user,
|
|
1721
|
-
append_audit_event=append_audit_event,
|
|
1722
|
-
))
|
|
1723
|
-
|
|
1724
|
-
app.include_router(create_memory_router(
|
|
1725
|
-
service=MEMORY_SERVICE,
|
|
1726
|
-
require_user=require_user,
|
|
1727
|
-
get_current_user=get_current_user,
|
|
1728
|
-
gate_read=PLATFORM.gate_read,
|
|
1729
|
-
gate_write=PLATFORM.gate_write,
|
|
1730
|
-
append_audit_event=append_audit_event,
|
|
1731
|
-
))
|
|
1486
|
+
create_hooks_router=create_hooks_router,
|
|
1487
|
+
create_agent_registry_router=create_agent_registry_router,
|
|
1488
|
+
agent_registry=AGENT_REGISTRY,
|
|
1489
|
+
create_memory_router=create_memory_router,
|
|
1490
|
+
memory_service=MEMORY_SERVICE,
|
|
1491
|
+
platform=PLATFORM,
|
|
1492
|
+
)
|
|
1732
1493
|
|
|
1733
1494
|
from latticeai.api.review_queue import create_review_queue_router
|
|
1734
1495
|
|
|
@@ -1742,39 +1503,29 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1742
1503
|
inputs={"__review_item__": item.get("id")},
|
|
1743
1504
|
)
|
|
1744
1505
|
|
|
1745
|
-
|
|
1746
|
-
|
|
1506
|
+
BRAIN_NETWORK = register_review_and_brain_tail_routers(
|
|
1507
|
+
app,
|
|
1508
|
+
create_review_queue_router=create_review_queue_router,
|
|
1509
|
+
review_queue=REVIEW_QUEUE,
|
|
1747
1510
|
require_user=require_user,
|
|
1748
1511
|
gate_read=PLATFORM.gate_read,
|
|
1749
1512
|
gate_write=PLATFORM.gate_write,
|
|
1750
1513
|
run_review_item=_run_review_item,
|
|
1751
1514
|
append_audit_event=append_audit_event,
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
require_user=require_user,
|
|
1757
|
-
))
|
|
1758
|
-
|
|
1759
|
-
app.include_router(create_portability_router(
|
|
1760
|
-
service=KG_PORTABILITY,
|
|
1761
|
-
require_user=require_user,
|
|
1515
|
+
create_browser_router=create_browser_router,
|
|
1516
|
+
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1517
|
+
create_portability_router=create_portability_router,
|
|
1518
|
+
kg_portability=KG_PORTABILITY,
|
|
1762
1519
|
require_admin=require_admin,
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
BRAIN_NETWORK = BrainNetwork(
|
|
1766
|
-
identity=DEVICE_IDENTITY,
|
|
1767
|
-
portability=KG_PORTABILITY,
|
|
1520
|
+
build_brain_network=build_brain_network,
|
|
1521
|
+
device_identity=DEVICE_IDENTITY,
|
|
1768
1522
|
data_dir=DATA_DIR,
|
|
1523
|
+
create_network_router=create_network_router,
|
|
1524
|
+
create_garden_router=create_garden_router,
|
|
1525
|
+
gardener=gardener,
|
|
1526
|
+
create_setup_router=create_setup_router,
|
|
1527
|
+
model_router=router,
|
|
1769
1528
|
)
|
|
1770
|
-
app.include_router(create_network_router(
|
|
1771
|
-
network=BRAIN_NETWORK,
|
|
1772
|
-
identity=DEVICE_IDENTITY,
|
|
1773
|
-
require_user=require_user,
|
|
1774
|
-
))
|
|
1775
|
-
|
|
1776
|
-
app.include_router(create_garden_router(gardener=gardener, require_user=require_user))
|
|
1777
|
-
app.include_router(create_setup_router(model_router=router, require_user=require_user))
|
|
1778
1529
|
|
|
1779
1530
|
# ── Entry Point ────────────────────────────────────────────────────────────────
|
|
1780
1531
|
|