ltcai 5.6.0 → 6.1.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 +45 -25
- package/docs/CHANGELOG.md +74 -0
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +1 -1
- package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +1 -1
- package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +1 -1
- package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +1 -1
- package/frontend/openapi.json +39 -0
- package/frontend/src/App.tsx +5 -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/components/ProductFlow.tsx +7 -0
- package/frontend/src/features/review/ReviewCard.tsx +96 -0
- package/frontend/src/features/review/ReviewInbox.tsx +112 -0
- package/frontend/src/features/review/reviewHelpers.ts +69 -0
- package/frontend/src/i18n.ts +18 -8
- package/frontend/src/pages/Act.tsx +5 -177
- package/frontend/src/routes.ts +1 -0
- package/frontend/src/styles.css +20 -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/chat.py +52 -33
- package/latticeai/api/review_queue.py +7 -3
- package/latticeai/app_factory.py +253 -475
- package/latticeai/cli/__init__.py +1 -0
- package/latticeai/cli/runtime.py +37 -0
- 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/app_context.py +1 -0
- package/latticeai/services/review_queue.py +20 -4
- package/latticeai/services/tool_dispatch.py +82 -25
- package/ltcai_cli.py +5 -31
- 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 +5 -5
- package/static/app/assets/{index-xRn29gI8.css → index-B744yblP.css} +1 -1
- package/static/app/assets/index-DYaUKNfl.js +16 -0
- package/static/app/assets/index-DYaUKNfl.js.map +1 -0
- package/static/app/index.html +2 -2
- package/telegram_bot.py +3 -9
- 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
|
|
@@ -145,30 +153,21 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
145
153
|
from latticeai.api.invitations import create_invitations_router
|
|
146
154
|
from latticeai.api.marketplace import create_marketplace_router
|
|
147
155
|
from latticeai.api.models import create_models_router
|
|
148
|
-
from latticeai.api.chat import create_chat_router
|
|
156
|
+
from latticeai.api.chat import build_recent_chat_context, create_chat_router
|
|
149
157
|
from latticeai.api.search import create_search_router
|
|
150
158
|
from latticeai.api.tools import create_tools_router
|
|
151
159
|
from latticeai.api.static_routes import create_static_routes_router
|
|
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__``
|
|
@@ -182,6 +181,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
182
181
|
configure_tool_dispatch,
|
|
183
182
|
get_tool_permission,
|
|
184
183
|
list_tool_permissions,
|
|
184
|
+
build_agent_runtime,
|
|
185
185
|
tool_response as _tool_response,
|
|
186
186
|
)
|
|
187
187
|
from latticeai.core.tool_registry import TOOL_CATALOG_BRIEF as _TOOL_CATALOG_BRIEF # noqa: F401
|
|
@@ -191,7 +191,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
191
191
|
)
|
|
192
192
|
from p_reinforce import PReinforceGardener
|
|
193
193
|
from setup_wizard import get_recommendations, scan_environment
|
|
194
|
-
from tools import ensure_agent_root
|
|
194
|
+
from tools import ensure_agent_root, execute_tool, knowledge_save
|
|
195
195
|
|
|
196
196
|
try:
|
|
197
197
|
import keyring
|
|
@@ -281,10 +281,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
281
281
|
return True
|
|
282
282
|
return False
|
|
283
283
|
|
|
284
|
-
# ── Session store — delegated to latticeai.
|
|
285
|
-
_SESSION_TTL = 60 * 60 * 24
|
|
286
|
-
_session_store = _SessionStore()
|
|
287
|
-
|
|
284
|
+
# ── Session store — delegated to latticeai.runtime.bootstrap ──────────────────
|
|
288
285
|
def _check_rate_limit(ip: str, action: str, max_calls: int, window_secs: float) -> None:
|
|
289
286
|
_check_ip_rate_limit(ip, action, max_calls=max_calls, window_secs=window_secs)
|
|
290
287
|
|
|
@@ -294,17 +291,15 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
294
291
|
def user_id_for_email(email: Optional[str]) -> Optional[str]:
|
|
295
292
|
return _user_id_for_email(load_users(), email)
|
|
296
293
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def invalidate_session(token: str) -> None:
|
|
307
|
-
_session_store.invalidate(token)
|
|
294
|
+
# Session token lifecycle (store + create/get/invalidate closures) lives in
|
|
295
|
+
# the bootstrap seam; user_id_for_email is injected as the subject resolver.
|
|
296
|
+
_session_runtime = build_session_runtime(user_id_resolver=user_id_for_email)
|
|
297
|
+
_SESSION_TTL = _session_runtime["_SESSION_TTL"]
|
|
298
|
+
_session_store = _session_runtime["_session_store"]
|
|
299
|
+
create_session = _session_runtime["create_session"]
|
|
300
|
+
get_session_email = _session_runtime["get_session_email"]
|
|
301
|
+
get_session_user_id = _session_runtime["get_session_user_id"]
|
|
302
|
+
invalidate_session = _session_runtime["invalidate_session"]
|
|
308
303
|
|
|
309
304
|
# ── User Management Logic ──────────────────────────────────────────────────
|
|
310
305
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
@@ -360,54 +355,40 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
360
355
|
# file is left untouched on disk as the import source.
|
|
361
356
|
CONVERSATIONS = _brain_runtime["CONVERSATIONS"]
|
|
362
357
|
# 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,
|
|
358
|
+
# reindexes can fire the pre_index/post_index lifecycle hooks. The registry
|
|
359
|
+
# + watcher pair is assembled behind the hooks_runtime seam.
|
|
360
|
+
_hooks_runtime = build_hooks_runtime(
|
|
386
361
|
data_dir=DATA_DIR,
|
|
387
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
388
362
|
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),
|
|
363
|
+
knowledge_graph_getter=lambda: KNOWLEDGE_GRAPH,
|
|
401
364
|
)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
365
|
+
HOOKS_REGISTRY = _hooks_runtime["HOOKS_REGISTRY"]
|
|
366
|
+
LOCAL_KG_WATCHER = _hooks_runtime["LOCAL_KG_WATCHER"]
|
|
367
|
+
# ── Persistence/service graph: workspace store, realtime feed, plugin/memory
|
|
368
|
+
# registries, ingestion pipeline, device identity, and portability services.
|
|
369
|
+
_persistence_runtime = build_persistence_runtime(
|
|
407
370
|
data_dir=DATA_DIR,
|
|
371
|
+
base_dir=BASE_DIR,
|
|
408
372
|
enable_graph=ENABLE_GRAPH,
|
|
409
|
-
|
|
373
|
+
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
374
|
+
hooks_registry=HOOKS_REGISTRY,
|
|
375
|
+
history_file=HISTORY_FILE,
|
|
376
|
+
conversations=CONVERSATIONS,
|
|
377
|
+
user_id_for_email=user_id_for_email,
|
|
378
|
+
audit=lambda action, detail, user: append_audit_event(action, user_email=user, **detail),
|
|
410
379
|
)
|
|
380
|
+
REALTIME_BUS = _persistence_runtime["REALTIME_BUS"]
|
|
381
|
+
WORKSPACE_OS = _persistence_runtime["WORKSPACE_OS"]
|
|
382
|
+
WORKSPACE_SERVICE = _persistence_runtime["WORKSPACE_SERVICE"]
|
|
383
|
+
INVITATION_STORE = _persistence_runtime["INVITATION_STORE"]
|
|
384
|
+
PLUGINS_DIR = _persistence_runtime["PLUGINS_DIR"]
|
|
385
|
+
PLUGIN_REGISTRY = _persistence_runtime["PLUGIN_REGISTRY"]
|
|
386
|
+
TEMPLATE_CATALOG = _persistence_runtime["TEMPLATE_CATALOG"]
|
|
387
|
+
AGENT_REGISTRY = _persistence_runtime["AGENT_REGISTRY"]
|
|
388
|
+
MEMORY_SERVICE = _persistence_runtime["MEMORY_SERVICE"]
|
|
389
|
+
INGESTION_PIPELINE = _persistence_runtime["INGESTION_PIPELINE"]
|
|
390
|
+
DEVICE_IDENTITY = _persistence_runtime["DEVICE_IDENTITY"]
|
|
391
|
+
KG_PORTABILITY = _persistence_runtime["KG_PORTABILITY"]
|
|
411
392
|
|
|
412
393
|
def _require_graph():
|
|
413
394
|
if not ENABLE_GRAPH or KNOWLEDGE_GRAPH is None:
|
|
@@ -1071,134 +1052,38 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1071
1052
|
except Exception as exc:
|
|
1072
1053
|
logging.warning("garden vault import skipped: %s", exc)
|
|
1073
1054
|
|
|
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,
|
|
1055
|
+
_lifespan_runtime = build_lifespan_runtime(
|
|
1056
|
+
app_mode=APP_MODE,
|
|
1057
|
+
enable_telegram=ENABLE_TELEGRAM,
|
|
1058
|
+
autoload_models=AUTOLOAD_MODELS,
|
|
1059
|
+
is_public_mode=IS_PUBLIC_MODE,
|
|
1060
|
+
public_model=PUBLIC_MODEL,
|
|
1061
|
+
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
1062
|
+
local_model=LOCAL_MODEL,
|
|
1063
|
+
local_draft_model=LOCAL_DRAFT_MODEL,
|
|
1064
|
+
model_idle_unload_seconds=MODEL_IDLE_UNLOAD_SECONDS,
|
|
1065
|
+
model_router=router,
|
|
1066
|
+
local_kg_watcher=LOCAL_KG_WATCHER,
|
|
1067
|
+
local_server_processes=LOCAL_SERVER_PROCESSES,
|
|
1068
|
+
logger=logging,
|
|
1193
1069
|
)
|
|
1070
|
+
autoload_default_model = _lifespan_runtime["autoload_default_model"]
|
|
1071
|
+
unload_idle_models_loop = _lifespan_runtime["unload_idle_models_loop"]
|
|
1072
|
+
_spawn = _lifespan_runtime["_spawn"]
|
|
1073
|
+
lifespan = _lifespan_runtime["lifespan"]
|
|
1194
1074
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1075
|
+
_web_runtime = build_web_runtime(
|
|
1076
|
+
app_mode=APP_MODE,
|
|
1077
|
+
app_version=APP_VERSION,
|
|
1078
|
+
lifespan=lifespan,
|
|
1079
|
+
default_host=DEFAULT_HOST,
|
|
1080
|
+
default_port=DEFAULT_PORT,
|
|
1081
|
+
cors_extra_origins=CORS_EXTRA_ORIGINS,
|
|
1082
|
+
cors_allow_network=CORS_ALLOW_NETWORK,
|
|
1083
|
+
static_dir=STATIC_DIR,
|
|
1084
|
+
)
|
|
1085
|
+
app = _web_runtime["app"]
|
|
1086
|
+
CORS_ALLOWED_ORIGINS = _web_runtime["CORS_ALLOWED_ORIGINS"]
|
|
1202
1087
|
ensure_agent_root()
|
|
1203
1088
|
|
|
1204
1089
|
OPEN_REGISTRATION = CONFIG.open_registration
|
|
@@ -1227,7 +1112,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1227
1112
|
get_current_user=get_current_user,
|
|
1228
1113
|
get_user_api_key=get_user_api_key,
|
|
1229
1114
|
)
|
|
1230
|
-
|
|
1115
|
+
_static_routes_bundle = build_static_routes_bundle(
|
|
1116
|
+
create_static_routes_router=create_static_routes_router,
|
|
1231
1117
|
static_dir=STATIC_DIR,
|
|
1232
1118
|
invite_gate_enabled=INVITE_GATE_ENABLED,
|
|
1233
1119
|
invite_code=INVITE_CODE,
|
|
@@ -1235,102 +1121,70 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1235
1121
|
model_router=router,
|
|
1236
1122
|
require_user=require_user,
|
|
1237
1123
|
)
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1124
|
+
STATIC_ROUTES = _static_routes_bundle["STATIC_ROUTES"]
|
|
1125
|
+
ui_file_response = _static_routes_bundle["ui_file_response"]
|
|
1126
|
+
local_sysinfo = _static_routes_bundle["local_sysinfo"]
|
|
1241
1127
|
|
|
1242
1128
|
# ── Auth & Admin routers (latticeai.api) ─────────────────────────────────────
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1129
|
+
_foundation_router_bundle = build_auth_admin_security_router_bundle(
|
|
1130
|
+
create_auth_router=create_auth_router,
|
|
1131
|
+
load_users=load_users,
|
|
1132
|
+
save_users=save_users,
|
|
1133
|
+
hash_password=hash_password,
|
|
1134
|
+
verify_and_migrate_password=verify_and_migrate_password,
|
|
1135
|
+
create_session=create_session,
|
|
1136
|
+
get_session_email=get_session_email,
|
|
1137
|
+
invalidate_session=invalidate_session,
|
|
1138
|
+
extract_bearer_token=_extract_bearer_token,
|
|
1139
|
+
get_user_role=get_user_role,
|
|
1140
|
+
require_user=require_user,
|
|
1141
|
+
check_ip_rate_limit=_check_rate_limit,
|
|
1142
|
+
client_ip=_client_ip,
|
|
1143
|
+
get_sso_settings=get_sso_settings,
|
|
1144
|
+
get_sso_discovery=_get_sso_discovery,
|
|
1251
1145
|
public_sso_config=public_sso_config,
|
|
1252
|
-
open_registration=OPEN_REGISTRATION,
|
|
1146
|
+
open_registration=OPEN_REGISTRATION,
|
|
1147
|
+
session_ttl=_SESSION_TTL,
|
|
1253
1148
|
require_auth=REQUIRE_AUTH,
|
|
1254
1149
|
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,
|
|
1150
|
+
create_admin_router=create_admin_router,
|
|
1151
|
+
require_admin=require_admin,
|
|
1152
|
+
get_history=get_history,
|
|
1153
|
+
get_audit_log=_get_audit_log,
|
|
1154
|
+
audit_file=AUDIT_FILE,
|
|
1155
|
+
public_user=public_user,
|
|
1156
|
+
load_vpc_config=load_vpc_config,
|
|
1276
1157
|
save_vpc_config=save_vpc_config,
|
|
1277
1158
|
build_admin_audit_report=build_admin_audit_report,
|
|
1278
1159
|
build_sensitivity_report=build_sensitivity_report,
|
|
1279
1160
|
append_audit_event=append_audit_event,
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1161
|
+
save_sso_config=save_sso_config,
|
|
1162
|
+
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
1163
|
+
enable_graph=ENABLE_GRAPH,
|
|
1164
|
+
logger=logging,
|
|
1165
|
+
invite_code=INVITE_CODE,
|
|
1166
|
+
invite_gate_enabled=INVITE_GATE_ENABLED,
|
|
1283
1167
|
default_port=DEFAULT_PORT,
|
|
1284
1168
|
policy_matrix=policy_matrix,
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1169
|
+
build_product_hardening_status=build_product_hardening_status,
|
|
1170
|
+
config=CONFIG,
|
|
1171
|
+
kg_portability=KG_PORTABILITY,
|
|
1172
|
+
device_identity=DEVICE_IDENTITY,
|
|
1173
|
+
create_invitations_router=create_invitations_router,
|
|
1289
1174
|
invitation_store=INVITATION_STORE,
|
|
1290
1175
|
workspace_service=WORKSPACE_SERVICE,
|
|
1291
|
-
require_admin=require_admin,
|
|
1292
|
-
require_user=require_user,
|
|
1293
1176
|
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,
|
|
1177
|
+
create_security_router=_create_security_router,
|
|
1329
1178
|
classify_sensitive_message=classify_sensitive_message,
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1179
|
+
)
|
|
1180
|
+
auth_router = _foundation_router_bundle["auth_router"]
|
|
1181
|
+
admin_router = _foundation_router_bundle["admin_router"]
|
|
1182
|
+
invitations_router = _foundation_router_bundle["invitations_router"]
|
|
1183
|
+
security_router = _foundation_router_bundle["security_router"]
|
|
1184
|
+
_graph_stats_safe = _foundation_router_bundle["_graph_stats_safe"]
|
|
1185
|
+
_product_hardening_status = _foundation_router_bundle["_product_hardening_status"]
|
|
1186
|
+
_security_audit_events_safe = _foundation_router_bundle["_security_audit_events_safe"]
|
|
1187
|
+
_security_list_uploaded_files = _foundation_router_bundle["_security_list_uploaded_files"]
|
|
1334
1188
|
|
|
1335
1189
|
# ── Static UI/status routes moved to latticeai.api.static_routes ──
|
|
1336
1190
|
|
|
@@ -1365,22 +1219,18 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1365
1219
|
return KNOWLEDGE_GRAPH if (ENABLE_GRAPH and KNOWLEDGE_GRAPH) else None
|
|
1366
1220
|
|
|
1367
1221
|
|
|
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,
|
|
1222
|
+
_context_runtime = build_context_runtime(
|
|
1223
|
+
graph_store=_workspace_graph(),
|
|
1224
|
+
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1225
|
+
memory_service=MEMORY_SERVICE,
|
|
1226
|
+
gardener=gardener,
|
|
1227
|
+
require_auth=REQUIRE_AUTH,
|
|
1228
|
+
allowed_scopes_for_user=lambda user_email: PLATFORM.allowed_scopes(user_email),
|
|
1383
1229
|
)
|
|
1230
|
+
SEARCH_SERVICE = _context_runtime["SEARCH_SERVICE"]
|
|
1231
|
+
BRAIN_MEMORY = _context_runtime["BRAIN_MEMORY"]
|
|
1232
|
+
CONTEXT_ASSEMBLER = _context_runtime["CONTEXT_ASSEMBLER"]
|
|
1233
|
+
_scoped_hybrid_search = _context_runtime["_scoped_hybrid_search"]
|
|
1384
1234
|
|
|
1385
1235
|
|
|
1386
1236
|
# ── Telegram chat mirror: registered only when ENABLE_TELEGRAM is truthy.
|
|
@@ -1393,9 +1243,34 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1393
1243
|
_spawn(broadcast_web_chat(role, text), name="telegram_broadcast")
|
|
1394
1244
|
on_chat_message = _telegram_chat_mirror
|
|
1395
1245
|
|
|
1246
|
+
def _recent_chat_context(
|
|
1247
|
+
limit: int = 10,
|
|
1248
|
+
include_image_missing_replies: bool = True,
|
|
1249
|
+
user_email: Optional[str] = None,
|
|
1250
|
+
conversation_id: Optional[str] = None,
|
|
1251
|
+
) -> str:
|
|
1252
|
+
return build_recent_chat_context(
|
|
1253
|
+
get_history=get_history,
|
|
1254
|
+
limit=limit,
|
|
1255
|
+
include_image_missing_replies=include_image_missing_replies,
|
|
1256
|
+
user_email=user_email,
|
|
1257
|
+
conversation_id=conversation_id,
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
CHAT_AGENT_RUNTIME = build_agent_runtime(
|
|
1261
|
+
model_router=router,
|
|
1262
|
+
execute_tool=execute_tool,
|
|
1263
|
+
recent_chat_context=_recent_chat_context,
|
|
1264
|
+
clear_history=clear_history,
|
|
1265
|
+
knowledge_save=knowledge_save,
|
|
1266
|
+
audit=append_audit_event,
|
|
1267
|
+
hooks=HOOKS_REGISTRY,
|
|
1268
|
+
brain_memory=BRAIN_MEMORY,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1396
1271
|
# ── Typed dependency context (latticeai.services.app_context) ────────────────
|
|
1397
1272
|
# One context object replaces the historical 25-30-kwarg router wiring.
|
|
1398
|
-
context =
|
|
1273
|
+
context = build_app_context(
|
|
1399
1274
|
config=CONFIG,
|
|
1400
1275
|
data_dir=DATA_DIR,
|
|
1401
1276
|
static_dir=STATIC_DIR,
|
|
@@ -1409,6 +1284,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1409
1284
|
chat_service=CHAT_SERVICE,
|
|
1410
1285
|
context_assembler=CONTEXT_ASSEMBLER,
|
|
1411
1286
|
brain_memory=BRAIN_MEMORY,
|
|
1287
|
+
chat_agent_runtime=CHAT_AGENT_RUNTIME,
|
|
1412
1288
|
gardener=gardener,
|
|
1413
1289
|
hooks=HOOKS_REGISTRY,
|
|
1414
1290
|
realtime_bus=REALTIME_BUS,
|
|
@@ -1449,8 +1325,16 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1449
1325
|
)
|
|
1450
1326
|
app.state.context = context
|
|
1451
1327
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1328
|
+
register_foundation_routers(
|
|
1329
|
+
app,
|
|
1330
|
+
static_router=STATIC_ROUTES.router,
|
|
1331
|
+
auth_router=auth_router,
|
|
1332
|
+
admin_router=admin_router,
|
|
1333
|
+
invitations_router=invitations_router,
|
|
1334
|
+
security_router=security_router,
|
|
1335
|
+
create_workspace_router=create_workspace_router,
|
|
1336
|
+
context=context,
|
|
1337
|
+
)
|
|
1454
1338
|
|
|
1455
1339
|
|
|
1456
1340
|
# ── v2 Agentic Workspace Platform: cross-system wiring ───────────────────────
|
|
@@ -1480,130 +1364,55 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1480
1364
|
agent_registry=AGENT_REGISTRY,
|
|
1481
1365
|
)
|
|
1482
1366
|
|
|
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(
|
|
1367
|
+
_automation_runtime = build_automation_runtime(
|
|
1492
1368
|
store=WORKSPACE_OS,
|
|
1493
|
-
|
|
1494
|
-
wf_id, None, None, with_agent=False, inputs=inputs,
|
|
1495
|
-
),
|
|
1369
|
+
platform=PLATFORM,
|
|
1496
1370
|
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
1371
|
workspace_graph=_workspace_graph,
|
|
1519
1372
|
append_audit_event=append_audit_event,
|
|
1520
1373
|
hooks=HOOKS_REGISTRY,
|
|
1521
1374
|
)
|
|
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)
|
|
1375
|
+
REVIEW_QUEUE = _automation_runtime["REVIEW_QUEUE"]
|
|
1376
|
+
TRIGGER_SERVICE = _automation_runtime["TRIGGER_SERVICE"]
|
|
1377
|
+
AGENT_RUNTIME = _automation_runtime["AGENT_RUNTIME"]
|
|
1378
|
+
RUN_EXECUTOR = _automation_runtime["RUN_EXECUTOR"]
|
|
1379
|
+
bind_trigger_hook_runner(registry=HOOKS_REGISTRY, trigger_service=TRIGGER_SERVICE)
|
|
1532
1380
|
app.state.run_executor = RUN_EXECUTOR
|
|
1533
1381
|
app.state.run_reconciliation = RUN_EXECUTOR.reconcile_startup()
|
|
1534
1382
|
TRIGGER_SERVICE.start()
|
|
1535
1383
|
|
|
1536
1384
|
# ── 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,
|
|
1385
|
+
bind_builtin_hook_runners(
|
|
1386
|
+
registry=HOOKS_REGISTRY,
|
|
1543
1387
|
append_audit_event=append_audit_event,
|
|
1544
1388
|
get_tool_permission=get_tool_permission,
|
|
1545
1389
|
classify_sensitive_message=classify_sensitive_message,
|
|
1546
1390
|
)
|
|
1547
1391
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1392
|
+
register_platform_feature_routers(
|
|
1393
|
+
app,
|
|
1394
|
+
create_plugins_router=create_plugins_router,
|
|
1395
|
+
plugin_registry=PLUGIN_REGISTRY,
|
|
1550
1396
|
require_user=require_user,
|
|
1551
1397
|
require_admin=require_admin,
|
|
1552
1398
|
append_audit_event=append_audit_event,
|
|
1553
|
-
|
|
1554
|
-
plugin_runners_factory=lambda: PLATFORM.plugin_capability_runners(None, None),
|
|
1399
|
+
platform=PLATFORM,
|
|
1555
1400
|
ui_file_response=ui_file_response,
|
|
1556
1401
|
static_dir=STATIC_DIR,
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
app.include_router(create_workflow_designer_router(
|
|
1402
|
+
create_workflow_designer_router=create_workflow_designer_router,
|
|
1560
1403
|
store=WORKSPACE_OS,
|
|
1561
|
-
require_user=require_user,
|
|
1562
1404
|
get_current_user=get_current_user,
|
|
1563
|
-
gate_read=PLATFORM.gate_read,
|
|
1564
|
-
gate_write=PLATFORM.gate_write,
|
|
1565
1405
|
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
1406
|
hooks=HOOKS_REGISTRY,
|
|
1571
1407
|
run_executor=RUN_EXECUTOR,
|
|
1572
1408
|
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,
|
|
1409
|
+
create_agents_router=create_agents_router,
|
|
1586
1410
|
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
|
-
))
|
|
1411
|
+
create_marketplace_router=create_marketplace_router,
|
|
1412
|
+
template_catalog=TEMPLATE_CATALOG,
|
|
1413
|
+
create_realtime_router=create_realtime_router,
|
|
1414
|
+
realtime_bus=REALTIME_BUS,
|
|
1415
|
+
)
|
|
1607
1416
|
|
|
1608
1417
|
|
|
1609
1418
|
# ── Health & Info ──────────────────────────────────────────────────────────────
|
|
@@ -1612,26 +1421,23 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1612
1421
|
# ── Health / status / engine-summary router (latticeai.api.health, v1.2.0) ───
|
|
1613
1422
|
# /health, /mode, /runtime_features, /engines(GET) now live in the health router.
|
|
1614
1423
|
# Heavier engine mutation endpoints remain below in server_app.
|
|
1615
|
-
MODEL_SERVICE =
|
|
1424
|
+
MODEL_SERVICE = build_model_service(
|
|
1616
1425
|
model_router=router,
|
|
1617
1426
|
runtime_features=runtime_features,
|
|
1618
1427
|
is_public=IS_PUBLIC_MODE,
|
|
1619
1428
|
)
|
|
1620
|
-
|
|
1429
|
+
register_health_and_model_routers(
|
|
1430
|
+
app,
|
|
1431
|
+
create_health_router=create_health_router,
|
|
1621
1432
|
model_service=MODEL_SERVICE,
|
|
1622
1433
|
engine_status=engine_status,
|
|
1623
1434
|
get_current_user=get_current_user,
|
|
1624
1435
|
require_auth=REQUIRE_AUTH,
|
|
1625
1436
|
app_version=APP_VERSION,
|
|
1626
1437
|
app_mode=APP_MODE,
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
# ── Model / Engine router (latticeai.api.models, v1.3.0) ─────────────────────
|
|
1631
|
-
app.include_router(create_models_router(
|
|
1438
|
+
create_models_router=create_models_router,
|
|
1632
1439
|
model_router=router,
|
|
1633
1440
|
require_user=require_user,
|
|
1634
|
-
get_current_user=get_current_user,
|
|
1635
1441
|
load_users=load_users,
|
|
1636
1442
|
get_user_role=get_user_role,
|
|
1637
1443
|
install_engine=install_engine,
|
|
@@ -1643,7 +1449,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1643
1449
|
sse_event=sse_event,
|
|
1644
1450
|
ensure_ollama_server=ensure_ollama_server,
|
|
1645
1451
|
local_binary=local_binary,
|
|
1646
|
-
engine_status=engine_status,
|
|
1647
1452
|
filter_lower_family_versions=filter_lower_family_versions,
|
|
1648
1453
|
list_compat_profiles=_list_compat_profiles,
|
|
1649
1454
|
set_user_api_key=set_user_api_key,
|
|
@@ -1652,14 +1457,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1652
1457
|
cloud_verify_ttl_seconds=CLOUD_VERIFY_TTL_SECONDS,
|
|
1653
1458
|
is_public_mode=IS_PUBLIC_MODE,
|
|
1654
1459
|
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
1655
|
-
|
|
1656
|
-
))
|
|
1460
|
+
)
|
|
1657
1461
|
|
|
1658
1462
|
|
|
1659
1463
|
# ── Chat / Completion ──────────────────────────────────────────────────────────
|
|
1660
1464
|
|
|
1661
|
-
app.include_router(create_chat_router(context))
|
|
1662
|
-
|
|
1663
1465
|
def _embedding_info() -> dict:
|
|
1664
1466
|
from latticeai.core.embedding_providers import PROVIDER_TYPES, embedding_provider_profiles
|
|
1665
1467
|
info = EMBEDDER.as_dict()
|
|
@@ -1676,20 +1478,21 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1676
1478
|
return None
|
|
1677
1479
|
return PLATFORM.allowed_scopes(user)
|
|
1678
1480
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1481
|
+
register_interaction_routers(
|
|
1482
|
+
app,
|
|
1483
|
+
create_chat_router=create_chat_router,
|
|
1484
|
+
context=context,
|
|
1485
|
+
create_search_router=create_search_router,
|
|
1486
|
+
search_service=SEARCH_SERVICE,
|
|
1681
1487
|
allowed_workspaces_for=_allowed_workspaces_for,
|
|
1682
1488
|
require_user=require_user,
|
|
1683
1489
|
embedding_info=_embedding_info,
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
app.include_router(create_tools_router(
|
|
1490
|
+
create_tools_router=create_tools_router,
|
|
1687
1491
|
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1688
1492
|
config=CONFIG,
|
|
1689
1493
|
data_dir=DATA_DIR,
|
|
1690
1494
|
static_dir=STATIC_DIR,
|
|
1691
1495
|
model_router=router,
|
|
1692
|
-
require_user=require_user,
|
|
1693
1496
|
require_admin=require_admin,
|
|
1694
1497
|
get_current_user=get_current_user,
|
|
1695
1498
|
clear_history=clear_history,
|
|
@@ -1707,28 +1510,13 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1707
1510
|
install_mcp=install_mcp,
|
|
1708
1511
|
mcp_public_item=mcp_public_item,
|
|
1709
1512
|
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
|
-
))
|
|
1513
|
+
create_hooks_router=create_hooks_router,
|
|
1514
|
+
create_agent_registry_router=create_agent_registry_router,
|
|
1515
|
+
agent_registry=AGENT_REGISTRY,
|
|
1516
|
+
create_memory_router=create_memory_router,
|
|
1517
|
+
memory_service=MEMORY_SERVICE,
|
|
1518
|
+
platform=PLATFORM,
|
|
1519
|
+
)
|
|
1732
1520
|
|
|
1733
1521
|
from latticeai.api.review_queue import create_review_queue_router
|
|
1734
1522
|
|
|
@@ -1742,39 +1530,29 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1742
1530
|
inputs={"__review_item__": item.get("id")},
|
|
1743
1531
|
)
|
|
1744
1532
|
|
|
1745
|
-
|
|
1746
|
-
|
|
1533
|
+
BRAIN_NETWORK = register_review_and_brain_tail_routers(
|
|
1534
|
+
app,
|
|
1535
|
+
create_review_queue_router=create_review_queue_router,
|
|
1536
|
+
review_queue=REVIEW_QUEUE,
|
|
1747
1537
|
require_user=require_user,
|
|
1748
1538
|
gate_read=PLATFORM.gate_read,
|
|
1749
1539
|
gate_write=PLATFORM.gate_write,
|
|
1750
1540
|
run_review_item=_run_review_item,
|
|
1751
1541
|
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,
|
|
1542
|
+
create_browser_router=create_browser_router,
|
|
1543
|
+
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1544
|
+
create_portability_router=create_portability_router,
|
|
1545
|
+
kg_portability=KG_PORTABILITY,
|
|
1762
1546
|
require_admin=require_admin,
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
BRAIN_NETWORK = BrainNetwork(
|
|
1766
|
-
identity=DEVICE_IDENTITY,
|
|
1767
|
-
portability=KG_PORTABILITY,
|
|
1547
|
+
build_brain_network=build_brain_network,
|
|
1548
|
+
device_identity=DEVICE_IDENTITY,
|
|
1768
1549
|
data_dir=DATA_DIR,
|
|
1550
|
+
create_network_router=create_network_router,
|
|
1551
|
+
create_garden_router=create_garden_router,
|
|
1552
|
+
gardener=gardener,
|
|
1553
|
+
create_setup_router=create_setup_router,
|
|
1554
|
+
model_router=router,
|
|
1769
1555
|
)
|
|
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
1556
|
|
|
1779
1557
|
# ── Entry Point ────────────────────────────────────────────────────────────────
|
|
1780
1558
|
|